8278398: jwebserver: Add test to confirm maximum request time
Reviewed-by: dfuchs, michaelm
This commit is contained in:
parent
dd76a28d44
commit
9f30ec174f
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2022, 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
|
||||
@ -521,6 +521,18 @@ class ServerImpl implements TimeSource {
|
||||
public void run () {
|
||||
/* context will be null for new connections */
|
||||
logger.log(Level.TRACE, "exchange started");
|
||||
|
||||
if (dispatcherThread == Thread.currentThread()) {
|
||||
try {
|
||||
// call selector to process cancelled keys
|
||||
selector.selectNow();
|
||||
} catch (IOException ioe) {
|
||||
logger.log(Level.DEBUG, "processing of cancelled keys failed: closing");
|
||||
closeConnection(connection);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
context = connection.getHttpContext();
|
||||
boolean newconnection;
|
||||
SSLEngine engine = null;
|
||||
|
@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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
|
||||
* @bug 8278398
|
||||
* @summary Tests the jwebserver's maximum request time
|
||||
* @modules jdk.httpserver
|
||||
* @library /test/lib
|
||||
* @run testng/othervm MaxRequestTimeTest
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
import jdk.test.lib.Platform;
|
||||
import jdk.test.lib.net.SimpleSSLContext;
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
import jdk.test.lib.process.ProcessTools;
|
||||
import jdk.test.lib.util.FileUtils;
|
||||
import org.testng.annotations.AfterTest;
|
||||
import org.testng.annotations.BeforeTest;
|
||||
import org.testng.annotations.Test;
|
||||
import static java.lang.System.out;
|
||||
import static java.net.http.HttpClient.Builder.NO_PROXY;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
/**
|
||||
* This test confirms that the jwebserver does not wait indefinitely for
|
||||
* a request to arrive.
|
||||
*
|
||||
* The jwebserver has a maximum request time of 5 seconds, which is set with the
|
||||
* "sun.net.httpserver.maxReqTime" system property. If this threshold is
|
||||
* reached, for example in the case of an HTTPS request where the server keeps
|
||||
* waiting for a plaintext request, the server closes the connection. Subsequent
|
||||
* requests are expected to be handled as normal.
|
||||
*
|
||||
* The test checks in the following order that:
|
||||
* 1. an HTTP request is handled successfully,
|
||||
* 2. an HTTPS request fails due to the server closing the connection
|
||||
* 3. another HTTP request is handled successfully.
|
||||
*/
|
||||
public class MaxRequestTimeTest {
|
||||
static final Path JAVA_HOME = Path.of(System.getProperty("java.home"));
|
||||
static final String JWEBSERVER = getJwebserver(JAVA_HOME);
|
||||
static final Path CWD = Path.of(".").toAbsolutePath().normalize();
|
||||
static final Path TEST_DIR = CWD.resolve("MaxRequestTimeTest");
|
||||
static final String LOOPBACK_ADDR = InetAddress.getLoopbackAddress().getHostAddress();
|
||||
static final AtomicInteger PORT = new AtomicInteger();
|
||||
|
||||
static SSLContext sslContext;
|
||||
|
||||
@BeforeTest
|
||||
public void setup() throws IOException {
|
||||
if (Files.exists(TEST_DIR)) {
|
||||
FileUtils.deleteFileTreeWithRetry(TEST_DIR);
|
||||
}
|
||||
Files.createDirectories(TEST_DIR);
|
||||
|
||||
sslContext = new SimpleSSLContext().get();
|
||||
if (sslContext == null)
|
||||
throw new AssertionError("Unexpected null sslContext");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaxRequestTime() throws Throwable {
|
||||
final var sb = new StringBuffer(); // stdout & stderr
|
||||
final var p = startProcess("jwebserver", sb);
|
||||
try {
|
||||
sendHTTPSRequest(); // server expected to terminate connection
|
||||
sendHTTPRequest(); // server expected to respond successfully
|
||||
sendHTTPSRequest(); // server expected to terminate connection
|
||||
sendHTTPRequest(); // server expected to respond successfully
|
||||
} finally {
|
||||
p.destroy();
|
||||
int exitCode = p.waitFor();
|
||||
checkOutput(sb, exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
static String expectedBody = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Directory listing for /</h1>
|
||||
<ul>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
""";
|
||||
|
||||
void sendHTTPRequest() throws IOException, InterruptedException {
|
||||
out.println("\n--- sendHTTPRequest");
|
||||
var client = HttpClient.newBuilder()
|
||||
.proxy(NO_PROXY)
|
||||
.build();
|
||||
var request = HttpRequest.newBuilder(URI.create("http://localhost:" + PORT.get() + "/")).build();
|
||||
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
assertEquals(response.body(), expectedBody);
|
||||
}
|
||||
|
||||
void sendHTTPSRequest() throws IOException, InterruptedException {
|
||||
out.println("\n--- sendHTTPSRequest");
|
||||
var client = HttpClient.newBuilder()
|
||||
.sslContext(sslContext)
|
||||
.proxy(NO_PROXY)
|
||||
.build();
|
||||
var request = HttpRequest.newBuilder(URI.create("https://localhost:" + PORT.get() + "/")).build();
|
||||
try {
|
||||
client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
throw new RuntimeException("Expected SSLHandshakeException not thrown");
|
||||
} catch (SSLHandshakeException expected) { // server closes connection when max request time is reached
|
||||
expected.printStackTrace(System.out);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTest
|
||||
public void teardown() throws IOException {
|
||||
if (Files.exists(TEST_DIR)) {
|
||||
FileUtils.deleteFileTreeWithRetry(TEST_DIR);
|
||||
}
|
||||
}
|
||||
|
||||
// --- infra ---
|
||||
|
||||
static String getJwebserver(Path image) {
|
||||
boolean isWindows = System.getProperty("os.name").startsWith("Windows");
|
||||
Path jwebserver = image.resolve("bin").resolve(isWindows ? "jwebserver.exe" : "jwebserver");
|
||||
if (Files.notExists(jwebserver))
|
||||
throw new RuntimeException(jwebserver + " not found");
|
||||
return jwebserver.toAbsolutePath().toString();
|
||||
}
|
||||
|
||||
// The stdout/stderr output line to wait for when starting the jwebserver
|
||||
static final String REGULAR_STARTUP_LINE_STRING_1 = "URL http://";
|
||||
static final String REGULAR_STARTUP_LINE_STRING_2 = "Serving ";
|
||||
|
||||
static void parseAndSetPort(String line) {
|
||||
PORT.set(Integer.parseInt(line.split(" port ")[1]));
|
||||
}
|
||||
|
||||
static Process startProcess(String name, StringBuffer sb) throws Throwable {
|
||||
// starts the process, parses the port and awaits startup line before sending requests
|
||||
return ProcessTools.startProcess(name,
|
||||
new ProcessBuilder(JWEBSERVER, "-p", "0").directory(TEST_DIR.toFile()),
|
||||
line -> {
|
||||
if (line.startsWith(REGULAR_STARTUP_LINE_STRING_2)) { parseAndSetPort(line); }
|
||||
sb.append(line + "\n");
|
||||
},
|
||||
line -> line.startsWith(REGULAR_STARTUP_LINE_STRING_1),
|
||||
30, // suitably high default timeout, not expected to timeout
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
static final int SIGTERM = 15;
|
||||
static final int NORMAL_EXIT_CODE = normalExitCode();
|
||||
|
||||
static int normalExitCode() {
|
||||
if (Platform.isWindows()) {
|
||||
return 1; // expected process destroy exit code
|
||||
} else {
|
||||
// signal terminated exit code on Unix is 128 + signal value
|
||||
return 128 + SIGTERM;
|
||||
}
|
||||
}
|
||||
|
||||
static void checkOutput(StringBuffer sb, int exitCode) {
|
||||
out.println("\n--- server output: \n" + sb);
|
||||
var outputAnalyser = new OutputAnalyzer(sb.toString(), "", exitCode);
|
||||
outputAnalyser.shouldHaveExitValue(NORMAL_EXIT_CODE)
|
||||
.shouldContain("Binding to loopback by default. For all interfaces use \"-b 0.0.0.0\" or \"-b ::\".")
|
||||
.shouldContain("Serving " + TEST_DIR + " and subdirectories on " + LOOPBACK_ADDR + " port " + PORT)
|
||||
.shouldContain("URL http://" + LOOPBACK_ADDR);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user