Path: blob/master/src/java.base/share/classes/sun/net/www/http/KeepAliveCache.java
67766 views
/*1* Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.net.www.http;2627import java.io.IOException;28import java.io.NotSerializableException;29import java.io.ObjectInputStream;30import java.io.ObjectOutputStream;31import java.net.URL;32import java.security.AccessController;33import java.security.PrivilegedAction;34import java.util.ArrayDeque;35import java.util.ArrayList;36import java.util.HashMap;37import java.util.List;38import java.util.concurrent.locks.Lock;39import java.util.concurrent.locks.ReentrantLock;4041import jdk.internal.misc.InnocuousThread;42import sun.security.action.GetIntegerAction;43import sun.net.www.protocol.http.HttpURLConnection;44import sun.util.logging.PlatformLogger;4546/**47* A class that implements a cache of idle Http connections for keep-alive48*49* @author Stephen R. Pietrowicz (NCSA)50* @author Dave Brown51*/52public class KeepAliveCache53extends HashMap<KeepAliveKey, ClientVector>54implements Runnable {55@java.io.Serial56private static final long serialVersionUID = -2937172892064557949L;5758// Keep alive time set according to priority specified here:59// 1. If server specifies a time with a Keep-Alive header60// 2. If user specifies a time with system property below61// 3. Default values which depend on proxy vs server and whether62// a Connection: keep-alive header was sent by server6364// name suffixed with "server" or "proxy"65private static final String keepAliveProp = "http.keepAlive.time.";6667private static final int userKeepAliveServer;68private static final int userKeepAliveProxy;6970static final PlatformLogger logger = HttpURLConnection.getHttpLogger();7172@SuppressWarnings("removal")73static int getUserKeepAliveSeconds(String type) {74int v = AccessController.doPrivileged(75new GetIntegerAction(keepAliveProp+type, -1)).intValue();76return v < -1 ? -1 : v;77}7879static {80userKeepAliveServer = getUserKeepAliveSeconds("server");81userKeepAliveProxy = getUserKeepAliveSeconds("proxy");82}8384/* maximum # keep-alive connections to maintain at once85* This should be 2 by the HTTP spec, but because we don't support pipe-lining86* a larger value is more appropriate. So we now set a default of 5, and the value87* refers to the number of idle connections per destination (in the cache) only.88* It can be reset by setting system property "http.maxConnections".89*/90static final int MAX_CONNECTIONS = 5;91static int result = -1;92@SuppressWarnings("removal")93static int getMaxConnections() {94if (result == -1) {95result = AccessController.doPrivileged(96new GetIntegerAction("http.maxConnections", MAX_CONNECTIONS))97.intValue();98if (result <= 0) {99result = MAX_CONNECTIONS;100}101}102return result;103}104105static final int LIFETIME = 5000;106107// This class is never serialized (see writeObject/readObject).108private final ReentrantLock cacheLock = new ReentrantLock();109private Thread keepAliveTimer = null;110111/**112* Constructor113*/114public KeepAliveCache() {}115116/**117* Register this URL and HttpClient (that supports keep-alive) with the cache118* @param url The URL contains info about the host and port119* @param http The HttpClient to be cached120*/121@SuppressWarnings("removal")122public void put(final URL url, Object obj, HttpClient http) {123cacheLock.lock();124try {125boolean startThread = (keepAliveTimer == null);126if (!startThread) {127if (!keepAliveTimer.isAlive()) {128startThread = true;129}130}131if (startThread) {132clear();133/* Unfortunately, we can't always believe the keep-alive timeout we got134* back from the server. If I'm connected through a Netscape proxy135* to a server that sent me a keep-alive136* time of 15 sec, the proxy unilaterally terminates my connection137* The robustness to get around this is in HttpClient.parseHTTP()138*/139final KeepAliveCache cache = this;140AccessController.doPrivileged(new PrivilegedAction<>() {141public Void run() {142keepAliveTimer = InnocuousThread.newSystemThread("Keep-Alive-Timer", cache);143keepAliveTimer.setDaemon(true);144keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2);145keepAliveTimer.start();146return null;147}148});149}150151KeepAliveKey key = new KeepAliveKey(url, obj);152ClientVector v = super.get(key);153154if (v == null) {155int keepAliveTimeout = http.getKeepAliveTimeout();156if (keepAliveTimeout == 0) {157keepAliveTimeout = getUserKeepAlive(http.getUsingProxy());158if (keepAliveTimeout == -1) {159// same default for server and proxy160keepAliveTimeout = 5;161}162} else if (keepAliveTimeout == -1) {163keepAliveTimeout = getUserKeepAlive(http.getUsingProxy());164if (keepAliveTimeout == -1) {165// different default for server and proxy166keepAliveTimeout = http.getUsingProxy() ? 60 : 5;167}168}169// at this point keepAliveTimeout is the number of seconds to keep170// alive, which could be 0, if the user specified 0 for the property171assert keepAliveTimeout >= 0;172if (keepAliveTimeout == 0) {173http.closeServer();174} else {175v = new ClientVector(keepAliveTimeout * 1000);176v.put(http);177super.put(key, v);178}179} else {180v.put(http);181}182} finally {183cacheLock.unlock();184}185}186187// returns the keep alive set by user in system property or -1 if not set188private static int getUserKeepAlive(boolean isProxy) {189return isProxy ? userKeepAliveProxy : userKeepAliveServer;190}191192/* remove an obsolete HttpClient from its VectorCache */193public void remove(HttpClient h, Object obj) {194cacheLock.lock();195try {196KeepAliveKey key = new KeepAliveKey(h.url, obj);197ClientVector v = super.get(key);198if (v != null) {199v.remove(h);200if (v.isEmpty()) {201removeVector(key);202}203}204} finally {205cacheLock.unlock();206}207}208209/* called by a clientVector thread when all its connections have timed out210* and that vector of connections should be removed.211*/212private void removeVector(KeepAliveKey k) {213assert cacheLock.isHeldByCurrentThread();214super.remove(k);215}216217/**218* Check to see if this URL has a cached HttpClient219*/220public HttpClient get(URL url, Object obj) {221cacheLock.lock();222try {223KeepAliveKey key = new KeepAliveKey(url, obj);224ClientVector v = super.get(key);225if (v == null) { // nothing in cache yet226return null;227}228return v.get();229} finally {230cacheLock.unlock();231}232}233234/* Sleeps for an alloted timeout, then checks for timed out connections.235* Errs on the side of caution (leave connections idle for a relatively236* short time).237*/238@Override239public void run() {240do {241try {242Thread.sleep(LIFETIME);243} catch (InterruptedException e) {}244245// Remove all outdated HttpClients.246cacheLock.lock();247try {248long currentTime = System.currentTimeMillis();249List<KeepAliveKey> keysToRemove = new ArrayList<>();250251for (KeepAliveKey key : keySet()) {252ClientVector v = get(key);253v.lock();254try {255KeepAliveEntry e = v.peek();256while (e != null) {257if ((currentTime - e.idleStartTime) > v.nap) {258v.poll();259e.hc.closeServer();260} else {261break;262}263e = v.peek();264}265266if (v.isEmpty()) {267keysToRemove.add(key);268}269} finally {270v.unlock();271}272}273274for (KeepAliveKey key : keysToRemove) {275removeVector(key);276}277} finally {278cacheLock.unlock();279}280} while (!isEmpty());281}282283/*284* Do not serialize this class!285*/286@java.io.Serial287private void writeObject(ObjectOutputStream stream) throws IOException {288throw new NotSerializableException();289}290291@java.io.Serial292private void readObject(ObjectInputStream stream)293throws IOException, ClassNotFoundException294{295throw new NotSerializableException();296}297}298299/* FILO order for recycling HttpClients, should run in a thread300* to time them out. If > maxConns are in use, block.301*/302class ClientVector extends ArrayDeque<KeepAliveEntry> {303@java.io.Serial304private static final long serialVersionUID = -8680532108106489459L;305private final ReentrantLock lock = new ReentrantLock();306307// sleep time in milliseconds, before cache clear308int nap;309310ClientVector(int nap) {311this.nap = nap;312}313314HttpClient get() {315lock();316try {317if (isEmpty()) {318return null;319}320321// Loop until we find a connection that has not timed out322HttpClient hc = null;323long currentTime = System.currentTimeMillis();324do {325KeepAliveEntry e = pop();326if ((currentTime - e.idleStartTime) > nap) {327e.hc.closeServer();328} else {329hc = e.hc;330if (KeepAliveCache.logger.isLoggable(PlatformLogger.Level.FINEST)) {331String msg = "cached HttpClient was idle for "332+ Long.toString(currentTime - e.idleStartTime);333KeepAliveCache.logger.finest(msg);334}335}336} while ((hc == null) && (!isEmpty()));337return hc;338} finally {339unlock();340}341}342343/* return a still valid, unused HttpClient */344void put(HttpClient h) {345lock();346try {347if (size() >= KeepAliveCache.getMaxConnections()) {348h.closeServer(); // otherwise the connection remains in limbo349} else {350push(new KeepAliveEntry(h, System.currentTimeMillis()));351}352} finally {353unlock();354}355}356357/* remove an HttpClient */358boolean remove(HttpClient h) {359lock();360try {361for (KeepAliveEntry curr : this) {362if (curr.hc == h) {363return super.remove(curr);364}365}366return false;367} finally {368unlock();369}370}371372final void lock() {373lock.lock();374}375376final void unlock() {377lock.unlock();378}379380/*381* Do not serialize this class!382*/383@java.io.Serial384private void writeObject(ObjectOutputStream stream) throws IOException {385throw new NotSerializableException();386}387388@java.io.Serial389private void readObject(ObjectInputStream stream)390throws IOException, ClassNotFoundException391{392throw new NotSerializableException();393}394}395396class KeepAliveKey {397private String protocol = null;398private String host = null;399private int port = 0;400private Object obj = null; // additional key, such as socketfactory401402/**403* Constructor404*405* @param url the URL containing the protocol, host and port information406*/407public KeepAliveKey(URL url, Object obj) {408this.protocol = url.getProtocol();409this.host = url.getHost();410this.port = url.getPort();411this.obj = obj;412}413414/**415* Determine whether or not two objects of this type are equal416*/417@Override418public boolean equals(Object obj) {419if ((obj instanceof KeepAliveKey) == false)420return false;421KeepAliveKey kae = (KeepAliveKey)obj;422return host.equals(kae.host)423&& (port == kae.port)424&& protocol.equals(kae.protocol)425&& this.obj == kae.obj;426}427428/**429* The hashCode() for this object is the string hashCode() of430* concatenation of the protocol, host name and port.431*/432@Override433public int hashCode() {434String str = protocol+host+port;435return this.obj == null? str.hashCode() :436str.hashCode() + this.obj.hashCode();437}438}439440class KeepAliveEntry {441HttpClient hc;442long idleStartTime;443444KeepAliveEntry(HttpClient hc, long idleStartTime) {445this.hc = hc;446this.idleStartTime = idleStartTime;447}448}449450451