001: /*
002: * @(#)KeepAliveCache.java 1.28 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: *
026: */
027:
028: package sun.net.www.http;
029:
030: import java.io.InputStream;
031: import java.io.IOException;
032: import java.io.NotSerializableException;
033: import java.util.*;
034: import java.net.URL;
035:
036: /**
037: * A class that implements a cache of idle Http connections for keep-alive
038: *
039: * @version 1.28 10/10/06
040: * @author Stephen R. Pietrowicz (NCSA)
041: * @author Dave Brown
042: */
043: public class KeepAliveCache extends Hashtable implements Runnable {
044: private static final long serialVersionUID = -2937172892064557949L;
045:
046: /* maximum # keep-alive connections to maintain at once
047: * This should be 2 by the HTTP spec, but because we don't support pipe-lining
048: * a larger value is more appropriate. So we now set a default of 5, and the value
049: * refers to the number of idle connections per destination (in the cache) only.
050: * It can be reset by setting system property "http.maxConnections".
051: */
052: static final int MAX_CONNECTIONS = 5;
053: static int result = -1;
054:
055: static int getMaxConnections() {
056: if (result == -1) {
057: result = ((Integer) java.security.AccessController
058: .doPrivileged(new sun.security.action.GetIntegerAction(
059: "http.maxConnections", MAX_CONNECTIONS)))
060: .intValue();
061: if (result <= 0)
062: result = MAX_CONNECTIONS;
063: }
064: return result;
065: }
066:
067: static final int LIFETIME = 5000;
068:
069: private Thread keepAliveTimer = null;
070:
071: /**
072: * Constructor
073: */
074: public KeepAliveCache() {
075: }
076:
077: /**
078: * Register this URL and HttpClient (that supports keep-alive) with the cache
079: * @param url The URL contains info about the host and port
080: * @param http The HttpClient to be cached
081: */
082: public synchronized void put(final URL url, Object obj,
083: HttpClient http) {
084: boolean startThread = (keepAliveTimer == null);
085: if (!startThread) {
086: if (!keepAliveTimer.isAlive()) {
087: startThread = true;
088: }
089: }
090: if (startThread) {
091: clear();
092: /* Unfortunately, we can't always believe the keep-alive timeout we got
093: * back from the server. If I'm connected through a Netscape proxy
094: * to a server that sent me a keep-alive
095: * time of 15 sec, the proxy unilaterally terminates my connection
096: * The robustness to to get around this is in HttpClient.parseHTTP()
097: */
098: final KeepAliveCache cache = this ;
099: java.security.AccessController
100: .doPrivileged(new java.security.PrivilegedAction() {
101: public Object run() {
102: // We want to create the Keep-Alive-Timer in the
103: // system threadgroup
104: ThreadGroup grp = Thread.currentThread()
105: .getThreadGroup();
106: ThreadGroup parent = null;
107: while ((parent = grp.getParent()) != null) {
108: grp = parent;
109: }
110:
111: keepAliveTimer = new Thread(grp, cache,
112: "Keep-Alive-Timer");
113: keepAliveTimer.setDaemon(true);
114: keepAliveTimer
115: .setPriority(Thread.MAX_PRIORITY - 2);
116: keepAliveTimer.start();
117: return null;
118: }
119: });
120: }
121:
122: KeepAliveKey key = new KeepAliveKey(url, obj);
123: ClientVector v = (ClientVector) super .get(key);
124:
125: if (v == null) {
126: int keepAliveTimeout = http.getKeepAliveTimeout();
127: v = new ClientVector(
128: keepAliveTimeout > 0 ? keepAliveTimeout * 1000
129: : LIFETIME);
130: v.put(http);
131: super .put(key, v);
132: } else {
133: v.put(http);
134: }
135: }
136:
137: /* remove an obsolete HttpClient from it's VectorCache */
138: public synchronized void remove(HttpClient h, Object obj) {
139: KeepAliveKey key = new KeepAliveKey(h.url, obj);
140: ClientVector v = (ClientVector) super .get(key);
141: if (v != null) {
142: v.remove(h);
143: if (v.empty()) {
144: removeVector(key);
145: }
146: }
147: }
148:
149: /* called by a clientVector thread when all it's connections have timed out
150: * and that vector of connections should be removed.
151: */
152: synchronized void removeVector(KeepAliveKey k) {
153: super .remove(k);
154: }
155:
156: /**
157: * Check to see if this URL has a cached HttpClient
158: */
159: public synchronized Object get(URL url, Object obj) {
160:
161: KeepAliveKey key = new KeepAliveKey(url, obj);
162: ClientVector v = (ClientVector) super .get(key);
163: if (v == null) { // nothing in cache yet
164: return null;
165: }
166: return v.get();
167: }
168:
169: /* Sleeps for an alloted timeout, then checks for timed out connections.
170: * Errs on the side of caution (leave connections idle for a relatively
171: * short time).
172: */
173: public void run() {
174: int total_cache;
175: do {
176: try {
177: Thread.sleep(LIFETIME);
178: } catch (InterruptedException e) {
179: }
180: synchronized (this ) {
181: /* Remove all unused HttpClients. Starting from the
182: * bottom of the stack (the least-recently used first).
183: * TODO: It'd be nice to not remove *all* connections
184: * that aren't presently in use. One could have been added
185: * a second ago that's still perfectly valid, and we're
186: * needlessly axing it. But it's not clear how to do this
187: * cleanly, and doing it right may be more trouble than it's
188: * worth.
189: */
190:
191: long currentTime = System.currentTimeMillis();
192:
193: Iterator itr = keySet().iterator();
194: ArrayList keysToRemove = new ArrayList();
195:
196: while (itr.hasNext()) {
197: KeepAliveKey key = (KeepAliveKey) itr.next();
198: ClientVector v = (ClientVector) get(key);
199: synchronized (v) {
200: int i;
201:
202: for (i = 0; i < v.size(); i++) {
203: KeepAliveEntry e = (KeepAliveEntry) v
204: .elementAt(i);
205: if ((currentTime - e.idleStartTime) > v.nap) {
206: HttpClient h = e.hc;
207: h.closeServer();
208: } else {
209: break;
210: }
211: }
212: v.subList(0, i).clear();
213:
214: if (v.size() == 0) {
215: keysToRemove.add(key);
216: }
217: }
218: }
219: itr = keysToRemove.iterator();
220: while (itr.hasNext()) {
221: removeVector((KeepAliveKey) itr.next());
222: }
223: }
224: } while (size() > 0);
225:
226: return;
227: }
228:
229: /*
230: * Do not serialize this class!
231: */
232: private void writeObject(java.io.ObjectOutputStream stream)
233: throws IOException {
234: throw new NotSerializableException();
235: }
236:
237: private void readObject(java.io.ObjectInputStream stream)
238: throws IOException, ClassNotFoundException {
239: throw new NotSerializableException();
240: }
241: }
242:
243: /* FILO order for recycling HttpClients, should run in a thread
244: * to time them out. If > maxConns are in use, block.
245: */
246:
247: class ClientVector extends java.util.Stack {
248: private static final long serialVersionUID = -8680532108106489459L;
249:
250: // sleep time in milliseconds, before cache clear
251: int nap;
252:
253: ClientVector(int nap) {
254: this .nap = nap;
255: }
256:
257: synchronized HttpClient get() {
258: if (empty()) {
259: return null;
260: } else {
261: // Loop until we find a connection that has not timed out
262: HttpClient hc = null;
263: long currentTime = System.currentTimeMillis();
264: do {
265: KeepAliveEntry e = (KeepAliveEntry) pop();
266: if ((currentTime - e.idleStartTime) > nap) {
267: e.hc.closeServer();
268: } else {
269: hc = e.hc;
270: }
271: } while ((hc == null) && (!empty()));
272: return hc;
273: }
274: }
275:
276: /* return a still valid, unused HttpClient */
277: synchronized void put(HttpClient h) {
278: if (size() > KeepAliveCache.getMaxConnections()) {
279: h.closeServer(); // otherwise the connection remains in limbo
280: } else {
281: push(new KeepAliveEntry(h, System.currentTimeMillis()));
282: }
283: }
284:
285: /*
286: * Do not serialize this class!
287: */
288: private void writeObject(java.io.ObjectOutputStream stream)
289: throws IOException {
290: throw new NotSerializableException();
291: }
292:
293: private void readObject(java.io.ObjectInputStream stream)
294: throws IOException, ClassNotFoundException {
295: throw new NotSerializableException();
296: }
297: }
298:
299: class KeepAliveKey {
300: private String protocol = null;
301: private String host = null;
302: private int port = 0;
303: private Object obj = null; // additional key, such as socketfactory
304:
305: /**
306: * Constructor
307: *
308: * @param url the URL containing the protocol, host and port information
309: */
310: public KeepAliveKey(URL url, Object obj) {
311: this .protocol = url.getProtocol();
312: this .host = url.getHost();
313: this .port = url.getPort();
314: this .obj = obj;
315: }
316:
317: /**
318: * Determine whether or not two objects of this type are equal
319: */
320: public boolean equals(Object obj) {
321: if ((obj instanceof KeepAliveKey) == false)
322: return false;
323: KeepAliveKey kae = (KeepAliveKey) obj;
324: return host.equals(kae.host) && (port == kae.port)
325: && protocol.equals(kae.protocol) && this .obj == kae.obj;
326: }
327:
328: /**
329: * The hashCode() for this object is the string hashCode() of
330: * concatenation of the protocol, host name and port.
331: */
332: public int hashCode() {
333: String str = protocol + host + port;
334: return this .obj == null ? str.hashCode() : str.hashCode()
335: + this .obj.hashCode();
336: }
337: }
338:
339: class KeepAliveEntry {
340: HttpClient hc;
341: long idleStartTime;
342:
343: KeepAliveEntry(HttpClient hc, long idleStartTime) {
344: this.hc = hc;
345: this.idleStartTime = idleStartTime;
346: }
347: }
|