Skip to content

Commit 6ba9f58

Browse files
Dhamoder Nallajerboaa
Dhamoder Nalla
authored andcommitted
8278067: Make HttpURLConnection default keep alive timeout configurable
Reviewed-by: andrew, sgehwolf Backport-of: d8f44aa39e921594505864e6270f42b745265293
1 parent 147b418 commit 6ba9f58

File tree

4 files changed

+261
-12
lines changed

4 files changed

+261
-12
lines changed

jdk/src/share/classes/java/net/doc-files/net-properties.html

+10
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,16 @@ <H2>Misc HTTP properties</H2>
172172
If HTTP keepalive is enabled (see above) this value determines the
173173
maximum number of idle connections that will be simultaneously kept
174174
alive, per destination.</P>
175+
<LI><P><B>http.keepAlive.time.server</B> and
176+
<B>http.keepAlive.time.proxy</B> </P>
177+
<P>These properties modify the behavior of the HTTP keepalive cache in the case
178+
where the server (or proxy) has not specified a keepalive time. If the
179+
property is set in this case, then idle connections will be closed after the
180+
specified number of seconds. If the property is set, and the server does
181+
specify a keepalive time in a "Keep-Alive" response header, then the time specified
182+
by the server is used. If the property is not set and also the server
183+
does not specify a keepalive time, then connections are kept alive for an
184+
implementation defined time, assuming {@code http.keepAlive} is {@code true}.</P>
175185
<LI><P><B>http.maxRedirects</B> (default: 20)<BR>
176186
This integer value determines the maximum number, for a given request,
177187
of HTTP redirects that will be automatically followed by the

jdk/src/share/classes/sun/net/www/http/HttpClient.java

+12-8
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,13 @@ static private int getDefaultPort(String proto) {
114114
recomputing the value of keepingAlive */
115115
int keepAliveConnections = -1; /* number of keep-alives left */
116116

117-
/**Idle timeout value, in milliseconds. Zero means infinity,
118-
* iff keepingAlive=true.
119-
* Unfortunately, we can't always believe this one. If I'm connected
120-
* through a Netscape proxy to a server that sent me a keep-alive
121-
* time of 15 sec, the proxy unilaterally terminates my connection
122-
* after 5 sec. So we have to hard code our effective timeout to
123-
* 4 sec for the case where we're using a proxy. *SIGH*
117+
/*
118+
* The timeout if specified by the server. Following values possible
119+
* 0: the server specified no keep alive headers
120+
* -1: the server provided "Connection: keep-alive" but did not specify a
121+
* a particular time in a "Keep-Alive:" headers
122+
* Positive values are the number of seconds specified by the server
123+
* in a "Keep-Alive" header
124124
*/
125125
int keepAliveTimeout = 0;
126126

@@ -816,7 +816,7 @@ private boolean parseHTTPHeader(MessageHeader responses, ProgressSource pi, Http
816816
responses.findValue("Keep-Alive"));
817817
/* default should be larger in case of proxy */
818818
keepAliveConnections = p.findInt("max", usingProxy?50:5);
819-
keepAliveTimeout = p.findInt("timeout", usingProxy?60:5);
819+
keepAliveTimeout = p.findInt("timeout", -1);
820820
}
821821
} else if (b[7] != '0') {
822822
/*
@@ -1070,6 +1070,10 @@ public String getProxyHostUsed() {
10701070
}
10711071
}
10721072

1073+
public boolean getUsingProxy() {
1074+
return usingProxy;
1075+
}
1076+
10731077
/**
10741078
* @return the proxy port being used for this client. Meaningless
10751079
* if getProxyHostUsed() gives null.

jdk/src/share/classes/sun/net/www/http/KeepAliveCache.java

+61-4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
import java.util.List;
3939

4040
import sun.security.action.GetIntegerAction;
41+
import sun.net.www.protocol.http.HttpURLConnection;
42+
import sun.util.logging.PlatformLogger;
4143

4244
/**
4345
* A class that implements a cache of idle Http connections for keep-alive
@@ -50,6 +52,32 @@ public class KeepAliveCache
5052
implements Runnable {
5153
private static final long serialVersionUID = -2937172892064557949L;
5254

55+
// Keep alive time set according to priority specified here:
56+
// 1. If server specifies a time with a Keep-Alive header
57+
// 2. If user specifies a time with system property below
58+
// 3. Default values which depend on proxy vs server and whether
59+
// a Connection: keep-alive header was sent by server
60+
61+
// name suffixed with "server" or "proxy"
62+
private static final String keepAliveProp = "http.keepAlive.time.";
63+
64+
private static final int userKeepAliveServer;
65+
private static final int userKeepAliveProxy;
66+
67+
static final PlatformLogger logger = HttpURLConnection.getHttpLogger();
68+
69+
@SuppressWarnings("removal")
70+
static int getUserKeepAliveSeconds(String type) {
71+
int v = AccessController.doPrivileged(
72+
new GetIntegerAction(keepAliveProp+type, -1)).intValue();
73+
return v < -1 ? -1 : v;
74+
}
75+
76+
static {
77+
userKeepAliveServer = getUserKeepAliveSeconds("server");
78+
userKeepAliveProxy = getUserKeepAliveSeconds("proxy");
79+
}
80+
5381
/* maximum # keep-alive connections to maintain at once
5482
* This should be 2 by the HTTP spec, but because we don't support pipe-lining
5583
* a larger value is more appropriate. So we now set a default of 5, and the value
@@ -127,15 +155,39 @@ public Void run() {
127155

128156
if (v == null) {
129157
int keepAliveTimeout = http.getKeepAliveTimeout();
130-
v = new ClientVector(keepAliveTimeout > 0 ?
131-
keepAliveTimeout * 1000 : LIFETIME);
132-
v.put(http);
133-
super.put(key, v);
158+
if (keepAliveTimeout == 0) {
159+
keepAliveTimeout = getUserKeepAlive(http.getUsingProxy());
160+
if (keepAliveTimeout == -1) {
161+
// same default for server and proxy
162+
keepAliveTimeout = 5;
163+
}
164+
} else if (keepAliveTimeout == -1) {
165+
keepAliveTimeout = getUserKeepAlive(http.getUsingProxy());
166+
if (keepAliveTimeout == -1) {
167+
// different default for server and proxy
168+
keepAliveTimeout = http.getUsingProxy() ? 60 : 5;
169+
}
170+
}
171+
// at this point keepAliveTimeout is the number of seconds to keep
172+
// alive, which could be 0, if the user specified 0 for the property
173+
assert keepAliveTimeout >= 0;
174+
if (keepAliveTimeout == 0) {
175+
http.closeServer();
176+
} else {
177+
v = new ClientVector(keepAliveTimeout * 1000);
178+
v.put(http);
179+
super.put(key, v);
180+
}
134181
} else {
135182
v.put(http);
136183
}
137184
}
138185

186+
// returns the keep alive set by user in system property or -1 if not set
187+
private static int getUserKeepAlive(boolean isProxy) {
188+
return isProxy ? userKeepAliveProxy : userKeepAliveServer;
189+
}
190+
139191
/* remove an obsolete HttpClient from its VectorCache */
140192
public synchronized void remove(HttpClient h, Object obj) {
141193
KeepAliveKey key = new KeepAliveKey(h.url, obj);
@@ -252,6 +304,11 @@ synchronized HttpClient get() {
252304
e.hc.closeServer();
253305
} else {
254306
hc = e.hc;
307+
if (KeepAliveCache.logger.isLoggable(PlatformLogger.Level.FINEST)) {
308+
String msg = "cached HttpClient was idle for "
309+
+ Long.toString(currentTime - e.idleStartTime);
310+
KeepAliveCache.logger.finest(msg);
311+
}
255312
}
256313
} while ((hc == null) && (!isEmpty()));
257314
return hc;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/*
2+
* Copyright (c) 2022, 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.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @library /lib/testlibrary
27+
* @bug 8278067
28+
* @run main/othervm -Dhttp.keepAlive.time.server=30 KeepAliveProperty long
29+
* @run main/othervm -Dhttp.keepAlive.time.server=1 KeepAliveProperty short
30+
* @run main/othervm -ea -Dhttp.keepAlive.time.server=0 KeepAliveProperty short
31+
*/
32+
33+
import java.net.*;
34+
import java.io.*;
35+
import java.nio.charset.*;
36+
import java.util.logging.*;
37+
import jdk.testlibrary.net.URIBuilder;
38+
import static java.net.Proxy.NO_PROXY;
39+
40+
public class KeepAliveProperty {
41+
42+
static volatile boolean pass = false;
43+
44+
static class Server extends Thread {
45+
final ServerSocket server;
46+
47+
Server (ServerSocket server) {
48+
super ();
49+
this.server = server;
50+
}
51+
52+
void readAll (Socket s) throws IOException {
53+
byte[] buf = new byte [128];
54+
int c;
55+
String request = "";
56+
InputStream is = s.getInputStream ();
57+
while ((c=is.read(buf)) > 0) {
58+
request += new String(buf, 0, c, StandardCharsets.US_ASCII);
59+
if (request.contains("\r\n\r\n")) {
60+
return;
61+
}
62+
}
63+
if (c == -1)
64+
throw new IOException("Socket closed");
65+
}
66+
67+
Socket s = null;
68+
String BODY;
69+
String CLEN;
70+
PrintStream out;
71+
72+
public void run() {
73+
try {
74+
s = server.accept();
75+
readAll(s);
76+
77+
BODY = "Hello world";
78+
CLEN = "Content-Length: " + BODY.length() + "\r\n";
79+
out = new PrintStream(new BufferedOutputStream(s.getOutputStream() ));
80+
81+
/* send the header */
82+
out.print("HTTP/1.1 200 OK\r\n");
83+
out.print("Content-Type: text/plain; charset=iso-8859-1\r\n");
84+
out.print(CLEN);
85+
out.print("\r\n");
86+
out.print(BODY);
87+
out.flush();
88+
} catch (Exception e) {
89+
pass = false;
90+
try {
91+
if (s != null)
92+
s.close();
93+
server.close();
94+
} catch (IOException unused) {}
95+
return;
96+
}
97+
98+
// second request may legitimately fail
99+
100+
try (Socket s2 = s; ServerSocket server2 = server; PrintStream out2 = out) {
101+
// wait for second request.
102+
readAll(s2);
103+
104+
BODY = "Goodbye world";
105+
CLEN = "Content-Length: " + BODY.length() + "\r\n";
106+
107+
/* send the header */
108+
out2.print("HTTP/1.1 200 OK\r\n");
109+
out2.print("Content-Type: text/plain; charset=iso-8859-1\r\n");
110+
out2.print(CLEN);
111+
out2.print("\r\n");
112+
out2.print(BODY);
113+
out2.flush();
114+
pass = !expectClose;
115+
if (!pass) System.out.println("Failed: expected close");
116+
} catch (Exception e) {
117+
pass = expectClose;
118+
if (!pass) System.out.println("Failed: did not expect close");
119+
}
120+
}
121+
}
122+
123+
static String fetch(URL url) throws Exception {
124+
InputStream in = url.openConnection(NO_PROXY).getInputStream();
125+
String s = "";
126+
byte b[] = new byte[128];
127+
int n;
128+
do {
129+
n = in.read(b);
130+
if (n > 0)
131+
s += new String(b, 0, n, StandardCharsets.US_ASCII);
132+
} while (n > 0);
133+
in.close();
134+
return s;
135+
}
136+
137+
static volatile boolean expectClose;
138+
139+
public static void main(String args[]) throws Exception {
140+
// exercise the logging code
141+
Logger logger = Logger.getLogger("sun.net.www.protocol.http.HttpURLConnection");
142+
logger.setLevel(Level.FINEST);
143+
ConsoleHandler h = new ConsoleHandler();
144+
h.setLevel(Level.FINEST);
145+
logger.addHandler(h);
146+
147+
expectClose = args[0].equals("short");
148+
InetAddress loopback = InetAddress.getLoopbackAddress();
149+
ServerSocket ss = new ServerSocket();
150+
ss.bind(new InetSocketAddress(loopback, 0));
151+
Server s = new Server(ss);
152+
s.start();
153+
154+
URL url = URIBuilder.newBuilder()
155+
.scheme("http")
156+
.loopback()
157+
.port(ss.getLocalPort())
158+
.toURL();
159+
System.out.println("URL: " + url);
160+
161+
if (!fetch(url).equals("Hello world"))
162+
throw new RuntimeException("Failed on first request");
163+
164+
// Wait a while to see if connection is closed
165+
Thread.sleep(3 * 1000);
166+
167+
try {
168+
if (!fetch(url).equals("Goodbye world"))
169+
throw new RuntimeException("Failed on second request");
170+
} catch (Exception e) {
171+
if (!expectClose)
172+
throw e;
173+
}
174+
175+
if (!pass)
176+
throw new RuntimeException("Failed in server");
177+
}
178+
}

0 commit comments

Comments
 (0)