Spring Framework WebFlux Reactive Programming - spring

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

Related

Java Spring WebFlux WebClient pass parameters to response

I want to use webflux to return a single result async. The response doesn't have an id of the object. So when I get the response async back from the remote reply, then I don't have a way to fetch that object from the database to get further information. So is there a way to pass my object id to the async response handler? I couldn't find any way. Here is my sample code
var monoReply = webClient.post().uri(url)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.body(Mono.just(myRequestObject), MyRequest.class)
.retrieve()
.bodyToMono(MyResponse.class);
monoReply.subscribe(BanlawApiServiceImpl::handleLoginResponse);
private static String handleLoginResponse(MyResponse myResponse) {
String token = myResponse.getToken();
//now I want to know the id of the database object I am dealing with. Response doesn't
have that id
}
You need to continue async flow using flatMap and fetch object from the database. As result handleLoginResponse should return Mono<T>
webClient.post().uri(url)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.body(Mono.just(myRequestObject), MyRequest.class)
.retrieve()
.bodyToMono(MyResponse.class)
.flatMap(response -> handleLoginResponse(response))
private static Mono<String> handleLoginResponse(MyResponse myResponse) {
...
}
Not sure why you are subscribing to the flow explicitly that usually is anti-pattern and should be avoided. In WebFlux subscription happens behind the scene.

webclient set header reactive way

I am new to WebFlux and reactive way of programming. I am using WebClient to call a REST service, when making the call, I need to set a HTTP header, the value for the HTTP header comes from a reactive stream, but the header method on the WebClient only takes String value...
Mono<String> getHeader() {
...
}
webClient
.post()
.uri("/test")
.body(Mono.just(req),req.getClass())
.header("myHeader",getHeader())
...
The above line won't compile, since header method takes an String for second argument. How can I set a header if the value comes from a reactive stream?
You just need to chain getHeader and web client request using flatMap to create reactive flow
return getHeader()
.flatMap(header ->
webClient
.post()
.uri("/test")
.header("myHeader", header)
.body(BodyInserters.fromValue(req))
...
)

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 WebClient returns Lambda instead of Data from Rest Service

I am calling a Rest Service with a WebClient but I only get a response back which is the following:
Object is reactor.core.publisher.LambdaSubscriber#4681c175
I was hoping to get a JSON string back. When I call the service with a RestTemplate I get the expected JSON string back. Here is the code I use to make the call that returns the Lambda object back:
Object objj= WebClient.create("http://localhost/printer-service/jobs")
.get()
.accept(MediaType.APPLICATION_STREAM_JSON)
.exchange()
.flatMapMany(cr -> cr.bodyToFlux(String.class))
.subscribe(System.out::println);
System.out.println("Object is "+objj);
Why is this call not returning the JSON string?

Diffrence b/w #ResponseStatus and ResponseEntity.created(location).build() in post method of rest controller in spring boot

For a POST method in Rest controller I want to return status code 201.
I saw two approaches for that.
First one is:
#PostMapping("/offers")
#ResponseStatus(HttpStatus.CREATED)
public Offer createOffer(#Valid #RequestBody Offer offer) {
return offerRepository.Save(offer);
}
Second approach is:
#PostMapping("/offers")
public ResponseEntity<Object> createOffer(#Valid #RequestBody Offer offer) {
return offerService.createOffer(offer);
}
Below is my service class:
#Override
public ResponseEntity<Object> createOffer(Offer offer) {
Offer uOffer=OfferRepository.save(offer);
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{jobTitle}").
buildAndExpand(uOffer.getJobTitle()).toUri();
return ResponseEntity.created(location).build();
}
So my question is: for first approach we are not using any ResponseEntity.created as we are simply returning #ResponseStatus(HttpStatus.CREATED) from controller. But in the second we are not using #ResponseStatus(HttpStatus.CREATED) and we are handling that status code 201 by using uri and response entity.
What is the difference b/w the both approaches? Both seems to be same as they are returning the same response code 201. which one is preferred?
In my opinion you should apply the following rules. If you want to return a ResponseEntity then use that to affect the status. Thus something like:
#PostMapping("/offers")
public ResponseEntity<Offer> createOffer(#Valid #RequestBody Offer offer) {
Offer offer = offerService.createOffer(offer);
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{jobTitle}").
buildAndExpand(uOffer.getJobTitle()).toUri();
return ResponseEntity.created(location)
.body(offer)
.build();
}
Do not allow your service to generate the ResponseEntity as this is a view class for controllers and should not be in a service.
The second option is by using the class rather then response entity. Then the example would be something like:
#PostMapping("/offers")
#ResponseStatus(HttpStatus.CREATED)
public Offer createOffer(#Valid #RequestBody Offer offer) {
// Do not return response entity but the offer
return offerService.createOffer(offer);
}
There is no difference in general when it comes to status codes. In the end, you still get an HTTP response with 201 status code.
However, in second approach you're also returning a Location header which is a preferred way to do. From Mozilla's HTTP guide:
The HTTP 201 Created success status response code indicates that the request has succeeded and has led to the creation of a resource. The new resource is effectively created before this response is sent back and the new resource is returned in the body of the message, its location being either the URL of the request, or the content of the Location header.
The first approach is the preferred one, since it allows you to keep your service layer decoupled from your web layer (service layer should not know about HttpEntity and all that stuff, so it could potentially be reused without the web layer).
You should refactor you service method to return Object instead of ResponseEntity<Object>.
What is the difference b/w the both approaches?
Using return ResponseEntity.created(location).build(); adds the Location header to the response.
Is also recommended to return the new resource in the body of the response.
201 Created
The HTTP 201 Created success status response code indicates that the request has succeeded and has led to the creation of a resource. The new resource is effectively created before this response is sent back and the new resource is returned in the body of the message, its location being either the URL of the request, or the content of the Location header.
Thus the best option would be:
ResponseEntity.created(location).body(uOffer);

Resources