blob: 1226b2510136de22b047a576fa985ed25fa3e171 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1996-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.net.www.http;
27
28import java.io.InputStream;
29import java.io.IOException;
30import java.io.NotSerializableException;
31import java.util.*;
32import java.net.URL;
33import java.util.concurrent.ConcurrentHashMap;
34
35/**
36 * A class that implements a cache of idle Http connections for keep-alive
37 *
38 * @author Stephen R. Pietrowicz (NCSA)
39 * @author Dave Brown
40 */
41public class KeepAliveCache extends ConcurrentHashMap implements Runnable {
42 private static final long serialVersionUID = -2937172892064557949L;
43
44 /* maximum # keep-alive connections to maintain at once
45 * This should be 2 by the HTTP spec, but because we don't support pipe-lining
46 * a larger value is more appropriate. So we now set a default of 5, and the value
47 * refers to the number of idle connections per destination (in the cache) only.
48 * It can be reset by setting system property "http.maxConnections".
49 */
50 static final int MAX_CONNECTIONS = 5;
51 static int result = -1;
52 static int getMaxConnections() {
53 if (result == -1) {
54 result = java.security.AccessController.doPrivileged(
55 new sun.security.action.GetIntegerAction("http.maxConnections",
56 MAX_CONNECTIONS))
57 .intValue();
58 if (result <= 0)
59 result = MAX_CONNECTIONS;
60 }
61 return result;
62 }
63
64 static final int LIFETIME = 5000;
65
66 private Thread keepAliveTimer = null;
67
68 /**
69 * Constructor
70 */
71 public KeepAliveCache() {}
72
73 /**
74 * Register this URL and HttpClient (that supports keep-alive) with the cache
75 * @param url The URL contains info about the host and port
76 * @param http The HttpClient to be cached
77 */
78 public synchronized void put(final URL url, Object obj, HttpClient http) {
79 boolean startThread = (keepAliveTimer == null);
80 if (!startThread) {
81 if (!keepAliveTimer.isAlive()) {
82 startThread = true;
83 }
84 }
85 if (startThread) {
86 clear();
87 /* Unfortunately, we can't always believe the keep-alive timeout we got
88 * back from the server. If I'm connected through a Netscape proxy
89 * to a server that sent me a keep-alive
90 * time of 15 sec, the proxy unilaterally terminates my connection
91 * The robustness to to get around this is in HttpClient.parseHTTP()
92 */
93 final KeepAliveCache cache = this;
94 java.security.AccessController.doPrivileged(
95 new java.security.PrivilegedAction() {
96 public Object run() {
97 // We want to create the Keep-Alive-Timer in the
98 // system threadgroup
99 ThreadGroup grp = Thread.currentThread().getThreadGroup();
100 ThreadGroup parent = null;
101 while ((parent = grp.getParent()) != null) {
102 grp = parent;
103 }
104
105 keepAliveTimer = new Thread(grp, cache, "Keep-Alive-Timer");
106 keepAliveTimer.setDaemon(true);
107 keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2);
108 keepAliveTimer.start();
109 return null;
110 }
111 });
112 }
113
114 KeepAliveKey key = new KeepAliveKey(url, obj);
115 ClientVector v = (ClientVector)super.get(key);
116
117 if (v == null) {
118 int keepAliveTimeout = http.getKeepAliveTimeout();
119 v = new ClientVector(keepAliveTimeout > 0?
120 keepAliveTimeout*1000 : LIFETIME);
121 v.put(http);
122 super.put(key, v);
123 } else {
124 v.put(http);
125 }
126 }
127
128 /* remove an obsolete HttpClient from it's VectorCache */
129 public synchronized void remove (HttpClient h, Object obj) {
130 KeepAliveKey key = new KeepAliveKey(h.url, obj);
131 ClientVector v = (ClientVector)super.get(key);
132 if (v != null) {
133 v.remove(h);
134 if (v.empty()) {
135 removeVector(key);
136 }
137 }
138 }
139
140 /* called by a clientVector thread when all it's connections have timed out
141 * and that vector of connections should be removed.
142 */
143 synchronized void removeVector(KeepAliveKey k) {
144 super.remove(k);
145 }
146
147 /**
148 * Check to see if this URL has a cached HttpClient
149 */
150 public synchronized Object get(URL url, Object obj) {
151
152 KeepAliveKey key = new KeepAliveKey(url, obj);
153 ClientVector v = (ClientVector)super.get(key);
154 if (v == null) { // nothing in cache yet
155 return null;
156 }
157 return v.get();
158 }
159
160 /* Sleeps for an alloted timeout, then checks for timed out connections.
161 * Errs on the side of caution (leave connections idle for a relatively
162 * short time).
163 */
164 public void run() {
165 int total_cache;
166 do {
167 try {
168 Thread.sleep(LIFETIME);
169 } catch (InterruptedException e) {}
170 synchronized (this) {
171 /* Remove all unused HttpClients. Starting from the
172 * bottom of the stack (the least-recently used first).
173 * REMIND: It'd be nice to not remove *all* connections
174 * that aren't presently in use. One could have been added
175 * a second ago that's still perfectly valid, and we're
176 * needlessly axing it. But it's not clear how to do this
177 * cleanly, and doing it right may be more trouble than it's
178 * worth.
179 */
180
181 long currentTime = System.currentTimeMillis();
182
183 Iterator itr = keySet().iterator();
184 ArrayList keysToRemove = new ArrayList();
185
186 while (itr.hasNext()) {
187 KeepAliveKey key = (KeepAliveKey)itr.next();
188 ClientVector v = (ClientVector)get(key);
189 synchronized (v) {
190 int i;
191
192 for (i = 0; i < v.size(); i++) {
193 KeepAliveEntry e = (KeepAliveEntry)v.elementAt(i);
194 if ((currentTime - e.idleStartTime) > v.nap) {
195 HttpClient h = e.hc;
196 h.closeServer();
197 } else {
198 break;
199 }
200 }
201 v.subList(0, i).clear();
202
203 if (v.size() == 0) {
204 keysToRemove.add(key);
205 }
206 }
207 }
208 itr = keysToRemove.iterator();
209 while (itr.hasNext()) {
210 removeVector((KeepAliveKey)itr.next());
211 }
212 }
213 } while (size() > 0);
214
215 return;
216 }
217
218 /*
219 * Do not serialize this class!
220 */
221 private void writeObject(java.io.ObjectOutputStream stream)
222 throws IOException {
223 throw new NotSerializableException();
224 }
225
226 private void readObject(java.io.ObjectInputStream stream)
227 throws IOException, ClassNotFoundException {
228 throw new NotSerializableException();
229 }
230}
231
232/* FILO order for recycling HttpClients, should run in a thread
233 * to time them out. If > maxConns are in use, block.
234 */
235
236
237class ClientVector extends java.util.Stack {
238 private static final long serialVersionUID = -8680532108106489459L;
239
240 // sleep time in milliseconds, before cache clear
241 int nap;
242
243
244
245 ClientVector (int nap) {
246 this.nap = nap;
247 }
248
249 synchronized HttpClient get() {
250 if (empty()) {
251 return null;
252 } else {
253 // Loop until we find a connection that has not timed out
254 HttpClient hc = null;
255 long currentTime = System.currentTimeMillis();
256 do {
257 KeepAliveEntry e = (KeepAliveEntry)pop();
258 if ((currentTime - e.idleStartTime) > nap) {
259 e.hc.closeServer();
260 } else {
261 hc = e.hc;
262 }
263 } while ((hc== null) && (!empty()));
264 return hc;
265 }
266 }
267
268 /* return a still valid, unused HttpClient */
269 synchronized void put(HttpClient h) {
270 if (size() > KeepAliveCache.getMaxConnections()) {
271 h.closeServer(); // otherwise the connection remains in limbo
272 } else {
273 push(new KeepAliveEntry(h, System.currentTimeMillis()));
274 }
275 }
276
277 /*
278 * Do not serialize this class!
279 */
280 private void writeObject(java.io.ObjectOutputStream stream)
281 throws IOException {
282 throw new NotSerializableException();
283 }
284
285 private void readObject(java.io.ObjectInputStream stream)
286 throws IOException, ClassNotFoundException {
287 throw new NotSerializableException();
288 }
289}
290
291
292class KeepAliveKey {
293 private String protocol = null;
294 private String host = null;
295 private int port = 0;
296 private Object obj = null; // additional key, such as socketfactory
297
298 /**
299 * Constructor
300 *
301 * @param url the URL containing the protocol, host and port information
302 */
303 public KeepAliveKey(URL url, Object obj) {
304 this.protocol = url.getProtocol();
305 this.host = url.getHost();
306 this.port = url.getPort();
307 this.obj = obj;
308 }
309
310 /**
311 * Determine whether or not two objects of this type are equal
312 */
313 public boolean equals(Object obj) {
314 if ((obj instanceof KeepAliveKey) == false)
315 return false;
316 KeepAliveKey kae = (KeepAliveKey)obj;
317 return host.equals(kae.host)
318 && (port == kae.port)
319 && protocol.equals(kae.protocol)
320 && this.obj == kae.obj;
321 }
322
323 /**
324 * The hashCode() for this object is the string hashCode() of
325 * concatenation of the protocol, host name and port.
326 */
327 public int hashCode() {
328 String str = protocol+host+port;
329 return this.obj == null? str.hashCode() :
330 str.hashCode() + this.obj.hashCode();
331 }
332}
333
334class KeepAliveEntry {
335 HttpClient hc;
336 long idleStartTime;
337
338 KeepAliveEntry(HttpClient hc, long idleStartTime) {
339 this.hc = hc;
340 this.idleStartTime = idleStartTime;
341 }
342}