javaspringreactive-programmingspring-reactive

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


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.


Solution

  • 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()));
        }
    
    }