I'm new to spring websocket. I want to send product changes to clients. For this, I want to do it as follows: Client creates a socket connection and subscribes destination:
var socket = new SockJS('/websocket');
var stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
stompClient.subscribe('/product/changes', function (scoredata) {
// We received product changes
});
});
//Send Ajax request and say server I want to know product with id=5 changes.
sendAjaxRequest(5);
I've configured spring app as follow:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/product/");
registry.setApplicationDestinationPrefixes("/app");
}
}
Now I need the following method:
@RestController
public class ProductController {
@GetMapping("product-{id}")
public void startSubscribe(@PathVariable("id") Long id) {
// register current websocket session with product id and
// then with convertAndSendToUser send changes to current user.
}
}
How do I to implement it?
My question in the first place would be, why are you trying to send a http request to a rest controller when you successfully integrated websockets with stomp? If I am understanding your use case correctly, there should be three solutions I can think of atm.
You can send your request directly from your client to the server via the open websocket connection. Spring can then determine which Websocket session made the call and you can implement your business logic. You need to activate another broker called "/queue" and specify the prefix for the user target that is needed when a subscription is not intended for a broadcast. On the client side, you must also change your subscription path. Finally, you must create a class that is commented with @Controller that contains your message mappings to receive messages from the connected client.
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue", "/product"); // <- added "/queue"
registry.setApplicationDestinationPrefixes("/app");
registry.setUserDestinationPrefix("/user");
}
}
@Controller
public class WebSocketContoller{
@Autowired
private SimpMessagingTemplate simpMessagingTemplate;
@MessageMapping("/product/register")
public void register(@Payload Long productId, @Header("simpSessionId") String sessionId) {
// register current websocket session with product id and
// then with convertAndSendToUser send changes to current user.
// Example of how to send a message to the user using the sessionId
String response = "This could also be one of your product objects of type Product";
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
messagingTemplate.convertAndSendToUser(sessionId,"/queue/product/changes", response, headerAccessor.getMessageHeaders());
}
}
stompClient.subscribe('/user/queue/product/changes', function (scoredata) {
// We received product changes
});
For detailed information you can also check this answer: https://stackoverflow.com/a/26288475/11133168
However, if you really want to consider using a rest controller to start registering your process, or if it just doesn't meet your requirements, you should look at the link below. Spring is also able to track active websocket sessions and their users through an exposed SimpUserRegistry bean. However, you will need to configure a custom ChannelInterceptor adapter for your client input channel, depending on the security of your applications, to determine a user. Check this answer for detailed information and code examples: https://stackoverflow.com/a/45359294/11133168
You could also subscribe to a specific product id topic so you don't even need to know which user wants to be notified about changes for a specific product.
//e.g if you want to be notified about changes for products with id 5
stompClient.subscribe('/product/changes/5', function (scoredata) {
// We received product changes
});
@Service
public class WebSocketProductService{
@Autowired
private SimpMessagingTemplate simpMessagingTemplate;
// This would be the method which should inform your clients about specific product
// changes, instead of the String parameters a Product object should be used instead,
// you have to call this method yourself on product changes or schedule it or sth.
public void sendProductChange(String product, String productId) {
this.simpMessagingTemplate.convertAndSend("/product/changes/"+productId, product);
}
}
Needed if you want to manage a list of product id subscriptions. Like explained in solution 1 you need a class annotated with @Controller which contains a method annotated with @SubscribeMapping. This method gets called if a a client tries to subscribe to the specified path.
@Controller
public class WebSocketContoller{
@SubscribeMapping("/product/changes/{productId}")
public void productIdSubscription(@DestinationVariable Long productId) {
//Manage your product id subscription list e.g.
}
}