How to convert webclient response to ResponseEntity? - spring

I am able to convert WebClient response to Response Entity with exchange() method which is deprecated now.
Please suggest other way of achieving the same result. Below is my code.
public ResponseEntity<TestClass> getTestDetails() {
ClientResponse clientResponse = webClientBuilder.build()
.get()
.uri("http://localhost:9090/test")
.headers(httpHeaders -> {
httpHeaders.add(Constants.ACCEPT, Constants.APPLICATION_JSON);
})
.exchange()
.block();
return clientResponse.toEntity(TestClass.class).block();
}

I did it in the following way:
public ResponseEntity<TestClass> getTestDetails() {
return webClientBuilder.build()
.get()
.uri("http://localhost:9090/test")
.headers(httpHeaders -> {
httpHeaders.add(Constants.ACCEPT, Constants.APPLICATION_JSON);
})
.retrieve()
.toEntity(TestClass.class)
.block();
}

Related

Spring boot - WebFlux - WebTestClient - convert response to responseEntity

I have a Reactive controller which returns:
#ResponseBody
#GetMapping("/data")
public Mono<ResponseEntity<Data>> getData() {
//service call which returns Mono<Data> dataMono
Mono<ResponseEntity<Data>> responseEntityMono = dataMono
.map(data -> ResponseEntity.ok(data))
.doFinally(signalType -> {});
return responseEntityMono;
}
I am using WebTestClient to test this endpoint, but I want to extract the response entity for cucumber to validate further.
I tried this:
#Autowired private WebTestClient webTestClient;
public ResponseEntity get() {
EntityExchangeResult < ResponseEntity > response = webTestClient.get()
.uri(uriBuilder ->
uriBuilder
.path(VISUALIZATION_URL)
.build())
.header("Accepts", "application/json")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_VALUE)
.expectBody(ResponseEntity.class)
.returnResult();
return response.getResponseBody();
}
but I am getting an error. I can get the JSON by doing:
public String get() {
BodyContentSpec bodyContentSpec = webTestClient.get()
.uri(uriBuilder ->
uriBuilder
.path(URL)
.build())
.header("Accepts", "application/json")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_VALUE)
.expectBody();
return new String(bodyContentSpec.returnResult().getResponseBody());
}
But I am trying to see if I can get the whole ResponseEntity so that I can validate headers, caching headers, and body.
You will never be able to get a ResponseEntity in your test. WebTestClient.ResponseSpec returned from exchange() is the only way to check the answer from your Controller. ResponseEntity is just the object you return in your method but once Jackson serializes it, it is converted to a regular HTTP response (in your case with JSON in his body and regular HTTP headers).

Spring WebClient - chaining async calls

I'm building a plain old Java desktop application with Swing, but needs to make Rest calls to a backend. Since we're in a modern async world, and the tools exists, i want to make async calls to the backend, display a progressbar, and hide it when the calls are done.
Using Spring WebClient seems the way to go, and it works well, until i need to chain multiple async Rest calls. In that case, the second call never respond... I see the backend really receives the 2 calls, but the client side never resumes.
public void notWorking(CurrentTool toolDto, Consumer<ToolDto> successCallback) {
webClient.post()
.uri(uriBuilder -> {
return uriBuilder
.path("/tools/{id}")
.build(toolDto.getTool().getToolId());
})
.body(BodyInserters.fromObject(toolDto.getTool()))
.retrieve()
.bodyToMono(Long.class)
.subscribe(id -> {
webClient.get()
.uri(uriBuilder -> {
return uriBuilder
.path("/tools/{id}")
.build(id);
})
.retrieve()
.bodyToMono(ToolDto.class)
.subscribe((response) -> {
successCallback.accept(response);
});
});
}
However, if I make the same calls but in a blocking way, everything works fine. (Except it's sync, so my loading bar won't work...)
public void working(CurrentTool toolDto, Consumer<ToolDto> successCallback) {
Long id = webClient.post()
.uri(uriBuilder -> {
return uriBuilder
.path("/tools/{id}")
.build(toolDto.getTool().getToolId());
})
.body(BodyInserters.fromObject(toolDto.getTool()))
.retrieve()
.bodyToMono(Long.class)
.block();
webClient.get()
.uri(uriBuilder -> {
return uriBuilder
.path("/tools/{id}")
.build(id);
})
.retrieve()
.bodyToMono(ToolDto.class)
.subscribe((response) -> {
successCallback.accept(response);
});
}
You should try to avoid nesting subscribes. You can use flatmap instead.
public void shouldBeWorking(CurrentTool toolDto, Consumer<ToolDto> successCallback) {
webClient.post()
.uri(uriBuilder -> uriBuilder
.path("/tools/{id}")
.build(toolDto.getTool().getToolId()))
.body(BodyInserters.fromObject(toolDto.getTool()))
.retrieve()
.bodyToMono(Long.class)
.flatmap(id -> webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/tools/{id}")
.build(id))
.retrieve()
.bodyToMono(ToolDto.class))
.subscribe(successCallback::accept);
}

Pass-through API / Preserve backend headers in Spring Webflux

I am building an application to call a back-end which responds with a mime-type response.
#Override
public Mono<String> getDocument() {
return webClient.get()
.uri(path)
.retrieve()
.bodyToMono(String.class);
}
From this request, I need to preserve the response headers and pass it through as the response. This is mostly because the response headers contain the dynamic content type of the file. I need to forward these headers (all as received) to the API response. For example :
Content-Type : application/pdf
Content-Disposition: attachment; filename="test.pdf"
Following is my handler.
public Mono<ServerResponse> getDocument(ServerRequest request) {
return ServerResponse
.ok()
.contentType(MediaType.APPLICATION_PDF)
.header("Content-Disposition", "attachment; filename=\"test.pdf\"")
.body(BodyInserters.fromPublisher(documentService.getDocument(), String.class));
}
The file is coming through from the API as an attachment as expected, but I do not want to hard code the content-type header. How can I achieve this?
Update with the handler code :
public Mono<ServerResponse> getDocument(ServerRequest request) {
return ServerResponse
.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromPublisher(documentService.getDocument(), String.class));
}
I was able to resolve the problem by returning a ResponseEntity from the service instead of the body and using that to construct the ServerResponse in the handler.
Service :
public Mono<ResponseEntity<String>> getDocument() {
return webClient.get()
.uri(path)
.retrieve()
.toEntity(String.class);
}
Handler :
public Mono<ServerResponse> getDocument(ServerRequest request) {
return documentService
.getDocument()
.flatMap(r -> ServerResponse
.ok()
.headers(httpHeaders -> httpHeaders.addAll(r.getHeaders()))
.body(BodyInserters.fromValue(r.getBody()))
);
}

Spring Boot 2 - Transforming a Mono to a rx.Observable?

I'm trying to use the HystrixObservableCommand with the Spring WebFlux WebClient and I wonder if there is a "clean" to transform a Mono to an rx.Observable. My initial code looks like this:
public Observable<Comment> getComment() {
return webClient.get()
.uri(url)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Comment.class)
// stuff missing here :(.
}
Is there an easy to do this ?
Regards
The recommended approach is to use RxJavaReactiveStreams, more specifically:
public Observable<Comment> getComment() {
Mono<Comment> mono = webClient.get()
.uri(url)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Comment.class);
return RxReactiveStreams.toObservable(mono); // <-- convert any Publisher to RxJava 1
}
You can use
Observable.fromFuture(webClient.get()
.uri(url)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Comment.class).toFuture());

Get API response error message using Web Client Mono in Spring Boot

I am using webflux Mono (in Spring boot 5) to consume an external API. I am able to get data well when the API response status code is 200, but when the API returns an error I am not able to retrieve the error message from the API. Spring webclient error handler always display the message as
ClientResponse has erroneous status code: 500 Internal Server Error, but when I use PostMan the API returns this JSON response with status code 500.
{
"error": {
"statusCode": 500,
"name": "Error",
"message":"Failed to add object with ID:900 as the object exists",
"stack":"some long message"
}
}
My request using WebClient is as follows
webClient.getWebClient()
.post()
.uri("/api/Card")
.body(BodyInserters.fromObject(cardObject))
.retrieve()
.bodyToMono(String.class)
.doOnSuccess( args -> {
System.out.println(args.toString());
})
.doOnError( e ->{
e.printStackTrace();
System.out.println("Some Error Happend :"+e);
});
My question is, how can I get access to the JSON response when the API returns an Error with status code of 500?
If you want to retrieve the error details:
WebClient webClient = WebClient.builder()
.filter(ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
if (clientResponse.statusCode().isError()) {
return clientResponse.bodyToMono(ErrorDetails.class)
.flatMap(errorDetails -> Mono.error(new CustomClientException(clientResponse.statusCode(), errorDetails)));
}
return Mono.just(clientResponse);
}))
.build();
with
class CustomClientException extends WebClientException {
private final HttpStatus status;
private final ErrorDetails details;
CustomClientException(HttpStatus status, ErrorDetails details) {
super(status.getReasonPhrase());
this.status = status;
this.details = details;
}
public HttpStatus getStatus() {
return status;
}
public ErrorDetails getDetails() {
return details;
}
}
and with the ErrorDetails class mapping the error body
Per-request variant:
webClient.get()
.exchange()
.map(clientResponse -> {
if (clientResponse.statusCode().isError()) {
return clientResponse.bodyToMono(ErrorDetails.class)
.flatMap(errorDetails -> Mono.error(new CustomClientException(clientResponse.statusCode(), errorDetails)));
}
return clientResponse;
})
Just as #Frischling suggested, I changed my request to look as follows
return webClient.getWebClient()
.post()
.uri("/api/Card")
.body(BodyInserters.fromObject(cardObject))
.exchange()
.flatMap(clientResponse -> {
if (clientResponse.statusCode().is5xxServerError()) {
clientResponse.body((clientHttpResponse, context) -> {
return clientHttpResponse.getBody();
});
return clientResponse.bodyToMono(String.class);
}
else
return clientResponse.bodyToMono(String.class);
});
I also noted that there's a couple of status codes from 1xx to 5xx, which is going to make my error handling easier for different cases
Look at .onErrorMap(), that gives you the exception to look at. Since you might also need the body() of the exchange() to look at, don't use retrieve, but
.exchange().flatMap((ClientResponse) response -> ....);

Resources