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()));
Related
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
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());
}
}
I am working with retrofit and need to be able to use multiple interceptors. Currently I am using one to automatically append an auth token but i need to be able to make calls with no auth token. If i add another interceptor with no auth token in the header how do I use that one instead of the auth token interceptor.
val interceptor: Interceptor = Interceptor { chain ->
val newRequest = chain.request().newBuilder().
addHeader("Auth_Token", pref.getString(PSPreferences.prefAuthKey, "")).
cacheControl(CacheControl.FORCE_NETWORK).
build()
chain.proceed(newRequest)
}
okHttpClient = OkHttpClient.Builder().
readTimeout(1, TimeUnit.MINUTES).
connectTimeout(1, TimeUnit.MINUTES).
addInterceptor(interceptor).build()
val retrofitInstance = Retrofit.Builder()
.baseUrl(APIEndpointInterface.BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
apiInterface = retrofitInstance.create<APIEndpointInterface>(APIEndpointInterface::class.java)
OkHttpClient maintains a list of the interceptors which you can access, however it is an unmodifiable collection.
This leaves us with three options I believe:
Create two OkHttpClient instances, and by deduction two Retrofit
instances, one for the unauthenticated requests, and one for the
authenticated requests.
Check if you should use the interceptor, e.g. in your authentication interceptor, you can first check if there exists a key in your preferences for the token, and if so use it; if not, you simply proceed without modifying anything. You do this for your unauthenticated interceptor too. I think this is the easiest solution for your case.
Create a single interceptor, which will maintain a modifiable list
of interceptors which you can add and remove at will. You would need
to keep a reference to this interceptor, maybe make it a Singleton.
For the third option, I have provided a very simple example:
public class HttpRequestResponseInterceptor implements Interceptor {
public final List<RequestInterceptor> requestInterceptors = new ArrayList<>();
public final List<ResponseInterceptor> responseInterceptors = new ArrayList<>();
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
for (RequestInterceptor interceptor : requestInterceptors) {
request = interceptor.intercept(request);
}
Response response = chain.proceed(request);
for (ResponseInterceptor interceptor : responseInterceptors) {
response = interceptor.intercept(response);
}
return response;
}
public interface RequestInterceptor {
Request intercept(Request request) throws IOException;
}
public interface ResponseInterceptor {
Response intercept(Response response) throws IOException;
}
}
In this case you would need to implement the custom interfaces RequestInterceptor and ResponseInterceptor.
An example of what an implementation of these interfaces would look like:
public class ExampleInterceptor implements HttpRequestResponseInterceptor.RequestInterceptor,
HttpRequestResponseInterceptor.ResponseInterceptor {
#Override
public Request intercept(Request request) throws IOException {
return request.newBuilder().addHeader("REQUEST_HEADER", "EXAMPLE").build();
}
#Override
public Response intercept(Response response) throws IOException {
return response.newBuilder().addHeader("RESPONSE_HEADER", "EXAMPLE").build();
}
}
You would then need to add this interceptor to our main interceptor twice, once to requestInterceptors and once to responseInterceptors (or only to one of these if it intercepts only requests or only responses).
This example is far from complete. The benefit of this solution is that it adds the ability to add and remove interceptors without having to recreate the OkHttpClient instance. It requires extra work if you want to support retrying requests, for example.
I've been doing some research using spring-webflux and I like to understand what should be the right way to handle errors using Router Functions.
I've created an small project to test a couple of scenarios, and I like to get feedback about it, and see what other people is doing.
So far what I doing is.
Giving the following routing function:
#Component
public class HelloRouter {
#Bean
RouterFunction<?> helloRouterFunction() {
HelloHandler handler = new HelloHandler();
ErrorHandler error = new ErrorHandler();
return nest(path("/hello"),
nest(accept(APPLICATION_JSON),
route(GET("/"), handler::defaultHello)
.andRoute(POST("/"), handler::postHello)
.andRoute(GET("/{name}"), handler::getHello)
)).andOther(route(RequestPredicates.all(), error::notFound));
}
}
I've do this on my handler
class HelloHandler {
private ErrorHandler error;
private static final String DEFAULT_VALUE = "world";
HelloHandler() {
error = new ErrorHandler();
}
private Mono<ServerResponse> getResponse(String value) {
if (value.equals("")) {
return Mono.error(new InvalidParametersException("bad parameters"));
}
return ServerResponse.ok().body(Mono.just(new HelloResponse(value)), HelloResponse.class);
}
Mono<ServerResponse> defaultHello(ServerRequest request) {
return getResponse(DEFAULT_VALUE);
}
Mono<ServerResponse> getHello(ServerRequest request) {
return getResponse(request.pathVariable("name"));
}
Mono<ServerResponse> postHello(ServerRequest request) {
return request.bodyToMono(HelloRequest.class).flatMap(helloRequest -> getResponse(helloRequest.getName()))
.onErrorResume(error::badRequest);
}
}
Them my error handler do:
class ErrorHandler {
private static Logger logger = LoggerFactory.getLogger(ErrorHandler.class);
private static BiFunction<HttpStatus,String,Mono<ServerResponse>> response =
(status,value)-> ServerResponse.status(status).body(Mono.just(new ErrorResponse(value)),
ErrorResponse.class);
Mono<ServerResponse> notFound(ServerRequest request){
return response.apply(HttpStatus.NOT_FOUND, "not found");
}
Mono<ServerResponse> badRequest(Throwable error){
logger.error("error raised", error);
return response.apply(HttpStatus.BAD_REQUEST, error.getMessage());
}
}
Here is the full sample repo:
https://github.com/LearningByExample/reactive-ms-example
Spring 5 provides a WebHandler, and in the JavaDoc, there's the line:
Use HttpWebHandlerAdapter to adapt a WebHandler to an HttpHandler. The WebHttpHandlerBuilder provides a convenient way to do that while also optionally configuring one or more filters and/or exception handlers.
Currently, the official documentation suggests that we should wrap the router function into an HttpHandler before booting up any server:
HttpHandler httpHandler = RouterFunctions.toHttpHandler(routerFunction);
With the help of WebHttpHandlerBuilder, we can configure custom exception handlers:
HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler(toHttpHandler(routerFunction))
.prependExceptionHandler((serverWebExchange, exception) -> {
/* custom handling goes here */
return null;
}).build();
If you think, router functions are not the right place to handle exceptions, you throw HTTP Exceptions, that will result in the correct HTTP Error codes.
For Spring-Boot (also webflux) this is:
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
.
.
.
new ResponseStatusException(HttpStatus.NOT_FOUND, "Collection not found");})
spring securities AccessDeniedException will be handled correctly, too (403/401 response codes).
If you have a microservice, and want to use REST for it, this can be a good option, since those http exceptions are quite close to business logic, and should be placed near the business logic in this case. And since in a microservice you shouldn't have to much businesslogic and exceptions, it shouldn't clutter your code, too... (but of course, it all depends).
Why not do it the old fashioned way by throwing exceptions from handler functions and implementing your own WebExceptionHandler to catch 'em all:
#Component
class ExceptionHandler : WebExceptionHandler {
override fun handle(exchange: ServerWebExchange?, ex: Throwable?): Mono<Void> {
/* Handle different exceptions here */
when(ex!!) {
is NoSuchElementException -> exchange!!.response.statusCode = HttpStatus.NOT_FOUND
is Exception -> exchange!!.response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR
}
/* Do common thing like logging etc... */
return Mono.empty()
}
}
Above example is in Kotlin, since I just copy pasted it from a project I´m currently working on, and since the original question was not tagged for java anyway.
You can write a Global exception handler with custom response data and response code as follows. The code is in Kotlin. But you can convert it to java easily:
#Component
#Order(-2)
class GlobalWebExceptionHandler(
private val objectMapper: ObjectMapper
) : ErrorWebExceptionHandler {
override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono<Void> {
val response = when (ex) {
// buildIOExceptionMessage should build relevant exception message as a serialisable object
is IOException -> buildIOExceptionMessage(ex)
else -> buildExceptionMessage(ex)
}
// Or you can also set them inside while conditions
exchange.response.headers.contentType = MediaType.APPLICATION_PROBLEM_JSON
exchange.response.statusCode = HttpStatus.valueOf(response.status)
val bytes = objectMapper.writeValueAsBytes(response)
val buffer = exchange.response.bufferFactory().wrap(bytes)
return exchange.response.writeWith(Mono.just(buffer))
}
}
A quick way to map your exceptions to http response status is to throw org.springframework.web.server.ResponseStatusException / or create your own subclasses...
Full control over http response status + spring will add a response body with the option to add a reason.
In Kotlin it could look as simple as
#Component
class MyHandler(private val myRepository: MyRepository) {
fun getById(req: ServerRequest) = req.pathVariable("id").toMono()
.map { id -> uuidFromString(id) } // throws ResponseStatusException
.flatMap { id -> noteRepository.findById(id) }
.flatMap { entity -> ok().json().body(entity.toMono()) }
.switchIfEmpty(notFound().build()) // produces 404 if not found
}
fun uuidFromString(id: String?) = try { UUID.fromString(id) } catch (e: Throwable) { throw BadRequestStatusException(e.localizedMessage) }
class BadRequestStatusException(reason: String) : ResponseStatusException(HttpStatus.BAD_REQUEST, reason)
Response Body:
{
"timestamp": 1529138182607,
"path": "/api/notes/f7b.491bc-5c86-4fe6-9ad7-111",
"status": 400,
"error": "Bad Request",
"message": "For input string: \"f7b.491bc\""
}
What I am currently doing is simply providing a bean my WebExceptionHandler :
#Bean
#Order(0)
public WebExceptionHandler responseStatusExceptionHandler() {
return new MyWebExceptionHandler();
}
The advantage than creating the HttpHandler myself is that I have a better integration with WebFluxConfigurer if I provide my own ServerCodecConfigurer for example or using SpringSecurity