diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/VariableProducesHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/VariableProducesHandler.java index 6c1e712350e67..77faad733c928 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/VariableProducesHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/VariableProducesHandler.java @@ -44,8 +44,18 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti //TODO? return; } - MediaType res = mediaTypeList.negotiateProduces(requestContext.serverRequest().getRequestHeader(HttpHeaders.ACCEPT)) - .getKey(); + MediaType res = null; + List accepts = requestContext.getHttpHeaders().getRequestHeader(HttpHeaders.ACCEPT); + for (String accept : accepts) { + res = mediaTypeList.negotiateProduces(accept).getKey(); + if (res != null) { + break; + } + } + if (res == null) { // fallback for some tests + res = mediaTypeList.negotiateProduces(requestContext.serverRequest().getRequestHeader(HttpHeaders.ACCEPT)) + .getKey(); + } if (res == null) { throw new WebApplicationException(Response .notAcceptable(Variant.mediaTypes(mediaTypeList.getSortedMediaTypes()).build()) diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchAcceptInHeader.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchAcceptInHeader.java deleted file mode 100644 index cfbd44a4795fc..0000000000000 --- a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchAcceptInHeader.java +++ /dev/null @@ -1,124 +0,0 @@ -package org.jboss.resteasy.reactive.server.vertx.test.matching; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; - -import java.util.function.Supplier; - -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.container.ContainerRequestContext; -import jakarta.ws.rs.container.ContainerRequestFilter; -import jakarta.ws.rs.container.PreMatching; -import jakarta.ws.rs.core.HttpHeaders; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.MultivaluedMap; -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 PreMatchAcceptInHeader { - - @RegisterExtension - static ResteasyReactiveUnitTest test = new ResteasyReactiveUnitTest() - .setArchiveProducer(new Supplier<>() { - @Override - public JavaArchive get() { - return ShrinkWrap.create(JavaArchive.class) - .addClass(PathSegmentTest.Resource.class); - } - }); - - @Test - void browserDefault() { - given().accept("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8") - .when() - .get("test") - .then() - .statusCode(200) - .body(containsString("")); - } - - @Test - void text() { - given().accept("text/plain") - .when() - .get("test") - .then() - .statusCode(200) - .body(equalTo("test")); - } - - @Test - void html() { - given().accept("text/html") - .when() - .get("test") - .then() - .statusCode(200) - .body(equalTo("test")); - } - - @Test - void json() { - given().accept("application/json") - .when() - .get("test") - .then() - .statusCode(404); - } - - @Test - void setAcceptToTextInFilter() { - given().accept("application/json") - .header("x-set-accept-to-text", "true") - .when() - .get("test") - .then() - .statusCode(200) - .body(equalTo("test")); - } - - @Path("/test") - public static class Resource { - - @GET - @Produces(MediaType.TEXT_PLAIN) - public String text() { - return "text"; - } - - @GET - @Produces(MediaType.TEXT_HTML) - public String html() { - return """ - - - - - Hello World - - - """; - } - } - - @PreMatching - @Provider - public static class SetAcceptHeaderFilter implements ContainerRequestFilter { - - @Override - public void filter(ContainerRequestContext requestContext) { - MultivaluedMap headers = requestContext.getHeaders(); - if ("true".equals(headers.getFirst("x-set-accept-to-text"))) { - headers.putSingle(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN); - } - } - } -} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchAcceptInHeaderTest.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchAcceptInHeaderTest.java new file mode 100644 index 0000000000000..16b31cd54b127 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/PreMatchAcceptInHeaderTest.java @@ -0,0 +1,230 @@ +package org.jboss.resteasy.reactive.server.vertx.test.matching; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.function.Supplier; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.PreMatching; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.ext.Provider; + +import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo; +import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyWriter; +import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; +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 PreMatchAcceptInHeaderTest { + + @RegisterExtension + static ResteasyReactiveUnitTest test = new ResteasyReactiveUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class); + } + }); + + @Test + void browserDefault() { + given().accept("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8") + .when() + .get("test") + .then() + .statusCode(200) + .body(containsString("")); + } + + @Test + void text() { + given().accept("text/plain") + .when() + .get("test") + .then() + .statusCode(200) + .body(equalTo("text")); + } + + @Test + void html() { + given().accept("text/html") + .when() + .get("test") + .then() + .statusCode(200) + .body(containsString("")); + } + + @Test + void json() { + given().accept("application/json") + .when() + .get("test") + .then() + .statusCode(406); + } + + @Test + void setAcceptToTextInFilter() { + given().accept("application/json") + .header("x-set-accept-to-text", "true") + .when() + .get("test") + .then() + .statusCode(200) + .body(equalTo("text")); + } + + @Test + void entityJsonWithoutAcceptToTextInFilter() { + given().accept("application/json") + .when() + .get("test/entity") + .then() + .statusCode(200) + .body(containsString("\"text\"")); + } + + @Test + void entityTextWithoutAcceptToTextInFilter() { + given().accept("text/plain") + .when() + .get("test/entity") + .then() + .statusCode(200) + .body(equalTo("text")); + } + + @Test + void entityTextWithAcceptToTextInFilter() { + given().accept("application/json") + .header("x-set-accept-to-text", "true") + .when() + .get("test/entity") + .then() + .statusCode(200) + .body(equalTo("text")); + } + + @Path("/test") + public static class Resource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String text() { + return "text"; + } + + @GET + @Produces(MediaType.TEXT_HTML) + public String html() { + return """ + + + + + Hello World + + + """; + } + + @GET + @Path("entity") + @Produces({ MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON }) + public Entity entity() { + return new Entity("text"); + } + } + + public record Entity(String value) { + } + + @PreMatching + @Provider + public static class SetAcceptHeaderFilter implements ContainerRequestFilter { + + @Override + public void filter(ContainerRequestContext requestContext) { + MultivaluedMap headers = requestContext.getHeaders(); + if ("true".equals(headers.getFirst("x-set-accept-to-text"))) { + headers.putSingle(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN); + } + } + } + + @Provider + @Produces(MediaType.TEXT_PLAIN) + public static class DummyTextMessageBodyWriter implements ServerMessageBodyWriter { + + @Override + public boolean isWriteable(Class type, Type genericType, ResteasyReactiveResourceInfo target, + MediaType mediaType) { + return Entity.class.equals(type); + } + + @Override + public void writeResponse(Object o, Type genericType, ServerRequestContext context) + throws WebApplicationException, IOException { + context.serverResponse().end(((Entity) o).value()); + } + + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return Entity.class.equals(type); + } + + @Override + public void writeTo(Object o, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, OutputStream entityStream) + throws IOException, WebApplicationException { + throw new IllegalStateException("should not be called"); + } + } + + @Provider + @Produces(MediaType.APPLICATION_JSON) + public static class DummyJsonMessageBodyWriter implements ServerMessageBodyWriter { + + @Override + public boolean isWriteable(Class type, Type genericType, ResteasyReactiveResourceInfo target, + MediaType mediaType) { + return Entity.class.equals(type); + } + + @Override + public void writeResponse(Object o, Type genericType, ServerRequestContext context) + throws WebApplicationException, IOException { + context.serverResponse().end("{\"value\":\"" + ((Entity) o).value() + "\"}"); + } + + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return Entity.class.equals(type); + } + + @Override + public void writeTo(Object o, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, OutputStream entityStream) + throws IOException, WebApplicationException { + throw new IllegalStateException("should not be called"); + } + } +}