Latest Spring Boot [2.5.0] webclient bug? - java

I have recently upgraded my project to the latest version of spring-boot 2.5.0 and got going with refactoring a ton of deprecated code. I noticed that awaitExchange() has now been deprecated and should be replaced with awaitExchange{it}
However, as soon as I replaced one with the other it appears I can no longer extract the body from the ClientResponse object by response.awaitBody() in a different class and keep getting No value received via onNext for awaitSingle. Is such behaviour by design?
Is there any other way to actually get hold of the body without having to use `
awaitExchange{ it.awaitBody() } in the class that makes the webservice call?

Since you did not show your code its hard to say what is the issue. But you can use WebClient in following ways
val client = WebClient.create()
val data: MultiValueMap<String, String> = LinkedMultiValueMap()
data["username"] = "johndoe"
data["target_site"] = "aloha"
client.create()
.post()
.uri("some uri")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData(data))
.retrieve()
.awaitBodyOrNull<String>() ?: throw Exception("Received null response")
Another way to do is
val response = client.get()
.uri("some uri")
.contentType(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(String::class.java)
.awaitSingle()
if (!response.statusCode.is2xxSuccessful) {
throw Exception("Received ${response.statusCodeValue} response.")
}

Related

How to handle 500 error code using retrieve in webclient

I've read quite a few documentations and other stackoverflow questions regarding this matter but I can't seem to get my code working.
So essentially I have a WebClient making a POST request.
IF the response status is 200, then I make another call to another endpoint using a different WebClient. After second webclient call, return a string.
ELSE I just return a String from the method e.g. "failed to create order.".
Simple enough. (this is all done in a seperate thread fyi, not the main thread.)
But I've noticed that if i do get back a 500 error code, WebClient throws an exception. What I want to do is capture the exception and handle that gracefully and return a String like "Error calling first endpoint etc."
This is what I have so far:
private String generateOrder(ImportedOrderDetails importedOrderDetails)
{
Order requestBody = generateRequestBody(importedOrderDetails);
OrderResponse responseForCreatingOrder = orderWebClient()
.post()
.body(Mono.just(requestBody), Order.class)
.retrieve()
.bodyToMono(OrderResponse.class)
.block();
if (responseForCreatingOrder.getResponseStatus().equals(SUCCESS))
{...other call using different webclient}
else{ return "Error creating order."}
This works fine when the response status is 200 but when its 500 it blows up.
OrderResponse is a custom object. orderWebClient() is just a method that returns a prebuilt WebClient containing the baseUrl and headers etc.
I came across this:
Spring WebClient - How to handle error scenarios I did try implementing it but couldn't figure out where to put the block method since I kept on getting the following:
reactor.core.Exceptions$ReactiveException: java.lang.Exception
at reactor.core.Exceptions.propagate(Exceptions.java:393)
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:97)
at reactor.core.publisher.Mono.block(Mono.java:1680)
I had to edit my code a bit to try and implement the answer to that question:
private Mono<? extends Throwable> handleError(String message) {
log.error("====---"+message);
return Mono.error(Exception::new);
}
private String generateOrder(ImportedOrderDetails importedOrderDetails)
{
Order requestBody = generateRequestBody(importedOrderDetails);
Mono<OrderResponse> responseForCreatingDemo = orderWebClient()
.post()
.body(Mono.just(requestBody), Order.class)
.retrieve()
.onStatus(
(HttpStatus::is5xxServerError),
(it -> handleError(it.statusCode().getReasonPhrase()))
)
.bodyToMono(OrderResponse.class);
System.out.println("-=-"+responseForCreatingDemo);
if (responseForCreatingOrder != null && responseForCreatingOrder.block().getHeader().getResponseStatus().equals(SUCCESS)){...}
The error was coming from the .block part in the if condition. I believe this is something pretty trivial and missing the big picture.
Any suggestions?
It seems you have two kinds of statuses:
Http status, defined by the protocol itself (see HTTP response status codes)
Something specific to the application you're working on, encapsulated into the OrderResponse class.
So you have to handle two "errors" instead of one, one of the possible solutions might look like
.retrieve()
.bodyToMono(OrderResponse.class)
// 4xx, 5xx errors and return "Unable to create order" String instead
.onErrorContinue(WebClientResponseException.class, (ex, v) ->
Mono.just("Unable to create order"))
// if application specific status is not "ok" return "Unable to create order"
.map(it -> it.ok ? "Ok response" : "Unable to create order")
.block();
Please note that this code sample ignores exception and does not even log it

Program stuck in socketRead0 method when requesting with OkHttp

I developed a Java library for Twitter API here using OkHttp3 4.8.1.
Unfortunately, it looks like after having sent a request, once everything is finished, the program never stops and is stuck in SocketInputStream.
When not using cache, it is stuck in waitForReferencePendingList method of Reference class instead :
I tried everything, closing connection explicitly in my code like this, updating the version of OkHttp, but still the same. Any idea ?
If needed, here is the full code where the request is done, in summary :
Request request = new Request.Builder()
.url(url)
.get()
.headers(Headers.of("Authorization", "Bearer " + bearerToken))
.build();
OkHttpClient client = new OkHttpClient.Builder().build()
Response response = client.newCall(request).execute();
String stringResponse = response.body().string();
return Optional.ofNullable(TwitterClient.OBJECT_MAPPER.readValue(stringResponse, classType));
Finally adding client.connectionPool().evictAll(); elsewhere (in my post request to get a bearer token) solved the problem !

How to get response in list form using Unirest?

My project is written in the Java programming language using the Spring Boot framework.
I use Unirest library for getting requests to some API and I want to get response in forms, which looks like List<SomeResponse>
Below I write some example of request, which I try to make.
Why do I need a list? Because this data structure is more convenient and used throughout the rest of the huge project.
I tried various options for receiving a response to the request, but I could not get the List <SomeResponse> in any way. At the moment, my efforts have led me only to the fact that I get an array of the objects I need. Of course, we could try to cast the array to list, but here, as it seems to me, we lose in speed.
try {
SomeResponse[] SomeResponses = Unirest.post(url)
.header("Content-Type", "application/json")
.header("Authorization", key)
.body("[\"" + address + "\"]")
.asObject(SomeResponse[].class)
.getBody();
return Result.ok(SomeResponses);
} catch (UnirestException e) {
return Result.error("Error in call API " + url);
}
Also, I have configured the Jackson library, which serializes the JSON format, which that we receive in response to a request, in POJO classes. Perhaps you could tell how to configure the mapper correctly so that it can accept and serialize this response.
Few words about versions of libraries and frameworks. I used Gradle to build and manage dependencies.
org.springframework.boot:spring-boot-starter:2.0.0.RELEASE
com.fasterxml.jackson.core:jackson-databind:2.10.1
com.mashape.unirest:unirest-java:1.4.9
Thanks a lot for your answers!
P.S.
Sorry, this question may have a bad structure, but this is the first time I am writing a problem on this platform, I promise to make my question better in the future.
Replace the SomeResponse[].class in asObject with a GenericType<List<SomeResponse>> object.
try {
List<SomeResponse> someResponses = Unirest.post(url)
.header("Content-Type", "application/json")
.header("Authorization", key)
.body("[\"" + address + "\"]")
.asObject(new GenericType<List<SomeResponse>>() {})
.getBody();
return Result.ok(someResponses);
} catch (UnirestException e) {
return Result.error("Error in call API " + url);
}

Spring WebClient to Download an Image

I have been looking into Reactive Programming and recently tried to build a POC with Spring WebFlux. I want to start simple and just use the WebClient to download an image; specifically https://greatatmosphere.files.wordpress.com/2013/02/great-atmosphere-149-tenaya-lake-yosemite-national-park-2.jpg
I have tried the following code
String block = WebClient.create("https://greatatmosphere.files.wordpress.com/2013/02/great-atmosphere-149-tenaya-lake-yosemite-national-park-2.jpg")
.get()
.accept(MediaType.IMAGE_JPEG)
.retrieve()
.bodyToMono(String.class)
.doOnError(WebClientResponseException.class,
ex -> System.out.println(ex.getStatusCode() + ": " + ex.getResponseBodyAsString()))
.log()
.block();
System.out.println("output:" + block);
but it does not work as expected. It seems that the data is continually be streamed and the get request does not terminate.
I am certain that I am missing something simple but I cannot seem to figure it out. I have tried a variety of parameters but the results are the same.
How do I use the WebClient to download the image and then terminate?
Catch image as byte[] not String
byte[] image = WebClient.create("https://greatatmosphere.files.wordpress.com/2013/02/great-atmosphere-149-tenaya-lake-yosemite-national-park-2.jpg")
.get()
.accept(MediaType.IMAGE_JPEG)
.retrieve()
.bodyToMono(byte[].class)
.block();

unable to POST request with sprint WebClient : always 400

very big problem since 48h00.
with postman, absolutly no problem to post my body. return is 200.
there is no authentication with concerned api.
but, when i use my java-code, always 400 is returned !!!!
String baseUrl = "myBaseUrl";
String uri = "myUri";
WebClient webClient = WebClient.create(baseUrl);
ClientResponse cresponse = webClient
.post()
.uri(uri)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.syncBody(myObject)
.exchange()
.block();
// always 400!!!! here !!!!!!!
System.out.println("result :" + cresponse.statusCode());
Probably something wrong with "myObject".
I think the problem is the way in you are feeding the body in your request. Use Mono.just to create a mono to feed the body as shown below
webClient.post().body(Mono.just(myObject)), MyObject.class).exchange().block().statusCode();

Categories