Spring WebFlux - how to catch conflict exception - spring

I have a method like below:
public String createFolder3(String folderName, String parentFolderId)
{
String requestJson = "{\"name\": " + folderName + "}";
return webClient.post()
.uri("/the/uri/goeshere/" + parentFolderId + "/children")
.body(Mono.just(requestJson), String.class)
.retrieve()
.onStatus(httpStatus -> HttpStatus.CONFLICT.equals(httpStatus),
clientResponse -> Mono.error(new Exception("Some Conflict Occurred")))
.bodyToMono(String.class).block();
}
But everytime I get the below (huge, I cut it short for brevity) exception. I don't want to display this huge exception on the server side console. What I am doing wrong or missing here?
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'processJob': Invocation of init method failed; nested exception is reactor.core.Exceptions$ReactiveException: java.lang.Exception: Some Error Occurred
.....
Caused by: reactor.core.Exceptions$ReactiveException: java.lang.Exception: Some Error Occurred
.....
Error has been observed at the following site(s):
|_ checkpoint ⇢ 409 from POST http:.....
....

If you want the response body to be returned in case of a 409, you could catch WebClientResponseException:
.retrieve()
.bodyToMono(String.class)
.onErrorResume(
WebClientResponseException.class,
e -> {
if (e.getStatusCode() == HttpStatus.CONFLICT) {
// log
return Mono.just(e.getResponseBodyAsString());
}
return Mono.error(e);
}
)

Related

WebClient encoding queryParams spring

I have trouble with WebClient encoding query parameters when the value of one parameter is decoded JSON value to String.
One of queryParams value is :
[ { "var": "report_days", "op": "=", "val": "7" } ]
it is decoded from HTTP method : ?filter=%5B%7B%22var%22%3A%22report_days%22%2C%22op%22%3A%22%3D%22%2C%22val%22%3A%227%22%7D%5D.
So decoding to MultiMap<String, String> is executed correctly, but in uriBuilder the exception is thrown.
return webClient.get()
.uri(uriBuilder -> uriBuilder.path("/nodes/last").queryParams(queryParams).build()) //Problem
.header(HttpHeaders.AUTHORIZATION, token)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(String.class)
.log();
Exception:
java.lang.IllegalArgumentException: Not enough variable values available to expand '"var"'
2021-11-22T11:17:38.252421700Z at org.springframework.web.util.UriComponents$VarArgsTemplateVariables.getValue(UriComponents.java:370)
2021-11-22T11:17:38.252461800Z Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
2021-11-22T11:17:38.252492300Z Error has been observed at the following site(s):
2021-11-22T11:17:38.252521200Z *__checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
2021-11-22T11:17:38.252586100Z *__checkpoint ⇢ HTTP GET "/nodeNew/all/last_protected?filter=%5B%7B%22var%22%3A%22report_days%22%2C%22op%22%3A%22%3D%22%2C%22val%22%3A%227%22%7D%5D" [ExceptionHandlingWebHandler]
2021-11-22T11:17:38.252628200Z Stack trace:
2021-11-22T11:17:38.252666300Z at org.springframework.web.util.UriComponents$VarArgsTemplateVariables.getValue(UriComponents.java:370)
2021-11-22T11:17:38.252699800Z at org.springframework.web.util.HierarchicalUriComponents$QueryUriTemplateVariables.getValue(HierarchicalUriComponents.java:1087)
2021-11-22T11:17:38.252723100Z at org.springframework.web.util.UriComponents.expandUriComponent(UriComponents.java:263)
2021-11-22T11:17:38.252738600Z at org.springframework.web.util.HierarchicalUriComponents.lambda$expandQueryParams$5(HierarchicalUriComponents.java:450)
2021-11-22T11:17:38.252754400Z at java.base/java.util.Map.forEach(Map.java:713)
Maybe is some of configuration to solve it? In queryParams might be another values but not in JSON format, so I would like to avoid do it in that way (that works now, but it have to forward all queryParams not only key "filter"):
return webClient.get()
.uri(uriBuilder -> uriBuilder.path("/nodes/last").queryParam(URLEncoder.encode(queryParams.getFirst("filter"), StandardCharsets.UTF_8)).build())
I came across with the same trouble recently and this did the job:
Create a method that returns a copy of webclient with a custom DefaultUriBuilderFactory
public WebClient getWebclientNoEncoded() {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(this.baseUrl); //Here comes your base url
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);
return this.webClient.mutate()
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.uriBuilderFactory(factory)
.build();
}
And then in the client method:
apiClient.getWebclientNoEncoded()
.get()
.uri(uriBuilder -> uriBuilder
.path("/foo")
.queryParam(UriUtils.encodeQueryParam(myJsonString, StandardCharsets.UTF_8.toString()))
.build())
.header(HttpHeaders.AUTHORIZATION, bearerToken)
.retrieve()
PD. Sorry about my poor english.

Webclient Error Handling - Spring Webflux

I want to throw my custom exceptions with the following conditions:
If I am getting proper error response in json format, I want to deserialize it and throw my exception named CommonException inside onStatus()
If I am receiving an HTML content as part of response or deserialization didnt happen successfully then I want to throw GenericException which I am creating inside onErrorMap()
While throwing a GenericException, I want to pass the same HttpStatus code to upstream which I am getting from downstream response.
IdVerificationResponse idVerificationResponse = client.get()
.uri(idVerificationUrl)
.headers(headers -> headers.addAll(httpEntity.getHeaders()))
.retrieve()
.onStatus(HttpStatus::isError, response ->
//Throw this one only if deserialization of error response to IdVerificationErrorResponse class happens successfully
response.bodyToMono(IdVerificationErrorResponse.class)
.flatMap(error -> Mono.error(CommonException.builder().message(error.getCustomMessage()).build()))
)
.bodyToMono(IdVerificationResponse.class)
.onErrorMap(error -> {
//Come over here only if there is an error in deserialization in onStatus()
//How to get the HttpStatus we are getting as part of error response from the downstream
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
ApiErrorDetails errorDetailsObj = ApiErrorDetails.builder().errorCode(httpStatus.name()).errorDescription("Error related to HTML")
.errorDetails("Error related to HTML").build();
ErrorDetails errorDetails = ErrorDetails.builder().errors(errorDetailsObj).build();
return GenericException.builder().errorDetails(errorDetails).httpStatus(httpStatus).build();
}).block();
Currently onErrorMap() is getting called everytime and overriding the exception I am throwing inside onStatus()
Found the solution
IdVerificationResponse idVerificationResponse = client.get()
.uri(processCheckUrl)
.headers(headers -> headers.addAll(httpEntity.getHeaders()))
.retrieve()
.onStatus(HttpStatus::isError, response -> {
HttpStatus errorCode = response.statusCode();
return response.bodyToMono(IdVerificationErrorResponse.class)
.onErrorMap(error -> new Exception("Throw your generic exception over here if there is any error in deserialization"))
.flatMap(error -> Mono.error(new Exception("Throw your custom exception over here after successful deserialization")));
})
.bodyToMono(IdVerificationResponse.class).block();

Spring WebClient Connection Refused Error

When I'm trying to send POST request with WebClient, I get the following error.However if I try to send the request to the same uri using postman it is successful.Please help me out on this I am stuck in this issue and I am new to Spring WebFlux.
threw exception [Request processing failed; nested exception is reactor.core.Exceptions$ReactiveException: io.netty.channel.AbstractChannel$AnnotatedConnectException: finishConnect(..) failed: Connection refused: localhost/127.0.0.1:8080] with root cause\n+ Throwable: java.net.ConnectException: finishConnect(..) failed: Connection refused\n at io.netty.channel.unix.Errors.throwConnectException(Errors.java:124)\n at io.netty.channel.unix.Socket.finishConnect(Socket.java:243)\n at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.doFinishConnect(AbstractEpollChannel.java:672)\n at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.finishConnect(AbstractEpollChannel.java:649)\n at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.epollOutReady(AbstractEpollChannel.java:529)\n at
My WebClient Code to Send Post Req:
String success =
webClient
.post()
.uri("/sendRequest")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.headers(
httpHeaders -> {
httpHeaders.add(
headerName, headerValue);
})
.body(Mono.just(messageBody), String.class)
.exchange()
.flatMap(
response -> {
HttpStatus httpStatus = response.statusCode();
if (httpStatus.is2xxSuccessful()) {
System.out.println("Message posted");
} else {
System.err.println("Message FAILED. Status=" + httpStatus.toString());
}
return response.bodyToMono(String.class);
})
.block();
}
//My WebClient Builder Code:
public WebClient getWebClient() {
return WebClient.builder().baseUrl(this.MyUrl()).build();
}

How to handle HTTP status code in Spring Webclient

I'm stuck trying to do simple error handling when calling a remote service. The service returns a Map. The behaviour I'm looking for is:
HTTP 200 --> Return body (Map<String, String>).
HTTP 500 --> Throw a particular exception
HTTP 404 --> Simply return Null.
Here's my code:
private Map<String, String> loadTranslations(String languageTag) {
try {
WebClient webClient = WebClient.create(serviceUrl);
Map<String, String> result = webClient.get()
.uri("/translations/{language}", languageTag)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(httpStatus -> HttpStatus.NOT_FOUND.equals(httpStatus),
clientResponse -> Mono.error(new MyServiceException(HttpStatus.NOT_FOUND)))
.onStatus(HttpStatus::is5xxServerError, response -> Mono.error(new MyServiceException(response.statusCode())))
.bodyToMono(Map.class)
.block();
return result;
} catch (MyServiceException ex) { // doesn't work as in reality it throws ReactiveException
....
}
}
I don't know how to have the result of block() return NULL (or something that I can interpret as "404 was received"). The idea would be to just return NULL on 404 and throw an exception on 500.
I tried returning Mono.empty() but in that case the result variable contains the body of the response as Dictionary (I'm using standard Spring error bodies that contain timestamp, path, message).
What I'm doing wrong?
Thank you,

Spring boot + REST exception Handler - always get 500 error

I have a REST service which could throw an exception.
This is my custom exception
public class CommentNotPostableException extends Exception {
public CommentNotPostableException(final String message) {
super(message);
}
}
Then, for my REST Api, I implemented a RestResponseEntityExceptionHandler which extends ResponseEntityExceptionHandler
One of its methods is
#ExceptionHandler(value = { CommentNotPostableException.class })
protected ResponseEntity<Object> handleCommentNotPostableException(CommentNotPostableException ex, WebRequest request) {
StringBuilder builder = new StringBuilder();
builder.append(ex.getMessage());
ApiError apiError = new ApiError(HttpStatus.valueOf(46),
ex.getLocalizedMessage(), builder.substring(0, builder.length()));
logger.error("Already posted", ex);
return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
}
which should get the exception...
Now my controller is (snippet)
public ResponseEntity<?> postComment(#Valid #RequestBody CommentDTO dto, Errors errors) throws CommentNotPostableException{
.....
if(service.hasAlreadyPosted(user, reservation)){
throw new CommentNotPostableException("Already posted");
}
....
}
So, when hitting the exception i should recevive an error 46, instead i'm getting a 500 error, even if my custom exception is taken into account... Is there some kind of ordering in exceptions?
{
"timestamp": 1496084392755,
"status": 500,
"error": "Internal Server Error",
"exception": "it.besmart.easyparking.exceptions.CommentNotPostableException",
"message": "Already posted",
"path": "/api/oauth/comment/new"
}
this is what i get from logs
2017-05-29 21:13:32 DEBUG i.b.e.w.r.CommentRestController[57] - dto è CommentDTO [comment=A, vote=3, reservationId=7161]
2017-05-29 21:13:32 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet][181] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is it.besmart.easyparking.exceptions.CommentNotPostableException: Already posted] with root cause
it.besmart.easyparking.exceptions.CommentNotPostableException: Already posted
at it.besmart.easyparking.web.restcontroller.CommentRestController.postComment(CommentRestController.java:78) ~[classes/:na]

Resources