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 toThreadLocal
but can be applied to aFlux
or aMono
instead of aThread
. This feature is calledContext
.
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: