Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for setting response status and response headers support via annotations #20242

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -169,6 +173,61 @@ MinNettyAllocatorMaxOrderBuildItem setMinimalNettyMaxOrderSize() {
return new MinNettyAllocatorMaxOrderBuildItem(3);
}

@BuildStep
MethodScannerBuildItem responseStatusSupport() {
return new MethodScannerBuildItem(new MethodScanner() {
@Override
public List<HandlerChainCustomizer> scan(MethodInfo method, ClassInfo actualEndpointClass,
Map<String, Object> 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.AFTER_METHOD_INVOKE));
}
});
}

@BuildStep
MethodScannerBuildItem responseHeaderSupport() {
return new MethodScannerBuildItem(new MethodScanner() {
@Override
public List<HandlerChainCustomizer> scan(MethodInfo method, ClassInfo actualEndpointClass,
Map<String, Object> 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<String, String> 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.AFTER_METHOD_INVOKE));
}
});
}

@BuildStep
void vertxIntegration(BuildProducer<MessageBodyWriterBuildItem> writerBuildItemBuildProducer) {
writerBuildItemBuildProducer.produce(new MessageBodyWriterBuildItem(ServerVertxBufferMessageBodyWriter.class.getName(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package io.quarkus.resteasy.reactive.server.test.responseheader;

import static org.junit.jupiter.api.Assertions.*;

import java.util.Map;

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 {
geoand marked this conversation as resolved.
Show resolved Hide resolved

@RegisterExtension
static QuarkusUnitTest TEST = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class));

@Test
public void should_return_added_headers_uni() {
Map<String, String> 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<String, String> 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_throw_exception_without_headers_uni() {
geoand marked this conversation as resolved.
Show resolved Hide resolved
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"));
}

@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<String> 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<String> getTestMulti() {
return Multi.createFrom().item("test");
}

@ResponseHeader(headers = {
@Header(name = "Access-Control-Allow-Origin", value = "*")
})
@GET
@Path(("/exception_uni"))
public Uni<String> throwExceptionUni() {
return Uni.createFrom().failure(new IllegalArgumentException());
}

@ResponseHeader(headers = {
@Header(name = "Access-Control-Allow-Origin", value = "*")
})
@GET
@Path("/exception_multi")
public Multi<String> throwExceptionMulti() {
return Multi.createFrom().failure(new IllegalArgumentException());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package io.quarkus.resteasy.reactive.server.test.responsestatus;

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 {
geoand marked this conversation as resolved.
Show resolved Hide resolved

@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_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);
}

@Path("/test")
public static class TestResource {

@Status(201)
@GET
@Path("/uni")
public Uni<String> getTestUni() {
return Uni.createFrom().item("test");
}

@Status(201)
@GET
@Path("/multi")
public Multi<String> getTestMulti() {
return Multi.createFrom().item("test");
}

@Status(201)
@GET
@Path(("/exception_uni"))
public Uni<String> throwExceptionUni() {
return Uni.createFrom().failure(new IllegalArgumentException());
}

@Status(201)
@GET
@Path("/exception_multi")
public Multi<String> throwExceptionMulti() {
return Multi.createFrom().failure(new IllegalArgumentException());
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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<String, String> headers;

public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}

public Map<String, String> getHeaders() {
return headers;
}

@Override
public void handle(ResteasyReactiveRequestContext requestContext) throws Exception {
if (headers != null) {
requestContext.setResponseHeaders(headers);
}
}

}
Original file line number Diff line number Diff line change
@@ -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.setResponseStatus(status);
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -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();
}
Loading