I created a basic web socket with a tutorial.
Here is a configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat");
registry.addEndpoint("/chat").withSockJS();
}
}
And here is the message handling controller:
#MessageMapping("/chat")
#SendTo("/topic/messages")
public OutputMessage send(Message message) throws Exception {
return new OutputMessage("Hello World!");
}
Everything works, but from my investigation, it looks like the WebSockets by default has an application scope (by connecting to the channel I can see all calls, from all users).
What I want to do is to be able to see only calls from the current user session or current view only.
Any ideas on how to apply these configurations?
I was able to solve this puzzle, so I'm sharing with you my findings.
First, I found information that a simple in-memory message broker can not handle this:
/*
* This enables a simple (in-memory) message broker for our application.
* The `/topic` designates that any destination prefixed with `/topic`
* will be routed back to the client.
* It's important to keep in mind, this will not work with more than one
* application instance, and it does not support all of the features a
* full message broker like RabbitMQ, ActiveMQ, etc... provide.
*/
But this was misleading, as it can be easily achieved by #SendToUser annotation.
Also, the important thing that now on the client-side, you need to add an additional prefix /user/ while subscribing to the channel, so the solution would be:
On the server-side: change #SendTo("/topic/messages") into #SendToUser("/topic/messages").
On the client-side: /topic/messages into the /user/topic/messages.
Related
Could anyone tell me if the server-side implementation is using stomp WebSocket, is the client also expected to implement stomp?
I am trying to implement a spring boot application and I am confused if I should go with or without stomp implementation. From my research, I understand, if you want to scale the application, it is better to use stomp and embedded broker( RabbitMQ for eg.) as it will handle the sessions, heartbeat etc. instead of an in-memory broker.
The examples available online just shows implementations with and without stomp.
I am basically trying to get different datasets from the table upon client request and write to a WebSocket continuously.
Could anyone please confirm if my understanding so far is correct?
What are the essential things I will have to take care of if I go with stomp + websocket?
Updating the usecase below:
The mobile client would be displaying charts upon user login. There would be links in the left panel for eg. Sales, Discounts etc. which upon clicking, the request will reach server through websocket channel. Server will check the datatype in the request, generate the model using data from DB and write the data to the websocket.
Updating code - v1
MyWebSocketHandler:
#Component
public class MyWebSocketHandler extends TextWebSocketHandler {
Logger logger = LoggerFactory.getLogger(getClass());
#Autowired
DashboardUtil dashboardutil;
#Resource(name = "socketSessionsMap")
private Map<String, WebSocketSession> socketSessionsMap;
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message)
throws InterruptedException, IOException {
try {
//Gets the socket session from map and writes a json to that socket - did for testing purpose.
socketSessionsMap.put("session", session);
//String payload = message.getPayload();
String jsonString = dashboardutil.getDataInJSON(); // gets hardcoded json model
session.sendMessage(new TextMessage(jsonString));
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
}
#Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
}
}
WebSecurityConfig:
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
#Autowired
private MyWebSocketHandler myWebSocketHandler;
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myWebSocketHandler, "/socketHandler").setAllowedOrigins("*").withSockJS();
}
}
Could anyone tell me if the server-side implementation is using stomp
WebSocket, is the client also expected to implement stomp?
You can register multiple handlers in your web socket configuration. So in theory you can provide a handler for STOMP and another one for plain web socket. If you only provide a STOMP handler then the handshake from a standard web socket client will fail.
From my research, I understand, if you want to scale the application,
it is better to use stomp and embedded broker( RabbitMQ for eg.) as it
will handle the sessions, heartbeat etc. instead of an in-memory
broker.
That's correct. STOMP also offers a few more nice features especially the subscription to certain endpoints.
I am basically trying to get different datasets from the table upon
client request and write to a WebSocket continuously.
That's a really simple description ...
You should think about if you need to share sessions across multiple instances or if you need to send messages between web socket sessions.
From your description it sounds like you just accept a web socket connection and continuously push data to the client. If you want to scale this application you can just put a load balancer in front of your instances and you are good to go.
With the example provided by spring.io and http://www.baeldung.com/websockets-spring is helped to create a websocket connection between client and server, but my case is.
- Some one is creating message from UI that is passed to Spring controller (Separate controller).
- From this controller I need to notify/send/broadcast this message to all connected clients.
- How the message is passed to handler from controller where message is received.
I also refereed WebSocket with Sockjs & Spring 4 but without Stomp here and the same question is posted.
Can some one help me here, Thanks in advance !!
I actually write for Baeldung too and am currently writing a small article about how to add security to websockets in Spring! There are just a few steps you need to do to get this all working!
Backend-wise (since you said the UI was already done or being built, I'll just focus on the backend here), it really involves three parts: (1) the necessary POJO's, the controller, and the configuration.
Your POJO's will be very simple - here we just use Greeting and Message which specify a name and basic text data type (I'll skip over this here to save space but you can see it in the resource below).
Your controller will look like this:
#Controller
public class GreetingController {
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(1000); // simulated delay
return new Greeting("Hello, " + message.getName() + "!");
}
}
Take a look at the annotations - those are really what set this controller apart from say a normal REST controller.
And your configuration looks like this - again take a look at the annotations - particularly '#EnableWebSocketMessageBroker' - and the class 'AbstractWebSocketMessageBrokerConfigurer':
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").withSockJS();
}
}
A look at this great resource too: https://spring.io/guides/gs/messaging-stomp-websocket/
I want to exchange messages by web sockets between 2 java apps.
I have the following server configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/queue", "/topic");
registry.setUserDestinationPrefix("/user");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//todo remove handshake handler when authorization is implemented
registry.addEndpoint("/ws").setAllowedOrigins("*").setHandshakeHandler(new TestHandshakeHandler()).withSockJS();
}
}
and inside class marked with #Controller I have wrote following theme:
#MessageMapping("/consumer/client/add")
public void addClientRequest(String msgReq) {
logger.info(msgReq);
}
and inside clien I do connect and in sime bean I wrote following:
#Autowired
private SimpMessagingTemplate simpMessagingTemplate;
...
simpMessagingTemplate.convertAndSend("/app/consumer/client/add", new StubObject("message"));
But after sending from client method addClientRequest doesn't invoke.
Please advice ways to troubleshot this issue.
Actually I don't understand issue. Maybe I send to wrong destination or I have issue with configuration or path is wrong or something else.
P.S.
I know that I can extend StompSessionHandlerAdapter
and obtain session from there but looks like it is the bad style and should be another way to achieve it
P.S.2
Inside class WebSocketTcpConnectionHandlerAdapter(inner class inside WebSocketStompClient) I see private volatile WebSocketSession session;
I want to obtain this object to send messages
I don't think it was designed to be used like this.
I think you must use a specific websocket client. This one for exemple :
http://www.programmingforliving.com/2013/08/jsr-356-java-api-for-websocket-client-api.html
This code :
#MessageMapping("/consumer/client/add")
public void addClientRequest(String msgReq) {
logger.info(msgReq);
}
Will NOT connect to a websocket client and wait to have messages. It expect a client to connect throught it and send messages.
I have a Spring Websocket Stomp application that accepts SUBSCRIBE requests.
In application I have a handler for SUBSCRIBE, that is,
#Component
public class SubscribeStompEventHandler implements ApplicationListener<SessionSubscribeEvent> {
#Override
public void onApplicationEvent(SessionSubscribeEvent event) {}
}
that I use to validate subscription.
In case if subscription is invalid, for instance, current user can not see that subscription, I would like Broker (I use SimpleMessagingBroker) to "forget" that subscription, or preferably, do not register it at all.
My questions are:
Can I make Broker to not register the subscription, if I move handling of subscription request to incoming message interceptor and stop message propagation?
What else could be used from this event handler to cancel the subscription?
You need to create you ChannelInterceptor implementation. Just extend ChannelInterceptorAdapter and override preSend(Message<?> message, MessageChannel channel). Here you will get access to headers with session information for validation. Also you need to registrate your interceptor
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.configureBrokerChannel().interceptors(new YourInterceptor())
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
}
More information here How to reject topic subscription based on user rights with Spring-websocket
I am struggling to find examples using the #Router annotation. If I am understanding the javadocs correctly:
#Service
public class AgentServiceImpl implements AgentService {
#Override
#Router(inputChannel = "agentLogin", defaultOutputChannel = "agentServiceResponse")
public AgentLoginResponse login(AgentLoginRequest request) {
}
}
In the xml examples with the router there was a service-activator, which is where I am getting hung up on trying to figure out how it will fit in.
Actually you do that wrong way. See #Router JavaDocs:
* Indicates that a method is capable of resolving to a channel or channel name
* based on a message, message header(s), or both.
So, your login method for the target router component must return a channel name or entire MessageChannel object.