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