Basic websocket with Spring without STOMP and SockJS - java

I have implemented the following websocket endpoint
#MessageMapping("/socket/{myId}/")
#SendTo("/queue/myqueue")
public MyObject getObject(#DestinationVariable String myId) throws Exception {
return new MyObject("MyId:" + myId);
}
Now how can I send message to that endpoint from one of my service.java class?
There will be front-end client as well, which will read the message from websocket once the service.java class's method send some message to websocket endpoint. I am a little confused that how can I do that?
Any help would be appreciated

When using a raw websocket(without STOMP), the message sent lacks of information to make Spring route it to a specific message handler method (we don't have any messaging protocol), so instead of annotating your controller, you'll have to implement a WebSocketHandler by extending TextWebSocketHandler
public void handleTextMessage(WebSocketSession session, TextMessage message){
}
Checkout an example here spring boot websocket without STOMP and SockJs

You should take a look at SimpMessagingTemplate.
For example, if you want to send a message for a specific user from your service class:
#Autowired
private SimpMessagingTemplate messagingTemplate;
public void sendMessage(User user, String message) {
Objects.requireNonNull(user);
Objects.requireNonNull(message);
messagingTemplate.convertAndSendToUser(user.getUsername(), "/queue/myqueue", message);
}

Related

Is it possible to have different controllers for each stomp endpoint in Spring Boot?

Is it possible to assign different Controller or at least a different MessageMapping for each of the stomp endpoints?
My goal is to have client1 connecting to /endpoint1 and client2 connecting to /endpoint2 without client1 being able to access any topics/queues of /endpoint2 and vice-versa (they are completely different applications).
So they would be completely encapsulated implementations based on the endpoint to which they connect.
Bonus points for being able to use different Jackson ObjectMapper for each endpoint as well.
So far I have created a websocket configuration with 2 endpoints (/endpoint1 and /endpoint2):
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/endpoint1", "/endpoint2")
.setAllowedOriginPatterns("*")
.withSockJS();
}
// etc...
}
I also have a Controller which can process requests and send them to appropriate user response queue, but it's accessible from both endpoints:
#Controller
public class WebSocketController {
#MessageMapping("/request")
#SendToUser("/queue/response")
public MyResponse handleMessage(MyRequest request) {
// implementation
}
}
Current behaviour:
It doesn't matter which endpoint is my client connecting to in my current implementation, both can access the same topics, which is unwanted behavior.
You should alter your application design so that clients would be only able to send messages to their respective STOMP destinations. You can name your STOMP destinations in a client-specific prefixed way such as:
/endpoint1/request
/endpoint2/request
You should then be able to define different #MessageMapping-annotated message handlers after the above naming pattern:
#Controller
public class WebSocketController {
#MessageMapping("/endpoint1/request")
#SendToUser("/endpoint1/queue/response")
public MyResponse handleClient1Message(MyRequest request) {
// process STOMP message from client 1
}
#MessageMapping("/endpoint2/request")
#SendToUser("/endpoint2/queue/response")
public MyResponse handleClient2Message(MyRequest request) {
// process STOMP message from client 2
}
}

HTTP POST API response without waiting for email notifications to complete Spring boot

In my Spring boot controller, I am having a method that inserts some records to the backend, at the end of this operation, I notify the user via Javax email based on the response received from from previous operation.
Currently I get response from API after email method completes.
Is there any way I can return response to the client once my first operation is completed while email notification happens in background
I tried already implement Async annotation in sendemail method of mail service. But I cannot find any difference in response time and I still get the response only after the email is sent.
My pseudo code
Controller:
#Autowired
private EmailService emailService;
#PostMapping(value = "create", produces = "text/plain")
private insertRecord()
{
response = <Insert into DB>;
sendEmail(response);
}
private sendEmail(response)
{
//check if email should be sent and if yes
emailservice.send(response);
}
Email service:
#Service
public class EmailService {
#Async
public static void sendEmail(MailEvent mailEvent) throws IOException {//send
email}
}
Starter
#SpringBootApplication
#EnableAsync
public class Starter {...}
i didn't notice my async method was designed as static, removing static works for me.
I think Spring Async is the way to go to solve your problem. Did you you also enable the async functionality with #EnableAsync and create an Executor bean? See this guide for the full tutorial: https://spring.io/guides/gs/async-method/

Sending STOMP messages from other layers of an application

I am building an application using Spring Websockets on a clustered tomcat environment with a RabbitMQ broker. I have an API module which needs to register the endpoint to listen to. I followed the normal examples and came up with this config:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer
{
#Override
public void configureMessageBroker(final MessageBrokerRegistry config)
{
config.enableStompBrokerRelay("/topic/")
.setRelayHost("localhost")
.setRelayPort(61613)
.setClientLogin("guest")
.setClientPasscode("guest");
}
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry)
{
registry.addEndpoint("/updates")
.setAllowedOrigins("*")
.withSockJS();
}
}
While this works, it doesn't solve my issue as it appears the WebSocket and relay config are all bundled into the API module therefore leaving other layers unable to reuse the broker. I need the stomp message broker relay configuration to happen at the service layer so that other modules of our app can push messages to topics in RabbitMQ which then turn around and notify the API module to update all open websockets.
Below is a sample diagram of the relevant layers in our application and what I am trying to accomplish. I need to allow the module "Cron Message Sender" to push messages to everyone who is subscribed to a message topic through our other API modules.
So the second approach did in fact work. I configured the websockets to be run independently (no relay) and then I made a separate AMQP message broker connection at the service layer to allow communication between services. In the API module, I simply listened to the AMQP message broker and then manually forwarded those messages to the SimpMessagingTemplate which notified the websocket subscribers. I am not sure if this is technically the "right" way to do it but it seems to be working great and I do not yet see any issues with the implementation. In fact, I actually think I may prefer this approach as I now just gave all my services the ability to talk to each other with more types of messages than what I originally needed for the websockets.
Here is the new configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer
{
#Override
public void configureMessageBroker(final MessageBrokerRegistry config)
{
config.enableSimpleBroker("/topic");
}
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry)
{
registry.addEndpoint("/updates")
.setAllowedOrigins("*")
.withSockJS();
}
}
And here is where I listen to the message broker and forward the messages to the websocket subscribers:
#Component
public class SendWebSocketUpdates
{
private static final Logger logger = LoggerFactory.getLogger(SendWebSocketUpdates.class);
private final Gson gson;
#Autowired
private SimpMessagingTemplate messagingTemplate;
#Autowired
private MessageBrokerConsumer<String> messageBrokerConsumer;
public SendWebSocketUpdates()
{
this.gson = new Gson();
}
#PostConstruct
public void init()
{
//listen for incoming AMQP messages from the rabbitmq server and forward them to the websocket subscribers
messageBrokerConsumer.addListener((message, topicName) -> {
final String destination = "/topic/" + topicName;
final String messageJson = gson.toJson(message.getBody());
//check to see if trace logging is enabled
if (logger.isTraceEnabled())
{
logger.trace("Sending Message to \"{}\": {}", destination, messageJson);
}
//broadcast the via a STOMP message to subscribers of this topic
messagingTemplate.convertAndSend(destination, messageJson);
});
}
}
It's easy to solve this problem. I waste a whole day to find the solution.
Here 's my answer for the same problem.
The key is setUserDestinationBroadcast and setUserRegistryBroadcast:
registry.enableStompBrokerRelay("/topic/", "/queue/", "/exchange/")
.setUserDestinationBroadcast("/topic/log-unresolved-user")
.setUserRegistryBroadcast("/topic/log-user-registry")

Push notifications using rabbitmq

I want to send notifications to mobile application using rabbitmq, the problem is that i never used amqp protocol, so i need some advices
1) As i read from here http://www.rabbitmq.com/alarms.html if i send message all cosumers will get it, do i need to create separate queue for each user?
2)I want to send push using GCM only when mobile application is turn off, can i do it using this structure(spring boot)?
#Controller
public class SampleController {
Logger logger = Logger.getLogger(SampleController.class);
#Autowired
RabbitTemplate template;
#RequestMapping("/")
#ResponseBody
String home() {
return "Empty mapping";
}
#RequestMapping("/process/{message}")
#ResponseBody
String error(#PathVariable("message") String message) {
logger.info(String.format("Emit '%s'",message));
String response = (String) template.convertSendAndReceive("query-example-2",message);
logger.info(String.format("Received on producer '%s'",response));
if(response==null) {
sendPushViaGCM(message);
}
return String.valueOf("returned from worker : " + response);
}
3) If mobile appliction is turn off and i send push using gcm how to delete message from rabbitmq queue to avoid double push when application is turn on
4)As i suggested, when client connect to my rabbitmq service all others will don't have permission to listen other queues until first one is not finished. Am i right?
Some code examples will be grateful

Where "user" comes from in convertAndSendToUser works in SockJS+Spring Websocket?

I would like to understand how convertAndSendToUser works in Spring SockJS+Websocket framework.
In client, we would connect as
stompClient.connect(login, password, callback())
which will result in connect request with "Stomp credentials" of login and password, that can be seen e.g. if we handle SessionConnectEvent http://www.sergialmar.com/2014/03/detect-websocket-connects-and-disconnects-in-spring-4/
But it remains unclear to me whether this will be the "user" meant in server-side send operation to a queue:
simpMessagingTemplate.convertAndSendToUser(username, "/queue/reply", message);
The closest I can get is to read this thread Sending message to specific user on Spring Websocket, answer by Thanh Nguyen Van, but it is still unclear.
Basically what I need to do, is to subscribe some clients to same topic, but on server, send them different data. Client may supply user identifier.
We know we can send messages to the client from a stomp server using the topic prefixes that he is subscribed to e.g. /topic/hello. We also know we can send messages to a specific user because spring provides the convertAndSendToUser(username, destination, message) API. It accepts a String username which means if we somehow have a unique username for every connection, we should be able to send messages to specific users subscribed to a topic.
What's less understood is, where does this username come from ?
This username is part of a java.security.Principal interface. Each StompHeaderAccessor or WebSocketSession object has instance of this principal and you can get the user name from it. However, as per my experiments, it is not generated automatically. It has to be generated manually by the server for every session.
To use this interface first you need to implement it.
class StompPrincipal implements Principal {
String name
StompPrincipal(String name) {
this.name = name
}
#Override
String getName() {
return name
}
}
Then you can generate a unique StompPrincipal for every connection by overriding the DefaultHandshakeHandler. You can use any logic to generate the username. Here is one potential logic which uses UUID :
class CustomHandshakeHandler extends DefaultHandshakeHandler {
// Custom class for storing principal
#Override
protected Principal determineUser(
ServerHttpRequest request,
WebSocketHandler wsHandler,
Map<String, Object> attributes
) {
// Generate principal with UUID as name
return new StompPrincipal(UUID.randomUUID().toString())
}
}
Lastly, you need to configure your websockets to use your custom handshake handler.
#Override
void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry
.addEndpoint("/stomp") // Set websocket endpoint to connect to
.setHandshakeHandler(new CustomHandshakeHandler()) // Set custom handshake handler
.withSockJS() // Add Sock JS support
}
That's It. Now your server is configured to generate a unique principal name for every connection. It will pass that principal as part of StompHeaderAccessor objects that you can access through connection event listeners, MessageMapping functions etc...
From event listeners :
#EventListener
void handleSessionConnectedEvent(SessionConnectedEvent event) {
// Get Accessor
StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage())
}
From Message Mapped APIs
#MessageMapping('/hello')
protected void hello(SimpMessageHeaderAccessor sha, Map message) {
// sha available in params
}
One last note about using convertAndSendToUser(...). When sending messages to a user, you will use something like this
convertAndSendToUser(sha.session.principal.name, '/topic/hello', message)
However, for subscribing the client, you will use
client.subscribe('/user/topic/hello', callback)
If you subscribe the client to /topic/hello you will only receive broadcasted messages.
I did not do any specific configuration and I can just do this:
#MessageMapping('/hello')
protected void hello(Principal principal, Map message) {
String username = principal.getName();
}
Similar to Wenneguen I was able to do just by injecting Principal in the MessageMapping method
public void processMessageFromClient(#Payload String message, Principal principal) {
the principal.getName() implementation is from org.springframework.security.authentication.UsernamePasswordAuthenticationToken
class

Categories

Resources