What is the expected behavior when WebClient bodyToMono encounters an empty body? In my specific example we are checking the status returned from a post call, and if it's an error converting it to our custom error format. If the conversion to the custom error format fails, we create a new error in our custom format stating that. But when a response came in that was an error with an empty body, it failed to send any error back at all because bodyToMono didn't fail as I had expected. See the below code block:
.retrieve()
.onStatus(HttpStatus::isError) { response ->
response.bodyToMono(ErrorResponse::class.java)
.doOnError {
throw APIException(
code = UNEXPECTED_RESPONSE_CODE,
reason = it.message ?: "Could not parse error response from Inventory Availability",
httpStatus = response.statusCode()
)
}
.map {
throw APIException(
reason = it.errors.reason,
code = it.errors.code,
httpStatus = response.statusCode()
)
}
}
To fix this we added the switchIfEmpty.
.retrieve()
.onStatus(HttpStatus::isError) { response ->
response.bodyToMono(ErrorResponse::class.java)
.switchIfEmpty { throw RuntimeException("Received Empty Response Body") }
.doOnError {
throw APIException(
code = UNEXPECTED_RESPONSE_CODE,
reason = it.message ?: "Could not parse error response from Inventory Availability",
httpStatus = response.statusCode()
)
}
.map {
throw APIException(
reason = it.errors.reason,
code = it.errors.code,
httpStatus = response.statusCode()
)
}
}
My question is this: Is this expected behavior from bodyToMono? Since I'm explicitly asking to map the response body to my object of ErrorResponse, I would expect an empty body to error and then hit the doOnError block, but instead it just "succeeds"/returns an empty mono...hence us adding the switchIfEmpty block. If the object we were mapping to had nullable fields, I could see it not having an error, but since all fields are required why does bodyToMono not throw an error when trying to map "nothing" to my object?
This is correct. A Mono can produce 0..1 elements and an empty body just produces a mono that completes without emitting a value, much like Mono.empty().
If no body is an error in your use case you can call .single() on your mono.
Related
I'm looking for a simple example of error handling with WebFlux. I've read lots of stuff online, but can't find something that fits what I want.
I'm running with Spring Boot 2.45
I am calling services like this:
Mono<ResponseObject> mono = webClient.post()
.uri(url.toString())
.header("Authorization", authToken)
.body(Mono.just(contract), contract.getClass())
.retrieve()
.bodyToMono(ResponseObject.class);
All of my services return Json that is deserialized to ResposeObject which looks something like this:
"success" : true,
"httpStatus": 200,
"messages" : [
"Informational message or, if not 200, then error messages"
],
result: {
"data": {}
}
data is simply a map of objects that are the result of the service call.
If there is an error, obviously success is false.
When I eventually do a ResponseObject response = mono.block(), I want to get a ResponseObject each time, even if there was an error. My service returns a ResponseObject even if it returns an http status of 400, but WebFlux seems to intercept this and throws an exception. Obviously, there might also be 400 and 500 errors where the service wasn't even called. But I still want to wrap whatever message I get into a ResponseObject. How can I eliminate all exceptions and always get a ResponseObject returned?
Update
Just want to clarify that the service itself is not a Reactive Webflux service. It is not returning a Mono. Instead, it is calling out to other Restful services, and I want to do that using Webflux. So what I do is I call the external service, and then this service does a block(). In most cases, I'm calling multiple services, and then I do a Mono.zip and call block() to wait for all of them.
This seems to be what I want to do: Spring Webflux : Webclient : Get body on error, but still can't get it working. Not sure what exchange() is
Correct way of handling this is via .onErrorResume that allows you to subscribe to a fallback publisher using a function, when any error occurs. You can look at the generated exception and return a custom fallback response.
You can do something like this:
Mono<ResponseObject> mono = webClient.post()
.uri(url.toString())
.header("Authorization", authToken)
.bodyValue(contract)
.exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
return response.bodyToMono(ResponseObject.class);
}
else if (response.statusCode().is4xxClientError()) {
return response.bodyToMono(ResponseObject.class);
}
else {
Mono<WebClientResponseException> wcre = response.createException();
// examine wcre and create custom ResponseObject
ResponseObject customRO = new ResponseObject();
customRO.setSuccess(false);
customRO.setHttpStatus(response.rawStatusCode());
// you can set more default properties in response here
return Mono.just( customRO );
}
});
Moreover, you should not be using .block() anywhere in your Java code. Just make sure to return a Mono<ResponseObject> from your REST controller. If you want to examine response before returning to client you can do so in a .map() hander like this at the end of pipeline (right after .onErrorResume handler)
.map(response -> {
// examine content of response
// in the end just return it
return response;
});
I don't know why my code is not working, I've tried with Postman and works fine:
But with RestTemplate I can´t get a response while it´s using the same endpoint... .
ResponseEntity<String> responseMS = template.exchange(notificationRestService, HttpMethod.DELETE, new HttpEntity<NotificationRestDTO[]>(arrNotif), String.class);
I've tried with List instead Array[]
When i made a PUT request it´s works fine but with one object:
ResponseEntity<String> responseMS = template.exchange(notificationRestService, HttpMethod.PUT, new HttpEntity<NotificationRestDTO>(notificationDTO), String.class);
Any help?? Thanks!!
From the comments it became clear that you're expecting it to return a 400 Bad Request response. RestTemplate will see these as "client errors" and it will throw a HttpClientErrorException.
If you want to handle cases like this, you should catch this exception, for example:
try {
ResponseEntity<String> responseMS = template.exchange(notificationRestService, HttpMethod.DELETE, new HttpEntity<NotificationRestDTO[]>(arrNotif), String.class);
} catch (HttpClientErrorException ex) {
String message = ex.getResponseBodyAsString();
}
In this case (since you expect a String), you can use the getResponseBodyAsString() method.
The ResponseEntity will only contain the data in case your request can be executed successfully (2xx status code, like 200, 204, ...). So, if you only expect a message to be returned if the request was not successfully, you can actually do what Mouad mentioned in the comments and you can use the delete() method of the RestTemplate.
I'm trying to call to the extension method CreateErrorResponse with a status code and string.
The problem that the function throws ArgumentException with additional information that the value dose not fall within the expected range.
I tried to send different values of arguments, explicitly cast the arguments and using different overloads.
Code
private HttpResponseException GetItemsException(IEnumerable<Guid> ids, string errorMessage, HttpStatusCode status)
{
HttpResponseMessage error = Request.CreateErrorResponse(status, errorMessage);
error.Headers.Add("ids", ids.Select(id => id.ToString()));
return new HttpResponseException(error);
}
I am getting HTTP 400 when I POST some JSON using RestSharp PCL.
When I send a string, it seems that the \" is included. Which it should not. This might be the reason why the POST does not work.
I am probably missing something that I need to fill in but please do help me to understand what I am missing.
Here is the code I am using
public async Task<bool> DoPost<T>(string endPoint, T content) where T : class
{
var body = JsonConvert.SerializeObject(content);
var request = new RestRequest(endPoint, Method.POST);
request.AddParameter("application/json", body, ParameterType.RequestBody);
try
{
var response = await _client.Execute(request, _cancellationToken.Token);
if (response.IsSuccess)
{
return true;
}
}
catch (Exception e)
{
throw new GTSWebServiceException(e.Message, e);
}
return false;
}
Have you checked this: How to POST request using RestSharp I know you are including the content type in the first argument but maybe you can play with RequestFormat? I doubt that's needed though. Also, have you checked whether your string does actually contain an escaped character like a double quote on it? If you are also seeing that slash on strings could it also be because you are debugging it? What do you receive in the payload coming through in the server that returns you the bad request?
I have the following code in my web application:
#ExceptionHandler(InstanceNotFoundException.class)
#ResponseStatus(HttpStatus.NO_CONTENT)
public ModelAndView instanceNotFoundException(InstanceNotFoundException e) {
return returnErrorPage(message, e);
}
Is it possible to also append a status message to the response? I need to add some additional semantics for my errors, like in the case of the snippet I posted I would like to append which class was the element of which the instance was not found.
Is this even possible?
EDIT: I tried this:
#ResponseStatus(value=HttpStatus.NO_CONTENT, reason="My message")
But then when I try to get this message in the client, it's not set.
URL u = new URL ( url);
HttpURLConnection huc = (HttpURLConnection) u.openConnection();
huc.setRequestMethod("GET");
HttpURLConnection.setFollowRedirects(true);
huc.connect();
final int code = huc.getResponseCode();
String message = huc.getResponseMessage();
Turns out I needed to activate custom messages on Tomcat using this parameter:
-Dorg.apache.coyote.USE_CUSTOM_STATUS_MSG_IN_HEADER=true
The message can be in the body rather than in header. Similar to a successful method, set the response (text, json, xml..) to be returned, but set the http status to an error value. I have found that to be more useful than the custom message in header. The following example shows the response with a custom header and a message in body. A ModelAndView that take to another page will also be conceptually similar.
#ExceptionHandler(InstanceNotFoundException.class)
public ResponseEntity<String> handle() {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("ACustomHttpHeader", "The custom value");
return new ResponseEntity<String>("the error message", responseHeaders, HttpStatus.INTERNAL_SERVER_ERROR);
}