Skip to content

Commit

Permalink
Merge pull request #13458 from mkouba/issue-13073
Browse files Browse the repository at this point in the history
Reactive routes - if needed propagate req. context activated by filter
  • Loading branch information
mkouba authored Nov 25, 2020
2 parents fdf128c + 2cda14d commit 04283ec
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -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();
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,12 +20,16 @@
*/
public abstract class RouteHandler implements Handler<RoutingContext> {

private static final String REQUEST_CONTEXT_STATE = "__cdi_req_ctx";

private final Event<SecurityIdentity> 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();
}

/**
Expand All @@ -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) {
Expand All @@ -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<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> 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<AsyncResult<Void>>() {
@Override
public void handle(AsyncResult<Void> result) {
requestContext.destroy(endState);
}
});
}
invoke(context);
} finally {
// Deactivate the context, i.e. cleanup the thread locals
Expand Down

0 comments on commit 04283ec

Please sign in to comment.