How to use Spring WebClient to make a subsequent call with different header setting?

3.6k views Asked by At

I need to call an third party API which requires an authentication call beforehand to get an authentication token. The Authentication API is in json but the subsequent call is in XML.

I have separately :

webclient.post().uri("/auth").header(ACCEPT,JSON).retrieve()
      .bodyToMono(AuthToken.class);
webclient.post().uri("/api").header(ACCEPT,XML).header("AUTH",authToken).retrive().bodyToFlux();

How should I implement the method to be able to access the second API? I tried to assign a variable inside the method with token = firstCall.block() but I've got block() is not supported error.

1

There are 1 answers

5
JEY On BEST ANSWER

You just have to transform the original flux like:

webclient.post().uri("/auth")
    .header(ACCEPT,JSON)
    .retrieve()
    .bodyToMono(AuthToken.class)
    .flatMapMany(authToken -> webclient.post().uri("/api")
    .header(ACCEPT,XML)
    .header("AUTH",authToken).retrive().bodyToFlux();

A better solution would be to use a ExchangeFilterFunction that will fetch the token for you https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/web-reactive.html#webflux-client-filter

Something like that (not tested might have bug):

WebClient authWebClient = WebClient.builder().build();
WebClient webClient = WebClient.builder()
        .filter(((request, next) -> authWebClient.post()
                .uri("/auth")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(AuthToken.class)
                .flatMap(authToken -> next.exchange(ClientRequest.from(request)
                        .headers(headers -> headers.add("AUTH", authToken))
                        .build()))
        ))
        .build();

webClient.post().uri("/api")
        .accept(MediaType.APPLICATION_XML)
        .retrieve()
        .bodyToFlux(MyData.class);

This is basic but you could add cache to avoid requesting or fetch again if token is expired... Be aware that builtin ExchangeFilterFunction exists for basic oauth2...

Wrap everything with a spring configuration:

@Configuration
public class WebClientConfiguration {
    @Bean
    public WebClient authWebClient(final WebClient.Builder webClientBuilder) {
        return webClientBuilder.build();
    }

    @Bean
    public ExchangeFilterFunction authFilter(final WebClient authWebClient) {
        return (request, next) -> authWebClient.post()
                .uri("/auth")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(AuthToken.class)
                .flatMap(authToken -> next.exchange(ClientRequest.from(request)
                        .headers(headers -> headers.add("AUTH", authToken.toString()))
                        .build()));
    }

    @Bean
    public WebClient webClient(final WebClient.Builder webClientBuilder, final ExchangeFilterFunction authFilter) {
        return webClientBuilder
                .filter(authFilter)
                .build();
    }
}

Or if you want to avoid lambda:

@Configuration
public class WebClientConfiguration {
@Bean
    public WebClient authWebClient(final WebClient.Builder webClientBuilder) {
        return webClientBuilder.build();
    }

    @Bean
    public WebClient webClient(final WebClient.Builder webClientBuilder, final AuthFilter authFilter) {
        return webClientBuilder
                .filter(authFilter)
                .build();
    }
    
    @Bean
    public AuthFilter authFilter(WebClient authWebClient) {
        return new AuthFilter(authWebClient);
    }
}

public class AuthFilter implements ExchangeFilterFunction {

    private final WebClient authWebClient;

    public AuthFilter(WebClient authWebClient) {
        this.authWebClient = authWebClient;
    }

    @Override
    public Mono<ClientResponse> filter(final ClientRequest request, final ExchangeFunction next) {
        return authWebClient.post()
                .uri("/auth")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(AuthToken.class)
                .flatMap(authToken -> next.exchange(ClientRequest.from(request)
                        .headers(headers -> headers.add("AUTH", authToken.toString()))
                        .build()));
    }

}