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;
33import jdk.internal.misc.InnocuousThread;
34
35/**
36 * A class that implements a cache of idle Http connections for keep-alive
37 *
38 * @author Stephen R. Pietrowicz (NCSA)
39 * @author Dave Brown
40 */
41public class KeepAliveCache
42    extends HashMap<KeepAliveKey, ClientVector>
43    implements Runnable {
44    private static final long serialVersionUID = -2937172892064557949L;
45
46    /* maximum # keep-alive connections to maintain at once
47     * This should be 2 by the HTTP spec, but because we don't support pipe-lining
48     * a larger value is more appropriate. So we now set a default of 5, and the value
49     * refers to the number of idle connections per destination (in the cache) only.
50     * It can be reset by setting system property "http.maxConnections".
51     */
52    static final int MAX_CONNECTIONS = 5;
53    static int result = -1;
54    static int getMaxConnections() {
55        if (result == -1) {
56            result = java.security.AccessController.doPrivileged(
57                new sun.security.action.GetIntegerAction("http.maxConnections",
58                                                         MAX_CONNECTIONS))
59                .intValue();
60            if (result <= 0)
61                result = MAX_CONNECTIONS;
62        }
63            return result;
64    }
65
66    static final int LIFETIME = 5000;
67
68    private Thread keepAliveTimer = null;
69
70    /**
71     * Constructor
72     */
73    public KeepAliveCache() {}
74
75    /**
76     * Register this URL and HttpClient (that supports keep-alive) with the cache
77     * @param url  The URL contains info about the host and port
78     * @param http The HttpClient to be cached
79     */
80    public synchronized void put(final URL url, Object obj, HttpClient http) {
81        boolean startThread = (keepAliveTimer == null);
82        if (!startThread) {
83            if (!keepAliveTimer.isAlive()) {
84                startThread = true;
85            }
86        }
87        if (startThread) {
88            clear();
89            /* Unfortunately, we can't always believe the keep-alive timeout we got
90             * back from the server.  If I'm connected through a Netscape proxy
91             * to a server that sent me a keep-alive
92             * time of 15 sec, the proxy unilaterally terminates my connection
93             * The robustness to get around this is in HttpClient.parseHTTP()
94             */
95            final KeepAliveCache cache = this;
96            java.security.AccessController.doPrivileged(
97                new java.security.PrivilegedAction<>() {
98                public Void run() {
99                    keepAliveTimer = InnocuousThread.newSystemThread("Keep-Alive-Timer", cache);
100                    keepAliveTimer.setDaemon(true);
101                    keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2);
102                    keepAliveTimer.start();
103                    return null;
104                }
105            });
106        }
107
108        KeepAliveKey key = new KeepAliveKey(url, obj);
109        ClientVector v = super.get(key);
110
111        if (v == null) {
112            int keepAliveTimeout = http.getKeepAliveTimeout();
113            v = new ClientVector(keepAliveTimeout > 0?
114                                 keepAliveTimeout*1000 : LIFETIME);
115            v.put(http);
116            super.put(key, v);
117        } else {
118            v.put(http);
119        }
120    }
121
122    /* remove an obsolete HttpClient from its VectorCache */
123    public synchronized void remove (HttpClient h, Object obj) {
124        KeepAliveKey key = new KeepAliveKey(h.url, obj);
125        ClientVector v = super.get(key);
126        if (v != null) {
127            v.remove(h);
128            if (v.empty()) {
129                removeVector(key);
130            }
131        }
132    }
133
134    /* called by a clientVector thread when all its connections have timed out
135     * and that vector of connections should be removed.
136     */
137    synchronized void removeVector(KeepAliveKey k) {
138        super.remove(k);
139    }
140
141    /**
142     * Check to see if this URL has a cached HttpClient
143     */
144    public synchronized HttpClient get(URL url, Object obj) {
145
146        KeepAliveKey key = new KeepAliveKey(url, obj);
147        ClientVector v = super.get(key);
148        if (v == null) { // nothing in cache yet
149            return null;
150        }
151        return v.get();
152    }
153
154    /* Sleeps for an alloted timeout, then checks for timed out connections.
155     * Errs on the side of caution (leave connections idle for a relatively
156     * short time).
157     */
158    @Override
159    public void run() {
160        do {
161            try {
162                Thread.sleep(LIFETIME);
163            } catch (InterruptedException e) {}
164            synchronized (this) {
165                /* Remove all unused HttpClients.  Starting from the
166                 * bottom of the stack (the least-recently used first).
167                 * REMIND: It'd be nice to not remove *all* connections
168                 * that aren't presently in use.  One could have been added
169                 * a second ago that's still perfectly valid, and we're
170                 * needlessly axing it.  But it's not clear how to do this
171                 * cleanly, and doing it right may be more trouble than it's
172                 * worth.
173                 */
174
175                long currentTime = System.currentTimeMillis();
176
177                ArrayList<KeepAliveKey> keysToRemove
178                    = new ArrayList<>();
179
180                for (KeepAliveKey key : keySet()) {
181                    ClientVector v = get(key);
182                    synchronized (v) {
183                        int i;
184
185                        for (i = 0; i < v.size(); i++) {
186                            KeepAliveEntry e = v.elementAt(i);
187                            if ((currentTime - e.idleStartTime) > v.nap) {
188                                HttpClient h = e.hc;
189                                h.closeServer();
190                            } else {
191                                break;
192                            }
193                        }
194                        v.subList(0, i).clear();
195
196                        if (v.size() == 0) {
197                            keysToRemove.add(key);
198                        }
199                    }
200                }
201
202                for (KeepAliveKey key : keysToRemove) {
203                    removeVector(key);
204                }
205            }
206        } while (size() > 0);
207
208        return;
209    }
210
211    /*
212     * Do not serialize this class!
213     */
214    private void writeObject(java.io.ObjectOutputStream stream)
215    throws IOException {
216        throw new NotSerializableException();
217    }
218
219    private void readObject(java.io.ObjectInputStream stream)
220    throws IOException, ClassNotFoundException {
221        throw new NotSerializableException();
222    }
223}
224
225/* FILO order for recycling HttpClients, should run in a thread
226 * to time them out.  If > maxConns are in use, block.
227 */
228
229
230class ClientVector extends java.util.Stack<KeepAliveEntry> {
231    private static final long serialVersionUID = -8680532108106489459L;
232
233    // sleep time in milliseconds, before cache clear
234    int nap;
235
236
237
238    ClientVector (int nap) {
239        this.nap = nap;
240    }
241
242    synchronized HttpClient get() {
243        if (empty()) {
244            return null;
245        } else {
246            // Loop until we find a connection that has not timed out
247            HttpClient hc = null;
248            long currentTime = System.currentTimeMillis();
249            do {
250                KeepAliveEntry e = pop();
251                if ((currentTime - e.idleStartTime) > nap) {
252                    e.hc.closeServer();
253                } else {
254                    hc = e.hc;
255                }
256            } while ((hc== null) && (!empty()));
257            return hc;
258        }
259    }
260
261    /* return a still valid, unused HttpClient */
262    synchronized void put(HttpClient h) {
263        if (size() >= KeepAliveCache.getMaxConnections()) {
264            h.closeServer(); // otherwise the connection remains in limbo
265        } else {
266            push(new KeepAliveEntry(h, System.currentTimeMillis()));
267        }
268    }
269
270    /*
271     * Do not serialize this class!
272     */
273    private void writeObject(java.io.ObjectOutputStream stream)
274    throws IOException {
275        throw new NotSerializableException();
276    }
277
278    private void readObject(java.io.ObjectInputStream stream)
279    throws IOException, ClassNotFoundException {
280        throw new NotSerializableException();
281    }
282}
283
284
285class KeepAliveKey {
286    private String      protocol = null;
287    private String      host = null;
288    private int         port = 0;
289    private Object      obj = null; // additional key, such as socketfactory
290
291    /**
292     * Constructor
293     *
294     * @param url the URL containing the protocol, host and port information
295     */
296    public KeepAliveKey(URL url, Object obj) {
297        this.protocol = url.getProtocol();
298        this.host = url.getHost();
299        this.port = url.getPort();
300        this.obj = obj;
301    }
302
303    /**
304     * Determine whether or not two objects of this type are equal
305     */
306    @Override
307    public boolean equals(Object obj) {
308        if ((obj instanceof KeepAliveKey) == false)
309            return false;
310        KeepAliveKey kae = (KeepAliveKey)obj;
311        return host.equals(kae.host)
312            && (port == kae.port)
313            && protocol.equals(kae.protocol)
314            && this.obj == kae.obj;
315    }
316
317    /**
318     * The hashCode() for this object is the string hashCode() of
319     * concatenation of the protocol, host name and port.
320     */
321    @Override
322    public int hashCode() {
323        String str = protocol+host+port;
324        return this.obj == null? str.hashCode() :
325            str.hashCode() + this.obj.hashCode();
326    }
327}
328
329class KeepAliveEntry {
330    HttpClient hc;
331    long idleStartTime;
332
333    KeepAliveEntry(HttpClient hc, long idleStartTime) {
334        this.hc = hc;
335        this.idleStartTime = idleStartTime;
336    }
337}
338