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 1 commit
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
@@ -0,0 +1,7 @@
package io.quarkus.resteasy.reactive.server.common;

public @interface Header {
geoand marked this conversation as resolved.
Show resolved Hide resolved
public String name();

public String value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.quarkus.resteasy.reactive.server.common;

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();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.quarkus.resteasy.reactive.server.common;

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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import org.jboss.jandex.DotName;
import org.jboss.resteasy.reactive.multipart.FileUpload;

import io.quarkus.resteasy.reactive.server.common.ResponseHeader;
import io.quarkus.resteasy.reactive.server.common.Status;

final class DotNames {

static final String POPULATE_METHOD_NAME = "populate";
Expand All @@ -19,6 +22,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_RESPONSE_CREATED));
}
});
}

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

@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,50 @@
package io.quarkus.resteasy.reactive.server.test.responseheader;

import java.util.Map;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

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.resteasy.reactive.server.common.Header;
import io.quarkus.resteasy.reactive.server.common.ResponseHeader;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
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() {
Map<String, String> expectedHeaders = Map.of(
"Access-Control-Allow-Origin", "*",
"Keep-Alive", "timeout=5, max=997");
RestAssured
.given()
.get("/test")
.then()
.statusCode(200)
.headers(expectedHeaders);
}

@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
public Uni<String> getTest() {
return Uni.createFrom().item("test");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.quarkus.resteasy.reactive.server.test.responsestatus;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

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.resteasy.reactive.server.common.Status;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
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() {
int expectedStatus = 201;
RestAssured
.given()
.get("/test")
.then()
.statusCode(expectedStatus);
}

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

@Status(201)
@GET
public Uni<String> getTest() {
return Uni.createFrom().item("test");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.quarkus.resteasy.reactive.server.runtime.responseheader;

import java.util.Map;

import javax.ws.rs.core.Response;

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 {
Response response = requestContext.getResponse().get();
if (headers != null) {
for (Map.Entry<String, String> header : headers.entrySet()) {
response.getHeaders().add(header.getKey(), header.getValue());
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.quarkus.resteasy.reactive.server.runtime.responsestatus;

import javax.ws.rs.core.Response;

import org.jboss.resteasy.reactive.server.core.LazyResponse;
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 {
Response response = requestContext.getResponse().get();

Response.ResponseBuilder responseBuilder = Response.fromResponse(response);
responseBuilder.status(status);
LazyResponse.Existing lazyResponse = new LazyResponse.Existing(responseBuilder.build());
requestContext.setResponse(lazyResponse);
}
}