Handle errors in WebClient with and without body - java

I have implemented the error handling in a filter that looks like this:
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
URI url = request.url();
HttpMethod method = request.method();
return next.exchange(request).flatMap(response -> {
if (response.statusCode().isError()) {
return response.bodyToMono(String.class).flatMap(responseBody -> {
Optional<Exception> exception = errorResponseHandler.handleError(method, response.statusCode(), url, responseBody);
if (exception.isPresent()) {
return Mono.error(exception.get());
} else {
// fallback
return Mono.error(new UnsupportedOperationException("The fallback functionality is still missing"));
}
});
} else {
return Mono.just(response);
}
});
}
This should work fine in the case where the response comes with a body as then the response.bodyToMono(String.class).flatMap(...) is executed. However when the body is empty nothing happens, but what I want is to also deal with the error. It is my understanding that I would do this something like this;
response.bodyToMono(String.class).flatMap(...)
.switchIfEmpty(Mono.error(new UnsupportedOperationException("The body was empty")));
This does not work as the expected type to be returned is Mono instead of Mono.
How can I achieve the handling of errors with and without response body, which is needed to construct to correct exception?

This question brought me onto the right track:
The switchIfEmpty invocation has to come before the flatMap. As there is no body, flatMap is not executed and neither is anything after, therefore the switchIfEmpty has to come first:
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
URI url = request.url();
HttpMethod method = request.method();
return next.exchange(request).flatMap(response -> {
if (response.statusCode().isError()) {
return response.bodyToMono(String.class)
.switchIfEmpty(Mono.error(new UnsupportedOperationException("The body was empty")));
.flatMap(responseBody -> {
Optional<Exception> exception = errorResponseHandler.handleError(method, response.statusCode(), url, responseBody);
if (exception.isPresent()) {
return Mono.error(exception.get());
} else {
// fallback
return Mono.error(new UnsupportedOperationException("The fallback functionality is still missing"));
}
});
} else {
return Mono.just(response);
}
});
}

Related

how to properly wrap method.invoke() (reflect) in a reactive java?

I'm trying to make a service in reactive java,
another service will send me requests.
I catch these requests and want to run methods according to the incoming URI
Here I receive a request from another service and run the desired method:
public Mono<Response> requestResponse(Request message, ByteBuf metadata) {
return controller.startMethod(message)
.onErrorResume(error -> Mono.just(buildResponse(error.getMessage(), message, Status.STATUS_INTERNAL_SERVER_ERROR)))
.switchIfEmpty(Mono.just(buildResponse("NULL", message, Status.STATUS_BAD_REQUEST)))
.doOnNext(logResponse());
}
This is the launch itself, the method is located by the annotation and the http method
public Mono<Response> startMethod(Request message) {
for (Method method : clazz.getDeclaredMethods()) {
if (isNecessaryMethod(method, message)) {
try {
return (Mono<Response>) method.invoke(context.getBean(clazz), initParameters(method, message));
} catch (Throwable e) {
return Mono.error(e);
}
}
}
return Mono.error(new PathNotFound());
}
Subsequently, the method should run, this is an example:
#Request(url = "/save-token", method = POST)
public Mono<Response> saveToken(String token) {
return Mono.empty();
}
I would like to know how to properly process method.invoke(...) so that everything works reactively and correctly

Returning an error changes method signature

I had a sense from tutorials that returning a throwable, shouldn't change method return type.
Here are my tries:
When using handle, everything is fine until I add .timeout(), then function return type is changed to Flux<Object>
private Flux<String> exampleHandle()
{
MutableHttpRequest<String> req = HttpRequest.GET("http://localhost:8080");
return httpClient.exchange(req, TokenResponse.class)
.handle((response, sink) -> {
Optional<TokenResponse> optionalBody = response.getBody();
if (optionalBody.isEmpty()) {
sink.error(new InitializationException("Failed to fetch authentication token. Body is null."));
} else {
TokenResponse tokenResponse = optionalBody.get();
String accessToken = tokenResponse.getAccessToken();
if (accessToken != null) {
sink.next(accessToken);
} else {
sink.error(new InitializationException("Failed to fetch authentication token. Authentication token is null."));
}
}
});
// .timeout(Duration.ofSeconds(10)); // Timeout changes return type to Flux<Object>
}
When using map and Flux.error (i tried Mono.error also), function return type is changed to Flux<Object> when I introduce Flux.error in map
private Flux<String> exampleMap()
{
MutableHttpRequest<String> req = HttpRequest.GET("http://localhost:8080");
return httpClient.exchange(req, TokenResponse.class)
.map(response -> {
Optional<TokenResponse> optionalBody = response.getBody();
if (optionalBody.isEmpty()) {
return Flux.error(new InitializationException("Failed to fetch authentication token. Body is null."));
} else {
TokenResponse tokenResponse = optionalBody.get();
String accessToken = tokenResponse.getAccessToken();
if (accessToken != null) {
return accessToken;
} else {
return Flux.error(new InitializationException("Failed to fetch authentication token. Authentication token is null."));
}
}
});
}
Can someone more knowledgeable explain me what am I doing wrong? Thank you!
You should move timeout before handle:
return httpClient.exchange(req, TokenResponse.class)
.timeout(Duration.ofSeconds(10))
.handle((response, sink) -> {
As for the map case, take a look at the method signature:
Flux map(Function<? super T,? extends V> mapper)
map takes a Function<T, U> and returns a Flux<U>, returning Flux.error is invalid. You could simply use throw inside map and Reactor will turn it into a proper error signal but I think handle fits better in this case.
Useful links:
Correct way of throwing exceptions with Reactor
map vs flatMap in reactor

Making HTTP requests from the post filter of Spring cloud gateway

I am new to reactive programming in Java and got stuck on this. I have implemented a filter that is working fine till the pre-filter phase but we want to make HTTP requests to other services in the post-filter.
My filter looks like this:
#Override
public GatewayFilter apply (AuthFilter.AuthenticationFilterConfigBean config) {
return (exchange, chain) -> {
ServerHttpRequest originalRequest = exchange.getRequest();
HttpHeaders requestHeaders = originalRequest.getHeaders();
WebClient.RequestHeadersSpec requestAuthentication = webClientAuthentication.post()
.uri("/xyzAuth")
.header("Header key","value")
.bodyValue("req body...");
Mono<ClientResponse> responseAuthentication = requestAuthentication.exchange();
return responseAuthentication.flatMap(clientResponseAuthentication -> {
return clientResponseAuthentication.bodyToMono(String.class).flatMap(bodyAuthentication -> {
if (!clientResponseAuthentication.statusCode().equals(HttpStatus.OK)) {
return this.handleExceptionCases(exchange, bodyAuthentication, clientResponseAuthentication.statusCode());
}
final AuthenticationResponse authenticationResponse;
try {
authenticationResponse = objectMapper.readValue(bodyAuthentication, AuthenticationResponse.class);
exchange.getRequest().mutate().headers(headersFromAuthService -> {
headersFromAuthService.addAll(getHeadermap(authenticationResponse.getHeaderInfo()));
}).build();
} catch (JsonProcessingException e) {
LOGGER.error("Failed parsing Authentication Response: " + e.getMessage());
return handleExceptionCases(exchange, null, HttpStatus.INTERNAL_SERVER_ERROR);
}
LOGGER.info("Prefilter Completed");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
LOGGER.info("Postfilter Begins");
ServerHttpResponse response = exchange.getResponse();
if(response.getStatusCode().is2xxSuccessful()) {
WebClient.RequestHeadersSpec requestPostHandler = webClientPostHandler.post()
.uri("/xyzPostHandler")
.header("Header key","value")
.bodyValue("req body....");
Mono<ClientResponse> responsePostHandler = requestPostHandler.exchange();
LOGGER.info("Going to call postHandler");
return responsePostHandler.flatMap(postHandlerResponse -> {
return postHandlerResponse.bodyToMono(void.class).flatMap(postHandlerBody -> {
if (!postHandlerBody.statusCode().equals(HttpStatus.OK)) {
return this.handleExceptionCases(exchange, postHandlerBody.toString(), postHandlerResponse.statusCode());
}
});
});
}
}));
});
});
};
}
The pre filters are working fine (both the Authentication request and PreHandler requests are getting made) and the actual API is also getting hit. The problem arises when I am trying this in the post filter.
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
LOGGER.info("Postfilter Begins");
ServerHttpResponse response = exchange.getResponse();
if(response.getStatusCode().is2xxSuccessful()) {
WebClient.RequestHeadersSpec requestPostHandler = webClientPostHandler.post()
.uri("/xyzPostHandler")
.header("Header key","value")
.bodyValue("req body....");
Mono<ClientResponse> responsePostHandler = requestPostHandler.exchange();
LOGGER.info("Going to call postHandler");
return responsePostHandler.flatMap(postHandlerResponse -> {
return postHandlerResponse.bodyToMono(void.class).flatMap(postHandlerBody -> {
if (!postHandlerBody.statusCode().equals(HttpStatus.OK)) {
return this.handleExceptionCases(exchange, postHandlerBody.toString(), postHandlerResponse.statusCode());
}
});
});
}}));
Eclipse is showing warning Void methods cannot return a value.
Is this the correct way to make HTTP requests in the post filter? And what am I doing wrong?

How to combine Flux and ResponseEntity in Spring Webflux controllers

I use Monos with ResponseEntitys in my Webflux controllers in order to manipulate headers and other response info. For example:
#GetMapping("/{userId}")
fun getOneUser(#PathVariable userId: UserId): Mono<ResponseEntity<UserDto>> {
return repository.findById(userId)
.map(User::asDto)
.map { ResponseEntity.ok(it) }
.defaultIfEmpty(ResponseEntity.notFound().build())
}
#GetMapping
fun getAllUsers(): Flux<UserDto> {
return repository.findAllActive().map(User::asDto)
}
both works fine but there are cases where it is required to have ResponseEntity in conjunction with Flux as well. What should the response type be? Is it correct to use ResponseEntity<Flux<T>>?
For example:
#GetMapping("/{userId}/options")
fun getAllUserOptions(#PathVariable userId: UserId): ??? {
return repository.findById(userId)
.flatMapIterable{ it.options }
.map { OptionDto.from(it) }
// if findById -> empty Mono then:
// return ResponseEntity.notFound().build() ?
// else:
// return the result of `.map { OptionDto.from(it) }` ?
}
The behaviour I'd like to achieve here is that getAllUserOptions returns 404 if repository.findById(userId) is an empty Mono, otherwise return user.options as Flux.
Update:
repository here is ReactiveCrudRepository
Use switchIfEmpty to throw an exception in case the user doesn't exist:
return repository
.findById(userId)
.switchIfEmpty(Mono.error(NotFoundException("User not found")))
.flatMapIterable{ it.options }
.map { OptionDto.from(it) }
Then with an exception handler translate it to a 404 response.
You can use by returning Mono with ResponseEntity
like this
public Mono<ResponseEntity<?>> oneRawImage(
#PathVariable String filename) {
// tag::try-catch[]
return imageService.findOneImage(filename)
.map(resource -> {
try {
return ResponseEntity.ok()
.contentLength(resource.contentLength())
.body(new InputStreamResource(
resource.getInputStream()));
} catch (IOException e) {
return ResponseEntity.badRequest()
.body("Couldn't find " + filename +
" => " + e.getMessage());
}
});
}
I have also example like this
public ResponseEntity<Mono<?>> newLive(#Valid #RequestBody Life life) {
Mono<Life> savedLive = liveRepository.save(life);
if (savedLive != null) {
return new ResponseEntity<>(savedLive, HttpStatus.CREATED);
}
return new ResponseEntity<>(Mono.just(new Life()), HttpStatus.I_AM_A_TEAPOT);
}
I dislike functional programming in the REST controllers.
Here is an example ReactiveController .
works for me, let me know if you have a trouble
#PostMapping(value = "/bizagi/sendmsg")
public Mono<ResponseEntity<?>> sendMessageToQueue(#RequestBody BizagiPost bizagiPost) {
Mono<BodyReturn> retorno = useCase.saveMsg(bizagiPost);
Map<String, Object> response = new HashMap<>();
return retorno.map(t ->
{
if (t.getStatusCode().equals("200")) {
response.put("message", t.getReazon());
return new ResponseEntity(t, HttpStatus.OK);
} else {
response.put("message", t.getReazon());
return new ResponseEntity(t, HttpStatus.BAD_REQUEST);
}
});
}

How to change response statuc code when using CompletableFuture.exceptionaly() in Spring?

I have a simple controller:
#RestController
public class SimpleController() {
public String get() {
if (System.nanoTime() % 2 == 0)
throw new IllegalArgumentException("oops");
return "ok"
}
}
Controller can throw simple exception, so i wrote controller advisor for it handling:
#ExceptionHandler(IllegalArgumentException.class)
#ResponseBody
public ResponseEntity<String> rejection(Rejection ex) {
return new ResponseEntity<>("bad", HttpStatus.CONFLICT);
}
Now i want to make get method async. But i don't know the best way for handling exception.
I tried:
public CompletableFuture<String> get() {
CompletableFuture.supplyAsync(
() -> {
if (System.nanoTime() % 2 == 0)
throw new IllegalArgumentException("oops");
return "ok";
}).exceptionally(thr -> {
//what should i do?
if (thr instanceof IllegalArgumentException)
throw ((IllegalArgumentException) t);
if (thr.getCause() instanceof IllegalArgumentException)
throw ((IllegalArgumentException) t.getCause());
return null;
}
}
But controller advisor still does not catch the exception.
Also i tried to return ResponseEntity("message", HttpStatuc.CONFLICT); in exceptionally block.
But in tests i still have MvcResult.getResponse().getStatus() == 200.
Any other idea?
Maybe it's a wrong way at all?
UPDATE
I don't know why, but it don't catch exceptions:
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncUncaughtExceptionHandler() {
#Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
System.out.println();
}
};
And even if it work, how to set http status to response?
Try to return a ResponseEntity instance:
return new ResponseEntity<>("bad", HttpStatus.CONFLICT);
out of exceptionally method.
Sorry guys, the problem not in code.
I just wrote bad tests.
Here the link for explaining:
https://sdqali.in/blog/2015/11/24/testing-async-responses-using-mockmvc/
Just use:
MvcResult result = mockMvc.perform(...).andReturn();
result = mockMvc.perform(asyncDispatch(result)).andReturn();
before you check the status or result response.

Categories