16c7dcdb04
Reviewed-by: djelinski
336 lines
15 KiB
Java
336 lines
15 KiB
Java
/*
|
|
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. 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.
|
|
*
|
|
* 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
/*
|
|
* @test
|
|
* @library /test/lib
|
|
* @bug 8291226 8291638 8330523
|
|
* @modules java.base/sun.net.www.http:+open
|
|
* java.base/sun.net.www.protocol.http:+open
|
|
* @run main/othervm KeepAliveTest
|
|
* @run main/othervm -Dhttp.keepAlive.time.server=100 KeepAliveTest
|
|
* @run main/othervm -Dhttp.keepAlive.time.proxy=200 KeepAliveTest
|
|
* @run main/othervm -Dhttp.keepAlive.time.server=100 -Dhttp.keepAlive.time.proxy=200 KeepAliveTest
|
|
* @run main/othervm -Dhttp.keepAlive.time.server=-100 KeepAliveTest
|
|
* @run main/othervm -Dhttp.keepAlive.time.proxy=-200 KeepAliveTest
|
|
* @run main/othervm -Dhttp.keepAlive.time.server=-100 -Dhttp.keepAlive.time.proxy=-200 KeepAliveTest
|
|
* @run main/othervm -Dhttp.keepAlive.time.server=0 KeepAliveTest
|
|
* @run main/othervm -Dhttp.keepAlive.time.proxy=0 KeepAliveTest
|
|
* @run main/othervm -Dhttp.keepAlive.time.server=0 -Dhttp.keepAlive.time.proxy=0 KeepAliveTest
|
|
* @run main/othervm -Dhttp.keepAlive.time.server=0 -Dhttp.keepAlive.time.proxy=-200 KeepAliveTest
|
|
* @run main/othervm -Dhttp.keepAlive.time.server=-100 -Dhttp.keepAlive.time.proxy=0 KeepAliveTest
|
|
* @run main/othervm -Dhttp.keepAlive.time.server=100 -Dhttp.keepAlive.time.proxy=0 KeepAliveTest
|
|
* @run main/othervm -Dhttp.keepAlive.time.server=0 -Dhttp.keepAlive.time.proxy=200 KeepAliveTest
|
|
* @run main/othervm -Dhttp.keepAlive.time.server=100 -Dhttp.keepAlive.time.proxy=-200 KeepAliveTest
|
|
* @run main/othervm -Dhttp.keepAlive.time.server=-100 -Dhttp.keepAlive.time.proxy=200 KeepAliveTest
|
|
*/
|
|
import java.io.BufferedReader;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.io.OutputStreamWriter;
|
|
import java.lang.reflect.Constructor;
|
|
import java.lang.reflect.Field;
|
|
import java.net.InetAddress;
|
|
import java.net.InetSocketAddress;
|
|
import java.net.Proxy;
|
|
import java.net.Proxy.Type;
|
|
import java.net.ServerSocket;
|
|
import java.net.Socket;
|
|
import java.net.URL;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.List;
|
|
import java.util.Map.Entry;
|
|
import java.util.concurrent.Phaser;
|
|
import java.util.logging.ConsoleHandler;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
import jdk.test.lib.net.URIBuilder;
|
|
|
|
import sun.net.www.http.HttpClient;
|
|
import sun.net.www.http.KeepAliveCache;
|
|
import sun.net.www.protocol.http.HttpURLConnection;
|
|
|
|
public class KeepAliveTest {
|
|
|
|
/*
|
|
* This test covers a matrix of 10 server scenarios and 16 client scenarios.
|
|
*/
|
|
private static final Logger logger = Logger.getLogger("sun.net.www.protocol.http.HttpURLConnection");
|
|
|
|
private static final String NEW_LINE = "\r\n";
|
|
|
|
private static final String CONNECTION_KEEP_ALIVE_ONLY = "Connection: keep-alive";
|
|
private static final String PROXY_CONNECTION_KEEP_ALIVE_ONLY = "Proxy-Connection: keep-alive";
|
|
private static final String KEEP_ALIVE_TIMEOUT_NEG = "Keep-alive: timeout=-20";
|
|
private static final String KEEP_ALIVE_TIMEOUT_ZERO = "Keep-alive: timeout=0";
|
|
private static final String KEEP_ALIVE_TIMEOUT = "Keep-alive: timeout=20";
|
|
private static final String KEEP_ALIVE_PROXY_TIMEOUT = "Keep-alive: timeout=120";
|
|
private static final String CONNECTION_KEEP_ALIVE_WITH_TIMEOUT = CONNECTION_KEEP_ALIVE_ONLY + NEW_LINE + KEEP_ALIVE_TIMEOUT;
|
|
|
|
private static final String[] serverHeaders = {
|
|
null,
|
|
CONNECTION_KEEP_ALIVE_ONLY + NEW_LINE,
|
|
CONNECTION_KEEP_ALIVE_WITH_TIMEOUT + NEW_LINE,
|
|
PROXY_CONNECTION_KEEP_ALIVE_ONLY + NEW_LINE,
|
|
PROXY_CONNECTION_KEEP_ALIVE_ONLY + NEW_LINE + CONNECTION_KEEP_ALIVE_ONLY + NEW_LINE,
|
|
PROXY_CONNECTION_KEEP_ALIVE_ONLY + NEW_LINE + KEEP_ALIVE_PROXY_TIMEOUT + NEW_LINE,
|
|
CONNECTION_KEEP_ALIVE_ONLY + NEW_LINE + KEEP_ALIVE_TIMEOUT_NEG + NEW_LINE,
|
|
CONNECTION_KEEP_ALIVE_ONLY + NEW_LINE + KEEP_ALIVE_TIMEOUT_ZERO + NEW_LINE,
|
|
PROXY_CONNECTION_KEEP_ALIVE_ONLY + NEW_LINE + KEEP_ALIVE_TIMEOUT_NEG + NEW_LINE,
|
|
PROXY_CONNECTION_KEEP_ALIVE_ONLY + NEW_LINE + KEEP_ALIVE_TIMEOUT_ZERO + NEW_LINE
|
|
};
|
|
|
|
private static KeepAliveCache keepAliveCache;
|
|
|
|
private static Constructor<?> keepAliveKeyClassconstructor;
|
|
|
|
// variables set by server thread
|
|
private volatile int serverPort;
|
|
private volatile boolean isProxySet;
|
|
|
|
private static final Phaser serverGate = new Phaser(2);
|
|
|
|
private void readAll(Socket s) throws IOException {
|
|
byte[] buf = new byte[128];
|
|
int c;
|
|
String request = "";
|
|
InputStream is = s.getInputStream();
|
|
while ((c = is.read(buf)) > 0) {
|
|
request += new String(buf, 0, c, StandardCharsets.US_ASCII);
|
|
if (request.contains("\r\n\r\n")) {
|
|
return;
|
|
}
|
|
}
|
|
if (c == -1) {
|
|
throw new IOException("Socket closed");
|
|
}
|
|
}
|
|
|
|
private void executeServer(int scenarioNumber) {
|
|
String scenarioHeaders = serverHeaders[scenarioNumber];
|
|
if (scenarioHeaders != null) {
|
|
// isProxySet should be set before Server is moved to Listen State.
|
|
isProxySet = scenarioHeaders.contains("Proxy");
|
|
}
|
|
try (ServerSocket serverSocket = new ServerSocket()) {
|
|
serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
|
|
serverPort = serverSocket.getLocalPort();
|
|
serverGate.arrive();
|
|
|
|
// Server will be waiting for clients to connect.
|
|
try (Socket socket = serverSocket.accept()) {
|
|
readAll(socket);
|
|
try (OutputStreamWriter out = new OutputStreamWriter(socket.getOutputStream())) {
|
|
String BODY = "SERVER REPLY: Hello world";
|
|
String CLEN = "Content-Length: " + BODY.length() + NEW_LINE;
|
|
|
|
// send common headers
|
|
out.write("HTTP/1.1 200 OK\r\n");
|
|
out.write("Content-Type: text/plain; charset=iso-8859-1\r\n");
|
|
|
|
// set scenario headers
|
|
if (scenarioHeaders != null) {
|
|
out.write(scenarioHeaders);
|
|
}
|
|
|
|
// send content
|
|
out.write(CLEN);
|
|
out.write(NEW_LINE);
|
|
out.write(BODY);
|
|
}
|
|
}
|
|
} catch (IOException ioe) {
|
|
throw new RuntimeException("IOException in server thread", ioe);
|
|
}
|
|
}
|
|
|
|
private void fetchInfo(int expectedValue, HttpURLConnection httpUrlConnection) throws Exception {
|
|
Object expectedKeepAliveKey = keepAliveKeyClassconstructor.newInstance(httpUrlConnection.getURL(), null);
|
|
Object clientVectorObjectInMap = keepAliveCache.get(expectedKeepAliveKey);
|
|
System.out.println("ClientVector for KeepAliveKey:" + clientVectorObjectInMap);
|
|
HttpClient httpClientCached = keepAliveCache.get(httpUrlConnection.getURL(), null);
|
|
System.out.println("HttpClient in Cache:" + httpClientCached);
|
|
|
|
if (httpClientCached != null) {
|
|
System.out.println("KeepingAlive:" + httpClientCached.isKeepingAlive());
|
|
System.out.println("UsingProxy:" + httpClientCached.getUsingProxy());
|
|
System.out.println("ProxiedHost:" + httpClientCached.getProxyHostUsed());
|
|
System.out.println("ProxiedPort:" + httpClientCached.getProxyPortUsed());
|
|
Class<?> clientVectorClass = Class.forName("sun.net.www.http.KeepAliveCache$ClientVector");
|
|
Field napField = clientVectorClass.getDeclaredField("nap");
|
|
napField.setAccessible(true);
|
|
int napValue = (int) napField.get(clientVectorObjectInMap);
|
|
int actualValue = napValue / 1000;
|
|
if (expectedValue == actualValue) {
|
|
System.out.printf("Cache time:%d\n", actualValue);
|
|
} else {
|
|
throw new RuntimeException("Sleep time of " + actualValue + " not expected (" + expectedValue + ")");
|
|
}
|
|
} else {
|
|
if (expectedValue == 0) {
|
|
System.out.println("Connection not cached.");
|
|
} else {
|
|
throw new RuntimeException("Connection was not cached although expected with sleep time of:" + expectedValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void connectToServerURL(int expectedValue) throws Exception {
|
|
// wait until ServerSocket moves to listening state.
|
|
serverGate.arriveAndAwaitAdvance();
|
|
URL url = URIBuilder.newBuilder().scheme("http").loopback().port(serverPort).toURL();
|
|
System.out.println("connecting to server URL: " + url + ", isProxySet: " + isProxySet);
|
|
HttpURLConnection httpUrlConnection = null;
|
|
|
|
// isProxySet is set to true when Expected Server Response contains Proxy-Connection header.
|
|
if (isProxySet) {
|
|
httpUrlConnection = (HttpURLConnection) url
|
|
.openConnection(new Proxy(Type.HTTP, new InetSocketAddress("localhost", serverPort)));
|
|
} else {
|
|
httpUrlConnection = (HttpURLConnection) url.openConnection();
|
|
}
|
|
|
|
try (InputStreamReader inputStreamReader = new InputStreamReader(httpUrlConnection.getInputStream());
|
|
BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
|
|
while (true) {
|
|
String line = bufferedReader.readLine();
|
|
if (line == null) {
|
|
break;
|
|
}
|
|
System.out.println(line);
|
|
}
|
|
}
|
|
for (Entry<String, List<String>> header : httpUrlConnection.getHeaderFields().entrySet()) {
|
|
System.out.println(header.getKey() + "=" + header.getValue());
|
|
}
|
|
fetchInfo(expectedValue, httpUrlConnection);
|
|
}
|
|
|
|
private int getExpectedCachingValue(int serverScenario) {
|
|
if (serverScenario == 2) {
|
|
// Connection: keep-alive && Keep-alive: timeout=20
|
|
//
|
|
// server side keep-alive timeout is what counts here
|
|
return 20;
|
|
} else if (serverScenario == 3 || serverScenario == 4 || serverScenario == 8) {
|
|
// Proxy-Connection: keep-alive
|
|
// Connection:keep-alive && Proxy-connection:keep-alive
|
|
// Proxy-Connection:keep-alive && Keep-alive:timeout=-20
|
|
//
|
|
// Proxy-connection:keep-alive is set, timeout could be invalid -> value of http.keepAlive.time.proxy or default of 60
|
|
int httpKeepAliveTimeProxy;
|
|
try {
|
|
httpKeepAliveTimeProxy = Integer.valueOf(System.getProperty("http.keepAlive.time.proxy"));
|
|
} catch (NumberFormatException e) {
|
|
httpKeepAliveTimeProxy = -1;
|
|
}
|
|
return httpKeepAliveTimeProxy < 0 ? 60 : httpKeepAliveTimeProxy;
|
|
} else if (serverScenario == 5) {
|
|
// Proxy-connection:keep-alive && Keep-alive:timeout=120
|
|
//
|
|
// server side keep-alive timeout is what counts here
|
|
return 120;
|
|
} else if (serverScenario == 7 || serverScenario == 9) {
|
|
// Connection: keep-alive && Keep-alive: timeout=0
|
|
// Proxy-Connection:keep-alive && Keep-alive:timeout=0
|
|
//
|
|
// no caching
|
|
return 0;
|
|
} else {
|
|
// No server parameters
|
|
// Connection: keep-alive
|
|
// Connection: keep-alive && Keep-alive: timeout=-20
|
|
//
|
|
// Nothing or Connection:keep-alive is set, timeout could be invalid -> value of http.keepAlive.time.server or default of 5
|
|
int httpKeepAliveTimeServer;
|
|
try {
|
|
httpKeepAliveTimeServer = Integer.valueOf(System.getProperty("http.keepAlive.time.server"));
|
|
} catch (NumberFormatException e) {
|
|
httpKeepAliveTimeServer = -1;
|
|
}
|
|
return httpKeepAliveTimeServer < 0 ? 5 : httpKeepAliveTimeServer;
|
|
}
|
|
}
|
|
|
|
private void runScenario(int scenarioNumber) throws Exception {
|
|
int expectedValue = getExpectedCachingValue(scenarioNumber);
|
|
System.out.println("Expecting Cache Time of " + expectedValue + " for server headers:");
|
|
if (serverHeaders[scenarioNumber] == null) {
|
|
System.out.println();
|
|
} else {
|
|
System.out.print(serverHeaders[scenarioNumber]);
|
|
}
|
|
Thread server = Thread.ofPlatform().start(() -> executeServer(scenarioNumber));
|
|
connectToServerURL(expectedValue);
|
|
server.join();
|
|
System.out.println();
|
|
}
|
|
|
|
private static void initialize() throws Exception {
|
|
System.clearProperty("http.proxyPort");
|
|
System.clearProperty("http.proxyHost");
|
|
System.setProperty("java.net.useSystemProxies", "false");
|
|
|
|
Field keepAliveField = sun.net.www.http.HttpClient.class.getDeclaredField("kac");
|
|
keepAliveField.setAccessible(true);
|
|
keepAliveCache = (KeepAliveCache) keepAliveField.get(null);
|
|
System.out.println("KeepAliveCache: " + keepAliveCache);
|
|
keepAliveKeyClassconstructor = Class.forName("sun.net.www.http.KeepAliveKey").getDeclaredConstructors()[0];
|
|
keepAliveKeyClassconstructor.setAccessible(true);
|
|
|
|
logger.setLevel(Level.FINEST);
|
|
ConsoleHandler h = new ConsoleHandler();
|
|
h.setLevel(Level.FINEST);
|
|
logger.addHandler(h);
|
|
|
|
System.out.println("Client properties: http.keepAlive.time.server=" + System.getProperty("http.keepAlive.time.server") +
|
|
", http.keepAlive.time.proxy=" + System.getProperty("http.keepAlive.time.proxy"));
|
|
}
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
if (args.length > 1) {
|
|
throw new IllegalArgumentException("Usage:java KeepAliveTest.java <scenarioNumber>");
|
|
} else if (args.length == 1) {
|
|
// an individual test scenario
|
|
try {
|
|
int scenarioNumber = Integer.valueOf(args[0]);
|
|
if (scenarioNumber < 0 || scenarioNumber > 9) {
|
|
throw new IllegalArgumentException("Scenario " + scenarioNumber + " does not exist");
|
|
}
|
|
initialize();
|
|
new KeepAliveTest().runScenario(scenarioNumber);
|
|
} catch (NumberFormatException e) {
|
|
throw new IllegalArgumentException("Scenario must be a number, got " + args[0]);
|
|
}
|
|
} else {
|
|
// all server scenarios
|
|
initialize();
|
|
for (int i = 0; i < 10; i++) {
|
|
new KeepAliveTest().runScenario(i);
|
|
}
|
|
}
|
|
}
|
|
}
|