Share data through a request lifecycle in Spring Webflux - spring

In Spring MVC I can use ThreadLocal to share data between different components through a request, and the data will be automatically cleared when the request is fulfilled. With WebFlux, since a request can be handled by multiple threads, this solution will not work. How to implement a similar solution so that initially a WebFilter can set some data in the request context, then the data can be accessed and modified in the controllers, and any event handlers that the request has gone through?
I tried subscriberContext but it did not work. Here is my code. Why this does not work? Is there another way to share the data?
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
exchange.getResponse().beforeCommit(() -> {
return Mono.subscriberContext()
.map(context -> {
context.hasKey("test"); // this returns false
})
}
return Mono.subscriberContext()
.map(context -> {context.put("test", "test");})
.flatMap(chain.filter(exchange))
}

If I am not mistaken, it is because of .beforeCommit(). This Mono and the filter Mono do not share the same Subscriber.
To add the value, to the subscriberContext of the request. Try this:
chain.filter(exchange)
.subscriberContext(context -> context.put("KEY", "VALUE"));
You can also share values in ServerWebExchange attributes. This is Spring Web Flux not Reactor though. For example:
exchange.getAttributes().put("KEY", "VALUE");
Now, anywhere in your application where you have access to ServerWebExchange you can access the attribute using
exchange.getAttributes().get("KEY");

Related

Spring Webflux: how to manually write headers and body?

I'm using Spring WebFlux for my project that is intended to work as pub/sub service: http clients connect to it and wait for events (like PUSH or SSE).
I need to manually write headers and body to the response without using ServerResponse.
I need to do it manually because I'm implementing an SSE server and I must send custom headers into the response before any event actually arrives.
I'm trying to do it this way:
#Bean
RouterFunction<ServerResponse> getStuff() {
return route(GET("/stuff"),
request -> {
final ServerWebExchange exchange = request.exchange();
final byte[] bytes = "data".getBytes(StandardCharsets.UTF_8);
final DataBuffer buffer =exchange.getResponse().bufferFactory().wrap(bytes);
return exchange.getResponse().writeWith(Flux.just(buffer));
}
);
But it does not work because writeWith() returns Mono<Void> and getStuff() must return RouterFunction. Can anybody help me find a way around this?

How can I implement one "PostFilter" in SpringCloudGateway?

Forgive my poor English, please.
When using Spring Cloud Gateway, you can usually define global filters by implementing GlobalFilter. After doing some verification work in it, execute chain.filter(exchange). But if I want to do something with the response returned by the microservice in the filter, such as viewing the returned cookie, what should I do.
#Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// do something about the request
chain.filter(exchange);
// do something about the response
// does it works like above? I tried but failed, please help me .
}

Spring Webflux - How do I call an reactive endpoint within a WebFilter

I want to implement a WebFilter that reads a specific header of the incoming request, calls a GET request to another reactive REST endpoint with the value of this header, then mutates the original request with the value of the GET response.
I want to implement this in a WebFilter because I don't want to have to add this function call to every function in my #RestController.
Currently I have this:
#Component
class ExampleWebFilter(val webClients: WebClients) : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
println(exchange.request.headers)
println(exchange.request.path)
println(exchange.response)
val test = webClients.internalAuthServiceClient.get()
.uri("/api/authorisation/v1/test")
.header("authToken", "authToken123")
.retrieve().bodyToMono(String::class.java)
println(test)
exchange.mutate().request(
exchange.request.mutate().header("newheader", test).build()
)
return chain.filter(exchange)
}
}
#Component
class WebClients() {
val internalAuthServiceClient = WebClient.builder()
.baseUrl("lb://auth-service")
.build()
}
This obviously doesn't work right now. My WebClient is returning Mono so I can't use this directly in my mutate() call because that requires a String. I also can't really make the WebClient call a blocking operation for obvious reasons.
Does anyone know how I could solve this problem?
I don't use kotlin so you will have to convert but this is how you would do it in java. I'd imagine it will be pretty much the same though.
#Override
public Mono<Void> filter(ServerWebExchange serverWebExchange,
WebFilterChain webFilterChain) {
return webClients
.internalAuthServiceClient
.get()
.uri("/api/authorisation/v1/test")
.retrieve()
.bodyToMono(String.class)
//Gonna assume you tested the above and all works
//If get bad response or any error really
// then print error + return empty mono
.onErrorResume(e -> {
e.printStackTrace();
return Mono.empty();
})
//Map the header AFTER a response
//only maps if Mono is not empty
.map(header -> {
serverWebExchange
.getRequest()
.mutate()
.header("web-filter", header);
return serverWebExchange;
})
//return unchanged serverWebExchange if empty Mono
.defaultIfEmpty(serverWebExchange)
//Flatmap since filter returns Mono to prevent returning Mono<Mono<void>>
.flatMap(webFilterChain::filter);
}
The issue you are facing is due to you trying to do it in a synchronous way, whereas you need to map the header after you have received the response from the WebClient.

How to intercept a RequestBody before RestController and do some business rules handling from another microservice?

Basically, we have a big monolithic application built on Spring Boot v1.2 and we would like to handle some business rules processing from a MicroService (let's call it BR engine) built on Spring Boot v2.1.6.
How can I intercept the requestBody and send it first to BR engine and then once done, it will either proceed to the actual handler (Monolithic Controller) or not based from BR engine results - for simplicity let's say BR engine returns either true or false. If true, proceed to actual handler if false, return an exception.
I wanted to use HandlerInterceptorAdapter however, not sure how I can intercept the requestBody and pass it to a microservice - then from the results it will either proceed or not to the actual handler.
As an example, let's say I have a POST mapping to the Monolithic controller:
#PostMapping("/save")
public ResponseEntity<Client> save(#RequestBody ClientDTO dto) {
log.debug("Saving...");
Client newClient = Client.builder().build();
BeanUtils.copyProperties(dto, newClient);
return new ResponseEntity<>(clientService.save(newClient), HttpStatus.OK);
}
Now I wanted to intercept the ClientDTO requestBody and send it first to the BR engine and do some stuff from there. I have thought of using an interceptor and add it to my config which implements WebMvcConfigurer. However, I am not sure how can perform a restTemplate here and get a response from BR engine of pass or fail - if fail the actual handler will be skipped and just throw an exception
#Component
public class RuleEngineInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// intercept requestBody then send it to BR engine
// but how? and how can I get the response from BR engine
// to decide whether it will proceed to actual handler
// or not - probably return an exception.
return true;
}

Building a façade with spring which calls another server and returns its response

For an application I need to create a security façade in Spring 4.x.
This thiny layer must accepts any request from our mobile application and execute a security check for the provided token (with openId and Oauth).
Upon a successful validation, the request needs to be forwarded to the backend application, which does not need to be aware of the security token mechanism.
Thus, the flow will be something like this:
security_facade_url/path/of/the/request
With a header that indicates the backend to invoke upon successful validation of the token
Upon successful validation the security façade sends a request to the backend URL
backend_application_url/path/of/the/request
The façade must not have a controller which maps to any possible path of the request, but must call the request on the correct backend server, based on a value in the header of the request. Then return this response to the user.
What I have so far is an implementation of the HandlerInterceptor. This interceptor works, however, I am not really happy with the way I need to avoid the afterCompletion by throwing an exception in the postHandle method.
If I do not throw an error, the default error page is appended to the correct response in the afterCompletion step.
This is my code so far:
public class RequestProcessingInterceptor implements HandlerInterceptor {
private final Logger log = LoggerFactory.getLogger(RequestProcessingInterceptor.class);
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
log.info("Doing some security stuff now ...");
log.warn("... security ok ... since I am not really checking stuff");
return true;
}
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
log.info("Forwarding request and sending that info back ...");
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
WebResource service = client.resource(UriBuilder.fromUri("http://localhost:8080").build());
response.setContentType("application/json");
response.getWriter().write(service.path(modelAndView.getModel().get("path").toString()).accept("application/json").get(String.class));
response.setStatus(200);
throw new Exception("Need to avoid the execution of the afterCompletion. Only way to do so is by throwing an exception...");
}
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
Is there a more proper way to intervene with the Spring livecycle or obtain the behaviour as described above?
Found a better solution. For what I need, I do not need to manipulate the results in an interceptor.
A much cleaner way is to define a Controller which maps with the request methods.
#RequestMapping(method = {RequestMethod.GET, RequestMethod.PUT, RequestMethod.POST})
public void handleRequest(HttpServletRequest request, HttpServletResponse response) { // code omitted }
You should not try to avoid the call to afterCompletion. Just implement an empty method and let SpringFramework call it.
Provided your controller returns null indicating that no view has to be called, it should work with a smoother Spring integration.
But I cannot understand why you use Spring MVC here. As you only interact with low level HttpServletRequest and HttpServletResponse, you could as well use :
a dedicated servlet in charge to relay the request and response to the backend and write the returned value in the response
a filter that would do the security stuff before passing request to filter chain

Resources