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 all 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 @@ -323,6 +323,11 @@ public ServerHttpResponse setStatusCode(int code) {
return this;
}

@Override
public int getStatusCode() {
return response.getStatus();
}

@Override
public ServerHttpResponse end() {
try {
Expand Down Expand Up @@ -388,6 +393,13 @@ public ServerHttpResponse setResponseHeader(CharSequence name, Iterable<CharSequ
return this;
}

@Override
public void clearResponseHeaders() {
for (String header : response.getHeaderNames()) {
response.setHeader(header, null);
}
}

@Override
public Iterable<Map.Entry<String, String>> getAllResponseHeaders() {
List<Map.Entry<String, String>> ret = new ArrayList<>();
Expand Down
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.BEFORE_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.BEFORE_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,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<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_return_added_headers_completion() {
Map<String, String> 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<String, String> 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<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 = "*"),
@Header(name = "Keep-Alive", value = "timeout=5, max=997"),
})
@GET
@Path("/completion")
public CompletionStage<String> 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<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());
}

@ResponseHeader(headers = {
@Header(name = "Access-Control-Allow-Origin", value = "*")
})
@Path("/exception_completion")
public CompletionStage<String> 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();
}
}
}
Loading