Spring Webflux - send data stream to endpoint - java

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.

Related

How to write a simple unit test for GraphQL Subscription over websocket?

My backend is a webflux server using the graphql-java-kickstart (v11.0.0) library and I have a bunch of graphql http requests right now.
I'm using Spring's WebTestClient to test my graphql Queries and Mutations.
Basically, I'll just call it and compare the response like so
webTestClient
.post()
.uri("/graphql")
.header(...)
.body(Mono.just(request), JsonNode.class)
.exchange()
.expectStatus()
.isOk()
.expectBody(JsonNode.class)
.returnResult()
.getResponseBody()
But this is over a normal HTTP and simple REST protocol.
With GraphQL Subscriptions, I need to set up a websocket and do the Apollo (subscription-transport.ws) handshake protocol before sending the Subscription request in, and I have to wait for a response async.
I have manually verified the subscription works with the WebSocket Test Client chrome extension
What's the correct way to write a unit test for this?
Suppose I have a simple Subscription that just returns the timestamp back every second
type Subscription {
clock: String!
}
How do I write a unit test for this? What library or client should I use?
A link or example would be awesome!

Use Spring WebClient response in next API call?

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());

Why pass Mono<ServerResponse> from handler function in webflux router and not Flux<ServerResponse>

I am new to Spring. I was trying to make a sample application for spring webflux in functional way.
Why can't our handler function pass Flux. is there any way to make router function accept it as it is said that router function accept a subtype of serverResponse.
Show Handler code
public Mono<ServerResponse> getShowList(ServerRequest request){
Flux<Show> showList = showRepository.findAll();
Flux<ShowVo> showVoList= showList.map(s -> {
return new ShowVo(s.getId(), s.getTitle());
});
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(showVoList, ShowVo.class); }
Here i am passing the Mono <ServerResponse> but I want to it as Flux <ServerResponse> to the Router function
Router function code
#Bean
public RouterFunction<ServerResponse> routeShow(ShowHandler showHandler){
return RouterFunctions.route(RequestPredicates.GET("/shows").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), showHandler::getShowList)
}
}
So is there any way to do it, I have gone through different articles. All I can find is Mono but if I use annotation based webflux I can pass flux.
Why the webhandler doesn't accept a stream to the server, and you can only return a stream is because of the HTTP protocol specification.
If you wish to both stream data to the server, and stream data to the client (full duplex) you need to use websockets with webflux.
you can read all about it in the webflux documentation:
HTTP versus Websockets

Web service putting Soap message in queue with Spring Integration and Jms

I want to use Spring Integration to expose a simple web service that pushes incoming message into ActiveMQ and responds immediately. My go-to solution was MarshallingWebServiceInboundGateway connected to Jms.outboundAdapter with IntegrationFlow. Below the Gateway and IntegrationFlow snippets. Problem with this is Adapter does not provide response (duh) which Gateway expects. The response I get back from the service is empty 202, with delay of about 1500ms. This is caused by a reply timeout I see in TRACE logs:
"2020-04-14 17:17:50.101 TRACE 26524 --- [nio-8080-exec-6] o.s.integration.core.MessagingTemplate : Failed to receive message from channel 'org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel#518ffd27' within timeout: 1000"
No hard exceptions anywhere. The other problem is I cannot generate the response myself. I can't add anything to IntegrationFlow after the .handle with Adapter.
Any other way I can try to fulfill the scenario?
How, if at all possible, can I generate and return response in situation there is no better approach?
Most likely the proper way would be to use Gateways on both ends, but this is not possible. I cannot wait with response until message in the queue gets consumed and processed.
'''
#Bean
public MarshallingWebServiceInboundGateway greetingWebServiceInboundGateway() {
MarshallingWebServiceInboundGateway inboundGateway = new MarshallingWebServiceInboundGateway(
jaxb2Marshaller()
);
inboundGateway.setRequestChannelName("greetingAsync.input");
inboundGateway.setLoggingEnabled(true);
return inboundGateway;
}
#Bean
public IntegrationFlow greetingAsync() {
return f -> f
.log(LoggingHandler.Level.INFO)
.handle(Jms.outboundAdapter(this.jmsConnectionFactory)
.configureJmsTemplate(c -> {
c.jmsMessageConverter(new MarshallingMessageConverter(jaxb2Marshaller()));
})
.destination(JmsConfig.HELLO_WORLD_QUEUE));
}
'''
The logic and assumptions are fully correct: you can't return after one-way handle() and similar to that Jms.outboundAdapter().
But your problem that you fully miss one of the first-class citizens in Spring Integration - a MessageChannel. It is important to understand that even in the flow like yours there are channels between endpoints (DSL methods) - implicit (DirectChannel), like in your case, or explicit: when you use a channel() in between and can place there any possible implementation: https://docs.spring.io/spring-integration/docs/5.3.0.M4/reference/html/dsl.html#java-dsl-channels
One of the crucial channel implementation is a PublishSubscribeChannel (a topic in JMS specification) when you can send the same message to several subscribed endpoints: https://docs.spring.io/spring-integration/docs/5.3.0.M4/reference/html/core.html#channel-implementations-publishsubscribechannel
In your case the fists subscriber should be your existing, one-way Jms.outboundAdapter(). And another something what is going to generate response and reply it into a replyChannel header.
For this purpose Java DSL provides a nice hook via sub-flows configuration: https://docs.spring.io/spring-integration/docs/5.3.0.M4/reference/html/dsl.html#java-dsl-subflows
So, some sample of publish-subscriber could be like this:
.publishSubscribeChannel(c -> c
.subscribe(sf -> sf
.handle(Jms.outboundAdapter(this.jmsConnectionFactory))))
.handle([PRODUCE_RESPONSE])

Asynchronous Spring Implementation

So we have kind of a nasty spring mvc set up where the path of a request is basically:
gateway -> controller -> service -> dmzgateway -> dmzcontroller -> dmzservice -> external gateway
Essentially at the first gateway a user is passing in a date range and i'd like to asynchronously process each month within the range in the controller:
#Async
public Response processFoo(LocalDate fromdate,LocalDate toDate){
List<Response> responses;
CompletableFuture<Response> response;
while(fromDate.isBefore(toDate)){
response = CompleteableFuture.supplyAsync(processService(fromDate,fromDate.plusDays(30)));
fromDate = fromDate.plusDays(30);
}
responses.add(response.get());
}
This piece of code resides in my first controller and I've used #EnableAsync, created the task executor bean in config, made sure servlets support async but I keep losing these threads. Keep getting the "use request context" error but its already set in my web xml as well.
I'm just wondering if I'm approaching this problem incorrectly where I should be doing async somewhere else in this flow or what. Should I expect to lose threads with this architecture?

Categories