diff --git a/common/testing/http-junit5/src/main/java/io/helidon/common/testing/http/junit5/SocketHttpClient.java b/common/testing/http-junit5/src/main/java/io/helidon/common/testing/http/junit5/SocketHttpClient.java index 074bf961598..159d5c3341d 100644 --- a/common/testing/http-junit5/src/main/java/io/helidon/common/testing/http/junit5/SocketHttpClient.java +++ b/common/testing/http-junit5/src/main/java/io/helidon/common/testing/http/junit5/SocketHttpClient.java @@ -534,6 +534,7 @@ public SocketHttpClient manualRequest(String formatString, Object... args) throw /** * Continue sending more to text to socket. + * * @param payload text to be sent * @return this http client * @throws IOException @@ -548,6 +549,18 @@ public SocketHttpClient continuePayload(String payload) return this; } + /** + * Send single chunk as defined by RFC 9112 ยง7.1. + * + * @param payload of the chunk + * @return this http client + * @throws IOException + */ + public SocketHttpClient sendChunk(String payload) throws IOException { + continuePayload(Integer.toHexString(payload.length()) + EOL + payload + EOL); + return this; + } + /** * Override this to send a specific payload. * diff --git a/nima/tests/integration/webserver/webserver/src/test/java/io/helidon/nima/tests/integration/server/Continue100Test.java b/nima/tests/integration/webserver/webserver/src/test/java/io/helidon/nima/tests/integration/server/Continue100Test.java index 136ace714d5..4ace9e21e44 100644 --- a/nima/tests/integration/webserver/webserver/src/test/java/io/helidon/nima/tests/integration/server/Continue100Test.java +++ b/nima/tests/integration/webserver/webserver/src/test/java/io/helidon/nima/tests/integration/server/Continue100Test.java @@ -24,7 +24,11 @@ import io.helidon.nima.webserver.http.Handler; import io.helidon.nima.webserver.http.HttpRouting; import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.time.Duration; import java.util.Map; import java.util.Optional; @@ -90,6 +94,13 @@ static void routing(HttpRouting.Builder router) { ) .route(Http.Method.predicate(Http.Method.PUT, Http.Method.POST), PathMatchers.exact("/"), anyHandler) + .route(Http.Method.predicate(Http.Method.PUT), + PathMatchers.exact("/chunked"), (req, res) -> { + try (InputStream is = req.content().inputStream(); + OutputStream os = res.outputStream()) { + new ByteArrayInputStream(is.readAllBytes()).transferTo(os); + } + }) .route(Http.Method.predicate(Http.Method.GET), PathMatchers.exact("/"), (req, res) -> res.status(Http.Status.OK_200).send("GET TEST")); } @@ -98,6 +109,32 @@ public Continue100Test(WebServer server) { defaultPort = server.port(); } + @Test + void continue100ChunkedPut() throws Exception { + try (SocketHttpClient socketHttpClient = SocketHttpClient.create(defaultPort)) { + socketHttpClient + .manualRequest(""" + PUT /chunked HTTP/1.1 + Host: localhost:%d + Expect: 100-continue + Accept: */* + Transfer-Encoding: chunked + Content-Type: text/plain + + """, defaultPort) + .awaitResponse("HTTP/1.1 100 Continue", "\n\n") + .sendChunk("This ") + .sendChunk("is ") + .sendChunk("chunked!") + .sendChunk(""); + + String received = socketHttpClient.receive(); + assertThat(received, startsWith("HTTP/1.1 200 OK")); + assertThat(received, endsWith("This is chunked!")); + + checkKeepAlive(socketHttpClient); + } + } @Test void continue100Post() throws Exception { try (SocketHttpClient socketHttpClient = SocketHttpClient.create(defaultPort)) { diff --git a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http1/Http1ServerResponse.java b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http1/Http1ServerResponse.java index 72bfe1515b3..105cb7deb17 100644 --- a/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http1/Http1ServerResponse.java +++ b/nima/webserver/webserver/src/main/java/io/helidon/nima/webserver/http1/Http1ServerResponse.java @@ -152,8 +152,6 @@ public OutputStream outputStream() { } streamingEntity = true; - request.reset(); - this.outputStream = new BlockingOutputStream(headers, trailers, this::status, @@ -162,6 +160,7 @@ public OutputStream outputStream() { () -> { this.isSent = true; afterSend(); + request.reset(); }, ctx, sendListener,