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 jdk.internal.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", false); 100 keepAliveTimer.setDaemon(true); 101 keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2); 102 keepAliveTimer.start(); 103 return null; 104 } 105 }); 106 } 107 108 KeepAliveKey key = new KeepAliveKey(url, obj); 109 ClientVector v = super.get(key); 110 111 if (v == null) { 112 int keepAliveTimeout = http.getKeepAliveTimeout(); 113 v = new ClientVector(keepAliveTimeout > 0? 114 keepAliveTimeout*1000 : LIFETIME); 115 v.put(http); 116 super.put(key, v); 117 } else { 118 v.put(http); 119 } 120 } 121 122 /* remove an obsolete HttpClient from its VectorCache */ 123 public synchronized void remove (HttpClient h, Object obj) { 124 KeepAliveKey key = new KeepAliveKey(h.url, obj); 125 ClientVector v = super.get(key); 126 if (v != null) { 127 v.remove(h); 128 if (v.empty()) { 129 removeVector(key); 130 } 131 } 132 } 133 134 /* called by a clientVector thread when all its connections have timed out 135 * and that vector of connections should be removed. 136 */ 137 synchronized void removeVector(KeepAliveKey k) { 138 super.remove(k); 139 } 140 141 /** 142 * Check to see if this URL has a cached HttpClient 143 */ 144 public synchronized HttpClient get(URL url, Object obj) { 145 146 KeepAliveKey key = new KeepAliveKey(url, obj); 147 ClientVector v = super.get(key); 148 if (v == null) { // nothing in cache yet 149 return null; 150 } 151 return v.get(); 152 } 153 154 /* Sleeps for an alloted timeout, then checks for timed out connections. 155 * Errs on the side of caution (leave connections idle for a relatively 156 * short time). 157 */ 158 @Override 159 public void run() { 160 do { 161 try { 162 Thread.sleep(LIFETIME); 163 } catch (InterruptedException e) {} 164 synchronized (this) { 165 /* Remove all unused HttpClients. Starting from the 166 * bottom of the stack (the least-recently used first). 167 * REMIND: It'd be nice to not remove *all* connections 168 * that aren't presently in use. One could have been added 169 * a second ago that's still perfectly valid, and we're 170 * needlessly axing it. But it's not clear how to do this 171 * cleanly, and doing it right may be more trouble than it's 172 * worth. 173 */ 174 175 long currentTime = System.currentTimeMillis(); 176 177 ArrayList<KeepAliveKey> keysToRemove 178 = new ArrayList<>(); 179 180 for (KeepAliveKey key : keySet()) { 181 ClientVector v = get(key); 182 synchronized (v) { 183 int i; 184 185 for (i = 0; i < v.size(); i++) { 186 KeepAliveEntry e = v.elementAt(i); 187 if ((currentTime - e.idleStartTime) > v.nap) { 188 HttpClient h = e.hc; 189 h.closeServer(); 190 } else { 191 break; 192 } 193 } 194 v.subList(0, i).clear(); 195 196 if (v.size() == 0) { 197 keysToRemove.add(key); 198 } 199 } 200 } 201 202 for (KeepAliveKey key : keysToRemove) { 203 removeVector(key); 204 } 205 } 206 } while (size() > 0); 207 208 return; 209 } 210 211 /* 212 * Do not serialize this class! 213 */ 214 private void writeObject(java.io.ObjectOutputStream stream) 215 throws IOException { 216 throw new NotSerializableException(); 217 } 218 219 private void readObject(java.io.ObjectInputStream stream) 220 throws IOException, ClassNotFoundException { 221 throw new NotSerializableException(); 222 } 223 } 224 225 /* FILO order for recycling HttpClients, should run in a thread 226 * to time them out. If > maxConns are in use, block. 227 */ 228 229 230 class ClientVector extends java.util.Stack<KeepAliveEntry> { 231 private static final long serialVersionUID = -8680532108106489459L; 232 233 // sleep time in milliseconds, before cache clear 234 int nap; 235 236 237 238 ClientVector (int nap) { 239 this.nap = nap; 240 } 241 242 synchronized HttpClient get() { 243 if (empty()) { 244 return null; 245 } else { 246 // Loop until we find a connection that has not timed out 247 HttpClient hc = null; 248 long currentTime = System.currentTimeMillis(); 249 do { 250 KeepAliveEntry e = pop(); 251 if ((currentTime - e.idleStartTime) > nap) { 252 e.hc.closeServer(); 253 } else { 254 hc = e.hc; 255 } 256 } while ((hc== null) && (!empty())); 257 return hc; 258 } 259 } 260 261 /* return a still valid, unused HttpClient */ 262 synchronized void put(HttpClient h) { 263 if (size() >= KeepAliveCache.getMaxConnections()) { 264 h.closeServer(); // otherwise the connection remains in limbo 265 } else { 266 push(new KeepAliveEntry(h, System.currentTimeMillis())); 267 } 268 } 269 270 /* 271 * Do not serialize this class! 272 */ 273 private void writeObject(java.io.ObjectOutputStream stream) 274 throws IOException { 275 throw new NotSerializableException(); 276 } 277 278 private void readObject(java.io.ObjectInputStream stream) 279 throws IOException, ClassNotFoundException { 280 throw new NotSerializableException(); 281 } 282 } 283 284 285 class KeepAliveKey { 286 private String protocol = null; 287 private String host = null; 288 private int port = 0; 289 private Object obj = null; // additional key, such as socketfactory 290 291 /** 292 * Constructor 293 * 294 * @param url the URL containing the protocol, host and port information 295 */ 296 public KeepAliveKey(URL url, Object obj) { 297 this.protocol = url.getProtocol(); 298 this.host = url.getHost(); 299 this.port = url.getPort(); 300 this.obj = obj; 301 } 302 303 /** 304 * Determine whether or not two objects of this type are equal 305 */ 306 @Override 307 public boolean equals(Object obj) { 308 if ((obj instanceof KeepAliveKey) == false) 309 return false; 310 KeepAliveKey kae = (KeepAliveKey)obj; 311 return host.equals(kae.host) 312 && (port == kae.port) 313 && protocol.equals(kae.protocol) 314 && this.obj == kae.obj; 315 } 316 317 /** 318 * The hashCode() for this object is the string hashCode() of 319 * concatenation of the protocol, host name and port. 320 */ 321 @Override 322 public int hashCode() { 323 String str = protocol+host+port; 324 return this.obj == null? str.hashCode() : 325 str.hashCode() + this.obj.hashCode(); 326 } 327 } 328 329 class KeepAliveEntry { 330 HttpClient hc; 331 long idleStartTime; 332 333 KeepAliveEntry(HttpClient hc, long idleStartTime) { 334 this.hc = hc; 335 this.idleStartTime = idleStartTime; 336 } 337 }