Skip to content

Commit ca1a5b6

Browse files
authored
Merge pull request #49894 from geoand/#49875
Filter out "Transfer-Encoding" header from a REST Response
2 parents 09ba2a4 + 96f2665 commit ca1a5b6

File tree

5 files changed

+117
-32
lines changed

5 files changed

+117
-32
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package io.quarkus.resteasy.reactive.server.test.headers;
2+
3+
import static io.restassured.RestAssured.when;
4+
import static org.assertj.core.api.Assertions.*;
5+
6+
import java.io.ByteArrayInputStream;
7+
import java.io.InputStream;
8+
import java.nio.charset.StandardCharsets;
9+
import java.util.stream.Collectors;
10+
import java.util.stream.IntStream;
11+
12+
import jakarta.ws.rs.GET;
13+
import jakarta.ws.rs.Path;
14+
import jakarta.ws.rs.Produces;
15+
import jakarta.ws.rs.core.MediaType;
16+
import jakarta.ws.rs.core.Response;
17+
18+
import org.jboss.resteasy.reactive.RestResponse;
19+
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.api.extension.RegisterExtension;
21+
22+
import io.quarkus.test.QuarkusUnitTest;
23+
import io.restassured.http.Headers;
24+
25+
public class IgnoredResponseHeadersTest {
26+
27+
@RegisterExtension
28+
static QuarkusUnitTest TEST = new QuarkusUnitTest()
29+
.withApplicationRoot((jar) -> jar.addClasses(Resource.class));
30+
31+
@Test
32+
public void testResponse() {
33+
doTest("response");
34+
}
35+
36+
@Test
37+
public void testRestResponse() {
38+
doTest("rest-response");
39+
}
40+
41+
private static void doTest(String path) {
42+
Headers responseHeaders = when()
43+
.get("/resource/" + path)
44+
.then()
45+
.statusCode(200)
46+
.extract().headers();
47+
48+
assertThat(responseHeaders.getList("Transfer-Encoding"))
49+
.extracting("value")
50+
.singleElement().isEqualTo("chunked");
51+
}
52+
53+
@Path("resource")
54+
public static class Resource {
55+
56+
@Path("response")
57+
@Produces(MediaType.TEXT_PLAIN)
58+
@GET
59+
public Response response() {
60+
return Response.ok(largeString())
61+
.header("Transfer-Encoding", "chunked")
62+
.header("Content-Type", "text/plain")
63+
.build();
64+
}
65+
66+
@Path("rest-response")
67+
@Produces(MediaType.TEXT_PLAIN)
68+
@GET
69+
public RestResponse<InputStream> restResponse() {
70+
return RestResponse.ResponseBuilder.ok(largeString())
71+
.header("Transfer-Encoding", "chunked")
72+
.header("Content-Type", "text/plain")
73+
.build();
74+
}
75+
76+
private static InputStream largeString() {
77+
String content = IntStream.range(1, 100_000).mapToObj(i -> "Hello no." + i).collect(Collectors.joining(","));
78+
return new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
79+
}
80+
}
81+
}

extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/headers/VertxHeadersTest.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import static io.restassured.RestAssured.when;
44
import static org.assertj.core.api.Assertions.assertThat;
5-
import static org.hamcrest.CoreMatchers.is;
65

76
import java.io.IOException;
87

@@ -20,7 +19,6 @@
2019

2120
import io.quarkus.test.QuarkusUnitTest;
2221
import io.quarkus.vertx.web.RouteFilter;
23-
import io.restassured.http.Headers;
2422
import io.vertx.ext.web.RoutingContext;
2523

2624
public class VertxHeadersTest {
@@ -38,16 +36,6 @@ void testVaryHeaderValues() {
3836
assertThat(headers.getValues(HttpHeaders.VARY)).containsExactlyInAnyOrder("Origin", "Prefer");
3937
}
4038

41-
@Test
42-
void testTransferEncodingHeaderValues() {
43-
Headers headers = when().get("/test/response")
44-
.then()
45-
.statusCode(200)
46-
.header("Transfer-Encoding", is("chunked")).extract().headers();
47-
48-
assertThat(headers.asList()).noneMatch(h -> h.getName().equals("transfer-encoding"));
49-
}
50-
5139
public static class VertxFilter {
5240
@RouteFilter
5341
void addVary(final RoutingContext rc) {

independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public void accept(ResteasyReactiveRequestContext context) {
8686
private static final String LENGTH = "Length";
8787
private static final String LENGTH_LOWER = "length";
8888
private static final String CONTENT_TYPE = CONTENT + "-" + TYPE; // use this instead of the Vert.x constant because the TCK expects upper case
89-
private static final String TRANSFER_ENCODING = "Transfer-Encoding";
89+
public static final String TRANSFER_ENCODING = "Transfer-Encoding";
9090

9191
public final static List<Serialisers.BuiltinReader> BUILTIN_READERS = List.of(
9292
new Serialisers.BuiltinReader(String.class, ServerStringMessageBodyHandler.class,

independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ResponseHandler.java

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import java.io.ByteArrayInputStream;
44
import java.util.Collections;
55
import java.util.List;
6+
import java.util.Locale;
67
import java.util.Map;
8+
import java.util.Set;
79

810
import jakarta.ws.rs.core.GenericEntity;
911
import jakarta.ws.rs.core.HttpHeaders;
@@ -15,6 +17,7 @@
1517
import org.jboss.resteasy.reactive.server.core.EncodedMediaType;
1618
import org.jboss.resteasy.reactive.server.core.LazyResponse;
1719
import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
20+
import org.jboss.resteasy.reactive.server.core.ServerSerialisers;
1821
import org.jboss.resteasy.reactive.server.jaxrs.ResponseBuilderImpl;
1922
import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;
2023

@@ -26,6 +29,10 @@ public class ResponseHandler implements ServerRestHandler {
2629

2730
public static final ResponseHandler NO_CUSTOMIZER_INSTANCE = new ResponseHandler();
2831

32+
// TODO: we need to think about what other headers coming from the existing Response need to be ignored
33+
private static final Set<String> IGNORED_HEADERS = Collections.singleton(ServerSerialisers.TRANSFER_ENCODING.toLowerCase(
34+
Locale.ROOT));
35+
2936
private final List<ResponseBuilderCustomizer> responseBuilderCustomizers;
3037

3138
public ResponseHandler(List<ResponseBuilderCustomizer> responseBuilderCustomizers) {
@@ -39,14 +46,12 @@ private ResponseHandler() {
3946
@Override
4047
public void handle(ResteasyReactiveRequestContext requestContext) throws Exception {
4148
Object result = requestContext.getResult();
42-
if (result instanceof Response) {
49+
if (result instanceof Response existing) {
4350
boolean mediaTypeAlreadyExists = false;
4451
//we already have a response
4552
//set it explicitly
4653
ResponseBuilderImpl responseBuilder;
47-
Response existing = (Response) result;
48-
if (existing.getEntity() instanceof GenericEntity) {
49-
GenericEntity<?> genericEntity = (GenericEntity<?>) existing.getEntity();
54+
if (existing.getEntity() instanceof GenericEntity<?> genericEntity) {
5055
requestContext.setGenericReturnType(genericEntity.getType());
5156
responseBuilder = fromResponse(existing);
5257
responseBuilder.entity(genericEntity.getEntity());
@@ -56,17 +61,15 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti
5661
requestContext.setGenericReturnType(existing.getEntity().getClass());
5762
//TODO: super inefficient
5863
responseBuilder = fromResponse(existing);
59-
if ((result instanceof ResponseImpl)) {
64+
if ((result instanceof ResponseImpl responseImpl)) {
6065
// needed in order to preserve entity annotations
61-
ResponseImpl responseImpl = (ResponseImpl) result;
6266
if (responseImpl.getEntityAnnotations() != null) {
6367
requestContext.setAdditionalAnnotations(responseImpl.getEntityAnnotations());
6468
}
6569

66-
// this is a weird case where the response comes from the the rest-client
70+
// this is a weird case where the response comes from the rest-client
6771
if (responseBuilder.getEntity() == null) {
68-
if (responseImpl.getEntityStream() instanceof ByteArrayInputStream) {
69-
ByteArrayInputStream byteArrayInputStream = (ByteArrayInputStream) responseImpl.getEntityStream();
72+
if (responseImpl.getEntityStream() instanceof ByteArrayInputStream byteArrayInputStream) {
7073
responseBuilder.entity(byteArrayInputStream.readAllBytes());
7174
}
7275
}
@@ -88,31 +91,27 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti
8891
} else {
8992
requestContext.setResponse(new LazyResponse.Existing(responseBuilder.build()));
9093
}
91-
} else if (result instanceof RestResponse) {
94+
} else if (result instanceof RestResponse<?> existing) {
9295
boolean mediaTypeAlreadyExists = false;
9396
//we already have a response
9497
//set it explicitly
9598
ResponseBuilderImpl responseBuilder;
96-
RestResponse<?> existing = (RestResponse<?>) result;
97-
if (existing.getEntity() instanceof GenericEntity) {
98-
GenericEntity<?> genericEntity = (GenericEntity<?>) existing.getEntity();
99+
if (existing.getEntity() instanceof GenericEntity<?> genericEntity) {
99100
requestContext.setGenericReturnType(genericEntity.getType());
100101
responseBuilder = fromResponse(existing);
101102
responseBuilder.entity(genericEntity.getEntity());
102103
} else {
103104
//TODO: super inefficient
104105
responseBuilder = fromResponse(existing);
105-
if ((result instanceof RestResponseImpl)) {
106+
if ((result instanceof RestResponseImpl<?> responseImpl)) {
106107
// needed in order to preserve entity annotations
107-
RestResponseImpl<?> responseImpl = (RestResponseImpl<?>) result;
108108
if (responseImpl.getEntityAnnotations() != null) {
109109
requestContext.setAdditionalAnnotations(responseImpl.getEntityAnnotations());
110110
}
111111

112-
// this is a weird case where the response comes from the the rest-client
112+
// this is a weird case where the response comes from the rest-client
113113
if (responseBuilder.getEntity() == null) {
114-
if (responseImpl.getEntityStream() instanceof ByteArrayInputStream) {
115-
ByteArrayInputStream byteArrayInputStream = (ByteArrayInputStream) responseImpl.getEntityStream();
114+
if (responseImpl.getEntityStream() instanceof ByteArrayInputStream byteArrayInputStream) {
116115
responseBuilder.entity(byteArrayInputStream.readAllBytes());
117116
}
118117
}
@@ -198,6 +197,9 @@ private ResponseBuilderImpl fromResponse(Response response) {
198197
var headers = response.getHeaders();
199198
if (headers != null) {
200199
for (String headerName : headers.keySet()) {
200+
if (IGNORED_HEADERS.contains(headerName.toLowerCase(Locale.ROOT))) {
201+
continue;
202+
}
201203
List<Object> headerValues = headers.get(headerName);
202204
for (Object headerValue : headerValues) {
203205
b.header(headerName, headerValue);
@@ -214,6 +216,9 @@ private ResponseBuilderImpl fromResponse(RestResponse<?> response) {
214216
b.entity(response.getEntity());
215217
}
216218
for (String headerName : response.getHeaders().keySet()) {
219+
if (IGNORED_HEADERS.contains(headerName.toLowerCase(Locale.ROOT))) {
220+
continue;
221+
}
217222
List<Object> headerValues = response.getHeaders().get(headerName);
218223
for (Object headerValue : headerValues) {
219224
b.header(headerName, headerValue);

independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/headers/ChunkedHeaderTest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
import static org.hamcrest.CoreMatchers.is;
55
import static org.hamcrest.CoreMatchers.nullValue;
66

7+
import java.io.ByteArrayInputStream;
8+
import java.io.InputStream;
9+
import java.nio.charset.StandardCharsets;
10+
import java.util.stream.Collectors;
11+
import java.util.stream.IntStream;
12+
713
import jakarta.ws.rs.GET;
814
import jakarta.ws.rs.Path;
915
import jakarta.ws.rs.core.Response;
@@ -34,7 +40,12 @@ public static class TestResource {
3440
@GET
3541
@Path("hello")
3642
public Response hello() {
37-
return Response.ok("hello").header("Transfer-Encoding", "chunked").build();
43+
return Response.ok(largeString()).header("Transfer-Encoding", "chunked").build();
44+
}
45+
46+
private static InputStream largeString() {
47+
String content = IntStream.range(1, 100_000).mapToObj(i -> "Hello no." + i).collect(Collectors.joining(","));
48+
return new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
3849
}
3950
}
4051
}

0 commit comments

Comments
 (0)