Currently my post and get requests are handled through WebClients which has a common connection and read timeout in Spring Boot. I have 5 different classes each requiring its own set of connection and read timeout. I don't want to create 5 different WebClients, rather use the same Webclient but while sending a post or a get request from a particular class, specify the required connection and read timeout. Is there any way to implement this?
My current WebClient:
#Bean
public WebClient getWebClient(WebClient.Builder builder){
HttpClient httpClient = HttpClient.newConnection()
.tcpConfiguration(tcpClient -> {
tcpClient = tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeout*1000);
tcpClient = tcpClient.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler(readTimeout, TimeUnit.SECONDS)));
return tcpClient;
}).wiretap(true);
ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
return builder.clientConnector(connector).build();
}
A post request I'm using:
public WebClientResponse httpPost(String endpoint, String requestData, Map<String, Object> requestHeader) {
ClientResponse res = webClient.post().uri(endpoint)
.body(BodyInserters.fromObject(requestData))
.headers(x -> {
if(requestHeader != null && !requestHeader.isEmpty()) {
for (String s : requestHeader.keySet()) {
x.set(s, String.valueOf(requestHeader.get(s)));
}
}
})
.exchange()
.doOnSuccess(x -> log.info("response code = " + x.statusCode()))
.block();
return convertWebClientResponse(res);
}
You can configure request-level timeout in WebClient.
webClient.get()
.uri("https://baeldung.com/path")
.httpRequest(httpRequest -> {
HttpClientRequest reactorRequest = httpRequest.getNativeRequest();
reactorRequest.responseTimeout(Duration.ofSeconds(2));
});
Now what you can do is that based on the request you can add those values either from the properties file or you can hard code them.
Reference:- https://www.baeldung.com/spring-webflux-timeout
You can try timeout with webClient like below,
webClient.post()
.uri(..)
.body(..)
.retrieve()
.
.
.
.timeout(Duration.ofMillis(30);
30 is just example.
#Bean
public WebClient getWebClient() {
HttpClient httpClient = HttpClient.create()
.tcpConfiguration(client ->
client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 4000)
.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler(4))
.addHandlerLast(new WriteTimeoutHandler(4))));
ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient.wiretap(true));
return WebClient.builder()
.clientConnector(connector)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) // if you need a default header
.build();
}
or you can use what #Amol has suggested
Related
I am trying to update the token when I get a response with 401 status code.
In order to do that, I used web client. I know that this mainly used to do reactive development but since resttemplate will soon be deprecated I went for this option.
The issue I am facing is that when it does call the api endpoint to get the new token, it throws a 'java.lang.IllegalStateException: block()/blockFirst()/blockLast() '. And make sense as it stated in the exception message It is not supported in thread reactor-http-nio-3.
I saw that there is a map and flatmap option, but I couldn't figure out how to use it inside the doBeforeRetry() to make it process in a different stream.
I need to have that new token before retrying.
So the question is : How can I get the token via another call and then still do the retry ?
I was able to make it work by using a try catch but I would like to find the solution how to use it inside that retry method.
I also try to block the token request by replacing the token response by a Mono and block it by using myMono.toFuture().get() as stated here block()/blockFirst()/blockLast() are blocking error when calling bodyToMono AFTER exchange()
Here is the code :
Method responsible for the call :
public String getValueFromApi(HashMap<String, Object> filter) {
String response = "";
response = webclient
.post()
.uri(endpoint)
.header("token", token.getToken())
.bodyValue(filter)
.retrieve()
.bodyToMono(String.class)
.retryWhen(Retry.max(3).doBeforeRetry(
retrySignal -> tokenService.getTokenFromApi(env)
).filter(InvalidTokenException.class::isInstance))
.block();
return response;
}
Method that retrieve the token :
public void getTokenFromApi(Environment env) {
HashMap<String, String> requestBody = new HashMap<>();
requestBody.put("name", "name");
requestBody.put("password", "password");
String response = WebClient
.builder()
.baseUrl(BASE_PATH)
.defaultHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.build()
.post()
.uri(tokenUri)
.body(BodyInserters.fromValue(requestBody))
.retrieve()
.bodyToMono(String.class)
.block();
getTokenFromResponse(response);
}
private void getTokenFromResponse(String reponse) {
JsonObject tokenObject = new Gson().fromJson(reponse, JsonObject.class);
setToken(tokenObject.get("token").getAsString());
}
WebClient Builder :
#Bean
public WebClient webClientForApi(WebClient.Builder webClientBuilder) {
return webClientBuilder
.clientConnector(new ReactorClientHttpConnector(httpClient))
.filter(errorHandler())
.filter(logRequest())
.clone()
.baseUrl(BASE_PATH)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, "application/json")
.build();
}
public ExchangeFilterFunction errorHandler() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
if (clientResponse.statusCode().equals(HttpStatus.UNAUTHORIZED)) {
return Mono.error(InvalidTokenException::new);
} else if (clientResponse.statusCode() == HttpStatus.INTERNAL_SERVER_ERROR) {
return Mono.error(ApiInternalServerException::new);
} else {
return Mono.just(clientResponse);
}
});
}
private ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
logger.info("Request: {} {}", clientRequest.method(), clientRequest.url());
clientRequest.headers().forEach((name, values) -> values.forEach(value -> logger.info("{}={}", name, value)));
return Mono.just(clientRequest);
});
}
I have to consume this endpoint: localhost:5100/message/v1?topic=test.state.v2
This endpoint requires a body like this JSON:
{"topic":"test.state.v2"}
When I test this endpoint with this cURL returns correctly the response data.
curl -H "Content-Type: application/json" --request GET --data '{"topic": "test.state.v1"}' http://localhost:5100/test/message/v1
I'm using this client to consume:
public class MyClient {
//imports.. here
private final String dataStoreHostAndPort = "http://localhost:5100";
private finalJSONObject MY_BODY = new JSONObject().put("topic", "test.state.v1");
public MyClient(WebClient.Builder builder) {
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 200)
.followRedirect(true)
.responseTimeout(Duration.ofMillis(200));
webClient = WebClient.builder()
.baseUrl(dataStoreHostAndPort)
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
public Mono<String> getMessage() {
return this.webClient
.method(HttpMethod.GET)
.uri(builder -> builder
.path("/message/v1")
.queryParam("topic", "log.state.v1").build())
.body((BodyInserter<?, ? super ClientHttpRequest>) MY_BODY)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(String.class);
}
}
I used practically all forms of body(BodyInserter) and the uri() but nothing worked.
How can I send MY_BODY to Spring Reactive WebClient request in the same way than cURL does?
You can use .body(BodyInserters.fromValue(YOUR_VALUE_HERE))
I have a controller that uses RestTemplate to get data from several rest endpoints. Since RestTemplate is blocking, my web page is taking long time to load. In order to increase the performance, I am planning to replace all my usages of RestTemplate with WebClient. One of the methods I currently have that uses RestTemplate is as below.
public List<MyObject> getMyObject(String input){
URI uri = UriComponentsBuilder.fromUriString("/someurl")
.path("123456")
.build()
.toUri();
RequestEntity<?> request = RequestEntity.get(uri).build();
ParameterizedTypeReference<List<MyObject>> responseType = new ParameterizedTypeReference<List<MyObject>>() {};
ResponseEntity<List<MyObject>> responseEntity = restTemplate.exchange(request, responseType);
MyObject obj = responseEntity.getBody();
}
Now I want to replace my above method to use WebClient but I am new to WebClient and not sure where to start. Any direction and help is appreciated.
To help you I am giving you example how we can replace restTemple with webClient. I hope you have already setup your pom.xml
Created a Configuration class.
#Slf4j
#Configuration
public class ApplicationConfig {
/**
* Web client web client.
*
* #return the web client
*/
#Bean
WebClient webClient() {
return WebClient.builder()
.filter(this.logRequest())
.filter(this.logResponse())
.build();
}
private ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
log.info("WebClient request: {} {} {}", clientRequest.method(), clientRequest.url(), clientRequest.body());
clientRequest.headers().forEach((name, values) -> values.forEach(value -> log.info("{}={}", name, value)));
return Mono.just(clientRequest);
});
}
private ExchangeFilterFunction logResponse() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
log.info("WebClient response status: {}", clientResponse.statusCode());
return Mono.just(clientResponse);
});
}
}
Plus a service class calling WebClient
#Component
#RequiredArgsConstructor
public class MyObjectService {
private final WebClient webClient;
public Mono<List<Object>> getMyObject(String input) {
URI uri = UriComponentsBuilder.fromUriString("/someurl")
.path("123456")
.build()
.toUri();
ParameterizedTypeReference<List<MyObject>> responseType = new ParameterizedTypeReference<List<MyObject>>() {
};
return this.webClient
.get()
.uri(uri)
.exchange()
.flatMap(response -> response.bodyToMono(responseType));
}
}
This will give you a non blocking Mono of List<MyObject>, you can also extract body to flux by using response.bodyToFlux(responseType)
I hope this will give you a base to explore more.
I want to send link Request Parameters in Spring WebClient request link. For example:
https://www.test.com/notification?con=41280440000097&sec=1232
I tried this code:
WebClient client;
Map<String, String> map = new HashMap<>();
public Mono<Response> execute(Transaction transaction) {
map.put("some_key", "some_value");
Mono<PaymentTransaction> transactionMono = Mono.just(transaction);
return client.post().uri("/notification", token)
.accept(MediaType.APPLICATION_XML)
.contentType(MediaType.APPLICATION_XML)
.body(transactionMono, Transaction.class)
.attributes(Consumer<map>)
.retrieve()
.bodyToMono(Response.class);
}
But when I try to set the map I get Syntax error on token ">", Expression expected after this
What is the proper way to implement this without hardcoding the values into the address?
Does this work?
public Mono<PaymentResponse> execute(PaymentTransaction transaction, WebClient client) {
long conn = 1L;
int sec = 1232;
Mono<PaymentTransaction> transactionMono = Mono.just(transaction);
return client.post()
.uri(uriBuilder -> uriBuilder.scheme("https").host("www.test.com")
.path("notification")
.queryParam("con", conn)
.queryParam("sec", sec)
.build())
.accept(MediaType.APPLICATION_XML)
.contentType(MediaType.APPLICATION_XML)
.body(transactionMono, PaymentTransaction.class)
.retrieve()
.bodyToMono(PaymentResponse.class);
}
This works for me
WebClient client;
Map<String, String> map = new HashMap<>();
public Mono<Response> execute(Transaction transaction) {
Map<String,String> attributeMap = new HashMap<>();
attributeMap.put("some_key", "some_value");
Mono<PaymentTransaction> transactionMono = Mono.just(transaction);
return client.post().uri("/notification", token)
.accept(MediaType.APPLICATION_XML)
.contentType(MediaType.APPLICATION_XML)
.body(transactionMono, Transaction.class)
.attributes(map -> map.putAll(attributeMap))
.retrieve()
.bodyToMono(Response.class);
}
I have a Vertx application with a router endpoint:
router.route(HttpMethod.GET, Constants.ENDPOINT).blockingHandler(this::getItems);
This router calls a method, that is supposed to return a JSON object in the browser, or whatever client is calling this endpoint. The JSON object actually comes from a completely different service. I am using Vert.x's WebClient library to call this service.
private void getItems(RoutingContext routingContext) {
HttpServerResponse response = routingContext.response();
response.setChunked(true);
response.putHeader("content-type", "text/plain");
response.putHeader("Access-Control-Allow-Origin", "*");
JsonObject data = new JsonObject();
WebClient webClient = WebClient.create(vertx);
webClient.post(80, "my-site.com", "/api/items")
.as(BodyCodec.jsonArray())
.putHeader("Accept", "application/json")
.putHeader("Content-Type", "application/json")
.sendJsonObject(new JsonObject().put("mutator", "*"), ar -> {
if (ar.succeeded()) {
HttpResponse<JsonArray> result = ar.result();
JsonArray body = result.body();
System.out.println(body);
data.put("data", body.getJsonObject(0));
} else {
data.put("data", ar.cause().getMessage());
}
});
response.write(data.encode());
routingContext.response().end();
}
The data I get from my-site.com is fine and displays in the console with my System.out command. The problem is that I cannot get it into response.write.
Reading up, I see that this is related to futures. I don't really understand the concept, so I've been doing a lot of reading but cannot find any examples that fit my particular code.
How would I go about implementing futures so that the data I receive from my-site.com gets put into my Json object (data), and then can be used in response.write?
In your impl data will be an empty JSON object because Webclient is async. You are writing the response to the client before the response from Webclient is ready.
Move the write into the webclient response and end the context there. E.g:
...
if (ar.succeeded()) {
HttpResponse<JsonArray> result = ar.result();
JsonArray body = result.body();
System.out.println(body);
data.put("data", body.getJsonObject(0));
} else {
data.put("data", ar.cause().getMessage());
}
response.write(data.encode());
routingContext.response().end();
...
The Vert.x documentation about async coordination is very good and uses futures in the examples. Here is the way I would implement it using Vert.x futures:
private void getItems(RoutingContext routingContext) {
HttpServerResponse response = routingContext.response();
response.setChunked(true);
response.putHeader("content-type", "text/plain");
response.putHeader("Access-Control-Allow-Origin", "*");
// init a future that should hold a JsonObject result
Future<JsonObject> future = Future.future();
JsonObject data = new JsonObject();
WebClient webClient = WebClient.create(vertx);
webClient.post(80, "my-site.com", "/api/items")
.as(BodyCodec.jsonArray())
.putHeader("Accept", "application/json")
.putHeader("Content-Type", "application/json")
.sendJsonObject(new JsonObject().put("mutator", "*"), ar -> {
if (ar.succeeded()) {
HttpResponse<JsonArray> result = ar.result();
JsonArray body = result.body();
System.out.println(body);
data.put("data", body.getJsonObject(0));
// set future to be completed, with data object as its JsonObject result
future.complete(data);
} else {
data.put("data", ar.cause().getMessage());
future.complete(data);
// we can also set the future as failed and give it a Throwable
// future.fail(ar.cause());
}
});
// handle when the future is completed
future.setHandler(jsonObjectAsyncResult -> {
if(jsonObjectAsyncResult.succeeded()) {
response.write(data.encode());
routingContext.response().end();
}
});
}