I have a problem with getting a response body with a reactive spring. The service that I'm integrating with can have a different JSON body in response, so I cannot have a DTO that I can map the response to, so I was trying with String.
To check the solution for now I have mocked a simple server with SoapUi to return code 400 and JSON :
{
"errorCode" : "code",
"errorMessage" : "message"
}
and I would like to be able to log in in console in case of having an error, so here is part of my code:
.bodyValue(eventDataDto)
.exchange()
.doOnSuccess((response) -> {
if (response.rawStatusCode() != HttpStatus.OK.value()) {
throw new RuntimeException("Sending request failed with status: " + response.rawStatusCode() +
" with error message: " + response.bodyToMono(String.class);
}
})
.doOnError((throwable) -> {
throw new RuntimeException(throwable);
})
.block();
but the response that I'm getting is:
Sending request failed with status: 400 with error message: checkpoint("Body from POST http://localhost:8095/test/ [DefaultClientResponse]")
I was trying to use subscribe, map, and flatMap on that mono, but the only result was that I was getting a different class type on that error message, but nothing close to what I'm looking for.
I would appreciate any help with getting the response body in any form, that I could use its content.
EDIT WITH ANSWER:
A minute after posting it I have found a solution:
So I have tried with subscribe on mono:
.bodyValue(eventDataDto)
.exchange()
.doOnSuccess((response) -> {
if (response.rawStatusCode() != HttpStatus.OK.value()) {
Mono<String> bodyMono = response.bodyToMono(String.class);
bodyMono.subscribe(body -> {
throw new RuntimeException(
"Sending request failed with status: " + response.rawStatusCode() +
" with error message: " + body);
});
}
})
.doOnError((throwable) -> {
throw new RuntimeException(throwable);
})
.block();
I completly forgot that Mono will not give a result until subscribed.
You are receiving the proper result as your HTTP call succeeds (go-through) but returns a 400 ~ Bad Request response code which is a full concise HTTP response (which is the correct response you have configured as per your description in the OP).
In order to have the response body retrieved, either being the proper response body on a successful (200) or unsuccessful (400) response, you should use:
Either the toEntity operator
Or the bodyToMono one
Here down how your call chain would look like mapping the error response content using bodyToMono:
webClient.get()
.uri("some-uri")
.bodyValue(eventDataDto)
.exchange()
.flatMap((response) -> {
if (response.rawStatusCode() != HttpStatus.OK.value()) {
return response.bodyToMono(String.class)
.map(errorContent -> "Sending request failed with status: " + response.rawStatusCode() + " with error message: " + errorContent);
} else {
return response.bodyToMono(String.class);
})
.block();
Related
I'm trying to create a resilient sse (server sent event) client in reactive programming.
The sse endpoint is authenticated, therefore I have to add an authorization header to each request.
The authorization token expires after 1 hour.
Below is a snippet of my code
webClient.get()
.uri("/events")
.headers(httpHeaders -> httpHeaders.setBearerAuth(authService.getIdToken()))
.retrieve()
.bodyToFlux(ServerSideEvent.class)
.timeout(Duration.ofSeconds(TIMEOUT))
.retryWhen(Retry.fixedDelay(Long.MAX_VALUE, Duration.ofSeconds(RETRY_DELAY)))
.subscribe(
content -> {
handleEvent(content);
},
error -> logger.error("Error receiving SSE: {}", error),
() -> logger.info("Completed!!!"));
If after 1 hour the connection is lost for any reason, this code stops working because the token is expired.
How can I refresh the token into the retry logic or in some other way?
Thank you
You can use webclient filter.
A filter can intercept and modify client request and response.
Example:
WebClient.builder().filter((request, next) -> {
ClientRequest newReuqest = ClientRequest.from(request)
.header("Authorization", "YOUR_TOKEN")
.build();
return next.exchange(newRequest);
}).build();
UPDATE:
Sorry, Not read your question clearly. Try this:
Assume that server return 401 code when token expired.
WebClient.builder().filter((request, next) -> {
final Mono<ClientResponse> response = next.exchange(request);
return response.filter(clientResponse -> clientResponse.statusCode() != HttpStatus.UNAUTHORIZED)
// handle 401 Unauthorized (token expired)
.switchIfEmpty(next.exchange(ClientRequest.from(request)
.headers(httpHeaders -> httpHeaders.setBearerAuth(getNewToken()))
.build()));
}).build();
Or you can cache your token (e.g. Save to redis and set TTL in one hour), when the token is empty from redis, get new one then save to redis again.
API (Name it as api-1) has following property -
For 2XX it can return body
For 5XX it doesn’t return body
In another API (api-2), Our requirement is if api-1 status code is 2XX return “ABC” else return “XYZ” from webclient call, So we don’t wanted to consume response body in either cases.
Which one should work retrieve() or exchangeToMono() ?
When I use retrieve() I couldn’t return ABC or XYZ based on http response. If I use exchangeToMono() I was getting InvalidStateException with following message “The underlying HTTP client completed without emitting a response.”
Any help is appreciated.
Here is my approach.
String result = client
.get()
.uri("http://localhost:8080/endpoint")
.retrieve()
.toBodilessEntity()
.onErrorResume(WebClientResponseException.class, e -> Mono.just(ResponseEntity.status(e.getStatusCode()).build()))
.map(entity -> entity.getStatusCode().isError() ? "error" : "ok")
.block();
result is equal to ok when the request is completed successfully, and error otherwise.
You can change the condition in map if you want to return error only on 5xx exceptions.
EDIT: Or as Toerktumlare mentioned in his comment, you can use onStatus to omit an exception.
String result = client
.get()
.uri("http://localhost:8080/endpoint")
.retrieve()
.onStatus(HttpStatus::isError, e -> Mono.empty())
.toBodilessEntity()
.map(entity -> entity.getStatusCode().isError() ? "error" : "ok")
.block();
I make a post request to my rest controller and as a result I want to get information about the error in case of incorrect data. Error information is generated in #RestControllerAdvice.
Here is my advice class:
#RestControllerAdvice
public class RestControllerErrorHandler {
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> handleCustomerException(MethodArgumentNotValidException exception) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", new Date());
body.put("exception", "MethodArgumentNotValidException");
List<String> errors = exception
.getBindingResult()
.getFieldErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
body.put("errors", errors);
return body;
}
}
Here is the result of the error that I get.
{
"timestamp": "2020-07-07T20:20:44.778+00:00",
"exception": "MethodArgumentNotValidException",
"errors": [
"Login length: 6 - min and 10 - max",
"Password length: 6 - min and 10 - max"
]
}
This is how I call method POST from ajax:
$(document).ready(function () {
$("#sendForm").click(function () {
const login = $('input[name=login]').val();
const password = $('input[name=password]').val();
$.ajax({
type: "POST",
url: "/api/users",
contentType: 'application/json',
data: JSON.stringify({"login": login, "password": password}),
dataType: "json",
success: function (data) {
alert('success: ' + data.id + " " + data.login + " " + data.password)
},
error: function (requestObject, error, errorThrown) {
alert(error);//this field return "error" string
alert(errorThrown);//for some reason this is empty when I get an error
}
});
});
});
How can I read it to get exactly the error information? I want to display this: "Login length: 6 - min and 10 - max", "Password length: 6 - min and 10 - max" in alert().
so, what you should note - the response is an object (array-like) which has a subkey named errors which itself contains the data (non-keyed array). So, you should change the Ajax success part to this:
success: function (data) {
var parsed_response = JSON.parse(data);
alert(parsed_response.errors[0]+ " " + parsed_response.errors[1]);
},
as you see, with parsed response, you get the access to the members of the object.
update:
if you want to get the error response, that it means the "response object" (which contained timespamp, exception, errors... members) you had prepared for your response, doesnt matter, as it will not be returned in that (erroneus) situation. So, in that case, as you already mentioned, you are getting alert of the error message, and depending on that (i.e. 404, 410, ...) it will tell you what happens on backend. you will not get any response in that case, because "error" function itself says that server didn't return the readable (outputed) answer, and you have to fix that error (i.e. 410, 503 or whatever, search for them in google).
I might also suggest to save debug file in backend language when receiving the ajax request, so you will find out in which line the backend code breaks.
i.e.
save_to_file("a1");
your codes
save_to_file("a2");
your another codes
save_to_file("a3");
and so on...
so, some of the backend code has error.
found a solution:
error: function (requestObject, error, errorThrown) {
const result = JSON.parse(requestObject.responseText);
alert('error: ' + result.errors[0]);//"Login length: 6 - min and 10 - max"
}
I need to get some respones from some URL.
For this purpose I use http://unirest.io/java.html and Java.
Map<String, String> map = new HashMap<>();
map.put(key1, value1);
...
map.put(keyN, valueN);
String authToken = "{token}";
HttpResponse<String> response = Unirest.post(url)
.header("Authorization","Bearer " + authToken)
.header("Content-Type", "application/json")
.fields(map)
.asString();
As a result I receive response.getStatus() = 302 and some unexpected body.
At the same time I use Postman software to get the same responses. The settings are the following:
POST: url
Authorization: Type -> Bearer Token; Token = {{authToken}} // get the value from the previous request
Header :
"Authorization" : "Bearer " + {{authToken}}
Content-Type: application/json
Body:
{
key1 : value1,
...
keyN : valueN
}
And I get some expected response.
What makes the difference?
A 302 is a redirect response. Is it possible Postman is following the redirect and returning the resultant page? Take a look at the Location header in the response you get in Java, and see if following that gives you the same results you're seeing in Postman.
I am studying vertx.io web client and I am already blocked doing a simple get... Uff. Here is what I put together (I am very new at vertx.io):
private void getUserEmail(String accessToken, Handler<AsyncResult<String>> handler) {
String url = "https://graph.facebook.com/me";
HttpRequest<JsonObject> req = webClient.get(url).as(BodyCodec.jsonObject());
req.addQueryParam("access_token", accessToken);
req.addQueryParam("fields", "name,email");
MultiMap headers = req.headers();
headers.set("Accept", "application/json");
req.send(h -> {
if (h.succeeded()) {
log.info(h.result().toString());
handler.handle(new FutureFactoryImpl().succeededFuture(h.result().bodyAsString()));
} else {
log.error(h.cause());
handler.handle(new FutureFactoryImpl().failedFuture(h.cause()));
}
});
}
I think it should be enought but instead it's not. When I send request I get this error back:
io.vertx.core.json.DecodeException: Failed to decode: Unrecognized token 'Not': was expecting 'null', 'true', 'false' or NaN
at [Source: Not Found; line: 1, column: 4]
Of course if I do the same get by browser I get the expected data. I read the tutorial and examined the examples, what am I missing?
You're receiving a 404 error with the body: Not Found and the codec tries to parse it as JSON and fails. You need to verify if the request you're sending is correct.