2

How can I put query params into all requests in spring webflux avoiding code duplication in all places where I open http connection?

I try to make a configuration for a WebClient bean with a filter that should put a query param in URL every time when I do http call but I cannot find a way how to do it. Is it possible?

Configuration class:

@Configuration
public class IConnectionConfiguration {

    @Value("${api.key}")
    private String apiKey;

    @Value("${api.url}")
    private String url;

    @Bean
    public WebClient webClient(@Autowired ExchangeFilterFunction tokenFilter) {
        return WebClient.builder().baseUrl(url)
                .filters(exchangeFilterFunctions -> {
                    exchangeFilterFunctions.add(tokenFilter);
                })
                .build();
    }

    @Bean
    public ExchangeFilterFunction tokenFilter() {
        return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
            //todo put token into request as a query param
            return Mono.just(clientRequest);
        });
    }

}

Api calls layer:

@Service
public class Client {

    @Autowired
    private WebClient client;

    //todo test method
    public void testConnection() {
        String s = client.get()
                .uri(uriBuilder ->
                        uriBuilder.path("/something/{val}/something")
                              //todo line which I'm trying to avoid
                              //.queryParam("token", "token_value")
                                .build("123-test"))
                .retrieve()
                .bodyToMono(String.class)
                .block();

        System.out.println(s);
    }

}
Aleksei
  • 97
  • 1
  • 2
  • 9

4 Answers4

2

Another possibility that does not require overriding or using filter: Use UriComponentsBuilder with the query parameter that should be used for all requests. This instance of UriComponentsBuilder can than be used to instantiate a DefaultUriBuilderFactory for the WebClient.

Based on the code of the question:

public class IConnectionConfiguration {

    private static final String TOKEN_QUERY_PARAMETER = "TOKEN";

    @Value("${api.key}")
    private String apiKey;

    @Value("${api.url}")
    private String url;

    @Bean
    public WebClient webClient() {
        DefaultUriBuilderFactory defaultUriBuilderFactory = new DefaultUriBuilderFactory(uriComponentsBuilder());
        return WebClient.builder()
                .uriBuilderFactory(defaultUriBuilderFactory)
                .build();
    }

    private UriComponentsBuilder uriComponentsBuilder() {
        return UriComponentsBuilder.fromHttpUrl(url)
                .queryParam(TOKEN_QUERY_PARAMETER, apiKey);
    }

}

The token will now be attached to every request that is issued by the WebClient.

meberhard
  • 1,797
  • 1
  • 19
  • 24
  • Neat, but does not work well if supplying a uri using `webClient.get().uri(...)` for example. In that case the uriBuilderFactory supplied here is not used at all. – Thomas Timbul Jan 11 '23 at 10:21
1

I found another way, without custom UriBuilderFactory:

        this.client = WebClient.builder().baseUrl(url)
            .filter((request, next) -> {
                URI reqUri = request.url()
                URI newUri = new URI(reqUri.getScheme()
                                , reqUri.getAuthority(), reqUri.getPath()
                                , reqUri.getQuery() + "&key=${apiKey}"
                                , null)
                ClientRequest filtered = ClientRequest.from(request)
                    .url(newUri)
                    .build()
                next.exchange(filtered)
            }).build();
    }

This is groovy code but you get the gist, I am sure.

igavryus
  • 11
  • 1
1

Although this is an older question, this is the solution I went with myself in the end:

WebClient webClient = WebClient.builder()
                        .filter(ExchangeFilterFunction.ofRequestProcessor(
                            req -> 
                                Mono.just(ClientRequest.from(req).url(
                                    UriComponentsBuilder.fromUri(req.url())
                                        .queryParam("apiKey", "...")
                                        .build(true)
                                        .toUri()
                            ).build()))
                        )
                        .build();

This has the following advantages (depending on use case):

  • It is applied on all requests, whereas the answers using UriBuilderFactory does not work when supplying a new uri template such as when using webClient.get().uri(String uri, Function <UriBuilder, URI> uriFunction)
  • It uses UriComponentsBuilder for a cleaner way of modifying the URI.

Note that as per its documentation, UriComponentsBuilder created from an existing URI stores query params in raw form, which means that they are already fully encoded. Any new parameters added must also be added in fully encoded form, and in the end build(true) must be called with the true arguments to avoid double encoding.

Thomas Timbul
  • 1,634
  • 6
  • 14
0

Seems like I found a solution with a uriBuilderFactory override.

Firstly we need an additional class:

public class QueryParamsUriBuilderFactory extends DefaultUriBuilderFactory {

    private final MultiValueMap<String, String> queryParams;

    public QueryParamsUriBuilderFactory(String baseUrl, MultiValueMap<String, String> queryParams) {
        super(baseUrl);
        this.queryParams = queryParams;
    }

    @Override
    public UriBuilder builder() {
        return super.builder().queryParams(queryParams);
    }

}

The next step is to update a configuration:

@Configuration
public class IConnectionConfiguration {

    @Value("${api.key}")
    private String apiKey;

    @Value("${api.url}")
    private String baseUrl;

    @Bean
    public WebClient webClient(UriBuilderFactory uriBuilderFactoryWithAuthenticationToken) {
        return WebClient.builder()
            .uriBuilderFactory(uriBuilderFactoryWithAuthenticationToken)
                .build();
    }

    @Bean
    public UriBuilderFactory uriBuilderFactoryWithAuthenticationToken() {
        MultiValueMap<String, String> tokenQueryParam = new LinkedMultiValueMap<>();
        tokenQueryParam.add("token", apiKey);

        return new QueryParamsUriBuilderFactory(baseUrl, tokenQueryParam);
    }

}

Now it will automatically add token query param if we use uri(Function<UriBuilder, URI> var1) function to setup uri. Unfortunately it does not work for uri(URI var1) but we can override all methods related to uri in our new QueryParamsUriBuilderFactory to fix it for all possible methods for uri setup.

Aleksei
  • 97
  • 1
  • 2
  • 9