diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/pom.xml b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/pom.xml index bac406ccdbc1c..3807caaaff689 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/pom.xml +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/pom.xml @@ -99,6 +99,11 @@ quarkus-jaxrs-client-reactive-deployment test + + io.quarkus + quarkus-reactive-routes-deployment + test + diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/headers/VertxHeadersTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/headers/VertxHeadersTest.java new file mode 100644 index 0000000000000..c2b902c7e2d50 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/headers/VertxHeadersTest.java @@ -0,0 +1,63 @@ +package io.quarkus.resteasy.reactive.server.test.headers; + +import static io.restassured.RestAssured.when; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.ext.Provider; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.vertx.web.RouteFilter; +import io.vertx.ext.web.RoutingContext; + +public class VertxHeadersTest { + + @RegisterExtension + static QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar.addClasses(VertxFilter.class, JaxRsFilter.class, TestResource.class)); + + @Test + void testVaryHeaderValues() { + var headers = when().get("/test") + .then() + .statusCode(200) + .extract().headers(); + assertThat(headers.getValues(HttpHeaders.VARY)).containsExactlyInAnyOrder("Origin", "Prefer"); + } + + public static class VertxFilter { + @RouteFilter + void addVary(final RoutingContext rc) { + rc.response().headers().add(HttpHeaders.VARY, "Origin"); + rc.next(); + } + } + + @Provider + public static class JaxRsFilter implements ContainerResponseFilter { + @Override + public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext) + throws IOException { + responseContext.getHeaders().add(HttpHeaders.VARY, "Prefer"); + } + } + + @Path("test") + public static class TestResource { + + @GET + public String test() { + return "test"; + } + } +} diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java index 4ad07b6084ea4..19da861778f6f 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java @@ -13,6 +13,7 @@ import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -71,7 +72,13 @@ public void accept(ResteasyReactiveRequestContext context) { } }; - private static final String CONTENT_TYPE = "Content-Type"; // use this instead of the Vert.x constant because the TCK expects upper case + private static final String CONTENT = "Content"; + private static final String CONTENT_LOWER = "content"; + private static final String TYPE = "Type"; + private static final String TYPE_LOWER = "type"; + private static final String LENGTH = "Length"; + private static final String LENGTH_LOWER = "length"; + private static final String CONTENT_TYPE = CONTENT + "-" + TYPE; // use this instead of the Vert.x constant because the TCK expects upper case static { primitivesToWrappers.put(boolean.class, Boolean.class); @@ -484,13 +491,27 @@ public static void encodeResponseHeaders(ResteasyReactiveRequestContext requestC MultivaluedMap headers = response.getHeaders(); for (Map.Entry> entry : headers.entrySet()) { if (entry.getValue().size() == 1) { + String header = entry.getKey(); + boolean useSet = requireSingleHeader(header); Object o = entry.getValue().get(0); if (o == null) { - vertxResponse.setResponseHeader(entry.getKey(), ""); + if (useSet) { + vertxResponse.setResponseHeader(header, ""); + } else { + vertxResponse.addResponseHeader(header, ""); + } } else if (o instanceof CharSequence) { - vertxResponse.setResponseHeader(entry.getKey(), (CharSequence) o); + if (useSet) { + vertxResponse.setResponseHeader(header, (CharSequence) o); + } else { + vertxResponse.addResponseHeader(header, (CharSequence) o); + } } else { - vertxResponse.setResponseHeader(entry.getKey(), (CharSequence) HeaderUtil.headerToString(o)); + if (useSet) { + vertxResponse.setResponseHeader(header, (CharSequence) HeaderUtil.headerToString(o)); + } else { + vertxResponse.addResponseHeader(header, (CharSequence) HeaderUtil.headerToString(o)); + } } } else { List strValues = new ArrayList<>(entry.getValue().size()); @@ -502,4 +523,15 @@ public static void encodeResponseHeaders(ResteasyReactiveRequestContext requestC } } + private static boolean requireSingleHeader(String header) { + if (!(header.startsWith(CONTENT) || header.startsWith(CONTENT_LOWER))) { + return false; + } + if (header.length() < CONTENT.length() + 2) { + return false; + } + String substring = header.substring(CONTENT.length() + 1).toLowerCase(Locale.ROOT); + return substring.equals(TYPE_LOWER) || substring.equals(LENGTH_LOWER); + } + }