How to send message when Websocket opened on Webflux - java

I created WebsocketHandler as it was shown in the Webflux websocket doc.
#Override
public Mono<Void> handle(WebSocketSession session) {
Flux<String> messages =
session.receive()
.map(webSocketMessage -> "New message");
return session.send(messages.map(session::textMessage));
}
But the problem is, I want to send some kind of "Hello" message right after the websocket connection opened. How can I achieve that?

After some research I found that, this can be solved with the Flux itself. It is enough that we add startWith method to the Flux. As in the definition of the startWith method.
Prepend the given values before this Flux sequence.
So we prepend our Hello message to the start of the Flux and it will be published first.
#Override
public Mono<Void> handle(WebSocketSession session) {
Flux<String> messages =
session.receive()
.map(webSocketMessage -> "New message")
.startWith("Hello");
return session.send(messages.map(session::textMessage));
}
P.S. Bear in mind that you can also prepend Publisher<? extends T> publisher i.e. Flux or Mono, not just plain values.

Related

How to call subflows after aggregate() method in scatter-gather pattern in Spring Integration

Here I'm using scatter gather pattern. If I want to call another IntegrationFlow after aggregate() and before to(), how do I do that? can I use recipientFlow here so that I can make that flow conditional as well?
#Bean
public IntegrationFlow flow() {
return flow ->
flow.handle(validatorService, "validateRequest")
.split()
.channel(c -> c.executor(Executors.newCachedThreadPool()))
.scatterGather(
scatterer ->
scatterer
.applySequence(true)
.recipientFlow(flow1())
.recipientFlow(flow2())
.recipientFlow(flow3()),
gatherer ->
gatherer
.releaseLockBeforeSend(true)
.releaseStrategy(group -> group.size() == 2))
.aggregate(lionService.someMethod())
// here I want to call other Integration flows
.gateway(someFlow())
.to(someFlow2());
}
#Bean
public IntegrationFlow flow1() {
return flow ->
flow.channel(c -> c.executor(Executors.newCachedThreadPool()))
.enrichHeaders(h -> h.errorChannel("flow1ErrorChannel", true))
.handle(cdRequestService, "prepareCDRequestFromLoanRequest");
}
//same way I have flow2 and flow3, and I have set an custom error channel header for all the flows
#Bean
public IntegrationFlow someFlow() {
return flow ->
flow.filter("headers.sourceSystemCode.equals("001")").channel(c -> c.executor(Executors.newCachedThreadPool()))
.enrichHeaders(h -> h.errorChannel("someFlow1ErrorChannel", true))
.handle( Http.outboundGateway("http://localhost:4444/test2")
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class)).bridge();
}
Till now whenever any error occurred in any of the flow it goes through the custom error channels that have been assigned to them then I process the error but when I have used someFlow1() in .gateway(someFlow()) then the error occurring in that flow is not going to the assigned error channel. How to resolve that?
Inside errorhandler class I'm doing something like below --
//errorhandlerclass
#ServiceActivator(inputChannel = "flow1ErrorChannel")
public Message<?> processDBError(MessagingException payload) {
logger.atSevere().withStackTrace(StackSize.FULL).withCause(payload).log(
Objects.requireNonNull(payload.getFailedMessage()).toString());
MessageHeaders messageHeaders = Objects.requireNonNull(payload.getFailedMessage()).getHeaders();
return MessageBuilder.withPayload(
new LionException(ErrorCode.DATABASE_ERROR.getErrorData()))
.setHeader(MessageHeaders.REPLY_CHANNEL, messageHeaders.get("originalErrorChannel"))
.build();
}
#ServiceActivator(inputChannel = "someFlow1ErrorChannel")
public Message<?> processDBError(MessagingException payload) {
logger.atSevere().withStackTrace(StackSize.FULL).withCause(payload).log(
Objects.requireNonNull(payload.getFailedMessage()).toString());
MessageHeaders messageHeaders = Objects.requireNonNull(payload.getFailedMessage()).getHeaders();
return MessageBuilder.withPayload(
new LionException(ErrorCode.CUSTOM_ERROR.getErrorData()))
.setHeader(MessageHeaders.REPLY_CHANNEL, messageHeaders.get("originalErrorChannel"))
.build();
}
Again, if there's any error in someFlow() then error is shown but I want it to go to that method where I'm processing the error as per my requirement.
Also, you can see I've used filter in someFlow() so when the filter expression evaluates true then no problem but when it become false then it's throwing error but I want it to escape and go to next i.e.,.to(someFlow2()). I've used .bridge() by thinking that it'll return to previous context but that's not happening. I know there's some gap in my understanding. Kindly help with the above two problems.
To call another flow and come back to the main one you can use a gateway(). But that flow has to return in the end. There is no something like conditional flow: you may send to the channel (next endpoint in the flow) or not via filter() endpoint (or operator if you wish). The to() operator is terminal in the current flow, but you continue your logic in that destination flow whatever you want. Looks like you need to dedicate some of your time to understand what is a message channel and how it connects endpoints in Spring Integration. The IntegrationFlow is just logical container to express a business task - at runtime it is all endpoints and channels between them.

Exception in not mapped inside flatMap

I have a piece of code that calls an external service. And I wanted to map error from this service. I tried to do that this way:
public Mono<Void> patch(String userId, Payload payload) {
return Mono.just(payload)
.flatMap(it -> client.patch(userId, PatchRequest.buildRequest(payload, userId))
.onErrorMap(throwable -> GeneralActionException.ofFailedSetup()));
}
But when I mocked the client to return RuntimeException
Mockito.when(client.patch(any(), any())).thenThrow(new RuntimeException());
It turned out my test:
StepVerifier.create(sut.patch(userId, payload))
.verifyError(GeneralActionException.class);
failed, because the returned error was RuntimeException:
However when I change the code a little, just like that:
public Mono<Void> patch(String userId, Payload payload) {
return Mono.just(payload)
.flatMap(it -> client.patch(userId, PatchRequest.buildRequest(payload, userId)))
.onErrorMap(throwable -> GeneralActionException.ofFailedSetup());
}
It turned out the test succeeded. The question is why? Because I don't understand why it works differently in both cases and especially why in the first example when error mapping is inside flatMap it doesn't map it to GeneralException?
Ok, I solved it. I mocked client wrongly.
Instead of:
Mockito.when(client.patch(any(), any())).thenThrow(new RuntimeException());
it should be:
Mockito.when(client.patch(any(), any())).thenReturn(Mono.error(RuntimeException::new));
as the client returns Mono<Void>

Handling exception that might occur in an integration flow

I have a REST controller which calls a gateway annotated with #MessagingGateway(errorChannel = ERROR_CHANNEL)
This way, whatever error occurs downstream the integration flow initiated by the gateway will flow into an error channel which will be handled by another integration flow, this is working as expected.
Now, there is another scenario where an integration flow reads messages from Kafka, routes those messages to another channel, one more integration flow processes those messages and another flow sends an HTTP request to a remote service.
public IntegrationFlowBuilder attachmentEventTenantRouter(String tenantId) {
return attachmentEventBaseFlow(".*")
.filter(Message.class, m -> m.getHeaders().get(KafkaConstants.HEADER_PREFIX + MessageHeader.TENANT_ID_KEY) != null && m.getHeaders().get(KafkaConstants.HEADER_PREFIX + MessageHeader.TENANT_ID_KEY, String.class).equalsIgnoreCase(tenantId));
}
private IntegrationFlowBuilder attachmentEventBaseFlow(String eventRegex) {
return IntegrationFlows
.from(Kafka.messageDrivenChannelAdapter(kafkaListenerContainerFactory.createContainer(topic)).errorChannel(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME))
.log(LoggingHandler.Level.DEBUG, "Inside Kafka Consumer")
.filter(Message.class, m -> filter(m, eventRegex))
.transform(messageToEventTransformer);
}
#Bean
public IntegrationFlow kafkaConsumerFlow() {
return fromKafkaFlowHelper.attachmentEventTenantRouter(TENANT_ID)
.route(Message.class, m -> m.getHeaders().get(KafkaConstants.HEADER_PREFIX + MessageHeader.EVENT_TYPE_KEY, String.class), p -> p
.resolutionRequired(false)
.channelMapping("eventType", "transformMessagesFromKafkaAndPublishAnotherEvent")
.defaultOutputChannel("nullChannel"))
.get();
}
#Bean
public IntegrationFlow transformMessagesFromKafkaAndPublishAnotherEvent() {
return flow -> flow
.transform(transformer)
.handle( getKafkaHandler() );
}
#Bean
public IntegrationFlow sendHttpRequestToRemoteServiceFromKafkaEvent() {
return flow -> flow
.transform(transformer)
.handle(gatewayCall, e -> e.advice(expressionAdvice()));
}
How can I do to handle the exceptions that might occur in the flows above?
As you can see, I am using a ExpressionEvaluatingRequestHandlerAdvice which does the work for the handle method, but not sure how to handle exceptions that might occur in the transformers?
The massage gateway with an error channel configured does the trick when the gateway is called by a rest controller, but when the flow is initiated by the Kafka consumer, I'm lost how to achieve this.
Thanks.
EDITED AFTER Artem's RESPONSE TO ADD CLARIFICATION:
This is the configuration of the integration flow that posts a request to a remote service and whose exceptions does not seem to be caught and routed to the errorChannel without a ExpressionEvaluatingRequestHandlerAdvice:
#Bean
public IntegrationFlow sendHttpRequestToRemoteServiceFromKafkaEvent() {
return flow -> flow
.transform(transformer)
.handle(getOAuth2Handler(HttpMethod.PUT, "remote url"), e -> e.advice(expressionEvaluatingRequestHandlerAdvice));
}
private OAuth2RequestHandler getOAuth2Handler(HttpMethod httpMethod, String url) {
return new OAuth2RequestHandler(oAuth2RestTemplate, httpMethod, url);
}
And class OAuth2RequestHandler which implements a MessageHandler
#Override
public void handleMessage(org.springframework.messaging.Message<?> message) throws MessagingException {
String requestBody = (String) message.getPayload();
ResponseEntity<String> response = oAuth2RestTemplate.exchange(url, httpMethod, new HttpEntity<>(requestBody), String.class);
}
I see you use already an errorChannel() on the Kafka message-driven channel adapter. So, what is the question?
This part of your flow is exact equivalent to mentioned #MesaagingGateway with its errorChannel configuration.

How to make reactive #WebFilter work properly with #RequestBody?

I am trying to make a reactive #WebFilter that executes stuff before and after the actual server exchange (i. e. the controller code handling the request):
public static class Foobar implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return Mono.empty()
.doOnEach(s -> /* BEFORE */))
.then(chain.filter(exchange) /* CONTROLLER */)
.and(Mono.empty().doOnEach(s -> /* AFTER */))
.and(Mono.empty().doFinally(s -> /* FINALLY */));
}
}
Everything works as expected for simple GET requests that return a Mono:
#RestController
#RequestMapping
public static class Foo {
#GetMapping
#PostMapping(value = "foo")
public Mono<String> foo(ServerWebExchange exchange) {
return Mono.just("FOOBAR").map(e -> "OK");
}
}
But something really unexpected happens when the controller receives a parameter annotated as #RequestBody. Say, for example a POST request that takes a Mono<String> from the client:
#RestController
#RequestMapping
public static class Bar {
#PostMapping(value = "bar")
public Mono<String> bar(ServerWebExchange exchange, #RequestBody Mono<String> text) {
return text.map(s -> "OK");
}
}
In this case, all steps in my filter are executed before the controller gets to complete the request. This means that the web exchange is committed independently of the filter and therefore I cannot do anything right after the response is sent back to the client.
So I'm wondering:
Is this some kind of Spring bug?
Am I doing something wrong?
Or is this simply the expected behavior?
I've created a small Gist containing a test case that reproduces the problem:
https://gist.github.com/guillermocalvo/740b4fcab471ebc6fe69227fee6d79d5
Edit after Brian's comment:
I still think this might be a bug because Mono.then doesn't seem to have any effect at all:
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.doOnSubscribe(s -> logger.info("onSubscribe response committed:" +
exchange.getResponse().isCommitted()))
.then().doOnEach(s -> logger.info("then doOnEach response committed:" +
exchange.getResponse().isCommitted()))
.doFinally(s -> logger.info("doFinally response committed:" +
exchange.getResponse().isCommitted()));
}
Additionally, if I put stuff in doOnEach is not executed either:
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.doOnSubscribe(s -> logger.info("FILTER-BEFORE-CHAIN/commited=" +
response.isCommitted()))
.doOnEach(s -> logger.info("FILTER-AFTER-CHAIN/commited=" +
response.isCommitted()))
.doFinally(s -> logger.info("FILTER-FINALLY/commited=" +
response.isCommitted()));
}
I don't think this is a bug in Spring (nor in Reactor in this case), but rather a wrong choice of operators to achieve what you're trying to do.
Mono.doOnEach is executed for each signal (next element, completion, error, cancel); this will be executed several times per request in our case.
Mono.and joins the termination signals - so it waits for both Mono to be done and then completes. But both Monos are not executed sequentially, they're subscribed at the same time. Mono.just completes right away, independently of what happens with the filter chain.
In your case, you don't need to have something more complex than adding one operator when the processing starts (doOnSubscribe happens when the mapping has been done and we're starting to execute the handler) and another one when we're done (doFinally happens when it's done, with completion, error or cancellation).
#Component
public class MyFilter implements WebFilter {
Logger logger = LoggerFactory.getLogger(getClass());
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.doOnSubscribe(s -> logger.info("onSubscribe response committed:" +
exchange.getResponse().isCommitted()))
.doFinally(s -> logger.info("doFinally response committed:" +
exchange.getResponse().isCommitted()));
}
}
Note that this approach works as long as you're not performing blocking operations in the DoOnXYZ methods, as they're side effects methods. Doing so will create significant performance problems in your application (and reactor might even reject that operation with an exception). So logging, adding an element to a map are fine - but publishing something to an event queue, for example, is not. In this case, you should use instead chain operations with Mono.then().
Edit
I don't think there is a bug with Mono.then() - doOnEach only works with signals sent downstream (next, error, complete). In this case, you should only get the complete signal. If you'd like to get the context in all cases, you can take a look at Mono.deferWithContext.

Conditional handler in integration flow

I'm developing web hook notification service that allows clients to subscribe/unsubscribe to messages flowing through middle-ware and get notified about the messages (according to provided criteria) by posting the message payload to provided callback URL.
The message delivery looks like this :
flowBuilder
.enrichHeaders(e->e.header(MessageHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE,true))
.handle(Http.outboundChannelAdapter(message-> {
String subscriptionId = message.getHeaders().get(SUBSCRIPTION_ID_HEADER_NAME, String.class);
return subscriptionsStore.get(UUID.fromString(subscriptionId)).getCallbackUrl(); //potential NPE if subscription was removed
},restTemplateBuilder.build())
.get()
As you can see, the implementation of uriFunction fetches the callback URL from subscriptionsStore by subscription id(part of the message header).
My question is about the situation where the client has already unsubscribed with his subscription id and I'm after the conditional handler.
I know that I can filter messages with subscription id are still present in subscription store, but this is not the proper solution, as client might unsubscribe between filter and handle operations still causing NRE in uriFunction.
Another solution is to enreach the header with callback URL and filter then by header having non-empty value, but I don't want to compromise neither header nor payload of original message.
I can think about another approach: to calculate the URI of non-existing subscriptions as some static value and add interceptor to RestTempalte to simulate the HTTP OK replay for this specific URI value...
So my question is about the proper way to handle this case by using the standard EIP or another Spring integration feature I'm not aware about...
Thanks
UPDATE
I've added the DedicatedMessage class that holds the context :
public static class DedicatedMessage extends GenericMessage<Object> implements MessageDecorator{
#Getter
#Transient
private Subscription subscription;
public DedicatedMessage(Subscription subscription,Object payload,Map<String,Object> headers) {
super(payload,headers);
this.subscription = subscription;
}
#Override
public Message<?> decorateMessage(Message<?> message) {
return new DedicatedMessage (subscription,message.getPayload(),message.getHeaders());
}
}
and changed the flow as :
flowBuilder
.enrichHeaders(e->e.header(MessageHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE,true))
.handle((payload, headers) -> {
String subscriptionId = (String) headers.get(SUBSCRIPTION_ID_HEADER_NAME);
Subscription subscription = subscriptionsCache.get(UUID.fromString(subscriptionId));
return Optional.ofNullable(subscription)
.map(s-> new DedicatedMessage(s, payload,headers))
.orElse(null);
})
.handle(Http.outboundChannelAdapter(message->((DedicatedMessage)message).getSubscription().getCallbackUrl()
,restTemplateBuilder.build())
.get()
Any issues with this apporach ?
I am not sure what your NRE abbreviation means, but you could throw a NoSuchSubscriptionException from your subscriptionsStore.get() method and then ignore/report that exception in an ExpressionEvaluatingRequestHandlerAdvice applied to the outbound channel adapter in its endpoint.advice() chain.

Categories

Resources