J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 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 | |
| 26 | package sun.net.www.http; |
| 27 | |
| 28 | import java.io.InputStream; |
| 29 | import java.io.IOException; |
| 30 | import java.io.NotSerializableException; |
| 31 | import java.util.*; |
| 32 | import java.net.URL; |
| 33 | import 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 | */ |
| 41 | public 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 | |
| 237 | class 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 | |
| 292 | class 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 | |
| 334 | class KeepAliveEntry { |
| 335 | HttpClient hc; |
| 336 | long idleStartTime; |
| 337 | |
| 338 | KeepAliveEntry(HttpClient hc, long idleStartTime) { |
| 339 | this.hc = hc; |
| 340 | this.idleStartTime = idleStartTime; |
| 341 | } |
| 342 | } |