Use Spring WebClient response in next API call? - java

I want to use WebClient response in the next API call. So before calling the next api some of the fields from the response extract and use them in the next api call. There is a way to block WebClient response and use it. But is there any way to do it without blocking? So my code looks like this
response = getUserByWebClient1(); // web client call 1
extract id from response
getRolesByUserId(id); // webclient call 2

This is not specific to WebClient, but a general concept with reactive types, here Reactor Flux and Mono.
You can use the flatMap operator to achieve just that.
// given UserService with a method "Mono<User> getCurrentUser()"
// and RolesService with a method "Mono<RoleDetails> getRolesForUser(Long userId)"
Mono<RolesDetails> roles = userService.getCurrentUser()
.flatMap(user -> rolesService.getRolesForUser(user.getId());

Related

Java Mono or Flux needed as response for "when" mock with webflux

I am writing some contract tests and I am trying to mock my controller in order to test the wanted method. My method should only return status code 200, so not an object, and I do not know how to write this with Mono or Flux and I get an error because of that.
I tried something like this, but it does not work:
Mono<Integer> response = Mono.just(Response.SC_OK);
when(orchestration.paymentReceived(purchase)).thenReturn(response);
How should I write my "when" part in order to verify it returns status code 200?
In order to check response status code you will need to write a more complicated test, using WebTestClient. Like so:
Service service = Mockito.mock(Service.class);
WebTestClient client = WebTestClient.bindToController(new TestController(service)).build();
Now you are able to test:
serialization to JSON or other types
content type
response code
path to your method
invoked method (POST,GET,DELETE, etc)
Unit tests do not cover above topics.
// init mocks
when(service.getPersons(anyInt())).thenReturn(Mono.just(person));
// execute rest resource
client.get() // invoked method
.uri("/persons/1") // requested path
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk() // response code
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody()
.jsonPath("$.firstName").isEqualTo(person.getFirstName())
.jsonPath("$.lastName").isEqualTo(person.getLastName())
// verify you have called your expected methods
verify(service).getPerson(1);
You can find more examples here. Above test is also does not require Spring context, can work with mock services.

Rest Service Post Calls Not working with Spring Webclient

I need to invoke a rest service asynchronously and I thought of using spring reactive's webclient instead of the AsyncRestTemplate. However my url is not getting invoked at all with the below code.
Mono<Test> asyncResponse = webClientBuilder.build().post().uri(url).contentType(MediaType.APPLICATION_JSON)
.header("h1", h1).header("h2", h2)
.body(BodyInserters.fromObject(request))
.retrieve().bodyToMono(Test.class);
However if I do the same synchronously everything works fine.
webClientBuilder.build().post().uri(url).contentType(MediaType.APPLICATION_JSON)
.header("h1", h1).header("h2", h2)
.body(BodyInserters.fromObject(request))
.exchange();`
What am I doing wrong?
exchange doesn't mean synchronous. It responds Mono. You need to subscribe() or block() your stream somewhere.
Difference with exchange and retrieve is : They differ in return types; the exchange method provides a ClientResponse along with its status, headers while the retrieve method is the shortest path to fetching a body directly. you can refer this

Use spring reactive webclient to pass protobuff request

I am using spring framework reactive webclient to make a call like below
webClient.post()
.uri("/v/score/$model")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(gson.toJson(request))
.accept(MediaType.APPLICATION_JSON)
.header("Client-Id", clientId)
.awaitExchange()
.awaitBody<ScoringResponse>()
which is working fine. Now I wan to pass the request as a protobuff object instead of json. How can I do that ?
Set the media type to application/octet-stream and pass your proto model in a byte array form by using the .toByteArray() method. On the receiving end you can use the static method {proto generated class}.parseFrom({your bytes come here}) to rebuild the proto object.
Do not forget the POST method request is basically a body content ;)

Spring Webflux - send data stream to endpoint

I have a question regarding Spring Webflux. I wanted to create a reactive endpoint that consumes content type text/event-stream. Not produce but consume. One of our services needs to send a lot of small objects to another one and we thought that streaming it this way might be a good solution.
#PostMapping(value = "/consumeStream", consumes = MediaType.TEXT_EVENT_STREAM_VALUE)
public Mono<Void> serve(#RequestBody Flux<String> data) {
return data.doOnNext(s -> System.out.println("MessageReceived")).then();
}
I am trying to use Spring WebClient to establish a connection to the endpoint and stream data to it. For example using code:
WebClient.builder().baseUrl("http://localhost:8080")
.clientConnector(new ReactorClientHttpConnector())
.build()
.post()
.uri("/test/serve")
.contentType(MediaType.TEXT_EVENT_STREAM)
.body(BodyInserters.fromPublisher(flux, String.class))
.exchange()
.block();
The flux is a stream that produces a single value every 1 sec.
The problem I have is that the WebClient fully reads the publisher and then sends the data as a whole and not streams it one by one.
Is there anything I can do to do this using this client or any other ? I do not want to go the websockets way.
SSE standard does not allow POST. There is no way to specify method even in browser API https://www.w3.org/TR/eventsource/
Server Side Events as name states are designed for delivering events from the server to the client.

Spring Reactive WebFlux reports empty flux when using application/stream+json

I have a reactive core WebClient to post to a given endpoint. The payload is a flux of JsonNodes and the content-type is application/stream+json
JsonNode response = localEP.post().uri( "/createItem" )
.contentType(MediaType.APPLICATION_STREAM_JSON)
.body( BodyInserters.fromPublisher(itemData, JsonNode.class ))
.retrieve()
.bodyToMono( JsonNode.class )
.block();
On the server end I have tried both a Spring Controller style and Spring Web Reactive FunctionHandler to process the payload of the above call with a payload that is a Flux.
#PostMapping(path = "/dev/jobad/dynamo", consumes = MediaType.APPLICATION_STREAM_JSON_VALUE)
#ResponseStatus(HttpStatus.CREATED)
public Flux<JsonNode> loadItems (#RequestBody Flux<JsonNode> items) {
items.subscribe(storage::add);
JsonNode response = new ObjectMapper().createObjectNode().put( "shady", "shade" );
return Flux.just( response );
}
The return to the client is always ok, however the server reports that the content of the flux is empty. If I change (#RequestBody Flux<JsonNode> items to (#RequestBody JsonNode items The payload is received fine. The WebClient logs appear to indicate that it has written the data on the wire and processed the response. However the body seems to empty
Reactor.core.Exceptions$ErrorCallbackNotImplemented: org.springframework.web.server.ServerWebInputException: Response status 400 with reason "Request body is missing: public reactor.core.publisher.Flux<com.fasterxml.jackson.databind.JsonNode> com.talroo.rest.JobResource.loadJobs(reactor.core.publisher.Flux<com.fasterxml.jackson.databind.JsonNode>)"
Caused by: org.springframework.web.server.ServerWebInputException:
Response status 400 with reason "Request body is missing: public
reactor.core.publisher.Flux<com.fasterxml.jackson.databind.JsonNode>
What do I need to do to be able to handle the request body of a post as a Flux?
First, I don't think Spring officially supports reading/writing Jackson JsonNode instances directly from Controllers. Your application is supposed to ask for a domain object or something like a Map<String, String>.
Now in Jackson's model, a JsonNode represents any node in the JSON tree - as it is a tree, you can expect to get a Flux of nodes, but you are apparently able to get the root node - which explains the behavior you're seeing.
So I think your application should rather rely on higher-level classes and let Jackson deserialize them for you.
Note that your controller implementation is also breaking a few rules:
you should not call blocking operators, such as block, within a method that returns a reactive type (your controller is not breaking this one, but close)
you should not break the reactive pipeline and decouple the reading of the request and the writing of the response; chances are the HTTP exchange could be closed before your controller has a chance to read the whole request. Calling subscribe just does that.

Categories