SimpMessagingTemplate.convertAndSend not works - spring-boot

I'm trying to send message to subscribed user with spring-starter-websocket, but it looks like convertAndSend simply doesn't send message. #SendTo works.
My config:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
private final HttpHandshakeInterceptor httpHandshakeInterceptor;
public WebSocketConfig(HttpHandshakeInterceptor httpHandshakeInterceptor) {
this.httpHandshakeInterceptor = httpHandshakeInterceptor;
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket").setAllowedOrigins("*").withSockJS().setInterceptors(httpHandshakeInterceptor);
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/notifications");
registry.setApplicationDestinationPrefixes("/app");
}
Controller:
#Controller
public class NotificationController {
private final NotificationService notificationService;
private final SimpMessagingTemplate template;
public NotificationController(NotificationService notificationService, SimpMessagingTemplate template) {
this.notificationService = notificationService;
this.template = template;
}
#MessageMapping("/echo")
public void send(String text) {
template.convertAndSend("/notifications", text);
}
}
Test Case:
#Autowired
NotificationService notificationService;
#Test
public void testSubscribe() throws ExecutionException, InterruptedException {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Authorization", "Bearer " + token);
StompSession stompSession = stompClient.connect(WEBSOCKET_URI, new WebSocketHttpHeaders(httpHeaders),
new StompSessionHandlerAdapter(){}).get();
stompSession.subscribe(WEBSOCKET_TOPIC, new DefaultStompFrameHandler());
stompSession.send("/app/echo", "Test".getBytes());
Assert.assertEquals("Test", blockingQueue.poll(1, SECONDS));
}
class DefaultStompFrameHandler implements StompFrameHandler {
#Override
public Type getPayloadType(StompHeaders stompHeaders) {
return byte[].class;
}
#Override
public void handleFrame(StompHeaders stompHeaders, Object o) {
blockingQueue.offer(new String((byte[]) o));
}
}
As result, i have emзty queue. #SentTo works perfect.
UPD: convertAndSend works, but only from controller.
P.S. Sorry for my English, hope you understood problem :-)

Related

I am not receiving WebSocket messages (SimpMessagingTemplate)

I am not able to get my WS working. I wrote this test and it is still failing when I use simpMessagingTemplate but when I use stompSession.send it works. Could anyone help me where I am making mistake?
The problem is NOT in the test PROBABLY, I am not able to make simpMessagingTemplate working in any service.
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ExtendWith(MockitoExtension.class)
public class WebSocketSpec {
#LocalServerPort
private Integer port;
#Autowired
private SimpMessagingTemplate simpMessagingTemplate;
private WebSocketStompClient webSocketStompClient;
#BeforeEach
public void initialize(){
webSocketStompClient = new WebSocketStompClient(new SockJsClient(List.of(new WebSocketTransport(new StandardWebSocketClient()))));
}
#Test #DisplayName("test subscription to /topic/plcs/connection-status")
void testSubscriptionToPlcConnectionEndpoint() throws Exception{
CountDownLatch latch = new CountDownLatch(1);
webSocketStompClient.setMessageConverter(new MappingJackson2MessageConverter());
StompSession stompSession = webSocketStompClient.connect("ws://localhost:" + port+"/api/ws", new StompSessionHandlerAdapter() {
}).get(1, TimeUnit.SECONDS);
stompSession.subscribe("/topic/plcs/connection-status", new StompFrameHandler() {
#Override
public Type getPayloadType(StompHeaders stompHeaders) {
return String.class;
}
#Override
public void handleFrame(StompHeaders stompHeaders, Object o) {
latch.countDown();
System.out.println("RECEIVED: "+o);
}
});
//stompSession.send("/topic/plcs/connection-status", "SENT FROM TEST!!!");
simpMessagingTemplate.convertAndSend("/topic/plcs/connection-status", "SENT FROM TEST!!!");
if (!latch.await(5, TimeUnit.SECONDS)){
fail("Message not received");
}
}
}
My configuration for WS looks like this:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/queue", "/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").withSockJS();
}
}
So, there are actually two problems ...
first is that you are using wrong messageConvertor (new MappingJackson2MessageConverter() instead of new StringMessageConverter())
the simpMessagingTemplate.convertAndSend("/topic/plcs/connection-status", "SENT FROM TEST!!!"); is fired before the client is subscribed, I have just tried to put there some delay before it with Thread.sleep(500); and it worked.

Flutter Client Subscription to Spring Boot Websocket Server

Below is my Spring Boot Code for scheduling messages to its connected clients.
But my FLUTTER application is not able to receive the the pushed messages from the websocket server.
#Service
public class GreetingService {
private final SimpMessagingTemplate simpMessagingTemplate;
private static final String WS_MESSAGE_TRANSFER_DESTINATION = "/topic/greetings";
private List<String> userNames = new ArrayList<>();
GreetingService(SimpMessagingTemplate simpMessagingTemplate) {
this.simpMessagingTemplate = simpMessagingTemplate;
}
public void sendMessages() {
for (String userName : userNames) {
simpMessagingTemplate.convertAndSendToUser(userName, WS_MESSAGE_TRANSFER_DESTINATION,
"Hallo " + userName + " at " + new Date().toString());
}
}
public void addUserName(String username) {
userNames.add(username);
}
}
Flutter Code :-
var channel = IOWebSocketChannel.connect("ws://1f470ad1bdc8.ngrok.io/ws");
channel.stream.listen((message) {
channel.sink.add("received!");
});
You have to create a Spring Configuration class for initializing the subscription paths.
#Configuration
#EnableWebSocketMessageBroker
public class WSocketBrokerConfiguration implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/socket")
.setAllowedOrigins("*");
}
}

Injecting dependency inside AbstractWebSocketHandler

How to inject a dependency inside a Web Socket handler:
public class WebsocketHandler extends AbstractWebSocketHandler {
#Autowired
GreetingMap greetingMap;
#Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
// NullPointerException here
String greeting = greetingMap.getSampleGreetings().get("hello") + " " + message.getPayload();
session.sendMessage(new TextMessage(greeting));
}
}
The code above throws NullPointerException
What could be missing here?
Try using dependency injection with constructor instead #Autowired:
private GreetingMap greetingMap;
public WebsocketHandler(GreetingMap greetingMap){
this.greetingMap = greetingMap
}
I think the problem is that SocketHanler is not a spring bean, but is created by "new" operator:
#Configuration
#EnableWebSocket
public class WebSocketsConfiguration implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new SocketHandler(), "/socket")
.setAllowedOrigins("*");
}
}
What you need to do in this case, is to inject your dependency into WebSocketConfiguration and pass it manually to SocketHandler constructor:
#Configuration
#EnableWebSocket
public class WebSocketsConfiguration implements WebSocketConfigurer {
#Autowired
MyDependency myDependency;
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new SocketHandler(myDependency), "/socket")
.setAllowedOrigins("*");
}
}
And in the handler, you need to add constructor that receives the dependency
public class SocketHandler extends AbstractWebSocketHandler {
private MyDependency myDependency;
public SocketHandler(MyDependency myDependency) {
this.myDependency = myDependency;
}
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
System.out.println(String.format("Message from client: %s", message));
}
}

Getting Spring simpMessagingTemplate to work with websocket

I have been trying to get simpMessagingTemplate to send to websocket in Spring but to no avail. From what I can see of related stackoverflow posts and other guides, I have provided the necessary configuration and mapping of paths.
My code is shown as below:
RestController (which I use to invoke sending of the message to the websocket):
#RestController
public class RestControllers {
#Autowired
private SimpMessagingTemplate template;
#RequestMapping("/test")
public String doTest() {
Message m = new Message();
m.setFrom("foo");
m.setText("bar");
template.convertAndSend("/app/chat/test-topic", m);
return m.toString();
}
}
Controller:
#Controller
public class ChatController
{
#MessageMapping("/chat/{topic}")
#SendTo("/topic/messages")
public OutputMessage send(#DestinationVariable("topic") String topic,
Message message) throws Exception
{
System.out.println("THE MESSAGE WAS RECEIVED:" + message.toString());
return new OutputMessage(message.getFrom(), message.getText(), topic);
}
}
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) //?? alternative only?
{
registry.addEndpoint("/chat").setAllowedOrigins("*").withSockJS();
}
}

Spring - access #Autowired service from AbstractMessageHandler

I have a SpringBootApplication which subscribes to MQTT broker. MQTT messages need to be saved to Database, but I cannot access my #Autowired service.
Exception I get:
Field deviceService in com.example.MqttMessageHandler required a bean of type 'com.example.service.DeviceService' that could not be found.
MQTTApiApplication.java
#SpringBootApplication(scanBasePackages = "{com.example}")
public class MQTTApiApplication {
public static void main(String[] args) {
SpringApplicationBuilder(MQTTApiApplication.class)
.web(false).run(args);
}
#Bean
public IntegrationFlow mqttInFlow() {
return IntegrationFlows.from(mqttInbound())
.handle(new MqttMessageHandler())
.get();
}
}
MqttMessageHandler.java
public class MqttMessageHandler extends AbstractMessageHandler {
#Autowired
DeviceService deviceService;
#Override
protected void handleMessageInternal(Message<?> message) throws Exception {
deviceService.saveDevice(new Device());
}
}
Application.java
#SpringBootApplication
public class MQTTApiApplication {
public static void main(String[] args) {
SpringApplication.run(MQTTApiApplication.class, args);
}
#Bean
public IntegrationFlow mqttinFlow(MqttMessageHandler handler) {
return IntegrationFlows
.from(mqttInbound())
.handle(handler).get();
}
}
MqqtMessageHandler.java
#Component
public class MqttMessageHandler extends AbstractMessageHandler{
#Autowired
private DeviceService deviceService;
#Override
protected void handleMessageInternal(String message) {
//...
}
}
DeviceService.java
#Service
public class DeviceService {
#Autowired
private DeviceRepository repository;
//...
}
DeviceController.java
#RestController
#RequestMapping("/")
public class DeviceController {
#Autowired
private IntegrationFlow flow;
//...
}
DeviceRepository.java
#Repository
public class DeviceRepository {
public void save() {
//...
}
}

Resources