37

I am new to Spring Reactive framework & trying to convert Springboot 1.5.x code into Springboot 2.0. I need to return response header after some filtering, body & status code from Spring 5 WebClient ClientResponse. I do not want to use block() method as it will convert it into sync call. I am able to get responsebody pretty easily using bodyToMono. Also, I am getting status code, headers & body if I am just returning ClientResponse but I need to process response based on statusCode & header parameters. I tried subscribe, flatMap etc. but nothing works.

E.g. - Below code will return response Body

Mono<String> responseBody =  response.flatMap(resp -> resp.bodyToMono(String.class));

But similar paradigm is not working to get statusCode & Response headers. Can someone help me in extracting statusCode & header parameters using Spring 5 reactive framework.

daemonThread
  • 1,014
  • 2
  • 12
  • 23
Renus11
  • 371
  • 1
  • 3
  • 5

11 Answers11

26

You can use the exchange function of webclient e.g.

Mono<String> reponse = webclient.get()
.uri("https://stackoverflow.com")
.exchange()
.doOnSuccess(clientResponse -> System.out.println("clientResponse.headers() = " + clientResponse.headers()))
.doOnSuccess(clientResponse -> System.out.println("clientResponse.statusCode() = " + clientResponse.statusCode()))
.flatMap(clientResponse -> clientResponse.bodyToMono(String.class));

then you can convert bodyToMono etc

dazito
  • 7,740
  • 15
  • 75
  • 117
Kevin Hussey
  • 1,582
  • 1
  • 10
  • 17
  • 13
    But this just prints the HttpStatus code. What if I need to return its value? Would that be possible? – C96 Jul 16 '20 at 22:39
  • 1
    This should be marked as the accepted answer! It worked for me, thank you! – actunderdc Nov 13 '20 at 11:04
  • 1
    @C96 these are async calls so you cannot return values in the traditional sense. You should be able to return `Mono` and `Flux` only. Do the processing inside the `doOnSuccess` method. – thisisshantzz Dec 14 '20 at 23:20
  • @thisishantzz could you please point me to an example? – C96 Dec 15 '20 at 13:40
20

After Spring Boot 2.4.x / Spring 5.3, WebClient exchange method is deprecated in favor of retrieve, so you have to get the headers and response status using ResponseEntity like the following example:

webClient
        .method(HttpMethod.POST)
        .uri(uriBuilder -> uriBuilder.path(loginUrl).build())
        .bodyValue(new LoginBO(user, passwd))
        .retrieve()
        .toEntity(LoginResponse.class)
        .filter(
            entity ->
                entity.getStatusCode().is2xxSuccessful()
                    && entity.getBody() != null
                    && entity.getBody().isLogin())
        .flatMap(entity -> Mono.justOrEmpty(entity.getHeaders().getFirst(tokenHeader)));
jkamcc
  • 361
  • 2
  • 3
17

I needed to check the response details(headers, status, etc) and body as well.

The only way I was able to do it was by using .exchange() with two subscribe() as the following example:

    Mono<ClientResponse> clientResponse = WebClient.builder().build()
            .get().uri("https://stackoverflow.com")
            .exchange();

    clientResponse.subscribe((response) -> {

        // here you can access headers and status code
        Headers headers = response.headers();
        HttpStatus stausCode = response.statusCode();

        Mono<String> bodyToMono = response.bodyToMono(String.class);
        // the second subscribe to access the body
        bodyToMono.subscribe((body) -> {

            // here you can access the body
            System.out.println("body:" + body);

            // and you can also access headers and status code if you need
            System.out.println("headers:" + headers.asHttpHeaders());
            System.out.println("stausCode:" + stausCode);

        }, (ex) -> {
            // handle error
        });
    }, (ex) -> {
        // handle network error
    });

I hope it helps. If someone knows a better way to do it, please let us know.

Rafael Amaral
  • 189
  • 1
  • 4
  • How can I read the status code from inside this subscribe() -> {}? Like, if I need to pass the status code to another method – C96 Jul 16 '20 at 20:44
6

As discussed above, the exchange has been deprecated so we are using retrieve(). This is how I'm returning the code after making a request.

public HttpStatus getResult() {
    WebClient.ResponseSpec response = client
            .get()
            .uri("/hello")
            .accept(MediaType.APPLICATION_JSON)
            .retrieve();

    return Optional.of(response.toBodilessEntity().block().getStatusCode()).get();
}

Another option as per the comment, I've tried recently. This is usually recommended for Async calls but we can use it for both.

MyClass responseMono = this.webClient
                .get()
                .uri("myapipath"}")
                .retrieve()
                .bodyToMono(MyClass.class)
                .block();
        return responseMono;
Athar
  • 579
  • 5
  • 12
  • This will blow up if status code is not successful. Let's say I want to check status code and in case 404 do some action. So `block()` will throw `Suppressed: java.lang.Exception: #block terminated with an error` and method won't return result. Solution by `daemonThread` works though. I'm wondering how to achive this with `retrieve()` – noriks Jan 25 '22 at 18:16
  • 1
    As long as the API returns a valid status code then it should be fine. Even tho it failed for whatever reason the status code should be available to get it. one of the options is to use the following code snippet MyClass responseMono = this.webClient .get() .uri("myapipath"}") .retrieve() .bodyToMono(MyClass.class) .block(); return responseMono; – Athar Feb 04 '22 at 10:51
5
 httpClient
            .get()
            .uri(url)
            .retrieve()
            .toBodilessEntity()
            .map(reponse -> Tuple2(reponse.statusCode, reponse.headers))
Juan Rada
  • 3,513
  • 1
  • 26
  • 26
4

For status code you can try this:

Mono<HttpStatus> status = webClient.get()
                .uri("/example")
                .exchange()
                .map(response -> response.statusCode());

For headers:

Mono<HttpHeaders> result = webClient.get()
                .uri("/example")
                .exchange()
                .map(response -> response.headers().asHttpHeaders());
daemonThread
  • 1,014
  • 2
  • 12
  • 23
  • 3
    How can I print the "status" value? Like just "200" not the whole Mono<> object – C96 Jul 16 '20 at 20:52
3

You can configure spring boot >= 2.1.0 to log request and response if you are using the WebClient:

spring.http.log-request-details: true
logging.level.org.springframework.web.reactive.function.client.ExchangeFunctions: TRACE

As desribed in the sprint boot docs, if you want headers to be logged, too, you have to add

Consumer<ClientCodecConfigurer> consumer = configurer ->
    configurer.defaultCodecs().enableLoggingRequestDetails(true);

WebClient webClient = WebClient.builder()
    .exchangeStrategies(ExchangeStrategies.builder().codecs(consumer).build())
    .build();

But be aware that this can log sensitve information.

Tobske
  • 518
  • 3
  • 12
  • 5
    The asker says **...I need to process response based on statusCode & header parameters.**. But the code you provided is for Logging configuration, Meaning that it is not helpful in the context of the question. Therefore -1. – Adindu Stevens Mar 17 '19 at 12:14
  • @AdinduStevens, I'm sorry that I was not getting that from the question. I will leave the answer here for the case someone lands here and only wants to log the status coder and header paramters. – Tobske Mar 18 '19 at 11:55
0

You can use flatMap to extract the object from Mono

0

Send your request as MAP Get your response as MAP check all status code in a filter function

private static Mono<ClientResponse>  filterFunc(ClientResponse response) {
        HttpStatus status = response.statusCode();
        if (HttpStatus.BAD_REQUEST.equals(status)) {
            return response.bodyToMono(String.class)
                    .flatMap(body -> Mono.error(new MyCustomHttpErrorException(  HttpStatus.BAD_REQUEST ,  body) ));
        } else if (!HttpStatus.OK.equals(status)) {
            return response.bodyToMono(String.class)
                    .flatMap(body -> Mono.error(  new MyCustomHttpErrorException(  status ,  body) )); 
        }

       return Mono.just(response);
    }

  this.webClient = builder.baseUrl(serverUrl)
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
                .filter(ExchangeFilterFunction.ofResponseProcessor(MyClass::exchangeFilterResponseProcessor))  
                .build();


   Mono<Map<String, String>> mnoObjStream;
        mnoObjStream = webClient.post().uri("/abc")
                .body(BodyInserters
                        .fromFormData("key1","val1").with("key2","val2").with("key3","val3"))
                        .retrieve()  // Note use retrieve  because  exchange() has been deprecated.
                .bodyToMono(new ParameterizedTypeReference<Map<String, String>>() {});  
        resp = mnoObjStream.block();
marcg
  • 552
  • 1
  • 8
  • 20
0

The exchangeToMono excepts a Function as the responseHandler. If we create a class that implements the Function, we can store header, status inside the class variable, and have the method return the body.

MyClientResponsehandler handler = new ()..
String bodyAsString = webClient.get().exchangeToMono(handler).block();

class MyClientResponseHandler implements Function<ClientResponse, Mono<String>>{
  private List<String> contentDispositions;
  @Override
  public Mono<String> apply(ClientResponse cr){
    contentsDisposition = cr.headers.header("CONTENTDISPO ...");
    return Mono.just(cr).flatMap(cr1->cr1.bodyToMono(String.class));
  }
} 

This way the body can be retrieved from the API call after the block(). The header, status can be stored in handler for post processing

user1132593
  • 448
  • 1
  • 5
  • 8
-1

To Get Headers

HttpHeaders result = webClient.get()
            .uri("/example")
            .exchange()
            .map(response -> response.headers().asHttpHeaders()).block();