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 }