tbjorch's answer led me down the right path, but one thing I discovered is I should construct the HTTP exceptions using the static .create
methods for both HttpClientErrorException
and HttpServerErrorException
. If I didn't do this, all my exceptions would get thrown as just the Exception
superclass, which was undesirable since it lost the context of the original Http status code.
I am new to using Spring WebClient. The API I'm implementing responds with different JSON structures based on success or failure. In the case of a failure, I want to be able to deserialize the response body and return the error message. Based on the code below, I get an error message stating:
java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-2
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:83)
I agree, it doesn't make sense to block in 2 places. But I'm not understanding how I can deserialize the error response to grab the error message. Everything I read on the web and StackOverflow indicates people just return Mono.error
or throw exceptions, but they don't seem to be deserializing the response body in those scenarios. Here's my code:
public boolean updatePassword(String id, String data) {
final var responseSpec =
client
.post()
.uri(builder -> builder.path("/{id}/change_password").build(id))
.header(HttpHeaders.AUTHORIZATION, "Bearer " staticData.getAPIToken())
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(data))
.retrieve()
.onStatus(
HttpStatus::isError,
error -> {
final var errorResponse = error.bodyToMono(ErrorResponse.class).block();
final var errorMsg = errorResponse.getCause().getSummary();
if (error.statusCode().is4xxClientError()) {
throw new HttpClientErrorException(error.statusCode(), errorMsg);
}
if (error.statusCode().is5xxServerError()) {
throw new HttpServerErrorException(error.statusCode(), errorMsg);
}
return Mono.error(
new HttpServerErrorException(
HttpStatus.INTERNAL_SERVER_ERROR, "Something else."));
})
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {});
final var response = responseSpec.block();
return response.containsKey("password") && response.containsKey("provider");
}
PS. I've also tried using .exchangeToMono
instead of .retrieve
, so that I can inspect the response status code, and then use different types for the .bodyToMono
function, but as soon as I write two different types between a success and failure, I get an error stating that the type argument can't be inferred.
CodePudding user response:
Not able to try your code exactly, but try something like this:
public boolean updatePassword(String id, String data) {
final var responseSpec =
client
.post()
.uri(builder -> builder.path("/{id}/change_password").build(id))
.header(HttpHeaders.AUTHORIZATION, "Bearer " /* staticData.getAPIToken()*/)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(data))
.retrieve()
.onStatus(
HttpStatus::isError,
error -> error.bodyToMono(ErrorResponse.class)
.map(errorResponse -> {
final var errorMsg = errorResponse.getCause().getSummary();
if (error.statusCode().is4xxClientError()) {
throw new HttpClientErrorException(error.statusCode(), errorMsg);
}
if (error.statusCode().is5xxServerError()) {
throw new HttpServerErrorException(error.statusCode(), errorMsg);
}
return new HttpServerErrorException(
HttpStatus.INTERNAL_SERVER_ERROR, "Something else.");
}))
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {});
final var response = responseSpec.block();
return response.containsKey("password") && response.containsKey("provider");
}