| /* |
| * Copyright 1996-2006 Sun Microsystems, Inc. All Rights Reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Sun designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Sun in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| * CA 95054 USA or visit www.sun.com if you need additional information or |
| * have any questions. |
| */ |
| |
| package sun.net.www.http; |
| |
| import java.io.InputStream; |
| import java.io.IOException; |
| import java.io.NotSerializableException; |
| import java.util.*; |
| import java.net.URL; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| /** |
| * A class that implements a cache of idle Http connections for keep-alive |
| * |
| * @author Stephen R. Pietrowicz (NCSA) |
| * @author Dave Brown |
| */ |
| public class KeepAliveCache extends ConcurrentHashMap implements Runnable { |
| private static final long serialVersionUID = -2937172892064557949L; |
| |
| /* maximum # keep-alive connections to maintain at once |
| * This should be 2 by the HTTP spec, but because we don't support pipe-lining |
| * a larger value is more appropriate. So we now set a default of 5, and the value |
| * refers to the number of idle connections per destination (in the cache) only. |
| * It can be reset by setting system property "http.maxConnections". |
| */ |
| static final int MAX_CONNECTIONS = 5; |
| static int result = -1; |
| static int getMaxConnections() { |
| if (result == -1) { |
| result = java.security.AccessController.doPrivileged( |
| new sun.security.action.GetIntegerAction("http.maxConnections", |
| MAX_CONNECTIONS)) |
| .intValue(); |
| if (result <= 0) |
| result = MAX_CONNECTIONS; |
| } |
| return result; |
| } |
| |
| static final int LIFETIME = 5000; |
| |
| private Thread keepAliveTimer = null; |
| |
| /** |
| * Constructor |
| */ |
| public KeepAliveCache() {} |
| |
| /** |
| * Register this URL and HttpClient (that supports keep-alive) with the cache |
| * @param url The URL contains info about the host and port |
| * @param http The HttpClient to be cached |
| */ |
| public synchronized void put(final URL url, Object obj, HttpClient http) { |
| boolean startThread = (keepAliveTimer == null); |
| if (!startThread) { |
| if (!keepAliveTimer.isAlive()) { |
| startThread = true; |
| } |
| } |
| if (startThread) { |
| clear(); |
| /* Unfortunately, we can't always believe the keep-alive timeout we got |
| * back from the server. If I'm connected through a Netscape proxy |
| * to a server that sent me a keep-alive |
| * time of 15 sec, the proxy unilaterally terminates my connection |
| * The robustness to to get around this is in HttpClient.parseHTTP() |
| */ |
| final KeepAliveCache cache = this; |
| java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction() { |
| public Object run() { |
| // We want to create the Keep-Alive-Timer in the |
| // system threadgroup |
| ThreadGroup grp = Thread.currentThread().getThreadGroup(); |
| ThreadGroup parent = null; |
| while ((parent = grp.getParent()) != null) { |
| grp = parent; |
| } |
| |
| keepAliveTimer = new Thread(grp, cache, "Keep-Alive-Timer"); |
| keepAliveTimer.setDaemon(true); |
| keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2); |
| keepAliveTimer.start(); |
| return null; |
| } |
| }); |
| } |
| |
| KeepAliveKey key = new KeepAliveKey(url, obj); |
| ClientVector v = (ClientVector)super.get(key); |
| |
| if (v == null) { |
| int keepAliveTimeout = http.getKeepAliveTimeout(); |
| v = new ClientVector(keepAliveTimeout > 0? |
| keepAliveTimeout*1000 : LIFETIME); |
| v.put(http); |
| super.put(key, v); |
| } else { |
| v.put(http); |
| } |
| } |
| |
| /* remove an obsolete HttpClient from it's VectorCache */ |
| public synchronized void remove (HttpClient h, Object obj) { |
| KeepAliveKey key = new KeepAliveKey(h.url, obj); |
| ClientVector v = (ClientVector)super.get(key); |
| if (v != null) { |
| v.remove(h); |
| if (v.empty()) { |
| removeVector(key); |
| } |
| } |
| } |
| |
| /* called by a clientVector thread when all it's connections have timed out |
| * and that vector of connections should be removed. |
| */ |
| synchronized void removeVector(KeepAliveKey k) { |
| super.remove(k); |
| } |
| |
| /** |
| * Check to see if this URL has a cached HttpClient |
| */ |
| public synchronized Object get(URL url, Object obj) { |
| |
| KeepAliveKey key = new KeepAliveKey(url, obj); |
| ClientVector v = (ClientVector)super.get(key); |
| if (v == null) { // nothing in cache yet |
| return null; |
| } |
| return v.get(); |
| } |
| |
| /* Sleeps for an alloted timeout, then checks for timed out connections. |
| * Errs on the side of caution (leave connections idle for a relatively |
| * short time). |
| */ |
| public void run() { |
| int total_cache; |
| do { |
| try { |
| Thread.sleep(LIFETIME); |
| } catch (InterruptedException e) {} |
| synchronized (this) { |
| /* Remove all unused HttpClients. Starting from the |
| * bottom of the stack (the least-recently used first). |
| * REMIND: It'd be nice to not remove *all* connections |
| * that aren't presently in use. One could have been added |
| * a second ago that's still perfectly valid, and we're |
| * needlessly axing it. But it's not clear how to do this |
| * cleanly, and doing it right may be more trouble than it's |
| * worth. |
| */ |
| |
| long currentTime = System.currentTimeMillis(); |
| |
| Iterator itr = keySet().iterator(); |
| ArrayList keysToRemove = new ArrayList(); |
| |
| while (itr.hasNext()) { |
| KeepAliveKey key = (KeepAliveKey)itr.next(); |
| ClientVector v = (ClientVector)get(key); |
| synchronized (v) { |
| int i; |
| |
| for (i = 0; i < v.size(); i++) { |
| KeepAliveEntry e = (KeepAliveEntry)v.elementAt(i); |
| if ((currentTime - e.idleStartTime) > v.nap) { |
| HttpClient h = e.hc; |
| h.closeServer(); |
| } else { |
| break; |
| } |
| } |
| v.subList(0, i).clear(); |
| |
| if (v.size() == 0) { |
| keysToRemove.add(key); |
| } |
| } |
| } |
| itr = keysToRemove.iterator(); |
| while (itr.hasNext()) { |
| removeVector((KeepAliveKey)itr.next()); |
| } |
| } |
| } while (size() > 0); |
| |
| return; |
| } |
| |
| /* |
| * Do not serialize this class! |
| */ |
| private void writeObject(java.io.ObjectOutputStream stream) |
| throws IOException { |
| throw new NotSerializableException(); |
| } |
| |
| private void readObject(java.io.ObjectInputStream stream) |
| throws IOException, ClassNotFoundException { |
| throw new NotSerializableException(); |
| } |
| } |
| |
| /* FILO order for recycling HttpClients, should run in a thread |
| * to time them out. If > maxConns are in use, block. |
| */ |
| |
| |
| class ClientVector extends java.util.Stack { |
| private static final long serialVersionUID = -8680532108106489459L; |
| |
| // sleep time in milliseconds, before cache clear |
| int nap; |
| |
| |
| |
| ClientVector (int nap) { |
| this.nap = nap; |
| } |
| |
| synchronized HttpClient get() { |
| if (empty()) { |
| return null; |
| } else { |
| // Loop until we find a connection that has not timed out |
| HttpClient hc = null; |
| long currentTime = System.currentTimeMillis(); |
| do { |
| KeepAliveEntry e = (KeepAliveEntry)pop(); |
| if ((currentTime - e.idleStartTime) > nap) { |
| e.hc.closeServer(); |
| } else { |
| hc = e.hc; |
| } |
| } while ((hc== null) && (!empty())); |
| return hc; |
| } |
| } |
| |
| /* return a still valid, unused HttpClient */ |
| synchronized void put(HttpClient h) { |
| if (size() > KeepAliveCache.getMaxConnections()) { |
| h.closeServer(); // otherwise the connection remains in limbo |
| } else { |
| push(new KeepAliveEntry(h, System.currentTimeMillis())); |
| } |
| } |
| |
| /* |
| * Do not serialize this class! |
| */ |
| private void writeObject(java.io.ObjectOutputStream stream) |
| throws IOException { |
| throw new NotSerializableException(); |
| } |
| |
| private void readObject(java.io.ObjectInputStream stream) |
| throws IOException, ClassNotFoundException { |
| throw new NotSerializableException(); |
| } |
| } |
| |
| |
| class KeepAliveKey { |
| private String protocol = null; |
| private String host = null; |
| private int port = 0; |
| private Object obj = null; // additional key, such as socketfactory |
| |
| /** |
| * Constructor |
| * |
| * @param url the URL containing the protocol, host and port information |
| */ |
| public KeepAliveKey(URL url, Object obj) { |
| this.protocol = url.getProtocol(); |
| this.host = url.getHost(); |
| this.port = url.getPort(); |
| this.obj = obj; |
| } |
| |
| /** |
| * Determine whether or not two objects of this type are equal |
| */ |
| public boolean equals(Object obj) { |
| if ((obj instanceof KeepAliveKey) == false) |
| return false; |
| KeepAliveKey kae = (KeepAliveKey)obj; |
| return host.equals(kae.host) |
| && (port == kae.port) |
| && protocol.equals(kae.protocol) |
| && this.obj == kae.obj; |
| } |
| |
| /** |
| * The hashCode() for this object is the string hashCode() of |
| * concatenation of the protocol, host name and port. |
| */ |
| public int hashCode() { |
| String str = protocol+host+port; |
| return this.obj == null? str.hashCode() : |
| str.hashCode() + this.obj.hashCode(); |
| } |
| } |
| |
| class KeepAliveEntry { |
| HttpClient hc; |
| long idleStartTime; |
| |
| KeepAliveEntry(HttpClient hc, long idleStartTime) { |
| this.hc = hc; |
| this.idleStartTime = idleStartTime; |
| } |
| } |