1 A different world

In a regular Spring Web project, it is very easy to get the Request object, and quite a few libraries provide static methods to do so. The code to get it is as follows.

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

In class RequestContextHolder provides static methods, which means you can call them anywhere. And it uses ThreadLocal to hold the Request object, which means that different threads are able to get their own Request objects.

But in the world of responsive WebFlux, there is no similar Holder class provided, and WebFlux is not thread-aware, any thread can handle any request at any time, and if it feels more efficient to switch the current thread, it will do so. But in a Servlet Based application, it will schedule a thread for a request to handle the entire process.

This huge difference means that you can’t simply save and fetch Request through ThreadLocal anymore.

2 Save it first, then get it

In order to get the Request object easily 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 does it exist?

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.

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

We can get the Request object directly through the ServerWebExchange.

1
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 Reavtive request is needed, and reactor.util.context.Context can satisfy the requirement. Check the official documentation of reactor https://projectreactor.io/docs/core/release/reference/#context to see the following passage.

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 doesn’t work in some scenarios, so check it out if you’re interested.

3 Code implementation

3.1 WebFilter fetching and saving

First, get the Request object in WebFilter and save it, the code is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@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));
    }
}

Get the ServerHttpRequest object from the ServerWebExchange and put it into the Context via the put method.

3.2 Tool class Holder

Implement a tool class to provide static methods that can be used in any scenario after Filter.

1
2
3
4
5
6
7
8
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));
    }
}

3.3 Using in the Controller

We try to use ReactiveRequestContextHolder in the Controller to get the Request.

1
2
3
4
5
6
7
8
9
@RestController
public class GetRequestController {

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

The above method gets the Request object and then gets the Header in the Request.

Start the application and test it as follows.

1
2
3
4
5
6
7
8
$ 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

The request header user can be successfully retrieved.

4 Summary

The code can be found at https://github.com/LarryDpk/pkslow-samples

Reference https://juejin.cn/post/6976144399717793823