Home | History | Annotate | Download | only in http
      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 }
    348