From 7ef036e4b11f237966fc25bffb25454dd1ca3427 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 6 Oct 2025 16:19:58 -0400 Subject: [PATCH 1/7] support 1xx codes --- .../robaho/net/httpserver/ExchangeImpl.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/robaho/net/httpserver/ExchangeImpl.java b/src/main/java/robaho/net/httpserver/ExchangeImpl.java index 66e366d..3a1ea7f 100644 --- a/src/main/java/robaho/net/httpserver/ExchangeImpl.java +++ b/src/main/java/robaho/net/httpserver/ExchangeImpl.java @@ -242,9 +242,13 @@ public void sendResponseHeaders(int rCode, long contentLen) boolean flush = false; /* check for response type that is not allowed to send a body */ - if (rCode == 101) { - logger.log(Level.DEBUG, () -> "switching protocols"); - + var informational = rCode >= 100 && rCode < 200; + + if (informational) { + + if (rCode == 101) { + logger.log(Level.DEBUG, () -> "switching protocols"); + } if (contentLen != 0) { String msg = "sendResponseHeaders: rCode = " + rCode + ": forcing contentLen = 0"; @@ -253,8 +257,7 @@ public void sendResponseHeaders(int rCode, long contentLen) contentLen = 0; flush = true; - } else if ((rCode >= 100 && rCode < 200) /* informational */ - || (rCode == 204) /* no content */ + } else if ((rCode == 204) /* no content */ || (rCode == 304)) /* not modified */ { if (contentLen != -1) { @@ -285,6 +288,9 @@ public void sendResponseHeaders(int rCode, long contentLen) close = true; flush = true; } + else if (informational) { + flush = true; + } else if (http10) { o.setWrappedStream(new UndefLengthOutputStream(this, ros)); close = true; @@ -323,7 +329,7 @@ else if (http10) { writeHeaders(rspHdrs, ros); this.rspContentLen = contentLen; - sentHeaders = true; + sentHeaders = !informational; if(logger.isLoggable(Level.TRACE)) { logger.log(Level.TRACE, "Sent headers: noContentToSend=" + noContentToSend); } From f1273f3fc9365240daa78c4f6cf14cf7caf36fa5 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Tue, 7 Oct 2025 11:16:35 -0400 Subject: [PATCH 2/7] Create InputRead100Test.java --- src/test/java/InputRead100Test.java | 186 ++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 src/test/java/InputRead100Test.java diff --git a/src/test/java/InputRead100Test.java b/src/test/java/InputRead100Test.java new file mode 100644 index 0000000..e6bb04c --- /dev/null +++ b/src/test/java/InputRead100Test.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2021, 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 id=default + * @bug 8349670 + * @summary Test 100 continue response handling + * @run junit/othervm InputRead100Test + */ +/** + * @test id=preferIPv6 + * @bug 8349670 + * @summary Test 100 continue response handling ipv6 + * @run junit/othervm -Djava.net.preferIPv6Addresses=true InputRead100Test + */ +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +import org.junit.jupiter.api.Test; + +import static java.nio.charset.StandardCharsets.*; + +public class InputRead100Test { + private static final String someContext = "/context"; + + static class ServerThreadFactory implements ThreadFactory { + static final AtomicLong tokens = new AtomicLong(); + + @Override + public Thread newThread(Runnable r) { + var thread = new Thread(r, "Server-" + tokens.incrementAndGet()); + thread.setDaemon(true); + return thread; + } + } + + static { + Logger.getLogger("").setLevel(Level.ALL); + Logger.getLogger("").getHandlers()[0].setLevel(Level.ALL); + } + + @Test + public void testContinue() throws Exception { + System.out.println("testContinue()"); + InetAddress loopback = InetAddress.getLoopbackAddress(); + HttpServer server = HttpServer.create(new InetSocketAddress(loopback, 0), 0); + ExecutorService executor = Executors.newCachedThreadPool(new ServerThreadFactory()); + server.setExecutor(executor); + try { + server.createContext( + someContext, + msg -> { + System.err.println("Handling request: " + msg.getRequestURI()); + byte[] reply = "Here is my reply!".getBytes(UTF_8); + try { + try { + msg.getResponseHeaders().add("Header", "BeforeContinue"); + msg.sendResponseHeaders(100, -1); + BufferedReader r = new BufferedReader(new InputStreamReader(msg.getRequestBody())); + r.read(); + msg.sendResponseHeaders(200, reply.length); + msg.getResponseBody().write(reply); + msg.getResponseBody().close(); + Thread.sleep(50); + } catch (IOException | InterruptedException ie) { + ie.printStackTrace(); + } + } finally { + System.err.println("Request handled: " + msg.getRequestURI()); + } + }); + server.start(); + System.out.println("Server started at port " + server.getAddress().getPort()); + + runRawSocketHttpClient(loopback, server.getAddress().getPort(), 64 * 1024 + 16); + } finally { + System.out.println("shutting server down"); + executor.shutdown(); + server.stop(0); + } + System.out.println("Server finished."); + } + + static void runRawSocketHttpClient(InetAddress address, int port, int contentLength) + throws Exception { + Socket socket = null; + PrintWriter writer = null; + BufferedReader reader = null; + final String CRLF = "\r\n"; + try { + socket = new Socket(address, port); + writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream())); + System.out.println("Client connected by socket: " + socket); + String body = "I will send all the data."; + if (contentLength <= 0) contentLength = body.getBytes(UTF_8).length; + + writer.print("GET " + someContext + "/ HTTP/1.1" + CRLF); + writer.print("User-Agent: Java/" + System.getProperty("java.version") + CRLF); + writer.print("Host: " + address.getHostName() + CRLF); + writer.print("Accept: */*" + CRLF); + writer.print("Content-Length: " + contentLength + CRLF); + writer.print("Connection: keep-alive" + CRLF); + writer.print("Expect: 100-continue" + CRLF); + writer.print(CRLF); // Important, else the server will expect that + // there's more into the request. + writer.flush(); + System.out.println("Client wrote request to socket: " + socket); + + writer.print(body); + writer.flush(); + System.out.println("Client wrote body to socket: " + socket); + + reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); + System.out.println("Client start reading from server:"); + String line = reader.readLine(); + for (; line != null; line = reader.readLine()) { + if (line.isEmpty()) { + break; + } + System.out.println("\"" + line + "\""); + } + System.out.println("Client finished reading from server"); + } finally { + // give time to the server to try & drain its input stream + Thread.sleep(500); + // closes the client outputstream while the server is draining + // it + if (writer != null) { + writer.close(); + } + // give time to the server to trigger its assertion + // error before closing the connection + Thread.sleep(500); + if (reader != null) + try { + reader.close(); + } catch (IOException logOrIgnore) { + logOrIgnore.printStackTrace(); + } + if (socket != null) { + try { + socket.close(); + } catch (IOException logOrIgnore) { + logOrIgnore.printStackTrace(); + } + } + } + System.out.println("Client finished."); + } +} From 152a0ddde8362290761eb6b730ecb332d3fbaaae Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Wed, 8 Oct 2025 17:20:03 -0400 Subject: [PATCH 3/7] no contentlength header --- src/main/java/robaho/net/httpserver/ExchangeImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/robaho/net/httpserver/ExchangeImpl.java b/src/main/java/robaho/net/httpserver/ExchangeImpl.java index 3a1ea7f..9e535f1 100644 --- a/src/main/java/robaho/net/httpserver/ExchangeImpl.java +++ b/src/main/java/robaho/net/httpserver/ExchangeImpl.java @@ -290,6 +290,7 @@ public void sendResponseHeaders(int rCode, long contentLen) } else if (informational) { flush = true; + noContentLengthHeader = true; } else if (http10) { o.setWrappedStream(new UndefLengthOutputStream(this, ros)); From a498e88f9302ebeaa7d95c9e5064ecfdaa008e62 Mon Sep 17 00:00:00 2001 From: robert engels Date: Wed, 8 Oct 2025 17:23:55 -0500 Subject: [PATCH 4/7] update build for gradle 9.1.0 --- build.gradle | 43 +++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/build.gradle b/build.gradle index f9d8909..e47b7dc 100644 --- a/build.gradle +++ b/build.gradle @@ -92,22 +92,15 @@ sourceSets { } } -def getGitVersion () { - def output = new ByteArrayOutputStream() - exec { - commandLine 'git', 'rev-list', '--tags', '--max-count=1' - standardOutput = output - } - def revision = output.toString().trim() - output.reset() - exec { - commandLine 'git', 'describe', '--tags', revision - standardOutput = output - } - return output.toString().trim() -} +// Use a lazy Provider to get the git version. This is the modern, configuration-cache-friendly approach. +def gitVersionProvider = project.providers.exec { + // 1. Describe the latest revision with a tag + commandLine = ['git', 'describe', '--tags', '--always'] + ignoreExitValue = true // Don't fail the build if git fails (e.g., no tags exist) +}.standardOutput.asText.map { it.trim() } -version = getGitVersion() +// Apply the git version to your project +version = gitVersionProvider.get() task showGitVersion { doLast { @@ -116,9 +109,6 @@ task showGitVersion { } build { - doFirst { - getGitVersion - } } jar { @@ -181,16 +171,17 @@ task runSimpleFileServer(type: JavaExec) { } dependsOn testClasses classpath sourceSets.test.runtimeClasspath - main "SimpleFileServer" + + // FIX 1: Use 'mainClass' instead of 'main' + // FIX 2: Replace "SimpleFileServer" with the FULLY QUALIFIED class name + // (e.g., if it's in a package named com.example) + mainClass = "com.example.SimpleFileServer" + args = ['fileserver','443','fileserver/logfile.txt'] - // args = ['fileserver','8080','fileserver/logfile.txt'] - javaLauncher = javaToolchains.launcherFor { + + javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(23) } - // debugOptions { - // enabled = true - // suspend = true - // } } task testJar(type: Jar) { @@ -205,7 +196,7 @@ task runAllTests(type: Test) { } publish { - // dependsOn runAllTests + dependsOn runAllTests } publishing { From 7896dac6c8848f8099d135845c3532fad9a483d6 Mon Sep 17 00:00:00 2001 From: robert engels Date: Wed, 8 Oct 2025 17:29:47 -0500 Subject: [PATCH 5/7] test 100 Continue to Expect header --- src/test/java/InputRead100Test.java | 89 +++++++++-------------------- 1 file changed, 27 insertions(+), 62 deletions(-) diff --git a/src/test/java/InputRead100Test.java b/src/test/java/InputRead100Test.java index e6bb04c..34d142f 100644 --- a/src/test/java/InputRead100Test.java +++ b/src/test/java/InputRead100Test.java @@ -1,26 +1,3 @@ -/* - * Copyright (c) 2021, 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 id=default * @bug 8349670 @@ -41,47 +18,28 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.Logger; -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; -import org.junit.jupiter.api.Test; +import org.testng.annotations.Test; import static java.nio.charset.StandardCharsets.*; public class InputRead100Test { private static final String someContext = "/context"; - static class ServerThreadFactory implements ThreadFactory { - static final AtomicLong tokens = new AtomicLong(); - - @Override - public Thread newThread(Runnable r) { - var thread = new Thread(r, "Server-" + tokens.incrementAndGet()); - thread.setDaemon(true); - return thread; - } - } - static { Logger.getLogger("").setLevel(Level.ALL); Logger.getLogger("").getHandlers()[0].setLevel(Level.ALL); } @Test - public void testContinue() throws Exception { + public static void testContinue() throws Exception { System.out.println("testContinue()"); InetAddress loopback = InetAddress.getLoopbackAddress(); HttpServer server = HttpServer.create(new InetSocketAddress(loopback, 0), 0); - ExecutorService executor = Executors.newCachedThreadPool(new ServerThreadFactory()); - server.setExecutor(executor); try { server.createContext( someContext, @@ -89,18 +47,10 @@ public void testContinue() throws Exception { System.err.println("Handling request: " + msg.getRequestURI()); byte[] reply = "Here is my reply!".getBytes(UTF_8); try { - try { - msg.getResponseHeaders().add("Header", "BeforeContinue"); - msg.sendResponseHeaders(100, -1); - BufferedReader r = new BufferedReader(new InputStreamReader(msg.getRequestBody())); - r.read(); - msg.sendResponseHeaders(200, reply.length); - msg.getResponseBody().write(reply); - msg.getResponseBody().close(); - Thread.sleep(50); - } catch (IOException | InterruptedException ie) { - ie.printStackTrace(); - } + msg.getRequestBody().readAllBytes(); + msg.sendResponseHeaders(200, reply.length); + msg.getResponseBody().write(reply); + msg.getResponseBody().close(); } finally { System.err.println("Request handled: " + msg.getRequestURI()); } @@ -108,10 +58,9 @@ public void testContinue() throws Exception { server.start(); System.out.println("Server started at port " + server.getAddress().getPort()); - runRawSocketHttpClient(loopback, server.getAddress().getPort(), 64 * 1024 + 16); + runRawSocketHttpClient(loopback, server.getAddress().getPort(), 0); } finally { System.out.println("shutting server down"); - executor.shutdown(); server.stop(0); } System.out.println("Server finished."); @@ -122,6 +71,9 @@ static void runRawSocketHttpClient(InetAddress address, int port, int contentLen Socket socket = null; PrintWriter writer = null; BufferedReader reader = null; + + boolean foundContinue = false; + final String CRLF = "\r\n"; try { socket = new Socket(address, port); @@ -141,19 +93,32 @@ static void runRawSocketHttpClient(InetAddress address, int port, int contentLen // there's more into the request. writer.flush(); System.out.println("Client wrote request to socket: " + socket); - + System.out.println("Client read 100 Continue response from server and headers"); + reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); + String line = reader.readLine(); + for (; line != null; line = reader.readLine()) { + if (line.isEmpty()) { + break; + } + System.out.println("interim response \"" + line + "\""); + if (line.startsWith("HTTP/1.1 100")) { + foundContinue = true; + } + } + if (!foundContinue) { + throw new IOException("Did not receive 100 continue from server"); + } writer.print(body); writer.flush(); System.out.println("Client wrote body to socket: " + socket); - reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); System.out.println("Client start reading from server:"); - String line = reader.readLine(); + line = reader.readLine(); for (; line != null; line = reader.readLine()) { if (line.isEmpty()) { break; } - System.out.println("\"" + line + "\""); + System.out.println("final response \"" + line + "\""); } System.out.println("Client finished reading from server"); } finally { From 2de1c20d9812b7c520b108115142086e99b7167e Mon Sep 17 00:00:00 2001 From: robert engels Date: Wed, 8 Oct 2025 17:31:08 -0500 Subject: [PATCH 6/7] handle manual 1xx responses including make connection upgrade generic rather than websocket specific --- src/main/java/robaho/net/httpserver/Code.java | 3 ++ .../robaho/net/httpserver/ExchangeImpl.java | 49 +++++++++---------- .../robaho/net/httpserver/ServerImpl.java | 9 +++- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/main/java/robaho/net/httpserver/Code.java b/src/main/java/robaho/net/httpserver/Code.java index a32a5cc..47c46ec 100644 --- a/src/main/java/robaho/net/httpserver/Code.java +++ b/src/main/java/robaho/net/httpserver/Code.java @@ -28,6 +28,7 @@ public class Code { public static final int HTTP_CONTINUE = 100; + public static final int HTTP_SWITCHING_PROTOCOLS = 101; public static final int HTTP_OK = 200; public static final int HTTP_CREATED = 201; public static final int HTTP_ACCEPTED = 202; @@ -71,6 +72,8 @@ static String msg(int code) { return " OK"; case HTTP_CONTINUE: return " Continue"; + case HTTP_SWITCHING_PROTOCOLS: + return " Switching Protocols"; case HTTP_CREATED: return " Created"; case HTTP_ACCEPTED: diff --git a/src/main/java/robaho/net/httpserver/ExchangeImpl.java b/src/main/java/robaho/net/httpserver/ExchangeImpl.java index 3a1ea7f..64851a4 100644 --- a/src/main/java/robaho/net/httpserver/ExchangeImpl.java +++ b/src/main/java/robaho/net/httpserver/ExchangeImpl.java @@ -40,8 +40,6 @@ import com.sun.net.httpserver.*; -import robaho.net.httpserver.websockets.WebSocketHandler; - class ExchangeImpl { Headers reqHdrs, rspHdrs; @@ -69,7 +67,8 @@ class ExchangeImpl { private static final String HEAD = "HEAD"; private static final String CONNECT = "CONNECT"; - + private static final String HEADER_CONNECTION = "Connection"; + private static final String HEADER_CONNECTION_UPGRADE = "Upgrade"; /* * streams which take care of the HTTP protocol framing * and are passed up to higher layers @@ -85,7 +84,7 @@ class ExchangeImpl { Map attributes; int rcode = -1; HttpPrincipal principal; - final boolean websocket; + boolean connectionUpgraded = false; ExchangeImpl( String m, URI u, Request req, long len, HttpConnection connection) throws IOException { @@ -97,11 +96,6 @@ class ExchangeImpl { this.method = m; this.uri = u; this.connection = connection; - this.websocket = WebSocketHandler.isWebsocketRequested(this.reqHdrs); - if (this.websocket) { - // length is indeterminate - len = -1; - } this.reqContentLen = len; /* ros only used for headers, body written directly to stream */ this.ros = req.outputStream(); @@ -135,6 +129,9 @@ private boolean isHeadRequest() { private boolean isConnectRequest() { return CONNECT.equals(getRequestMethod()); } + private boolean isUpgradeRequest() { + return HEADER_CONNECTION_UPGRADE.equalsIgnoreCase(reqHdrs.getFirst(HEADER_CONNECTION)); + } public void close() { if (closed) { @@ -170,7 +167,7 @@ public InputStream getRequestBody() { if (uis != null) { return uis; } - if (websocket || isConnectRequest()) { + if (connectionUpgraded || isConnectRequest() || isUpgradeRequest()) { // connection cannot be re-used uis = ris; } else if (reqContentLen == -1L) { @@ -232,7 +229,6 @@ public void sendResponseHeaders(int rCode, long contentLen) ros.write(statusLine.getBytes(ISO_CHARSET)); boolean noContentToSend = false; // assume there is content boolean noContentLengthHeader = false; // must not send Content-length is set - rspHdrs.set("Date", ActivityTimer.dateAndTime()); Integer bufferSize = (Integer)this.getAttribute(Attributes.SOCKET_WRITE_BUFFER); if(bufferSize!=null) { @@ -245,18 +241,17 @@ public void sendResponseHeaders(int rCode, long contentLen) var informational = rCode >= 100 && rCode < 200; if (informational) { - if (rCode == 101) { logger.log(Level.DEBUG, () -> "switching protocols"); + if (contentLen != 0) { + String msg = "sendResponseHeaders: rCode = " + rCode + + ": forcing contentLen = 0"; + logger.log(Level.WARNING, msg); + contentLen = 0; + } + connectionUpgraded = true; } - if (contentLen != 0) { - String msg = "sendResponseHeaders: rCode = " + rCode - + ": forcing contentLen = 0"; - logger.log(Level.WARNING, msg); - } - contentLen = 0; - flush = true; - + noContentLengthHeader = true; // the Content-length header must not be set for interim responses as they cannot have a body } else if ((rCode == 204) /* no content */ || (rCode == 304)) /* not modified */ { @@ -269,6 +264,10 @@ public void sendResponseHeaders(int rCode, long contentLen) noContentLengthHeader = (rCode != 304); } + if(!informational) { + rspHdrs.set("Date", ActivityTimer.dateAndTime()); + } + if (isHeadRequest() || rCode == 304) { /* * HEAD requests or 304 responses should not set a content length by passing it @@ -281,16 +280,16 @@ public void sendResponseHeaders(int rCode, long contentLen) noContentToSend = true; contentLen = 0; o.setWrappedStream(new FixedLengthOutputStream(this, ros, contentLen)); + } else if(informational && !connectionUpgraded) { + // don't want to set the stream for 1xx responses, except 101, the handler must call sendResponseHeaders again with the final code + flush = true; } else { /* not a HEAD request or 304 response */ if (contentLen == 0) { - if (websocket || isConnectRequest()) { + if (connectionUpgraded || isConnectRequest()) { o.setWrappedStream(ros); close = true; flush = true; } - else if (informational) { - flush = true; - } else if (http10) { o.setWrappedStream(new UndefLengthOutputStream(this, ros)); close = true; @@ -331,7 +330,7 @@ else if (http10) { this.rspContentLen = contentLen; sentHeaders = !informational; if(logger.isLoggable(Level.TRACE)) { - logger.log(Level.TRACE, "Sent headers: noContentToSend=" + noContentToSend); + logger.log(Level.TRACE, "sendResponseHeaders(), code="+rCode+", noContentToSend=" + noContentToSend + ", contentLen=" + contentLen); } if(flush) { ros.flush(); diff --git a/src/main/java/robaho/net/httpserver/ServerImpl.java b/src/main/java/robaho/net/httpserver/ServerImpl.java index 77ea234..214ad23 100644 --- a/src/main/java/robaho/net/httpserver/ServerImpl.java +++ b/src/main/java/robaho/net/httpserver/ServerImpl.java @@ -862,12 +862,17 @@ void sendReply( builder.append("HTTP/1.1 ") .append(code).append(Code.msg(code)).append("\r\n"); + var informational = (code >= 100 && code < 200); + if (text != null && text.length() != 0) { builder.append("Content-length: ") .append(text.length()).append("\r\n") .append("Content-type: text/html\r\n"); } else { - builder.append("Content-length: 0\r\n"); + if (!informational) { + // no body for 1xx responses + builder.append("Content-length: 0\r\n"); + } text = ""; } if (closeNow) { @@ -898,7 +903,7 @@ void logReply(int code, String requestStr, String text) { } else { r = requestStr; } - logger.log(Level.DEBUG, () -> "reply "+ r + " [" + code + " " + Code.msg(code) + "] (" + (text!=null ? text : "") + ")"); + logger.log(Level.DEBUG, () -> "reply "+ r + " [" + code + Code.msg(code) + "] (" + (text!=null ? text : "") + ")"); } void delay() { From ddf563034706c183d9d1eaac21ffa6de2d51fb20 Mon Sep 17 00:00:00 2001 From: robert engels Date: Wed, 8 Oct 2025 17:38:28 -0500 Subject: [PATCH 7/7] update comments --- src/main/java/robaho/net/httpserver/ExchangeImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/robaho/net/httpserver/ExchangeImpl.java b/src/main/java/robaho/net/httpserver/ExchangeImpl.java index 16d8b50..7da9ca0 100644 --- a/src/main/java/robaho/net/httpserver/ExchangeImpl.java +++ b/src/main/java/robaho/net/httpserver/ExchangeImpl.java @@ -287,7 +287,7 @@ public void sendResponseHeaders(int rCode, long contentLen) o.setWrappedStream(ros); close = true; flush = true; - } else { /* not a HEAD request or 304 response */ + } else { /* standard response with possible response data */ if (contentLen == 0) { if (http10) { o.setWrappedStream(new UndefLengthOutputStream(this, ros));