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.
Related
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("*");
}
}
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 :-)
I'm experiencing a slow Stomp connection between two Spring microservices, the client takes about 5 minutes to send the CONNECT Stomp message. Both microservices run on the same node.
Spring Boot version: 2.1.3.RELEASE
The Websocket server is configured in the following way:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-subscribe").setAllowedOrigins("*").withSockJS();
}
}
The client is configured this way:
#Component
public class WebsocketClient {
private GlobalConfiguration globalConfiguration;
private WebsocketListener websocketListener;
private WebSocketStompClient stompClient;
#Autowired
private JwtTokenGenerator jwtTokenGenerator;
private ApplicationContext appContext;
private Logger LOGGER = LoggerFactory.getLogger(WebsocketClient.class);
#Autowired
public WebsocketClient(ApplicationContext context,GlobalConfiguration globalConfiguration) {
Transport webSocketTransport = new WebSocketTransport(new StandardWebSocketClient());
List<Transport> transports = Collections.singletonList(webSocketTransport);
SockJsClient sockJsClient = new SockJsClient(transports);
sockJsClient.setMessageCodec(new Jackson2SockJsMessageCodec());
this.stompClient = new WebSocketStompClient(sockJsClient);
this.globalConfiguration = globalConfiguration;
this.websocketListener = new WebsocketListener(context);
}
public ListenableFuture<StompSession> connectToWebsocket(){
String url = globalConfiguration.getOrchestratorWs();
StompHeaders connectHeaders = new StompHeaders();
WebSocketHttpHeaders webSocketHttpHeaders = new WebSocketHttpHeaders();
webSocketHttpHeaders.add("Authorization",jwtTokenGenerator.token);
LOGGER.info("webSocketHttpHeaders: " + webSocketHttpHeaders.toString());
LOGGER.info("url: " + url);
this.stompClient.setMessageConverter(new MappingJackson2MessageConverter());
return stompClient.connect(url,webSocketHttpHeaders,connectHeaders, websocketListener);
}
}
And client-side we have this Websocket listener:
public class WebsocketListener extends StompSessionHandlerAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(WebsocketListener.class);
private GlobalConfiguration globalConfiguration;
private ApplicationContext appContext;
public WebsocketListener(ApplicationContext appContext) {
this.appContext = appContext;
this.globalConfiguration = appContext.getBean(GlobalConfiguration.class);
}
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
LOGGER.info("New session established : " + globalConfiguration.getId());
session.subscribe("/topic/injections/"+globalConfiguration.getId(), this);
LOGGER.info("Subscribed to /topic/injections/"+globalConfiguration.getId());
// session.send("/app/chat", getSampleMessage());
// logger.info("Message sent to websocket server");
}
#Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
LOGGER.error("Got an exception", exception);
}
#Override
public Type getPayloadType(StompHeaders headers) {
return Map.class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
LOGGER.info("Injection Received : " + payload);
InjectionHandler injectionHandler = new InjectionHandler(appContext,(HashMap) payload);
injectionHandler.start();
}
}
The afterConnected is invoked about 5 minutes after the ws://10.2.0.43:7071/ws-subscribe/513/8306a7ac357847678795b049450fb6c5/websocket
Inspecting the source code of the Stomp library, I saw that the code where the microservice remains stuck for those 5 minutes is the following:
public ListenableFuture<StompSession> connect(URI url, #Nullable WebSocketHttpHeaders handshakeHeaders, #Nullable StompHeaders connectHeaders, StompSessionHandler sessionHandler) {
Assert.notNull(url, "'url' must not be null");
ConnectionHandlingStompSession session = this.createSession(connectHeaders, sessionHandler);
WebSocketStompClient.WebSocketTcpConnectionHandlerAdapter adapter = new WebSocketStompClient.WebSocketTcpConnectionHandlerAdapter(session);
this.getWebSocketClient().doHandshake(adapter, handshakeHeaders, url).addCallback(adapter);
return session.getSessionFuture();
}
In particular, the session.getSessionFuture() call.
Any idea on what may cause the delay in the CONNECT message sent by the client?
I try to test a simple MQTT listener created with Spring Boot and Spring Integration, but don't get it working. I've tried many approaches. The most promising was:
#RunWith(SpringRunner.class)
#SpringBootTest
public class BasicMqttTest {
#Value("${mqtt.client.id}")
private String mqttClientId;
#Value("${mqtt.state.topic}")
private String mqttTopic;
#Autowired
MqttPahoClientFactory mqttPahoClientFactory;
protected IMqttClient client;
#Before
public void setUp() throws Exception {
client = mqttPahoClientFactory.getClientInstance(mqttPahoClientFactory.getConnectionOptions().getServerURIs()[0], mqttClientId);
client.connect();
}
#After
public void tearDown() throws Exception {
client.disconnect();
client.close();
}
#Test
public void contextLoads() throws Exception {
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload("MQTT!".getBytes());
client.publish(mqttTopic, mqttMessage);
}
}
However, the test runs with
2018-07-12 16:53:50.937 ERROR 21160 --- [T Rec: consumer] .m.i.MqttPahoMessageDrivenChannelAdapter : Lost connection: Verbindung wurde getrennt; retrying...
, and I don't see anything printed out.
The code is mostly from: https://github.com/spring-projects/spring-integration-samples/tree/master/basic/mqtt
. The example works nicely, but I need to be able to write proper integration tests.
The configuration is:
#Value("${mqtt.server.uri}")
private String mqttServerUri;
#Value("${mqtt.username}")
private String mqttUsername;
#Value("${mqtt.password}")
private String mqttPassword;
#Value("${mqtt.client.id}")
private String mqttClientId;
#Value("${mqtt.state.topic}")
private String mqttTopic;
#Value("${mqtt.completion.timeout}")
private Integer mqttCompletionTimeout;
#Value("${mqtt.quality.of.service}")
private Integer mqttQualityOfService;
#Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(new String[]{mqttServerUri});
options.setUserName(mqttUsername);
options.setPassword(mqttPassword.toCharArray());
factory.setConnectionOptions(options);
return factory;
}
#Bean
#Qualifier("MqttInboundChannel")
public MessageProducerSupport mqttInbound() {
MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(
mqttClientId,
mqttClientFactory(),
mqttTopic
);
adapter.setCompletionTimeout(mqttCompletionTimeout);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(mqttQualityOfService);
return adapter;
}
#Bean
public IntegrationFlow myMqttInFlow() {
return IntegrationFlows.from(mqttInbound)
.handle(message -> {
System.out.println(message);
}).get();
}
UPDATE:
Didn't work either:
#Autowired
protected MessageHandler mqttOutbound;
#Test
public void test0() throws Exception {
mqttOutbound.handleMessage(MessageBuilder.withPayload("test").build());
}
#SpringBootApplication
static class MqttSourceApplication {
#Autowired
private MqttPahoClientFactory mqttClientFactory;
#Bean
public MessageHandler mqttOutbound() {
MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler("test", mqttClientFactory);
messageHandler.setAsync(true);
messageHandler.setDefaultTopic("test");
messageHandler.setConverter(pahoMessageConverter());
return messageHandler;
}
#Bean
public DefaultPahoMessageConverter pahoMessageConverter() {
DefaultPahoMessageConverter converter = new DefaultPahoMessageConverter(1, false, "UTF-8");
return converter;
}
}
UPDATE
Even simpler ... same error:
#Autowired
MqttPahoClientFactory mqttPahoClientFactory;
private MessageHandler mqttOutbound;
#Before
public void setUp() throws Exception {
MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(mqttClientId, mqttPahoClientFactory);
messageHandler.setAsync(false);
messageHandler.setDefaultTopic(mqttTopic);
messageHandler.setConverter(new DefaultPahoMessageConverter());
mqttOutbound = messageHandler;
}
#Test
public void test0() throws Exception {
mqttOutbound.handleMessage(MessageBuilder.withPayload("test").build());
Thread.sleep(10000L);
}
Ok, resolved: Paho apparently closed my connection since both the test and main used the same client id. The solution was to replace the clientId with MqttAsyncClient.generateClientId() as suggested here: https://stackoverflow.com/a/48232793/2739681
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();
}
}