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