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

Reactive routes - if needed propagate req. context activated by filter #13458

Merged
merged 1 commit into from
Nov 25, 2020
Merged
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
@@ -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