View Javadoc
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 }