-
Bug
-
Resolution: Unresolved
-
P4
-
None
-
8
-
generic
-
generic
A DESCRIPTION OF THE PROBLEM :
While handling concurrent requests, the lightweight HTTP server sometimes fails to correctly parse request headers. Log statements from the JDK Lightweight HTTP Server show that the server fails to parse a request line because the URI is interrupted by header data (presumably from another, concurrent request).
The probability of reproducing this error increases with the size of the HTTP request body.
This issue does not occur on newer versions of OpenJDK (I tested versions 11 and 17). I am able to reproduce this problem on Java 8 builds from Eclipse Temurin, Oracle, and IBM. The latest build I tested with is Eclipse Temurin 1.8.0_345-b01.
REGRESSION : Last worked in version 11
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run a basic Lightweight HTTP Server such as the one included in the source code. Use a load generator to send concurrent requests to the server. Use large request bodies (~4MB) for the POST requests, and also send small GET requests.
Enable FINEST logging for the HTTP Server to help detect the error condition.
com.sun.net.httpserver.level = FINEST
java.util.logging.ConsoleHandler.level = FINEST
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Lightweight HTTP Server parses the valid HTTP request headers and handles the request.
ACTUAL -
Oct 24, 2022 3:24:48 PM sun.net.httpserver.ServerImpl logReply
FINE: y HTTP/1.1 [400 Bad Request] (Bad request line)
Oct 24, 2022 3:24:48 PM sun.net.httpserver.ServerImpl logReply
FINE: POST /ecHost: test-applications.contsec.com [404 Not Found] (No context found for request)
---------- BEGIN SOURCE ----------
public class FailsParseHeaders {
public static void main(final String[] args) throws IOException {
final HttpServer server = HttpServer.create();
final InetSocketAddress address = new InetSocketAddress("localhost", 8080);
server.setExecutor(Executors.newCachedThreadPool());
server.bind(address, 0);
// register routes
server.createContext(
"/ping",
exchange -> {
exchange.sendResponseHeaders(204, 0);
exchange.getResponseBody().close();
});
server.createContext(
"/echo",
exchange -> {
final int length =
exchange.getRequestHeaders().get("content-length").stream()
.findFirst()
.map(Integer::parseInt)
.orElse(0);
if (length > 0) {
exchange.sendResponseHeaders(200, length);
try (InputStream request = exchange.getRequestBody();
OutputStream response = exchange.getResponseBody()) {
byte[] buffer = new byte[1024];
int read;
while ((read = request.read(buffer, 0, buffer.length)) >= 0) {
response.write(buffer, 0, read);
}
}
} else {
discardInputStream(exchange);
exchange.sendResponseHeaders(204, 0);
}
});
server.start();
}
static void discardInputStream(final HttpExchange exchange) throws IOException {
try (InputStream body = exchange.getRequestBody()) {
while (body.read() >= 0) {}
}
}
}
---------- END SOURCE ----------
FREQUENCY : rarely
While handling concurrent requests, the lightweight HTTP server sometimes fails to correctly parse request headers. Log statements from the JDK Lightweight HTTP Server show that the server fails to parse a request line because the URI is interrupted by header data (presumably from another, concurrent request).
The probability of reproducing this error increases with the size of the HTTP request body.
This issue does not occur on newer versions of OpenJDK (I tested versions 11 and 17). I am able to reproduce this problem on Java 8 builds from Eclipse Temurin, Oracle, and IBM. The latest build I tested with is Eclipse Temurin 1.8.0_345-b01.
REGRESSION : Last worked in version 11
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run a basic Lightweight HTTP Server such as the one included in the source code. Use a load generator to send concurrent requests to the server. Use large request bodies (~4MB) for the POST requests, and also send small GET requests.
Enable FINEST logging for the HTTP Server to help detect the error condition.
com.sun.net.httpserver.level = FINEST
java.util.logging.ConsoleHandler.level = FINEST
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Lightweight HTTP Server parses the valid HTTP request headers and handles the request.
ACTUAL -
Oct 24, 2022 3:24:48 PM sun.net.httpserver.ServerImpl logReply
FINE: y HTTP/1.1 [400 Bad Request] (Bad request line)
Oct 24, 2022 3:24:48 PM sun.net.httpserver.ServerImpl logReply
FINE: POST /ecHost: test-applications.contsec.com [404 Not Found] (No context found for request)
---------- BEGIN SOURCE ----------
public class FailsParseHeaders {
public static void main(final String[] args) throws IOException {
final HttpServer server = HttpServer.create();
final InetSocketAddress address = new InetSocketAddress("localhost", 8080);
server.setExecutor(Executors.newCachedThreadPool());
server.bind(address, 0);
// register routes
server.createContext(
"/ping",
exchange -> {
exchange.sendResponseHeaders(204, 0);
exchange.getResponseBody().close();
});
server.createContext(
"/echo",
exchange -> {
final int length =
exchange.getRequestHeaders().get("content-length").stream()
.findFirst()
.map(Integer::parseInt)
.orElse(0);
if (length > 0) {
exchange.sendResponseHeaders(200, length);
try (InputStream request = exchange.getRequestBody();
OutputStream response = exchange.getResponseBody()) {
byte[] buffer = new byte[1024];
int read;
while ((read = request.read(buffer, 0, buffer.length)) >= 0) {
response.write(buffer, 0, read);
}
}
} else {
discardInputStream(exchange);
exchange.sendResponseHeaders(204, 0);
}
});
server.start();
}
static void discardInputStream(final HttpExchange exchange) throws IOException {
try (InputStream body = exchange.getRequestBody()) {
while (body.read() >= 0) {}
}
}
}
---------- END SOURCE ----------
FREQUENCY : rarely