Spring websocket: how intercept the send event through the AbstractSubProtocolEvent hierarchy? - spring

About Spring websocket about intercept Stomp events one approach is extends from the ChannelInterceptorAdapter class
It works how is expected. More details here:
Spring websocket: how convert easily the Message's payload to POJO in ChannelInterceptorAdapter
Now, according with this tutorial:
Detecting WebSocket Connects and Disconnects in Spring 4
the approach mentioned above is covered and other approach is work around with ApplicationEvents, it through with:
SessionConnectEvent
SessionConnectedEvent
SessionDisconnectEvent
These classes extends from:
AbstractSubProtocolEvent
And exists two subclasses more:
SessionSubscribeEvent
SessionUnsubscribeEvent
This list is confirmed in (by the same author):
how to capture connection event in my webSocket server with Spring 4?
The names are by themselves obvious to know what each one does, but just curious
Why there is no none for the send event?
It to react from:
stompClient.send('/app/ws/something',
{},
JSON.stringify({'content': $('#content').val()})
For this send event "seems" mandatory work with the ChannelInterceptorAdapter through the postSend(Message<?> message, MessageChannel channel, boolean sent) method yet

The events reflect major points in the lifecycle of a STOMP connection. They're not meant to be notifications for every message sent from the client. For that you can use #MessageMapping methods, or a ChannelInterceptor.

Related

DefaultSubscriptionRegistry and its protected methods

I am trying to implement a WebSocket feature with STOMP in my SpringBoot application. So far, this is going quite alright, but I'm running into one issue.
Unsubscribing from a topic seems to always be done from the browser's side. However, using #DestinationVariable I can create a number of topics (e.g. with the path /{game_id}/chat), and I need a security feature on the server's side.
Because messages are authorized, I am able to check whether the logged user actually has access to {game_id}. If they don't, the subscription should end (not the WebSocket connection!). To do this, I autowired DefaultSubscriptionRegistry to delete the subscription from the list, but this method is apparently protected. I now find myself not knowing how to delete this subscription (which is managed by the simple broker Spring provides) from inside of Spring.
I guess another way to do this is by mocking an unsubscribe message from the browser and having the MessageHandler handle it. But that gives its own challenges, mainly obtaining the ApplicationContext of the simple broker (that I did not personally edit).
Has anyone faced this challenge before? Are there good workarounds/alternatives to unsubscribe from the server side?
Rossen has given an answer on GitHub that I believe will help with this.
Essentially, the approach is to register a ChannelInterceptor that creates a mock unsubscribe message:
#Override
public Message<?> beforeHandle(Message<?> message, MessageChannel channel, MessageHandler handler) {
StompHeaderAccessor headers =
StompHeaderAccessor.create(StompCommand.UNSUBSCRIBE);
// ... add headers
Message<?> unsubscribe = MessageBuilder
.withPayload(new byte[0]).setHeaders(headers).build();
messageHandler.handleMessage(unsubscribe);
return message;
}

Is it possible for my Spring EventListener to wait for 2 events to progress?

I have a SpringBoot application and have to connect to a websocket server to receive messages, which my application then processes internally using an inbuilt FinitStateMachine (FSM).
What I want is to start the connect() call to the websocket server only after:
my spring application has initialized
my FSM builder has completed the build() call
I have used Spring's ApplicationEventPublisher to publish a custom event (FSMInitializedEvent) when my FSM is built. And the code which initiates connection to websocket server waits for this custom event, but I want my websocket connector code to ALSO wait for Spring's ApplicationReadyEvent. Is there a neat way to do this?
From my FSM's build():
applicationEventPublisher.publishEvent( new FSMInitializedEvent() );
Within my WebSocket connector class:
#EventListener(FSMInitializedEvent.class) <-- HERE i want to listen for this other event too before progressing - ApplicationReadyEvent.class
public void init() {
// code to initiate the connection
}
I could programmatically handle this but looking for some ready to use elegant solution. Also, my question is different what's been suggested here: Use #EventListener annotation on multiple events in Spring, as I want my listener to wait for multiple events to have happened as a precondition, before proceeding.

How to send messages with selectors in websocket topic in Spring Boot

CLARIFICATION:
Thanks to #JustinBertram comment I realized that this question does not make sense.
STOMP protocol does not support selectors by itself, you have to use brokers such as ActiveMQ that implement it. STOMP supports headers that can be used by brokers for filtering messages by selectors.
In my case, I'm not using any broker, just frontend with Angular + Stomp + SocksJS and backend with Spring Boot, so I can't use selectors.
The documentation of STOMP protocol does not make this clear to me and I got confused. See these references:
the specification:
Stomp brokers may support the selector header which allows you to
specify an SQL 92 selector on the message headers which acts as a
filter for content based routing.
this article:
The subscribe() method takes an optional headers argument to specify
additional headers when subscribing to a destination:
var headers = {ack: 'client', 'selector': "location = 'Europe'"};
client.subscribe("/queue/test", message_callback, headers);
The client specifies that it will handle the message acknowledgement
and is interested to receive only messages matching the selector
location = 'Europe'.
I'm implementing a backend in Spring Boot. For two-way communications with the frontend I'm using stomp over websockets.
I have followed this Spring Boot + Angular example
It works, but one of my requirements is that the backend has to send messages with selectors so that the frontend subscribes to a topic and only receives the filtered data, to avoid performance issues with real time data.
i.e. { 'selector': "location = 'Europe'" }
For that purpose, I'm trying to make the backend send the messages with selectors, but I can't make it work.
I have followed this article to implement the frontend with selectors and it works correctly, the problem is only the backend.
I tried with #SendTo annotation but it seems it doesn't have any params for that as per the article:
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(1000);
return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
}
Also I tried with the MessagingTemplate, but I don't know how to set the selector properties in the header:
http://assets.spring.io/wp/WebSocketBlogPost.html
MessageSendingOperations<String> messagingTemplate;
messagingTemplate.convertAndSend(destination, quote);
I really appreciate any help, I have read many articles and docs but I don't find anything talking in particular about this with a solution.
Well, it's possible to use the JMS' selectors with Spring (Boot) Websocket and a STOMP client. I found a native way.
The key thing is that the selector is applied to the org.springframework.messaging.Message instance, and it uses Spring's Spel language to apply the condition (it's not the JMS SQL-like).
So using the default SimpMessagingTemplate, in the backend you can send header variables like this:
this.messagingTemplate.convertAndSend(
"/topic/something", //your destination
payload, //any kind of payload (body)
Map.of("id", 1) //header with key/value
);
In the frontend, to enter a selector that will be evaluated by the org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry.filterSubscriptions, you must declare your Stomp/WebSockets headers as:
{"selector": "headers['nativeHeaders']['id'][0] == '999'"}
Yeah, it's horrible but it works.
As the default Message is GenericMessage, the headers are processed in a new key called "nativeHeaders".
The ['key'],[0] and == are Spring's Spel sintaxes.
Go ahead and filter your messages on the backend, not in the frontend, please!
The latest version of the STOMP specification doesn't include any specific statement about selectors and their syntax because it's really up to the broker implementation as to what is supported here. The specification now just states:
STOMP servers MAY support additional server specific headers to customize the delivery semantics of the subscription. Consult your server's documentation for details.
Brokers like ActiveMQ 5.x and ActiveMQ Artemis support the selector STOMP header and the syntax & behavior of the selector is based on JMS selectors.
Selectors in JMS are for selecting messages on consumption and are configured by the consuming client. You can't set the selector when sending the message.
JMS selectors select messages based on the headers or properties of the message, although some implementations go beyond this and allow selecting based on the content of the message itself. Therefore, if you want to have a selector location = 'Europe' on a consumer then you should set a header on the message when it is sent with the name location and the value of Europe.
The convertAndSend method is overloaded and provides a couple of ways to set a header:
Pass a map of key/value pairs to the convertAndSend method.
Implement a MessagePostProcessor and pass that to the convertAndSend method. Inside your post-processor you can invoke the javax.jms.Message#setStringProperty() method.

Spring Cloud Stream - Output Messages from EventListener

I'm trying to utilize the #DomainEvents mechanism provided by Spring Data to publish Events with Spring Cloud Stream (Spring Boot 2.0.0.M7 and Finchley.M5). I have a two-part question.
Why does SendTo not work on EventListeners?
Is there a better way to accomplish this?
The DomainEvent is being created and sent to the EventListener without issues. The problem is that the SendTo mechanism didn't seem to be working. The first method below would trigger, but not forward the message. Manually building the Message and sending it as shown in the second method works correctly.
#Async
#TransactionalEventListener
#SendTo(Sink.Output)
StreamedEvent handleEventWithSendTo(MyEvent event) {
// handle and create event
}
#TransactionalEventListener
void handleEvent(MyEvent event) {
// handle and create event
sink.output().send(MessageBuilder.withPayload(payload).build())
}
The call-out in the Spring Cloud Stream docs shows using SendTo on a StreamListener, which is not quite the same thing as an EventListener, but I thought it may work.
For the second part, using DomainEvents requires the service code persisting the Entity to know about the event (to either call registerEvent directly or some method on the Entity which represents the event). I was curious if using the Spring Data callback hooks (e.g. PreUpdate, PostUpdate) would be better. Or if there was a better way all together.

Spring Integration: Obtaining logs and handling callbacks from the default MQTT Paho client

Below is an interesting example of sending messages over MQTT with the standard outbound-channel-adapter (not the MQTT outbound adapter):
https://github.com/joshlong/spring-integration-mqtt
The authors implement their own message handler, and pass it to the adapter.
Now my question is: Is it possible to implement a custom message handler using the MQTT outbound adapter? Or is it only possible with the general outbound-channel-adapter of Spring Integration?
My objective is to obtain logs and handle callbacks from the Paho client, so I can for example handle connection errors, timeouts, etc...
Spring Integration 4.0 provides the MQTT module with MqttPahoMessageHandler as default implementation of AbstractMqttMessageHandler.
I'd say that you can extend from MqttPahoMessageHandler to achieve your MqttCallback wishes, but yes, you can use that custom MessageHandler implementation only from <int:outbound-channel-adapter ref="">.
The out-of-the-box <int-mqtt:outbound-channel-adapter> is just for population a bean for MqttPahoMessageHandler and you can't change that behaviour.
From other side, when you will start to do Spring Integration from JavaConfig you will get deal just only with classes, so there is no boundaries to restict you with custom tags:
#ServiceActivator(inputChannel = "sendToMqttChannel")
#Bean
public MessageHandler mqttHandler() {
return new MyMqttPahoMessageHandler();
}

Resources