|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.net.www.http; |
|
|
|
import java.io.IOException; |
|
import java.io.NotSerializableException; |
|
import java.io.ObjectInputStream; |
|
import java.io.ObjectOutputStream; |
|
import java.net.URL; |
|
import java.security.AccessController; |
|
import java.security.PrivilegedAction; |
|
import java.util.ArrayDeque; |
|
import java.util.ArrayList; |
|
import java.util.HashMap; |
|
import java.util.List; |
|
import java.util.concurrent.locks.Lock; |
|
import java.util.concurrent.locks.ReentrantLock; |
|
|
|
import jdk.internal.misc.InnocuousThread; |
|
import sun.security.action.GetIntegerAction; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public class KeepAliveCache |
|
extends HashMap<KeepAliveKey, ClientVector> |
|
implements Runnable { |
|
@java.io.Serial |
|
private static final long serialVersionUID = -2937172892064557949L; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static final int MAX_CONNECTIONS = 5; |
|
static int result = -1; |
|
@SuppressWarnings("removal") |
|
static int getMaxConnections() { |
|
if (result == -1) { |
|
result = AccessController.doPrivileged( |
|
new GetIntegerAction("http.maxConnections", MAX_CONNECTIONS)) |
|
.intValue(); |
|
if (result <= 0) { |
|
result = MAX_CONNECTIONS; |
|
} |
|
} |
|
return result; |
|
} |
|
|
|
static final int LIFETIME = 5000; |
|
|
|
|
|
private final ReentrantLock cacheLock = new ReentrantLock(); |
|
private Thread keepAliveTimer = null; |
|
|
|
|
|
|
|
*/ |
|
public KeepAliveCache() {} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@SuppressWarnings("removal") |
|
public void put(final URL url, Object obj, HttpClient http) { |
|
cacheLock.lock(); |
|
try { |
|
boolean startThread = (keepAliveTimer == null); |
|
if (!startThread) { |
|
if (!keepAliveTimer.isAlive()) { |
|
startThread = true; |
|
} |
|
} |
|
if (startThread) { |
|
clear(); |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
final KeepAliveCache cache = this; |
|
AccessController.doPrivileged(new PrivilegedAction<>() { |
|
public Void run() { |
|
keepAliveTimer = InnocuousThread.newSystemThread("Keep-Alive-Timer", cache); |
|
keepAliveTimer.setDaemon(true); |
|
keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2); |
|
keepAliveTimer.start(); |
|
return null; |
|
} |
|
}); |
|
} |
|
|
|
KeepAliveKey key = new KeepAliveKey(url, obj); |
|
ClientVector v = 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); |
|
} |
|
} finally { |
|
cacheLock.unlock(); |
|
} |
|
} |
|
|
|
|
|
public void remove(HttpClient h, Object obj) { |
|
cacheLock.lock(); |
|
try { |
|
KeepAliveKey key = new KeepAliveKey(h.url, obj); |
|
ClientVector v = super.get(key); |
|
if (v != null) { |
|
v.remove(h); |
|
if (v.isEmpty()) { |
|
removeVector(key); |
|
} |
|
} |
|
} finally { |
|
cacheLock.unlock(); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private void removeVector(KeepAliveKey k) { |
|
assert cacheLock.isHeldByCurrentThread(); |
|
super.remove(k); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public HttpClient get(URL url, Object obj) { |
|
cacheLock.lock(); |
|
try { |
|
KeepAliveKey key = new KeepAliveKey(url, obj); |
|
ClientVector v = super.get(key); |
|
if (v == null) { |
|
return null; |
|
} |
|
return v.get(); |
|
} finally { |
|
cacheLock.unlock(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public void run() { |
|
do { |
|
try { |
|
Thread.sleep(LIFETIME); |
|
} catch (InterruptedException e) {} |
|
|
|
|
|
cacheLock.lock(); |
|
try { |
|
long currentTime = System.currentTimeMillis(); |
|
List<KeepAliveKey> keysToRemove = new ArrayList<>(); |
|
|
|
for (KeepAliveKey key : keySet()) { |
|
ClientVector v = get(key); |
|
v.lock(); |
|
try { |
|
KeepAliveEntry e = v.peek(); |
|
while (e != null) { |
|
if ((currentTime - e.idleStartTime) > v.nap) { |
|
v.poll(); |
|
e.hc.closeServer(); |
|
} else { |
|
break; |
|
} |
|
e = v.peek(); |
|
} |
|
|
|
if (v.isEmpty()) { |
|
keysToRemove.add(key); |
|
} |
|
} finally { |
|
v.unlock(); |
|
} |
|
} |
|
|
|
for (KeepAliveKey key : keysToRemove) { |
|
removeVector(key); |
|
} |
|
} finally { |
|
cacheLock.unlock(); |
|
} |
|
} while (!isEmpty()); |
|
} |
|
|
|
|
|
|
|
*/ |
|
@java.io.Serial |
|
private void writeObject(ObjectOutputStream stream) throws IOException { |
|
throw new NotSerializableException(); |
|
} |
|
|
|
@java.io.Serial |
|
private void readObject(ObjectInputStream stream) |
|
throws IOException, ClassNotFoundException |
|
{ |
|
throw new NotSerializableException(); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
class ClientVector extends ArrayDeque<KeepAliveEntry> { |
|
@java.io.Serial |
|
private static final long serialVersionUID = -8680532108106489459L; |
|
private final ReentrantLock lock = new ReentrantLock(); |
|
|
|
|
|
int nap; |
|
|
|
ClientVector(int nap) { |
|
this.nap = nap; |
|
} |
|
|
|
HttpClient get() { |
|
lock(); |
|
try { |
|
if (isEmpty()) { |
|
return null; |
|
} |
|
|
|
|
|
HttpClient hc = null; |
|
long currentTime = System.currentTimeMillis(); |
|
do { |
|
KeepAliveEntry e = pop(); |
|
if ((currentTime - e.idleStartTime) > nap) { |
|
e.hc.closeServer(); |
|
} else { |
|
hc = e.hc; |
|
} |
|
} while ((hc == null) && (!isEmpty())); |
|
return hc; |
|
} finally { |
|
unlock(); |
|
} |
|
} |
|
|
|
|
|
void put(HttpClient h) { |
|
lock(); |
|
try { |
|
if (size() >= KeepAliveCache.getMaxConnections()) { |
|
h.closeServer(); |
|
} else { |
|
push(new KeepAliveEntry(h, System.currentTimeMillis())); |
|
} |
|
} finally { |
|
unlock(); |
|
} |
|
} |
|
|
|
|
|
boolean remove(HttpClient h) { |
|
lock(); |
|
try { |
|
for (KeepAliveEntry curr : this) { |
|
if (curr.hc == h) { |
|
return super.remove(curr); |
|
} |
|
} |
|
return false; |
|
} finally { |
|
unlock(); |
|
} |
|
} |
|
|
|
final void lock() { |
|
lock.lock(); |
|
} |
|
|
|
final void unlock() { |
|
lock.unlock(); |
|
} |
|
|
|
|
|
|
|
*/ |
|
@java.io.Serial |
|
private void writeObject(ObjectOutputStream stream) throws IOException { |
|
throw new NotSerializableException(); |
|
} |
|
|
|
@java.io.Serial |
|
private void readObject(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; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public KeepAliveKey(URL url, Object obj) { |
|
this.protocol = url.getProtocol(); |
|
this.host = url.getHost(); |
|
this.port = url.getPort(); |
|
this.obj = obj; |
|
} |
|
|
|
|
|
|
|
*/ |
|
@Override |
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
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; |
|
} |
|
} |