diff --git a/webserver/tests/webserver/src/test/java/io/helidon/webserver/tests/ContentEncodingEmptyTest.java b/webserver/tests/webserver/src/test/java/io/helidon/webserver/tests/ContentEncodingEmptyTest.java index 295c498d5e7..81d35d0cfb7 100644 --- a/webserver/tests/webserver/src/test/java/io/helidon/webserver/tests/ContentEncodingEmptyTest.java +++ b/webserver/tests/webserver/src/test/java/io/helidon/webserver/tests/ContentEncodingEmptyTest.java @@ -48,6 +48,11 @@ static void routing(HttpRules rules) { res.streamFilter(os -> os); // forces filter codepath res.status(Status.NO_CONTENT_204); res.send(); + }).post("hello_stream", (req, res) -> { + try (var out = res.outputStream()) { + res.status(Status.NO_CONTENT_204); + out.flush(); + } }); } @@ -68,4 +73,13 @@ void gzipEncodeEmptyEntityFilter() { .request(); assertThat(res.status().code(), is(204)); } + + @Test + void gzipEncodeEmptyEntityStream() { + Http1ClientResponse res = client.post("hello_stream") + .header(HeaderNames.CONTENT_TYPE, "application/json") + .header(HeaderNames.ACCEPT_ENCODING, "gzip") + .request(); + assertThat(res.status().code(), is(204)); + } } diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/http1/Http1ServerResponse.java b/webserver/webserver/src/main/java/io/helidon/webserver/http1/Http1ServerResponse.java index b2b0cb70c0e..b28e92739d6 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/http1/Http1ServerResponse.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/http1/Http1ServerResponse.java @@ -110,9 +110,7 @@ static void nonEntityBytes(ServerResponseHeaders headers, status = status == null ? Status.OK_200 : status; - if (status.code() == Status.NO_CONTENT_204.code() - || status.code() == Status.RESET_CONTENT_205.code() - || status.code() == Status.NOT_MODIFIED_304.code()) { + if (isNoEntityStatus(status)) { // https://www.rfc-editor.org/rfc/rfc9110#status.204 // A 204 response is terminated by the end of the header section; it cannot contain content or trailers // ditto for 205, and 304 @@ -431,6 +429,13 @@ private OutputStream outputStream(boolean skipEncoders) { return outputStreamFilter == null ? encodedOutputStream : outputStreamFilter.apply(encodedOutputStream); } + private static boolean isNoEntityStatus(Status status) { + int code = status.code(); + return code == Status.NO_CONTENT_204.code() + || code == Status.RESET_CONTENT_205.code() + || code == Status.NOT_MODIFIED_304.code(); + } + static class BlockingOutputStream extends OutputStream { private final ServerResponseHeaders headers; private final WritableHeaders trailers; @@ -693,9 +698,15 @@ private void sendFirstChunkOnly() { } private void sendHeadersAndPrepare() { + Status usedStatus = status.get(); + if (headers.contains(HeaderNames.CONTENT_LENGTH)) { contentLength = headers.contentLength().orElse(-1); isChunked = false; + } else if (isNoEntityStatus(usedStatus)) { + // force content length to zero to prevent validation errors + headers.set(HeaderValues.CONTENT_LENGTH_ZERO); + contentLength = 0; } else { contentLength = -1; // Add chunked encoding, if there is no other transfer-encoding headers @@ -710,7 +721,6 @@ private void sendHeadersAndPrepare() { } // at this moment, we must send headers - Status usedStatus = status.get(); sendListener.status(ctx, usedStatus); sendListener.headers(ctx, headers); BufferData bufferData = BufferData.growing(256);