How to create request with parameters with webflux Webclient? - java

At backend side I have REST controller with POST method:
#RequestMapping(value = "/save", method = RequestMethod.POST)
public Integer save(#RequestParam String name) {
//do save
return 0;
}
How can I create request using WebClient with request parameter?
WebClient.create(url).post()
.uri("/save")
//?
.exchange()
.block()
.bodyToMono(Integer.class)
.block();

There are many encoding challenges when it comes to creating URIs. For more flexibility while still being right on the encoding part, WebClient provides a builder-based variant for the URI:
WebClient.create().get()
.uri(builder -> builder.scheme("http")
.host("example.org").path("save")
.queryParam("name", "spring-framework")
.build())
.retrieve()
.bodyToMono(String.class);

From: https://www.callicoder.com/spring-5-reactive-webclient-webtestclient-examples/
In configuration class you can define the host:
#Bean(name = "providerWebClient")
WebClient providerWebClient(#Value("${external-rest.provider.base-url}") String providerBaseUrl) {
return WebClient.builder().baseUrl(providerBaseUrl)
.clientConnector(clientConnector()).build();
}
Then you can use the WebClient instace:
#Qualifier("providerWebClient")
private final WebClient webClient;
webClient.get()
.uri(uriBuilder -> uriBuilder.path("/provider/repos")
.queryParam("sort", "updated")
.queryParam("direction", "desc")
.build())
.header("Authorization", "Basic " + Base64Utils
.encodeToString((username + ":" + token).getBytes(UTF_8)))
.retrieve()
.bodyToFlux(GithubRepo.class);

Assuming that you already created your WebClient instance and configured it with baseUrl.
URI Path Component
this.webClient.get()
.uri("/products")
.retrieve();
result: /products
this.webClient.get()
.uri(uriBuilder - > uriBuilder
.path("/products/{id}")
.build(2))
.retrieve();
result: /products/2
this.webClient.get()
.uri(uriBuilder - > uriBuilder
.path("/products/{id}/attributes/{attributeId}")
.build(2, 13))
.retrieve();
result: /products/2/attributes/13
URI Query Parameters
this.webClient.get()
.uri(uriBuilder - > uriBuilder
.path("/peoples/")
.queryParam("name", "Charlei")
.queryParam("job", "Plumber")
.build())
.retrieve();
result:
/peoples/?name=Charlei/job=Plumber

Related

Spring Boot - Do a new WebClient call with result of another call

I'm trying to call an api with 2 call using webclient.
The first call return a token.
The second call use the token.
public Mono<GetToken> getToken{
return webClient
.get()
.uri(uriBuilder ->
uriBuilder
.path("api/getToken")
.build()
)
.retrieve()
.bodyToMono(Object.class);
}
public Mono<GetToken> getData{
return webClient
.get()
.uri(uriBuilder ->
uriBuilder
.path("api/getData/"+tokenID)
.build()
)
.retrieve()
.bodyToMono(Object2.class);
}
How can I use the data from the first request in the second without using the block() function
Use Mono#flatMap.
Mono<Object2> res = getToken().flatMap(token -> getData(token));

Apply retry to each and every endpoint in the spring webflux application

I have created below webclient and using it inside of my service to make HTTP third party calls.
#Configuration
public class WebclientConfig {
#Bean
public WebClient webClient() {
// custom client connector with connection pool disabled is being used as by default the connection pooling is done and connection reset happens after some idle time.
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.newConnection()))
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.build();
}
}
and in my service, I am calling the third party service like below.
private Flux<BusinessObject> getBusinessObjects(String serviceURL) {
return this.webClient.get()
.uri(serviceURL)
.retrieve()
.bodyToFlux(BusinessObject.class) //code below this, do I have to copy for each webclient request to configure the retry, even if the values are same
.retryWhen(Retry.backoff(3, Duration.of(2, ChronoUnit.SECONDS))
.doBeforeRetry((value) -> log.info("Retrying request " + value))
.filter(error -> error instanceof WebClientRequestException)
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) ->
new RuntimeException(retrySignal.failure().getMessage())));
}
My question is as in comment in above code.
I have multiple webclient calls, but I want to configure the retry backoff configuration at a single place. How can I do that? so that my code should look like below
private Flux<BusinessObject> getBusinessObjects(String serviceURL) {
return this.webClient.get()
.uri(serviceURL)
.retrieve()
.bodyToFlux(BusinessObject.class)
.somCommonRetryCodeWrappingTheRetryLogic();
}
You can use transform operator for this purpose:
private Flux<BusinessObject> getBusinessObjects(String serviceURL) {
return this.webClient.get()
.uri(serviceURL)
.retrieve()
.bodyToFlux(BusinessObject.class)
.transform(originalFlux -> wrapWithRetry(originalFlux));
}
private <T> Flux<T> wrapWithRetry(Flux<T> originalFlux) {
return originalFlux
.retryWhen(Retry.backoff(3, Duration.of(2, ChronoUnit.SECONDS))
.doBeforeRetry((value) -> log.info("Retrying request " + value))
.filter(error -> error instanceof WebClientRequestException)
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) ->
new RuntimeException(retrySignal.failure().getMessage())));
}
Only drawback is that if you have Mono use cases as well then you need to implement it twice.
If that is still too much copy-paste, you can consider defining an ExchangeFilterFunction to apply retry for every WebClient call automatically. Something like this:
WebClient.builder()
// ...
.filter((request, next) -> next.exchange(request).retry()) // add full retry config here
.build();

How to convert Spring WebClient Response to ResponseEntity?

I'm able to return ResponseEntity using toEntity() method like below:
#GetMapping("/uri")
public Mono<ResponseEntity<Data[]>> methodName() {
return webClient
.get()
.uri("http://localhost:8088/externalService")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(Data[].class);
}
But I want to process response headers before returning.
The above code converts WebClient response to ResponseEntity and returns immediately but I want to store it in a ResponseEntity variable, process it, and then return the ResponseEntity back.
I referred this -> Spring WebClient Documentation
WHen I tried to store it in a varibale, I get this error -> "block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3"
You can simply use the Reactor's map operator to modify the headers:
return webClient
.get()
.uri("http://localhost:8088/externalService")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(Data[].class)
.map(responseEntity -> responseEntity.getHeaders().add("header", "header-value");
Alternatively, you can use .handle operator in order to provide response processing:
.handle((responseEntity, sink) -> {
if(!isValid(responseEntity)){
sink.error(new InvalidResponseException());
} else if (isOk(responseEntity))
sink.next(responseEntity);
}
else {
//just ignore element
}
})
Spring Starter Web dependency was missing in my pom.xml. Found it and added it back.
Now able to get WebClient response in ResponseEntity format.

Add optional header to WebClient get request

Is there a way to add an optional header to WebClient get() request?
if (config.getRefresh()) {
webClient.header("Refresh-Cache", "true");
}
It seems the whole request is chained on webClient
return webClient
.get()
.uri(uri)
.header("Authorization", BEARER_TOKEN)
.retrieve()
.bodyToMono(String.class)
.block();
I try to switch to RequestHeadersSpec but got this generic type warning
WebClient.RequestHeadersSpec is a raw type. References to generic type
WebClient.RequestHeadersSpec<S> should be parameterized Java(16777788)
I know with post(), we can do this
requestBodySpec = webClientBuilder.build().post().uri(uri);
if (config.getRefresh()) {
requestBodySpec.header("Refresh-Cache", "true");
}
return requestBodySpec
.header("Authorization", BEARER_TOKEN)
.body(Mono.just(request), MyRequest.class)
.retrieve()
.bodyToMono(String.class)
.block();
To resolve the generic type warning, you can set the generic type as a wildcard (?) ie.
WebClient.RequestHeadersSpec<?> requestBodySpec = webClient.get().uri("https://google.com");
An alternate solution for adding a header based off of a flag in config is to use an ExchangeFilterFunction.
public class RefreshExchangeFilterFunction implements ExchangeFilterFunction {
private Config config;
#Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
if(config.isRefresh()) {
return next.exchange(ClientRequest.from(request)
.header("Refresh-Cache", "true")
.build());
}
return next.exchange(request);
}
}
This can then be applied to any/all web clients that need this behaviour
WebClient.builder()
.filter(refreshExchangeFilterFunction)
.build();

Spring WebClient filter Null from Json Request

I am using Spring WebClient Api to make a rest api call.
I have an entity object--JobInfo which acts as my POST Request pay-load.
The below Rest API fails because certain attributes of JobInfo are null.
private BatchInfo createBulkUploadJob(JobInfo jobInfo) {
return webClient.post()
.uri(URL.concat("/services/data/v47.0/jobs/ingest/"))
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "OAuth " + TOKEN)
.bodyValue(jobInfo)
.retrieve()
.bodyToMono(BatchInfo.class)
.block();
}
I need to filter out the Null attributes from sending it across the rest call.
I understand this can be easily achieved by including below annotation on JobInfo class.
#JsonInclude(JsonInclude.Include.NON_NULL)
But JobInfo is coming from a third party Jar, so I cannot touch this class.
Is there a way I can configure this in webClient to filter this out or any other option ?
Try with this:
private BatchInfo createBulkUploadJob(JobInfo jobInfo) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
ExchangeStrategies strategies = ExchangeStrategies
.builder()
.codecs(clientDefaultCodecsConfigurer -> {
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper, MediaType.APPLICATION_JSON));
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper, MediaType.APPLICATION_JSON));
}).build();
WebClient webClient = WebClient.builder().exchangeStrategies(strategies).build();
return webClient.post()
.uri(URL.concat("/services/data/v47.0/jobs/ingest/"))
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "OAuth " + TOKEN)
.bodyValue(jobInfo)
.retrieve()
.bodyToMono(BatchInfo.class)
.block();
}

Categories

Resources