Skip to content

Commit fc32524

Browse files
author
duke
committedJan 14, 2022
Automatic merge of jdk:master into master
2 parents 7644eaf + 9f30ec1 commit fc32524

File tree

2 files changed

+220
-1
lines changed

2 files changed

+220
-1
lines changed
 

‎src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -521,6 +521,18 @@ class Exchange implements Runnable {
521521
public void run () {
522522
/* context will be null for new connections */
523523
logger.log(Level.TRACE, "exchange started");
524+
525+
if (dispatcherThread == Thread.currentThread()) {
526+
try {
527+
// call selector to process cancelled keys
528+
selector.selectNow();
529+
} catch (IOException ioe) {
530+
logger.log(Level.DEBUG, "processing of cancelled keys failed: closing");
531+
closeConnection(connection);
532+
return;
533+
}
534+
}
535+
524536
context = connection.getHttpContext();
525537
boolean newconnection;
526538
SSLEngine engine = null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
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+
* @bug 8278398
27+
* @summary Tests the jwebserver's maximum request time
28+
* @modules jdk.httpserver
29+
* @library /test/lib
30+
* @run testng/othervm MaxRequestTimeTest
31+
*/
32+
33+
import java.io.IOException;
34+
import java.net.InetAddress;
35+
import java.net.URI;
36+
import java.net.http.HttpClient;
37+
import java.net.http.HttpRequest;
38+
import java.net.http.HttpResponse;
39+
import java.nio.file.Files;
40+
import java.nio.file.Path;
41+
import java.util.concurrent.TimeUnit;
42+
import java.util.concurrent.atomic.AtomicInteger;
43+
import javax.net.ssl.SSLContext;
44+
import javax.net.ssl.SSLHandshakeException;
45+
import jdk.test.lib.Platform;
46+
import jdk.test.lib.net.SimpleSSLContext;
47+
import jdk.test.lib.process.OutputAnalyzer;
48+
import jdk.test.lib.process.ProcessTools;
49+
import jdk.test.lib.util.FileUtils;
50+
import org.testng.annotations.AfterTest;
51+
import org.testng.annotations.BeforeTest;
52+
import org.testng.annotations.Test;
53+
import static java.lang.System.out;
54+
import static java.net.http.HttpClient.Builder.NO_PROXY;
55+
import static org.testng.Assert.*;
56+
57+
/**
58+
* This test confirms that the jwebserver does not wait indefinitely for
59+
* a request to arrive.
60+
*
61+
* The jwebserver has a maximum request time of 5 seconds, which is set with the
62+
* "sun.net.httpserver.maxReqTime" system property. If this threshold is
63+
* reached, for example in the case of an HTTPS request where the server keeps
64+
* waiting for a plaintext request, the server closes the connection. Subsequent
65+
* requests are expected to be handled as normal.
66+
*
67+
* The test checks in the following order that:
68+
* 1. an HTTP request is handled successfully,
69+
* 2. an HTTPS request fails due to the server closing the connection
70+
* 3. another HTTP request is handled successfully.
71+
*/
72+
public class MaxRequestTimeTest {
73+
static final Path JAVA_HOME = Path.of(System.getProperty("java.home"));
74+
static final String JWEBSERVER = getJwebserver(JAVA_HOME);
75+
static final Path CWD = Path.of(".").toAbsolutePath().normalize();
76+
static final Path TEST_DIR = CWD.resolve("MaxRequestTimeTest");
77+
static final String LOOPBACK_ADDR = InetAddress.getLoopbackAddress().getHostAddress();
78+
static final AtomicInteger PORT = new AtomicInteger();
79+
80+
static SSLContext sslContext;
81+
82+
@BeforeTest
83+
public void setup() throws IOException {
84+
if (Files.exists(TEST_DIR)) {
85+
FileUtils.deleteFileTreeWithRetry(TEST_DIR);
86+
}
87+
Files.createDirectories(TEST_DIR);
88+
89+
sslContext = new SimpleSSLContext().get();
90+
if (sslContext == null)
91+
throw new AssertionError("Unexpected null sslContext");
92+
}
93+
94+
@Test
95+
public void testMaxRequestTime() throws Throwable {
96+
final var sb = new StringBuffer(); // stdout & stderr
97+
final var p = startProcess("jwebserver", sb);
98+
try {
99+
sendHTTPSRequest(); // server expected to terminate connection
100+
sendHTTPRequest(); // server expected to respond successfully
101+
sendHTTPSRequest(); // server expected to terminate connection
102+
sendHTTPRequest(); // server expected to respond successfully
103+
} finally {
104+
p.destroy();
105+
int exitCode = p.waitFor();
106+
checkOutput(sb, exitCode);
107+
}
108+
}
109+
110+
static String expectedBody = """
111+
<!DOCTYPE html>
112+
<html>
113+
<head>
114+
<meta charset="utf-8"/>
115+
</head>
116+
<body>
117+
<h1>Directory listing for &#x2F;</h1>
118+
<ul>
119+
</ul>
120+
</body>
121+
</html>
122+
""";
123+
124+
void sendHTTPRequest() throws IOException, InterruptedException {
125+
out.println("\n--- sendHTTPRequest");
126+
var client = HttpClient.newBuilder()
127+
.proxy(NO_PROXY)
128+
.build();
129+
var request = HttpRequest.newBuilder(URI.create("http://localhost:" + PORT.get() + "/")).build();
130+
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
131+
assertEquals(response.body(), expectedBody);
132+
}
133+
134+
void sendHTTPSRequest() throws IOException, InterruptedException {
135+
out.println("\n--- sendHTTPSRequest");
136+
var client = HttpClient.newBuilder()
137+
.sslContext(sslContext)
138+
.proxy(NO_PROXY)
139+
.build();
140+
var request = HttpRequest.newBuilder(URI.create("https://localhost:" + PORT.get() + "/")).build();
141+
try {
142+
client.send(request, HttpResponse.BodyHandlers.ofString());
143+
throw new RuntimeException("Expected SSLHandshakeException not thrown");
144+
} catch (SSLHandshakeException expected) { // server closes connection when max request time is reached
145+
expected.printStackTrace(System.out);
146+
}
147+
}
148+
149+
@AfterTest
150+
public void teardown() throws IOException {
151+
if (Files.exists(TEST_DIR)) {
152+
FileUtils.deleteFileTreeWithRetry(TEST_DIR);
153+
}
154+
}
155+
156+
// --- infra ---
157+
158+
static String getJwebserver(Path image) {
159+
boolean isWindows = System.getProperty("os.name").startsWith("Windows");
160+
Path jwebserver = image.resolve("bin").resolve(isWindows ? "jwebserver.exe" : "jwebserver");
161+
if (Files.notExists(jwebserver))
162+
throw new RuntimeException(jwebserver + " not found");
163+
return jwebserver.toAbsolutePath().toString();
164+
}
165+
166+
// The stdout/stderr output line to wait for when starting the jwebserver
167+
static final String REGULAR_STARTUP_LINE_STRING_1 = "URL http://";
168+
static final String REGULAR_STARTUP_LINE_STRING_2 = "Serving ";
169+
170+
static void parseAndSetPort(String line) {
171+
PORT.set(Integer.parseInt(line.split(" port ")[1]));
172+
}
173+
174+
static Process startProcess(String name, StringBuffer sb) throws Throwable {
175+
// starts the process, parses the port and awaits startup line before sending requests
176+
return ProcessTools.startProcess(name,
177+
new ProcessBuilder(JWEBSERVER, "-p", "0").directory(TEST_DIR.toFile()),
178+
line -> {
179+
if (line.startsWith(REGULAR_STARTUP_LINE_STRING_2)) { parseAndSetPort(line); }
180+
sb.append(line + "\n");
181+
},
182+
line -> line.startsWith(REGULAR_STARTUP_LINE_STRING_1),
183+
30, // suitably high default timeout, not expected to timeout
184+
TimeUnit.SECONDS);
185+
}
186+
187+
static final int SIGTERM = 15;
188+
static final int NORMAL_EXIT_CODE = normalExitCode();
189+
190+
static int normalExitCode() {
191+
if (Platform.isWindows()) {
192+
return 1; // expected process destroy exit code
193+
} else {
194+
// signal terminated exit code on Unix is 128 + signal value
195+
return 128 + SIGTERM;
196+
}
197+
}
198+
199+
static void checkOutput(StringBuffer sb, int exitCode) {
200+
out.println("\n--- server output: \n" + sb);
201+
var outputAnalyser = new OutputAnalyzer(sb.toString(), "", exitCode);
202+
outputAnalyser.shouldHaveExitValue(NORMAL_EXIT_CODE)
203+
.shouldContain("Binding to loopback by default. For all interfaces use \"-b 0.0.0.0\" or \"-b ::\".")
204+
.shouldContain("Serving " + TEST_DIR + " and subdirectories on " + LOOPBACK_ADDR + " port " + PORT)
205+
.shouldContain("URL http://" + LOOPBACK_ADDR);
206+
}
207+
}

0 commit comments

Comments
 (0)
Please sign in to comment.