diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-servlet/runtime/src/main/java/io/quarkus/resteasy/reactive/server/servlet/runtime/ServletRequestContext.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-servlet/runtime/src/main/java/io/quarkus/resteasy/reactive/server/servlet/runtime/ServletRequestContext.java index f8f54895427da..88eef5573f02b 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-servlet/runtime/src/main/java/io/quarkus/resteasy/reactive/server/servlet/runtime/ServletRequestContext.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-servlet/runtime/src/main/java/io/quarkus/resteasy/reactive/server/servlet/runtime/ServletRequestContext.java @@ -323,6 +323,11 @@ public ServerHttpResponse setStatusCode(int code) { return this; } + @Override + public int getStatusCode() { + return response.getStatus(); + } + @Override public ServerHttpResponse end() { try { @@ -388,6 +393,13 @@ public ServerHttpResponse setResponseHeader(CharSequence name, Iterable> getAllResponseHeaders() { List> ret = new ArrayList<>(); diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/DotNames.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/DotNames.java index fbd7828f56ceb..b9e29ad437447 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/DotNames.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/DotNames.java @@ -6,6 +6,8 @@ import java.nio.file.Path; import org.jboss.jandex.DotName; +import org.jboss.resteasy.reactive.ResponseHeader; +import org.jboss.resteasy.reactive.Status; import org.jboss.resteasy.reactive.multipart.FileUpload; final class DotNames { @@ -19,6 +21,8 @@ final class DotNames { static final DotName FIELD_UPLOAD_NAME = DotName.createSimple(FileUpload.class.getName()); static final DotName PATH_NAME = DotName.createSimple(Path.class.getName()); static final DotName FILE_NAME = DotName.createSimple(File.class.getName()); + static final DotName RESPONSE_HEADER_ANNOTATION = DotName.createSimple(ResponseHeader.class.getName()); + static final DotName RESPONSE_STATUS_ANNOTATION = DotName.createSimple(Status.class.getName()); private DotNames() { } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index 9c893b38c71e3..2cd4d8cd01ae5 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -51,6 +51,7 @@ import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames; import org.jboss.resteasy.reactive.common.processor.scanning.ApplicationScanningResult; import org.jboss.resteasy.reactive.common.processor.scanning.ResourceScanningResult; +import org.jboss.resteasy.reactive.common.processor.transformation.AnnotationStore; import org.jboss.resteasy.reactive.common.processor.transformation.AnnotationsTransformer; import org.jboss.resteasy.reactive.common.util.Encode; import org.jboss.resteasy.reactive.server.core.Deployment; @@ -60,6 +61,7 @@ import org.jboss.resteasy.reactive.server.model.ContextResolvers; import org.jboss.resteasy.reactive.server.model.DynamicFeatures; import org.jboss.resteasy.reactive.server.model.Features; +import org.jboss.resteasy.reactive.server.model.FixedHandlerChainCustomizer; import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer; import org.jboss.resteasy.reactive.server.model.ParamConverterProviders; import org.jboss.resteasy.reactive.server.processor.scanning.MethodScanner; @@ -112,6 +114,8 @@ import io.quarkus.resteasy.reactive.server.runtime.exceptionmappers.AuthenticationRedirectExceptionMapper; import io.quarkus.resteasy.reactive.server.runtime.exceptionmappers.ForbiddenExceptionMapper; import io.quarkus.resteasy.reactive.server.runtime.exceptionmappers.UnauthorizedExceptionMapper; +import io.quarkus.resteasy.reactive.server.runtime.responseheader.ResponseHeaderHandler; +import io.quarkus.resteasy.reactive.server.runtime.responsestatus.ResponseStatusHandler; import io.quarkus.resteasy.reactive.server.runtime.security.EagerSecurityHandler; import io.quarkus.resteasy.reactive.server.runtime.security.SecurityContextOverrideHandler; import io.quarkus.resteasy.reactive.server.spi.AnnotationsTransformerBuildItem; @@ -169,6 +173,61 @@ MinNettyAllocatorMaxOrderBuildItem setMinimalNettyMaxOrderSize() { return new MinNettyAllocatorMaxOrderBuildItem(3); } + @BuildStep + MethodScannerBuildItem responseStatusSupport() { + return new MethodScannerBuildItem(new MethodScanner() { + @Override + public List scan(MethodInfo method, ClassInfo actualEndpointClass, + Map methodContext) { + AnnotationStore annotationStore = (AnnotationStore) methodContext + .get(EndpointIndexer.METHOD_CONTEXT_ANNOTATION_STORE); + AnnotationInstance annotationInstance = annotationStore.getAnnotation(method, + DotNames.RESPONSE_STATUS_ANNOTATION); + if (annotationInstance == null) { + return Collections.emptyList(); + } + AnnotationValue responseStatusValue = annotationInstance.value(); + if (responseStatusValue == null) { + return Collections.emptyList(); + } + ResponseStatusHandler handler = new ResponseStatusHandler(); + handler.setStatus(responseStatusValue.asInt()); + return Collections.singletonList(new FixedHandlerChainCustomizer(handler, + HandlerChainCustomizer.Phase.BEFORE_METHOD_INVOKE)); + } + }); + } + + @BuildStep + MethodScannerBuildItem responseHeaderSupport() { + return new MethodScannerBuildItem(new MethodScanner() { + @Override + public List scan(MethodInfo method, ClassInfo actualEndpointClass, + Map methodContext) { + AnnotationStore annotationStore = (AnnotationStore) methodContext + .get(EndpointIndexer.METHOD_CONTEXT_ANNOTATION_STORE); + AnnotationInstance annotationInstance = annotationStore.getAnnotation(method, + DotNames.RESPONSE_HEADER_ANNOTATION); + if (annotationInstance == null) { + return Collections.emptyList(); + } + AnnotationValue responseHeaderValue = annotationInstance.value("headers"); + if (responseHeaderValue == null) { + return Collections.emptyList(); + } + AnnotationInstance[] headersValue = responseHeaderValue.asNestedArray(); + Map headers = new HashMap<>(); + for (AnnotationInstance headerInstance : headersValue) { + headers.put(headerInstance.value("name").asString(), headerInstance.value("value").asString()); + } + ResponseHeaderHandler handler = new ResponseHeaderHandler(); + handler.setHeaders(headers); + return Collections.singletonList(new FixedHandlerChainCustomizer(handler, + HandlerChainCustomizer.Phase.BEFORE_METHOD_INVOKE)); + } + }); + } + @BuildStep void vertxIntegration(BuildProducer writerBuildItemBuildProducer) { writerBuildItemBuildProducer.produce(new MessageBodyWriterBuildItem(ServerVertxBufferMessageBodyWriter.class.getName(), diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/responseheader/ResponseHeaderTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/responseheader/ResponseHeaderTest.java new file mode 100644 index 0000000000000..92ffc9a9dad52 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/responseheader/ResponseHeaderTest.java @@ -0,0 +1,190 @@ +package io.quarkus.resteasy.reactive.server.test.responseheader; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.jboss.resteasy.reactive.Header; +import org.jboss.resteasy.reactive.ResponseHeader; +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; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.restassured.http.Headers; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; + +public class ResponseHeaderTest { + + @RegisterExtension + static QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + + @Test + public void should_return_added_headers_uni() { + Map expectedHeaders = Map.of( + "Access-Control-Allow-Origin", "*", + "Keep-Alive", "timeout=5, max=997"); + RestAssured + .given() + .get("/test/multi") + .then() + .statusCode(200) + .headers(expectedHeaders); + } + + @Test + public void should_return_added_headers_multi() { + Map expectedHeaders = Map.of( + "Access-Control-Allow-Origin", "*", + "Keep-Alive", "timeout=5, max=997"); + RestAssured + .given() + .get("/test/multi") + .then() + .statusCode(200) + .headers(expectedHeaders); + } + + @Test + public void should_return_added_headers_completion() { + Map expectedHeaders = Map.of( + "Access-Control-Allow-Origin", "*", + "Keep-Alive", "timeout=5, max=997"); + RestAssured + .given() + .get("/test/completion") + .then() + .statusCode(200) + .headers(expectedHeaders); + } + + @Test + public void should_return_added_headers_plain() { + Map expectedHeaders = Map.of( + "Access-Control-Allow-Origin", "*", + "Keep-Alive", "timeout=5, max=997"); + RestAssured + .given() + .get("/test/plain") + .then() + .statusCode(200) + .headers(expectedHeaders); + } + + @Test + public void should_throw_exception_without_headers_uni() { + Headers headers = RestAssured.given().get("/test/exception_uni") + .then().extract().headers(); + assertFalse(headers.hasHeaderWithName("Access-Control-Allow-Origin")); + + } + + @Test + public void should_throw_exception_without_headers_multi() { + Headers headers = RestAssured.given().get("/test/exception_multi") + .then().extract().headers(); + assertFalse(headers.hasHeaderWithName("Access-Control-Allow-Origin")); + } + + @Test + public void should_throw_exception_without_headers_completion() { + Headers headers = RestAssured.given().get("/test/exception_completion") + .then().extract().headers(); + assertFalse(headers.hasHeaderWithName("Access-Control-Allow-Origin")); + } + + @Test + public void should_throw_exception_without_headers_plain() { + Headers headers = RestAssured.given().get("/test/exception_plain") + .then().extract().headers(); + assertFalse(headers.hasHeaderWithName("Access-Control-Allow-Origin")); + } + + @Path("/test") + public static class TestResource { + + @ResponseHeader(headers = { + @Header(name = "Access-Control-Allow-Origin", value = "*"), + @Header(name = "Keep-Alive", value = "timeout=5, max=997"), + }) + @GET + @Path(("/uni")) + public Uni getTestUni() { + return Uni.createFrom().item("test"); + } + + @ResponseHeader(headers = { + @Header(name = "Access-Control-Allow-Origin", value = "*"), + @Header(name = "Keep-Alive", value = "timeout=5, max=997"), + }) + @GET + @Path("/multi") + public Multi getTestMulti() { + return Multi.createFrom().item("test"); + } + + @ResponseHeader(headers = { + @Header(name = "Access-Control-Allow-Origin", value = "*"), + @Header(name = "Keep-Alive", value = "timeout=5, max=997"), + }) + @GET + @Path("/completion") + public CompletionStage getTestCompletion() { + return CompletableFuture.supplyAsync(() -> "test"); + } + + @ResponseHeader(headers = { + @Header(name = "Access-Control-Allow-Origin", value = "*"), + @Header(name = "Keep-Alive", value = "timeout=5, max=997"), + }) + @GET + @Path("/plain") + public String getTestPlain() { + return "test"; + } + + @ResponseHeader(headers = { + @Header(name = "Access-Control-Allow-Origin", value = "*") + }) + @GET + @Path(("/exception_uni")) + public Uni throwExceptionUni() { + return Uni.createFrom().failure(new IllegalArgumentException()); + } + + @ResponseHeader(headers = { + @Header(name = "Access-Control-Allow-Origin", value = "*") + }) + @GET + @Path("/exception_multi") + public Multi throwExceptionMulti() { + return Multi.createFrom().failure(new IllegalArgumentException()); + } + + @ResponseHeader(headers = { + @Header(name = "Access-Control-Allow-Origin", value = "*") + }) + @Path("/exception_completion") + public CompletionStage throwExceptionCompletion() { + return CompletableFuture.failedFuture(new IllegalArgumentException()); + } + + @ResponseHeader(headers = { + @Header(name = "Access-Control-Allow-Origin", value = "*") + }) + @GET + @Path("/exception_plain") + public String throwExceptionPlain() { + throw new IllegalArgumentException(); + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/responsestatus/ResponseStatusTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/responsestatus/ResponseStatusTest.java new file mode 100644 index 0000000000000..3389d9ce9c23a --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/responsestatus/ResponseStatusTest.java @@ -0,0 +1,166 @@ +package io.quarkus.resteasy.reactive.server.test.responsestatus; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.jboss.resteasy.reactive.Status; +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; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; + +public class ResponseStatusTest { + + @RegisterExtension + static QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + + @Test + public void should_return_changed_status_uni() { + int expectedStatus = 201; + RestAssured + .given() + .get("/test/uni") + .then() + .statusCode(expectedStatus); + } + + @Test + public void should_return_changed_status_multi() { + int expectedStatus = 201; + RestAssured + .given() + .get("/test/multi") + .then() + .statusCode(expectedStatus); + } + + @Test + public void should_return_changed_status_completion() { + int expectedStatus = 201; + RestAssured + .given() + .get("/test/completion") + .then() + .statusCode(expectedStatus); + } + + @Test + public void should_return_changed_status_plain() { + int expectedStatus = 201; + RestAssured + .given() + .get("/test/plain") + .then() + .statusCode(expectedStatus); + } + + @Test + public void should_not_change_status_uni() { + int expectedStatus = 500; + RestAssured + .given() + .get("/test/exception_uni") + .then() + .statusCode(expectedStatus); + } + + @Test + public void should_not_change_status_multi() { + int expectedStatus = 500; + RestAssured + .given() + .get("/test/exception_multi") + .then() + .statusCode(expectedStatus); + } + + @Test + public void should_not_change_status_completion() { + int expectedStatus = 500; + RestAssured + .given() + .get("/test/exception_completion") + .then() + .statusCode(expectedStatus); + } + + @Test + public void should_not_change_status_plain() { + int expectedStatus = 500; + RestAssured + .given() + .get("/test/exception_plain") + .then() + .statusCode(expectedStatus); + } + + @Path("/test") + public static class TestResource { + + @Status(201) + @GET + @Path("/uni") + public Uni getTestUni() { + return Uni.createFrom().item("test"); + } + + @Status(201) + @GET + @Path("/multi") + public Multi getTestMulti() { + return Multi.createFrom().item("test"); + } + + @Status(201) + @GET + @Path("/completion") + public CompletionStage getTestCompletion() { + return CompletableFuture.supplyAsync(() -> "test"); + } + + @Status(201) + @GET + @Path("/plain") + public String getTestPlain() { + return "test"; + } + + @Status(201) + @GET + @Path(("/exception_uni")) + public Uni throwExceptionUni() { + return Uni.createFrom().failure(new IllegalArgumentException()); + } + + @Status(201) + @GET + @Path("/exception_multi") + public Multi throwExceptionMulti() { + return Multi.createFrom().failure(new IllegalArgumentException()); + } + + @Status(201) + @GET + @Path("/exception_completion") + public CompletionStage throwExceptionCompletion() { + return CompletableFuture.failedFuture(new IllegalArgumentException()); + } + + @Status(201) + @GET + @Path("/exception_plain") + public String throwExceptionPlain() { + throw new IllegalArgumentException(); + } + + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/responseheader/ResponseHeaderHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/responseheader/ResponseHeaderHandler.java new file mode 100644 index 0000000000000..e734b32f92741 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/responseheader/ResponseHeaderHandler.java @@ -0,0 +1,28 @@ +package io.quarkus.resteasy.reactive.server.runtime.responseheader; + +import java.util.Map; + +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; +import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; + +public class ResponseHeaderHandler implements ServerRestHandler { + private Map headers; + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public Map getHeaders() { + return headers; + } + + @Override + public void handle(ResteasyReactiveRequestContext requestContext) throws Exception { + if (headers != null) { + for (Map.Entry header : headers.entrySet()) { + requestContext.serverResponse().setResponseHeader(header.getKey(), header.getValue()); + } + } + } + +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/responsestatus/ResponseStatusHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/responsestatus/ResponseStatusHandler.java new file mode 100644 index 0000000000000..bbf672c17bf46 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/responsestatus/ResponseStatusHandler.java @@ -0,0 +1,22 @@ +package io.quarkus.resteasy.reactive.server.runtime.responsestatus; + +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; +import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; + +public class ResponseStatusHandler implements ServerRestHandler { + + private int status; + + public void setStatus(int status) { + this.status = status; + } + + public int getStatus() { + return status; + } + + @Override + public void handle(ResteasyReactiveRequestContext requestContext) throws Exception { + requestContext.serverResponse().setStatusCode(status); + } +} diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/Header.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/Header.java new file mode 100644 index 0000000000000..a54c37a0315c0 --- /dev/null +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/Header.java @@ -0,0 +1,11 @@ +package org.jboss.resteasy.reactive; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@Target(ElementType.ANNOTATION_TYPE) +public @interface Header { + public String name(); + + public String value(); +} diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/ResponseHeader.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/ResponseHeader.java new file mode 100644 index 0000000000000..98c22506d325d --- /dev/null +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/ResponseHeader.java @@ -0,0 +1,12 @@ +package org.jboss.resteasy.reactive; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface ResponseHeader { + public Header[] headers(); +} diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/Status.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/Status.java new file mode 100644 index 0000000000000..2d6112b0a6243 --- /dev/null +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/Status.java @@ -0,0 +1,12 @@ +package org.jboss.resteasy.reactive; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Status { + public int value(); +} diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/StreamingUtil.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/StreamingUtil.java index bbb7661451690..a4983bcd42ddd 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/StreamingUtil.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/StreamingUtil.java @@ -56,7 +56,7 @@ private static byte[] serialiseEntity(ResteasyReactiveRequestContext context, Ob ByteArrayOutputStream baos = new ByteArrayOutputStream(); boolean wrote = false; for (MessageBodyWriter writer : writers) { - // Spec(API) says we should use class/type/mediaType but doesn't talk about annotations + // Spec(API) says we should use class/type/mediaType but doesn't talk about annotations if (writer.isWriteable(entityClass, entityType, Serialisers.NO_ANNOTATION, mediaType)) { // FIXME: spec doesn't really say what headers we should use here writer.writeTo(entity, entityClass, entityType, Serialisers.NO_ANNOTATION, mediaType, @@ -76,10 +76,11 @@ public static void setHeaders(ResteasyReactiveRequestContext context, ServerHttp // FIXME: spec says we should flush the headers when first message is sent or when the resource method returns, whichever // happens first if (!response.headWritten()) { - response.setStatusCode(Response.Status.OK.getStatusCode()); + if (response.getStatusCode() == 0) { + response.setStatusCode(Response.Status.OK.getStatusCode()); + } response.setResponseHeader(HttpHeaders.CONTENT_TYPE, context.getResponseContentType().toString()); response.setChunked(true); - // FIXME: other headers? } } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/CompletionStageResponseHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/CompletionStageResponseHandler.java index 78730e984c11d..9be1673bc28c0 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/CompletionStageResponseHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/CompletionStageResponseHandler.java @@ -15,6 +15,7 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti result.handle((v, t) -> { if (t != null) { + requestContext.serverResponse().clearResponseHeaders(); requestContext.handleException(t); } else { requestContext.setResult(v); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ExceptionHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ExceptionHandler.java index dc6a06aa29c66..4791a382a26e0 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ExceptionHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ExceptionHandler.java @@ -10,6 +10,7 @@ public class ExceptionHandler implements ServerRestHandler { @Override public void handle(ResteasyReactiveRequestContext requestContext) throws Exception { + requestContext.serverResponse().clearResponseHeaders(); requestContext.mapExceptionIfPresent(); } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java index edb1625f17b8c..84c8d7bf6a726 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java @@ -147,6 +147,7 @@ public void onError(Throwable t) { protected void handleException(ResteasyReactiveRequestContext requestContext, Throwable t) { // in truth we can only send an exception if we haven't sent the headers yet, otherwise // it will appear to be an SSE value, which is incorrect, so we should only log it and close the connection + requestContext.serverResponse().clearResponseHeaders(); if (requestContext.serverResponse().headWritten()) { log.error("Exception in SSE server handling, impossible to send it to client", t); } else { diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/UniResponseHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/UniResponseHandler.java index b4e720adbc52a..fb05884ba388d 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/UniResponseHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/UniResponseHandler.java @@ -23,6 +23,7 @@ public void accept(Object v) { }, new Consumer() { @Override public void accept(Throwable t) { + requestContext.serverResponse().clearResponseHeaders(); requestContext.resume(t, true); } }); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerHttpResponse.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerHttpResponse.java index ae4f0b3b33d45..20b4c290eb934 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerHttpResponse.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ServerHttpResponse.java @@ -10,6 +10,8 @@ public interface ServerHttpResponse { ServerHttpResponse setStatusCode(int code); + int getStatusCode(); + ServerHttpResponse end(); boolean headWritten(); @@ -24,6 +26,8 @@ public interface ServerHttpResponse { ServerHttpResponse setResponseHeader(CharSequence name, Iterable values); + void clearResponseHeaders(); + Iterable> getAllResponseHeaders(); boolean closed(); diff --git a/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java b/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java index 25934998c399c..5fbafb14760b2 100644 --- a/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java +++ b/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java @@ -306,6 +306,11 @@ public ServerHttpResponse setStatusCode(int code) { return this; } + @Override + public int getStatusCode() { + return response.getStatusCode(); + } + @Override public ServerHttpResponse end() { if (!response.ended()) { @@ -349,6 +354,11 @@ public ServerHttpResponse setResponseHeader(CharSequence name, Iterable> getAllResponseHeaders() { return response.headers();