diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/MediaTypeMapper.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/MediaTypeMapper.java index 054febcab4d5c..79780144bb854 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/MediaTypeMapper.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/MediaTypeMapper.java @@ -58,15 +58,11 @@ public MediaTypeMapper(List runtimeResources) { @Override public void handle(ResteasyReactiveRequestContext requestContext) throws Exception { - String contentType = requestContext.serverRequest().getRequestHeader(HttpHeaders.CONTENT_TYPE); - // if there's no Content-Type it's */* - MediaType contentMediaType = contentType != null ? MediaType.valueOf(contentType) : MediaType.WILDCARD_TYPE; // find the best matching consumes type. Note that the arguments are reversed from their definition // of desired/provided, but we do want the result to be a media type we consume, since that's how we key // our methods, rather than the single media type we get from the client. This way we ensure we get the // best match. - MediaType consumes = MediaTypeHelper.getBestMatch(Collections.singletonList(contentMediaType), - consumesTypes); + MediaType consumes = MediaTypeHelper.getBestMatch(contentTypeFromRequest(requestContext), consumesTypes); Holder selectedHolder = resourcesByConsumes.get(consumes); // if we haven't found anything, try selecting the wildcard type, if any if (selectedHolder == null) { @@ -97,6 +93,18 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti requestContext.restart(selectedResource); } + private List contentTypeFromRequest(ResteasyReactiveRequestContext requestContext) { + List contentTypeList = requestContext.getHttpHeaders().getRequestHeader(HttpHeaders.CONTENT_TYPE); + if (contentTypeList.isEmpty()) { + return Collections.singletonList(MediaType.WILDCARD_TYPE); + } + List result = new ArrayList<>(contentTypeList.size()); + for (String s : contentTypeList) { + result.add(MediaType.valueOf(s)); + } + return result; + } + public MediaType selectMediaType(ResteasyReactiveRequestContext requestContext, Holder holder) { MediaType selected = null; List accepts = requestContext.getHttpHeaders().getRequestHeader(HttpHeaders.ACCEPT); diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchContentTypeInHeaderTest.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchContentTypeInHeaderTest.java new file mode 100644 index 0000000000000..e17f34ad9c259 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchContentTypeInHeaderTest.java @@ -0,0 +1,114 @@ +package org.jboss.resteasy.reactive.server.vertx.test.matching; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + +import java.io.IOException; +import java.util.function.Supplier; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.PreMatching; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.ext.Provider; + +import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class PreMatchContentTypeInHeaderTest { + + @RegisterExtension + static ResteasyReactiveUnitTest test = new ResteasyReactiveUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class); + } + }); + + @Test + public void filterNotSettingContentType() { + given() + .header("Content-Type", "application/json") + .body("[]") + .when() + .post("/test") + .then() + .statusCode(200) + .body(is("json-[]")); + + given() + .header("Content-Type", "text/plain") + .body("input") + .when() + .post("/test") + .then() + .statusCode(200) + .body(is("text-input")); + } + + @Test + public void filterSettingContentTypeToText() { + given() + .header("Content-Type", "application/json") + .header("test-content-type", "text/plain") + .body("[]") + .when() + .post("/test") + .then() + .statusCode(200) + .body(is("text-[]")); + } + + @Test + public void filterSettingContentTypeToJson() { + given() + .header("Content-Type", "text/plain") + .header("test-content-type", "application/json") + .body("input") + .when() + .post("/test") + .then() + .statusCode(200) + .body(is("json-input")); + } + + @Path("test") + public static class TestResource { + + @POST + @Consumes(MediaType.TEXT_PLAIN) + public String fromText(String input) { + return "text-" + input; + } + + @POST + @Consumes(MediaType.APPLICATION_JSON) + public String fromJson(String input) { + return "json-" + input; + } + } + + public record Result(String message) { + + } + + @Provider + @PreMatching + public static class Filter implements ContainerRequestFilter { + + @Override + public void filter(ContainerRequestContext context) throws IOException { + String testContentType = context.getHeaderString("test-content-type"); + if (testContentType != null) { + context.getHeaders().putSingle("Content-Type", testContentType); + } + } + } +}