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