diff --git a/webserver/tests/webserver/src/test/java/io/helidon/webserver/tests/HeadersServerTest.java b/webserver/tests/webserver/src/test/java/io/helidon/webserver/tests/HeadersServerTest.java index ac708821857..22ef244d9df 100644 --- a/webserver/tests/webserver/src/test/java/io/helidon/webserver/tests/HeadersServerTest.java +++ b/webserver/tests/webserver/src/test/java/io/helidon/webserver/tests/HeadersServerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -89,6 +89,16 @@ static void router(HttpRouting.Builder router) { res.send(DATA); } ); + router.route(GET, "/stream-with-trailers-and-length", + (req, res) -> { + res.header(HeaderNames.TRAILER, TEST_TRAILER_HEADER.name()); + res.header(HeaderNames.CONTENT_LENGTH, String.valueOf(DATA.length())); // must switch to chunked + try (var os = res.outputStream()) { + os.write(DATA.getBytes()); + } + res.trailers().add(TEST_TRAILER_HEADER); + } + ); } @Test @@ -149,6 +159,19 @@ void trailersNoTrailers(WebClient client) { + "response headers have trailer names definition 'Trailer: '")); } + @Test + void streamWithTrailersAndLength(WebClient client) throws IOException { + ClientResponseTyped res = client + .get("/stream-with-trailers-and-length") + .header(HeaderValues.TE_TRAILERS) + .request(InputStream.class); + assertThat(res.headers(), hasHeader(HeaderValues.TRANSFER_ENCODING_CHUNKED)); // trailers need chunked + try (var ins = res.entity()) { + assertThat(ins.readAllBytes(), is(DATA.getBytes())); + } + assertThat(res.trailers(), hasHeader(TEST_TRAILER_HEADER)); + } + private void checkCachedConnection(ClientResponseHeaders h) { if (clientPort == -1) { clientPort = h.get(CLIENT_PORT_HEADER_NAME).asInt().get(); 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 3f2804da792..b2b0cb70c0e 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 @@ -479,10 +479,15 @@ private BlockingOutputStream(ServerResponseHeaders headers, this.validateHeaders = validateHeaders; } - void checkResponseHeaders(){ - this.isChunked = !headers.contains(HeaderNames.CONTENT_LENGTH); - this.forcedChunked = headers.contains(HeaderValues.TRANSFER_ENCODING_CHUNKED) - || headers.contains(HeaderNames.TRAILER); + void checkResponseHeaders() { + if (headers.contains(HeaderNames.TRAILER)) { + headers.remove(HeaderNames.CONTENT_LENGTH); + isChunked = true; + forcedChunked = true; + } else { + isChunked = !headers.contains(HeaderNames.CONTENT_LENGTH); + forcedChunked = headers.contains(HeaderValues.TRANSFER_ENCODING_CHUNKED); + } } @Override @@ -557,13 +562,13 @@ void commit() { } if (sendTrailers) { - // not optimized, trailers enabled: we need to write trailers - trailers.set(STREAM_RESULT_NAME, streamResult.get()); - BufferData buffer = BufferData.growing(128); - writeHeaders(trailers, buffer, this.validateHeaders); - buffer.write('\r'); // "\r\n" - empty line after headers - buffer.write('\n'); - dataWriter.write(buffer); + // not optimized, trailers enabled: we need to write trailers + trailers.set(STREAM_RESULT_NAME, streamResult.get()); + BufferData buffer = BufferData.growing(128); + writeHeaders(trailers, buffer, this.validateHeaders); + buffer.write('\r'); // "\r\n" - empty line after headers + buffer.write('\n'); + dataWriter.write(buffer); } responseCloseRunnable.run();