In our project, we decided to try using spring webclient for requests to an external API. But we have no need for a reactive approach, so we decided to use webclient in synchronous mode.
The external API we use requires authorization followed by substitution of a token in the Authorization header of the request. We wrote an interceptor that, whenever an external API is called, is triggered and complements the request with an API token. But we encountered a problem that the same webclient cannot be used in the interceptor in synchronous mode because it gives an error:
block()/blockfirst()/blocklast() are blocking, which is not supported in thread parallel-1
Found an issue on github: https://github.com/spring-projects/spring-framework/issues/22919 But we did not receive a clear understanding of how to solve this problem. Please tell me what options there are to solve the problem? Sample of my code (This is just a minimal example, the actual code is more complex):
@Bean
public WebClient webClient() {
.....
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.baseUrl(properties.getBaseUrl())
.filter(retryRequest())
.filter(WebclientUtils.exchangeFilterLogRequest())
.filter(authExchangeFilterFunction())
.filter(httpStatusErrorHandling())
.filter(WebclientUtils.exchangeFilterLogResponse())
.build();
}
private ExchangeFilterFunction authExchangeFilterFunction() {
return (request, next) -> {
WebClient webClient = WebClient.builder()
.baseUrl(properties.getBaseUrl())
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.filter(ExchangeFilterFunctions.basicAuthentication(
properties.getAuth().getBasic().getUsername(),
properties.getAuth().getBasic().getPassword()
)
)
.filter(WebclientUtils.exchangeFilterLogRequest())
.filter(WebclientUtils.exchangeFilterLogResponse())
.build();
//generate form data
//.......
String token = webClient.post()
.uri("/oauth/token")
.body(BodyInserters.fromFormData(formData))
.retrieve()
.bodyToMono(String.class)
.block();
next.exchange(ClientRequest.from(request)
.header(HttpHeaders.AUTHORIZATION, token)
.build()))
}
}
You don't need to use block in this case, this very much about chaining operations with different operators, here flatMap
.
Also, it's a good idea to reuse WebClient
instances and to avoid allocating those for each call.
public class AuthExchangeFilterFunction implements ExchangeFilterFunction {
private final WebClient webClient;
public AuthExchangeFilterFunction(WebClient.Builder builder) {
this.webClient = builder.build();
}
@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
//generate form data
MultiValueMap<String, String> formData = ...;
return webClient.post()
.uri("/oauth/token")
.body(BodyInserters.fromFormData(formData))
.retrieve()
.bodyToMono(String.class)
.flatMap(token -> {
return next.exchange(ClientRequest.from(request)
.header(HttpHeaders.AUTHORIZATION, token)
.build());
});
}
}