Trying to extract the body of a POST request sent using Postman to my Spring application.
I've tried doing
ServerRequest.bodyToMono(String.class).toProducer().peek() but that returns null.
ServerRequest.bodyToMono(String.class).block() isn't supported anymore.
Also tried doing it this way:
Mono<String> bodyData = request.bodyToMono(String.class);
System.out.println("\nsubscribing to the Mono.....");
bodyData.subscribeOn(Schedulers.newParallel("requestBody")).subscribe(value -> {
log.debug(String.format("%n value consumed: %s" ,value));
});
But I can't seem to get anything to show up in the logs.
If you are looking for an example of a reactive rest endpoint that just stores the body in some cache the following example should achieve this
public class Example implements HandlerFunction<ServerResponse> {
private final CacheService cache;
#Override
public Mono<ServerResponse> handle(ServerRequest request) {
return request.bodyToMono(String.class)
.doOnNext(body -> cache.put(body))
.flatMap(body -> ServerResponse.noContent().build());
}
}
Related
Background
The class ClientResponse most probably internally has access to the originating request, as is clued by the documentation of methods such as createError():
Create a Mono that terminates with a WebClientResponseException, containing the response status, headers, body, and the originating request.
Question
Is there a way to access the originating request from the ClientResponse?
Motivation behind
I am writing my own ExchangeFilterFunction using ExchangeFilterFunction.ofResponseProcessor() inside which I want to access also the originating request:
#Bean
public ExchangeFilterFunction logResponse() {
return ExchangeFilterFunction.ofResponseProcessor(response -> {
return Mono.just(response)
.doOnEach(resp -> log.info("""
{} {}
{}
Response Headers: {}""",
ORIG_METHOD, ORIG_URI, // Here I need the originating request
response.statusCode(),
response.headers().asHttpHeaders())
);
});
}
Not sure if you can access ClientRequest from ClientResponse,
but you can have access to both of them using ExchangeFilterFunction
ExchangeFilterFunction is a #FunctionalInterface
that means we can use simple lambda for this task
#Bean
public ExchangeFilterFunction logResponse() {
return (request, next) -> {
// here you have access to request object
return next.exchange(request)
.doOnEach(response -> {/*here you have access to request AND response objects*/});
};
}
Also you are using Mono.doOnEach which takes Consumer<Signal<ClientResponse>> as argument
I would recommend to switch to Mono.doOnNext to have access to ClientResponse rather than Signal<ClientResponse>
So your example (without brackets) would look like this:
#Bean
public ExchangeFilterFunction logResponse() {
return (request, next) ->
next.exchange(request)
.doOnNext(response ->
log.info("""
{} {}
{}
Response Headers: {}""",
request.method(),
request.url,
response.statusCode(),
response.headers().asHttpHeaders()));
This is the object we are trying to retrieve:
// Lombok annotated
#Getter
#Setter
#ToString(callSuper = true)
public class GetTransactionsResponse {
public String name;
public List<Transaction> list;
}
We have an object that has metadata and a list of objects
We tried using spring's restTemplate in a method like the following:
public GetTransactionsResponse getTransactions(String token, Request request) {
var requestEntity = RequestEntity
.get(externalApiClient.getTransactionsPath())
.header(HttpHeaders.AUTHORIZATION, token)
.build();
return handleCall(requestEntity, GetTransactionsResponse.class);
}
private <T> T handleCall(RequestEntity<?> requestEntity, Class<T> clazz) {
try {
var result = restTemplate.exchange(requestEntity, clazz).getBody();
log.info("method: handleCall - requestEntity: {} - clazz: {} - result: {}", requestEntity, clazz, result);
return result;
} catch (Exception e) {
throw e
}
}
So we call the rest template but we are receiving null. Without nested data the previous method works but somehow returns null when using nested objects. Are we using rest template wrong?
First verify that your Rest API you created actually works. Test it with posman or ARC (Advanced Rest Client) - a plugin for chrome and see that you get the desired result. If it works, then you can send request to your API from any Http Client including Rest Template. If your API works but calling it from Rest Template doesn't then you will know that the problem is on your client side and you can try and figure out why. But first verify that your Rest API actually works and not returning null
So far, I've not seen any solutions that is working for me. I've tried this and this.
Here is my custom filter:
#Component
public class TestFilter implements GlobalFilter, Ordered {
#Autowired
private ModifyResponseBodyGatewayFilterFactory modifyFilter;
#Autowired
private rewriteBody bodyRewrite;
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).map(ex -> {
GatewayFilter delegate = modifyFilter.apply(new ModifyResponseBodyGatewayFilterFactory.Config()
.setRewriteFunction(byte[].class, byte[].class, bodyRewrite));
delegate.filter(exchange, chain);
return ex;
});
}
#Override
public int getOrder() {
return -1;
}
#Component
public class rewriteBody implements RewriteFunction<byte[], byte[]> {
#Override
public Publisher<byte[]> apply(ServerWebExchange exchange, byte[] body) {
byte[] newBody = "New response".getBytes();
return Mono.just(newBody);
}
}
}
The ModifyResponseBodyGatewayFilterFactory works if I implement it in a pre-filter, but how do I modify it in the post-filter.
To answer the question about modifying the response in post-filter. Firstly, need to understand the pre and post filters developed in Spring Cloud Gateway.
There is no specific separation for pre and post filters in Spring Cloud Gateway by any interface or any other component. It is simply 'How logic has been written for the same filter'.
If any logic written before chain.filter(exchange) method call are executed before running another 'filter in chain' or 'calling target service endpoint'. Since, the logic/code running before making call to another filter or target endpoint, it is called pre-filter and used for pre-processing like adding additional headers, security assertions, rate limiting and so on.
If any logic written after chain.filter(exchange) method call are executed after the processing completed in chain.filter(exchange) method, means the 'target service endpoint' has been completed and then the logic/lines written after chain.filter(exchange) is being executed. Therefore, it is called post-filter.
Since, it is just way of writing and placement of code decides whether it is for pre/post both can be written in single filter.
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return Mono.just(exchange)
.map(it -> {
it.getRequest().getHeaders().add("x-pre-header", "value");
return it;
})
.doOnNext(it -> {
logger.debug("Pre-Processing/PreFilter");
})
.map(it -> chain.filter(it))
.then()
.map(it -> {
exchange.getResponse().getHeaders().add("x-post-header", "value");
return it;
})
.doOnNext(it -> {
logger.debug("Post-Processing/PostFilter");
});
}
Additionally, sequence of execution of filters are controlled by ordering of filters.
Now, when question comes for modifying response body, it is very clear that response will be provided only when 'target service endpoint' called which requires chain.filter(exchange).
Here there is a twist, called 'response commit'. When response is already committed, cannot change in response body and as soon as chain.filter(exchange) is called, it will take micro/mili-seconds to write response to client and commit the response. Means, if any code written after then() method makes changes in response body it will throw exception 'response already committed'.
To avoid it, response body always modified while making chain.filter(exchange) call. Example, consider code written in ModifyResponseBodyGatewayFilterFactory's method filter(...) as:
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange.mutate()
.response(new ModifiedServerHttpResponse(exchange, config)).build());
}
Here, exchange is mutated and response body is set at the same moment. It will modify the response and invoke other filters in chain, if there is no any remaining filters in chain then it serve the response to client.
So conceptually, response body modification happens as post activity only it the filter comes later in chain. Needs like other filter should not be executed once response body modified / some specific filter needs to be executed after response body modification need to be managed by Filter's ordering.
Inside a REST controller, I need to call a REST to obtain a value to be used as a URI variable to a second REST call.
#PostMapping
public void abbina(#RequestBody DocumentsUploadRequest documentsUploadRequest) {
Mono<GetResult> result = WebClient
.create(url)
.get()
.....
.retrieve()
.bodyToMono(GetResult.class)
;
WebClient.post()
.uri(...)
.path("/{partita}")
.build(result.block().getValue()))
.....
.bodyToMono(PostResult.class)
....
}
The problem is that inside a WebFlux REST isn't possibile call block on a mono/flux.
The code throw
java.lang.IllegalStateException block()/blockFirst()/blockLast() are
blocking, which is not supported in thread reactor-http
I tried to change
.build(result.block().getValue()))
with
.build(result.share().block().getValue()))
But now the problem is that result.share().block() hangs indefinitely.
First of all, you should never block within a reactive pipeline. You should subscribe instead. In that particular case, the Spring Webflux framework will subscribe for you as long as you provide your publisher. To achieve this, the controller method has to return your Mono publisher like this:
#PostMapping
public Mono<Void> abbina(#RequestBody Attribute documentsUploadRequest) {
}
Here, the Mono<Void> defines that your publisher will complete without any value.
Then you have to build a reactive pipeline without blocking.
The result of the first HTTP call is a Mono of GetResult:
private Mono<GetResult> getResult() {
return WebClient
.get()
//...
.retrieve()
.bodyToMono(GetResult.class);
}
Similarly, the second HTTP call returns a Mono of PostResult:
private Mono<PostResult> postResult(String firstResult) {
return WebClient
.post()
//...
.retrieve()
.bodyToMono(PostResult.class);
}
Finally, combine these two publisher in order to build your pipeline using the flatmap operator:
#PostMapping
public Mono<Void> abbina(#RequestBody Attribute documentsUploadRequest) {
return getResult()
.flatMap(result -> postResult(result.getValue()))
.then();
}
I'd recommend taking a look at the following guide: Building a Reactive RESTful Web Service
Depends on request body content I need to redirect http requests to URL_1 or URL_2.
I started controller implementation:
#RestController
public class RouteController {
#Autowired
private RestTemplate restTemplate;
#RequestMapping(value = "/**")
public HttpServletResponse route(HttpServletRequest request) {
String body = IOUtils.toString(request.getReader());
if(isFirstServer(body)) {
//send request to URL_1 and get response
} else {
//send request to URL_2 and get response
}
}
}
Request might be GET or POST ot PUT or PATCH etc.
Could you help me to write that code?
I've asked a somehow similar question a while ago. Plea see Server side redirect for REST call for more context.
The best way (to my current understanding) you could achieve this is by manually invoking the desired endpoints from your initial endpoint.
#RestController
public class RouteController {
#Value("${firstUrl}")
private String firstUrl;
#Value("${secondUrl}")
private String secondUrl;
#Autowired
private RestTemplate restTemplate;
#RequestMapping(value = "/**")
public void route(HttpServletRequest request) {
String body = IOUtils.toString(request.getReader());
if(isFirstServer(body)) {
restTemplate.exchange(firstUrl,
getHttpMethod(request),
getHttpEntity(request),
getResponseClass(request),
getParams(params));
} else {
restTemplate.exchange(secondUrl,
getHttpMethod(request),
getHttpEntity(request),
getResponseClass(request),
getParams(params))
}
}
}
Example implementation for getHttpMethod:
public HttpMethod getHttpMethod(HttpServletRequest request) {
return HttpMethod.valueOf(request.getMethod());
}
Similar implementations for getHttpEntity, getResponseClass and getParams. They are used for converting the data from the HttpServletRequest request to the types required by the exchange method.
There seem to be a lot of better ways of doing this for a Spring MVC app, but I guess that it does not apply to your context.
Another way you could achieve this would be defining your own REST client and adding the routing logic there.