How to extract response header & status code from Spring 5 WebClient ClientResponse
Asked Answered
G

11

41

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.

Gert answered 7/5, 2018 at 23:46 Comment(0)
E
28

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

Exanthema answered 18/5, 2018 at 4:11 Comment(4)
But this just prints the HttpStatus code. What if I need to return its value? Would that be possible?Tattan
This should be marked as the accepted answer! It worked for me, thank you!Stonework
@Tattan 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.Passim
@thisishantzz could you please point me to an example?Tattan
R
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)));
Repetitious answered 19/1, 2021 at 12:45 Comment(0)
M
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.

Myall answered 9/12, 2018 at 16:12 Comment(1)
How can I read the status code from inside this subscribe() -> {}? Like, if I need to pass the status code to another methodTattan
J
9

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;
Jacie answered 28/6, 2021 at 18:42 Comment(2)
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()Langsyne
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;Jacie
C
6
 httpClient
            .get()
            .uri(url)
            .retrieve()
            .toBodilessEntity()
            .map(reponse -> Tuple2(reponse.statusCode, reponse.headers))
Caresa answered 5/5, 2021 at 13:46 Comment(0)
H
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());
Histogenesis answered 31/10, 2019 at 6:0 Comment(1)
How can I print the "status" value? Like just "200" not the whole Mono<> objectTattan
I
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.

Imogeneimojean answered 3/1, 2019 at 10:1 Comment(2)
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.Reprisal
@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.Imogeneimojean
T
0

You can use flatMap to extract the object from Mono

Tobacco answered 21/4, 2022 at 9:51 Comment(0)
U
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();
Uncircumcision answered 4/2, 2023 at 18:24 Comment(0)
D
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

Dissension answered 29/8, 2023 at 19:8 Comment(0)
A
0

This worked for me.

return webClient
    .post()
    .uri(URI.create("http://localhost:8104/build-upload/test-upload"))
    .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE)
    .body(BodyInserters.fromMultipartData(multiValueMap))
    .retrieve()
    .toEntity(String.class)
    .block();

Just convert the response into String. The response should be text for this to work though.

It returns ResponseEntity<String> which contains the body, headers and status code, in case of a success response.

Sample returned object: enter image description here

Amaras answered 7/3 at 7:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.