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 @java.io.Serial 53 private static final long serialVersionUID = -2937172892064557949L; 54 55 /* maximum # keep-alive connections to maintain at once 56 * This should be 2 by the HTTP spec, but because we don't support pipe-lining 57 * a larger value is more appropriate. So we now set a default of 5, and the value 58 * refers to the number of idle connections per destination (in the cache) only. 59 * It can be reset by setting system property "http.maxConnections". 60 */ 61 static final int MAX_CONNECTIONS = 5; 62 static int result = -1; 63 static int getMaxConnections() { 64 if (result == -1) { 65 result = AccessController.doPrivileged( 66 new GetIntegerAction("http.maxConnections", MAX_CONNECTIONS)) 67 .intValue(); 68 if (result <= 0) { 69 result = MAX_CONNECTIONS; 70 } 71 } 72 return result; 73 } 74 75 static final int LIFETIME = 5000; 76 77 private Thread keepAliveTimer = null; 78 79 /** 80 * Constructor 81 */ 82 public KeepAliveCache() {} 83 84 /** 85 * Register this URL and HttpClient (that supports keep-alive) with the cache 86 * @param url The URL contains info about the host and port 87 * @param http The HttpClient to be cached 88 */ 89 public synchronized void put(final URL url, Object obj, HttpClient http) { 90 boolean startThread = (keepAliveTimer == null); 91 if (!startThread) { 92 if (!keepAliveTimer.isAlive()) { 93 startThread = true; 94 } 95 } 96 if (startThread) { 97 clear(); 98 /* Unfortunately, we can't always believe the keep-alive timeout we got 99 * back from the server. If I'm connected through a Netscape proxy 100 * to a server that sent me a keep-alive 101 * time of 15 sec, the proxy unilaterally terminates my connection 102 * The robustness to get around this is in HttpClient.parseHTTP() 103 */ 104 final KeepAliveCache cache = this; 105 AccessController.doPrivileged(new PrivilegedAction<>() { 106 public Void run() { 107 keepAliveTimer = InnocuousThread.newSystemThread("Keep-Alive-Timer", cache); 108 keepAliveTimer.setDaemon(true); 109 keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2); 110 keepAliveTimer.start(); 111 return null; 112 } 113 }); 114 } 115 116 KeepAliveKey key = new KeepAliveKey(url, obj); 117 ClientVector v = super.get(key); 118 119 if (v == null) { 120 int keepAliveTimeout = http.getKeepAliveTimeout(); 121 v = new ClientVector(keepAliveTimeout > 0 ? 122 keepAliveTimeout * 1000 : LIFETIME); 123 v.put(http); 124 super.put(key, v); 125 } else { 126 v.put(http); 127 } 128 } 129 130 /* remove an obsolete HttpClient from its VectorCache */ 131 public synchronized void remove(HttpClient h, Object obj) { 132 KeepAliveKey key = new KeepAliveKey(h.url, obj); 133 ClientVector v = super.get(key); 134 if (v != null) { 135 v.remove(h); 136 if (v.isEmpty()) { 137 removeVector(key); 138 } 139 } 140 } 141 142 /* called by a clientVector thread when all its connections have timed out 143 * and that vector of connections should be removed. 144 */ 145 synchronized void removeVector(KeepAliveKey k) { 146 super.remove(k); 147 } 148 149 /** 150 * Check to see if this URL has a cached HttpClient 151 */ 152 public synchronized HttpClient get(URL url, Object obj) { 153 KeepAliveKey key = new KeepAliveKey(url, obj); 154 ClientVector v = super.get(key); 155 if (v == null) { // nothing in cache yet 156 return null; 157 } 158 return v.get(); 159 } 160 161 /* Sleeps for an alloted timeout, then checks for timed out connections. 162 * Errs on the side of caution (leave connections idle for a relatively 163 * short time). 164 */ 165 @Override 166 public void run() { 167 do { 168 try { 169 Thread.sleep(LIFETIME); 170 } catch (InterruptedException e) {} 171 172 // Remove all outdated HttpClients. 173 synchronized (this) { 174 long currentTime = System.currentTimeMillis(); 175 List<KeepAliveKey> keysToRemove = new ArrayList<>(); 176 177 for (KeepAliveKey key : keySet()) { 178 ClientVector v = get(key); 179 synchronized (v) { 180 KeepAliveEntry e = v.peek(); 181 while (e != null) { 182 if ((currentTime - e.idleStartTime) > v.nap) { 183 v.poll(); 184 e.hc.closeServer(); 185 } else { 186 break; 187 } 188 e = v.peek(); 189 } 190 191 if (v.isEmpty()) { 192 keysToRemove.add(key); 193 } 194 } 195 } 196 197 for (KeepAliveKey key : keysToRemove) { 198 removeVector(key); 199 } 200 } 201 } while (!isEmpty()); 202 } 203 204 /* 205 * Do not serialize this class! 206 */ 207 @java.io.Serial 208 private void writeObject(ObjectOutputStream stream) throws IOException { 209 throw new NotSerializableException(); 210 } 211 212 @java.io.Serial 213 private void readObject(ObjectInputStream stream) 214 throws IOException, ClassNotFoundException 215 { 216 throw new NotSerializableException(); 217 } 218 } 219 220 /* FILO order for recycling HttpClients, should run in a thread 221 * to time them out. If > maxConns are in use, block. 222 */ 223 class ClientVector extends ArrayDeque<KeepAliveEntry> { 224 @java.io.Serial 225 private static final long serialVersionUID = -8680532108106489459L; 226 227 // sleep time in milliseconds, before cache clear 228 int nap; 229 230 ClientVector(int nap) { 231 this.nap = nap; 232 } 233 234 synchronized HttpClient get() { 235 if (isEmpty()) { 236 return null; 237 } 238 239 // Loop until we find a connection that has not timed out 240 HttpClient hc = null; 241 long currentTime = System.currentTimeMillis(); 242 do { 243 KeepAliveEntry e = pop(); 244 if ((currentTime - e.idleStartTime) > nap) { 245 e.hc.closeServer(); 246 } else { 247 hc = e.hc; 248 } 249 } while ((hc == null) && (!isEmpty())); 250 return hc; 251 } 252 253 /* return a still valid, unused HttpClient */ 254 synchronized void put(HttpClient h) { 255 if (size() >= KeepAliveCache.getMaxConnections()) { 256 h.closeServer(); // otherwise the connection remains in limbo 257 } else { 258 push(new KeepAliveEntry(h, System.currentTimeMillis())); 259 } 260 } 261 262 /* remove an HttpClient */ 263 synchronized boolean remove(HttpClient h) { 264 for (KeepAliveEntry curr : this) { 265 if (curr.hc == h) { 266 return super.remove(curr); 267 } 268 } 269 return false; 270 } 271 272 /* 273 * Do not serialize this class! 274 */ 275 @java.io.Serial 276 private void writeObject(ObjectOutputStream stream) throws IOException { 277 throw new NotSerializableException(); 278 } 279 280 @java.io.Serial 281 private void readObject(ObjectInputStream stream) 282 throws IOException, ClassNotFoundException 283 { 284 throw new NotSerializableException(); 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 }