Spring WebClient ClientResponse times out on bodyToMono - spring

This is the API call (blocking) I am making to external application concurrently (max of 100 calls at the same time)
ClientResponse response = webclient.get()
.uri("https-url-here-with-query-params-here")
.header(HttpHeaders.AUTHORIZATION, "abcdefg")
.exchange()
.timeout(Duration.ofSeconds(300))
.block();
if (response.statusCode().is2xxSuccessful()) {
MyPojoEntity myPojo = response.bodyToMono(MyPojoEntity.class)
.timeout(Duration.ofSeconds(300))
.block();
}
What I observe is the call to response.bodyToMono(MyPojoEntity.class).timeout(Duration.ofSeconds(300)) is timing out after 5 minutes. My understanding is ClientResponse already has response body from the server and response.bodyToMono method just unmarshalling to pojo entity class. The payload is very small and shouldn't take more than few seconds to unmarshall it. May be it is still reading the response from server and timing out due to API issue on the server? If that is the case, then what does if (response.statusCode().is2xxSuccessful()) mean? I expect when response status is success, payload also to be there especially when i retrieve ClientResponse in a blocking way. Please help me to understand what is going on here.

Related

How to get response json from Spring WebClient

I've been trying to follow the simplest tutorials out there for how to use WebClient, which I understand to be the next greatest thing compared to RestTemplate.
For example, https://www.baeldung.com/spring-5-webclient#4-getting-a-response
So when I try to do the same thing with https://petstore.swagger.io/v2/pet/findByStatus?status=available which is supposed to return some json,
WebClient webClient = WebClient.create();
webClient.get().uri("https://petstore.swagger.io/v2/pet/findByStatus?status=available").exchange().block();
I have absolutely no idea how to proceed from the resultant DefaultClientResponse object. It shouldn't be this convoluted to arrive at the physical response body, but I digress.
How do I get the response body with the code I provided?
In the form you currently have it, and explaining the behaviour..
WebClient webClient = WebClient.create();
webClient.get()
.uri("https://petstore.swagger.io/v2/pet/findByStatus?status=available")
.exchange()
.block();
the block() starts the request by internally synchronously subscribing to the Mono, and returns the resulting ClientResponse. You could also handle this asynchronously by calling subscribe() on the Mono returned by the exchange() method, instead of block().
In this current form, after the block() you now have all the metadata (ie. from the response header) about the response in a ClientResponse object, including the success status. This does not mean that the response body has finished coming through. If you don't care about the response payload, you could confirm the success and leave it at that.
If you further want to look at the response body, you need to convert the response body stream into some class. A this point you can decide whether you want to read everything into a single Mono with bodyToMono or into a stream of objects (Flux) with bodyToFlux, such as in the case where the response is a JSON array that can be parsed into individual separate Java objects.
However, in your case, you just want to see the JSON as-is. So converting to a String is sufficient. You would just use bodyToMono which would return a Mono object.
WebClient webClient = WebClient.create();
String responseJson = webClient.get()
.uri("https://petstore.swagger.io/v2/pet/findByStatus?status=available")
.exchange()
.block()
.bodyToMono(String.class)
.block();
Here you use block() to wait for the response payload to arrive and be parsed into a String, but you could also subscribe to the Mono to receive it reactively when it is complete.
One thing to note is that retrieve() can be used instead of exchange() to shortcut the ClientResponse. In this case you let default behavior handle error responses. Using exchange() puts all the responsibility on the application for responding to error responses on the ClientResponse. Read more in the Javadoc. The retrieve() version would look as follows. No need to block() as you only care about the response data.
WebClient webClient = WebClient.create();
String responseJson = webClient.get()
.uri("https://petstore.swagger.io/v2/pet/findByStatus?status=available")
.retrieve()
.bodyToMono(String.class)
.block();
Here is how you make a request with RestTemplate
String json = new RestTemplate()
.getForEntity("https://petstore.swagger.io/v2/pet/findByStatus?status=available")
.getBody();
Here is how you make a request with requests
import requests
json = requests.get("https://petstore.swagger.io/v2/pet/findByStatus?status=available")
.content
Here is how you make a request with WebClient
String json = WebClient.create()
.get()
.uri("https://petstore.swagger.io/v2/pet/findByStatus?status=available")
.exchange()
.block()
.bodyToMono(String.class)
.block();

Spring Framework WebFlux Reactive Programming

I am trying to send an object to the endpoint but I do not understand why I can't do it with .get(), why .post() has to be used? What if the endpoint method takes an object and does something with it and returns an object? I may want to send an object to the endpoint which takes the object as an argument. Is there a way to do it? How to pass a customer object to getCustomer() endpoint.
WebClient.create("http://localhost:8080")
.get()//why this can not be used? why post has to be used?
.uri("client/getCustomer")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(customer)//with .get() body cannot be passed.
.retrieve()
.bodyToMono(Customer.class);
#GET
#Path("/getCustomer")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Customer getCustomer(Customer customer) {
//do something
return customer;
}
Edited
In GET methods, the data is sent in the URL. just like:
http://www.test.com/users/1
In POST methods, The data is stored in the request body of the
HTTP request.
Therefore we should not expect .get() method to have .bodyValue().
Now if you wanna send data using GET method, you should send them in the URL, like below snippet
WebClient.create("http://localhost:8080")
.get()
.uri("client/getCustomer/{customerName}" , "testName")
.retrieve()
.bodyToMono(Customer.class);
Useful Spring webClient sample:
spring 5 WebClient and WebTestClient Tutorial with Examples
Further information about POST and GET
HTTP Request Methods

How to handle Spring WebClient get application/octet-stream as body InputStream?

I am downloading files with a GET request. Some of them are quite large, so I want to get them as a stream and read the bytes in chunks as I can process them, never reading the whole file in memory.
org.springframework.web.reactive.function.client.WebClient seemed like a good fit, but I am running into "UnsupportedMediaTypeException: Content type 'application/octet-stream' not supported.
Here is some short sample code.
#Autowired WebClient.Builder webClientBuilder;
....
ClientResponse clientResponse = webClientBuilder.clientConnector(this.connector)
.build()
.get()
.uri(uri)
.accept(MediaType.APPLICATION_OCTET_STREAM)
.exhange()
.block(Duration.of(1, ChronoUnit.MINUTES));
// blows up here, inside of the body call
InputStream responseInputStream = clientResponse.body(BodyExtractors.toMono(InputStream.class)).block(Duration.of(1, ChronoUnit.MINUTES));
Here is a chunk of the stack trace.
org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'application/octet-stream' not supported
at org.springframework.web.reactive.function.BodyExtractors.lambda$readWithMessageReaders$20(BodyExtractors.java:254)
at java.util.Optional.orElseGet(Optional.java:267)
at org.springframework.web.reactive.function.BodyExtractors.readWithMessageReaders(BodyExtractors.java:250)
at org.springframework.web.reactive.function.BodyExtractors.lambda$toMono$2(BodyExtractors.java:92)
......
I am on spring-webflux 5.0.7.
I am sure spring webclient must support something beyond JSON. I just don't know how to do that. Help?
Not an expert, but instead of an InputStream, you can get a Flux<byte[]> where each published array will contain a slice of the response body), using
.get()
.uri(uri)
.accept(MediaType.APPLICATION_OCTET_STREAM)
.retrieve()
.bodyToFlux(byte[].class)
You can also do the same with ByteBuffer instead of byte[] if you prefer.

How do I get the HTTP status code of a given URL via Spring?

I am working in a Spring #Component class and I am trying to get the HTTP status code of a particular URL for further processing. I have a function as follows:
fun getStatus() : String
{
val webClient = WebClient.create("https://stackoverflow.com")
val result = webClient.get()
.exchange().map { res -> res.rawStatusCode() }
println(result)
return "statusGotten"
}
However, rather than getting the Int value of the status code (e.g. 200, or 401), I am simply getting: "MonoMap".
I am new to both Spring and Web Programming in general, so I'm a little confused how to proceed from here. I'm aware that "result" is being returned as a "Mono", but I'm less clear about what a "Mono" is, or how I might transform it into something with more scrutable properties, as even looking at "result" in the debugger doesn't shed any light as to whether the HTTP request was actually sent or was successful:
Am I calling the webclient incorrectly? Or merely failing to parse the resultant data in a meaningful way? Any suggestions on how or where I might learn more about the underlying topics would be much appreciated as well.
If you need a blocking way to do this is easy just
#Test
public void myTest(){
WebClient client = WebClient.builder().baseUrl("https://stackoverflow.com/").build();
ClientResponse resp = client
.get()
.uri("questions/")
.exchange()
.block();
System.out.println("Status code response is: "+resp.statusCode());
}
But for this you can use directly the RestTemplate instead the webclient... the recomended way to do this is non blocking what means you should return a Mono with the status and consume outside your method like for example:
public Mono<HttpStatus> myMethod(){
WebClient client = WebClient.builder().baseUrl("https://stackoverflow.com/").build();
return client
.get()
.uri("questions/")
.exchange()
.map( clientResp -> clientResp.statusCode());
}
The way of consume this Mono depends of your code...

Add Exception handler for Spring Web Client

I use this code for REST API requests.
WebClient.Builder builder = WebClient.builder().baseUrl(gatewayUrl);
ClientHttpConnector httpConnector = new ReactorClientHttpConnector(opt -> opt.sslContext(sslContext));
builder.clientConnector(httpConnector);
How I can add connection exception handler? I would like to implement some custom logic? Is this feature easy to implement?
If I understand your question in the context of failing the connection because of SSL credentials, then you should see the connection exception manifest itself on the REST response.
You can take care of that exception via the Flux result you get on WebClient.ResponseSpec#onStatus. The docs for #onStatus says:
Register a custom error function that gets invoked when the given
HttpStatus predicate applies. The exception returned from the function
will be returned from bodyToMono(Class) and bodyToFlux(Class). By
default, an error handler is register that throws a
WebClientResponseException when the response status code is 4xx or
5xx.
Take a look at this example:
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is4xxServerError, response -> ...) // This is in the docs there but is wrong/fatfingered, should be is4xxClientError
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToMono(Person.class);
Similarly for your question, the connection error should manifest itself after the call gets made and you can customize how it gets propogated in the reactive pipeline:
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> {
... Code that looks at the response more closely...
return Mono.error(new MyCustomConnectionException());
})
.bodyToMono(Person.class);
Hope that helps.

Resources