diff --git a/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/filter/UserFilterRequestContextPropagationTest.java b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/filter/UserFilterRequestContextPropagationTest.java new file mode 100644 index 0000000000000..1b15b98b24b45 --- /dev/null +++ b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/filter/UserFilterRequestContextPropagationTest.java @@ -0,0 +1,67 @@ +package io.quarkus.vertx.web.filter; + +import static org.hamcrest.Matchers.is; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; + +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.quarkus.vertx.web.Route; +import io.quarkus.vertx.web.RouteFilter; +import io.restassured.RestAssured; +import io.vertx.ext.web.RoutingContext; + +public class UserFilterRequestContextPropagationTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(FilterAndRoute.class, RequestFoo.class)); + + @Test + public void test() { + RestAssured.post("/hello").then().statusCode(200) + .body(is("11")); + } + + public static class FilterAndRoute { + + @Inject + RequestFoo foo; + + @RouteFilter + void filter1(RoutingContext rc) { + foo.setState(11); + rc.next(); + } + + @Route(path = "hello") + void hello(RoutingContext ctx) { + ctx.response().end("" + foo.getState()); + } + + } + + @RequestScoped + static class RequestFoo { + + private AtomicInteger state = new AtomicInteger(-1); + + void setState(int value) { + this.state.set(value); + } + + public int getState() { + return state.get(); + } + + } + +} diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/RouteHandler.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/RouteHandler.java index f378da5ee0e18..d489100b1894c 100644 --- a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/RouteHandler.java +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/RouteHandler.java @@ -3,7 +3,7 @@ import javax.enterprise.event.Event; import io.quarkus.arc.Arc; -import io.quarkus.arc.InjectableContext; +import io.quarkus.arc.InjectableContext.ContextState; import io.quarkus.arc.ManagedContext; import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.vertx.http.runtime.CurrentVertxRequest; @@ -20,12 +20,16 @@ */ public abstract class RouteHandler implements Handler { + private static final String REQUEST_CONTEXT_STATE = "__cdi_req_ctx"; + private final Event securityIdentityEvent; private final CurrentVertxRequest currentVertxRequest; + private final ManagedContext requestContext; public RouteHandler() { this.securityIdentityEvent = Arc.container().beanManager().getEvent().select(SecurityIdentity.class); this.currentVertxRequest = Arc.container().instance(CurrentVertxRequest.class).get(); + this.requestContext = Arc.container().requestContext(); } /** @@ -38,7 +42,6 @@ public RouteHandler() { @Override public void handle(RoutingContext context) { QuarkusHttpUser user = (QuarkusHttpUser) context.user(); - ManagedContext requestContext = Arc.container().requestContext(); //todo: how should we handle non-proactive authentication here? if (requestContext.isActive()) { if (user != null) { @@ -47,22 +50,31 @@ public void handle(RoutingContext context) { invoke(context); } else { try { - // Activate the context, i.e. set the thread locals - requestContext.activate(); + // First attempt to obtain the request context state. + // If there is a route filter and the request can have body (POST, PUT, etc.) the route + // method is invoked asynchronously (once all data are read). + // However, the request context is activated by the filter and we need to make sure + // the same context is then used in the route method + ContextState state = context.get(REQUEST_CONTEXT_STATE); + // Activate the context, i.e. set the thread locals, state can be null + requestContext.activate(state); currentVertxRequest.setCurrent(context); if (user != null) { securityIdentityEvent.fire(user.getSecurityIdentity()); } - // Reactive routes can use async processing (e.g. mutiny Uni/Multi) and context propagation - // 1. Store the state (which is basically a shared Map instance) - // 2. Terminate the context correcly when the response is disposed or an exception is thrown - InjectableContext.ContextState state = requestContext.getState(); - context.addEndHandler(new Handler>() { - @Override - public void handle(AsyncResult result) { - requestContext.destroy(state); - } - }); + if (state == null) { + // Reactive routes can use async processing (e.g. mutiny Uni/Multi) and context propagation + // 1. Store the state (which is basically a shared Map instance) + // 2. Terminate the context correcly when the response is disposed or an exception is thrown + final ContextState endState = requestContext.getState(); + context.put(REQUEST_CONTEXT_STATE, endState); + context.addEndHandler(new Handler>() { + @Override + public void handle(AsyncResult result) { + requestContext.destroy(endState); + } + }); + } invoke(context); } finally { // Deactivate the context, i.e. cleanup the thread locals