LarryDpk
发布于 2021-01-12 / 66 阅读
0

Get Request Object anywhere in Spring WebFlux

Get Request Object anywhere in Spring WebFlux

A different World

In a normal Spring Web project, we have handy way to get the Request Object and many libraries provide the static methods. The code as below:

ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
// get the request
HttpServletRequest request = requestAttributes.getRequest();

The class RequestContextHolder provides the static methods so we can call it wherever you want. It uses the ThreadLocal to store the Request object, that’s why different threads can only get their own request objects.

However, in the world of Spring WebFlux, there are not such Holder class. What’s more, WebFlux is not handling the request in the same way as a normal Spring MVC. It will not use a new thread just to handle one request.
So it can’t use the ThreadLocal to save Request anymore.

Save and Get

To make it easy to get the Request object later, we need to store it in a container with the same scope that we can use at the beginning. Two key issues need to be addressed here.

  • (1) Where does the Request object come from
  • (2) Where to store the Request

For question (1), we can think back to when the Request object appeared, and the easiest one to think of is the WebFilter, which has the following method signature.

public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain);

We can get the Request object directly through the ServerWebExchange:

ServerHttpRequest request = exchange.getRequest();

And since Filter can be executed before the application logic, the requirement is satisfied and problem (1) is solved.

For problem (2), a container with the same scope as the Reactive request is needed. We can use reactor.util.context.Context.

reactor official documentation message:

Since version 3.1.0, Reactor comes with an advanced feature that is somewhat comparable to ThreadLocal but can be applied to a Flux or a Mono instead of a Thread. This feature is called Context.

And the official website also gives an explanation why ThreadLocal does not work in some scenarios. Take a look if you are interested.

Implementation

WebFilter fetch and save

Firstly, we fetch the Request and same in WebFilter, code as below:

@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class ReactiveRequestContextFilter implements WebFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        return chain.filter(exchange)
                .subscriberContext(ctx -> ctx.put(ReactiveRequestContextHolder.CONTEXT_KEY, request));
    }
}

We get the ServerHttpRequest object from ServerWebExchange, and store it into Context with put() method.

utils class Holder

Now the request object is stored in Context, we need to implement a tool to get it back. So we implement a static method:

public class ReactiveRequestContextHolder {
    public static final Class<ServerHttpRequest> CONTEXT_KEY = ServerHttpRequest.class;

    public static Mono<ServerHttpRequest> getRequest() {
        return Mono.subscriberContext()
                .map(ctx -> ctx.get(CONTEXT_KEY));
    }
}

Use it in Controller

Now we can use the static method in Controller to fetch the Request:

@RestController
public class GetRequestController {

    @RequestMapping("/request")
    public Mono<String> getRequest() {
        return ReactiveRequestContextHolder.getRequest()
                .map(request -> request.getHeaders().getFirst("user"));
    }
}

Well Done!
Now we get the Request and also the Headers from the Request.

Start and test:

$ curl http://localhost:8088/request -H 'user: pkslow'
pkslow

$ curl http://localhost:8088/request -H 'user: larry'
larry

$ curl http://localhost:8088/request -H 'user: www.pkslow.com'
www.pkslow.com

Can get the Header user

Code

All Code here: https://github.com/LarryDpk/pkslow-samples


References:

Getting Request Object in Spring Webflux elastic thread