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 }