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 }