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
26package sun.net.www.http;
27
28import java.io.IOException;
29import java.io.NotSerializableException;
30import java.util.ArrayList;
31import java.util.HashMap;
32import 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 */
40public 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
240class 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
295class 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
339class 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