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