I tried to implement this example https://spring.io/guides/gs/messaging-stomp-websocket/ and it all worked fine. So i moved forward and tried to make it work with a standalone HornetQ.
So, i defined a topic in HornetQ config - /topic/requests
Here are the changes i've made
In index.html i got rid of sockJS
function connect() {
var ws = 'ws://127.0.0.1:61613/stomp';
stompClient = Stomp.client(ws);
stompClient.connect("guest", "guest", function(frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('jms.topic.requests', function(greeting){
showGreeting(JSON.parse(greeting.body).content);
});
});
}
Sending message from browser
function sendName() {
var name = document.getElementById('name').value;
stompClient.send("jms.topic.requests", {}, JSON.stringify({ 'name': name }));
}
ShowGreeting
function showGreeting(message) {
var response = document.getElementById('response');
console.log('response: ' + response);
var p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.appendChild(document.createTextNode(message));
response.appendChild(p);
}
Configured Spring to work with a standalone broker
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/stomp");
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/topic", "/queue");
//registry.setApplicationDestinationPrefixes("/jms");
}
Controller
#MessageMapping("/stomp")
#SendTo("/topic/requests")
public Greeting greeting(HelloMessage message) throws Exception {
System.out.println("Controller called!");
Thread.sleep(3000); // simulated delay
return new Greeting("Hello, " + message.getName() + "!");
}
As a result, it can connect to a running instance of hornetQ and send messages to a topic. However, instead of printing specified string back it just prints "undefined". I know that message is reaching the queue, as i have another browser-based subscriber.
That println in controller is never called, so i suspect i've failed to properly configure it, but i have no idea what would it be.
Greeting
public class Greeting {
private String content;
public Greeting(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
Related
I want to initiate a trigger(maybe a notification) from backend(based in spring boot) to a particular user whose userId is xyz.
the one way i have found is:
initially i connect to a websocket end point and subscribe to channel "/user/Notifications/xyz"
following is the relevant code in my angular typescript
connectToUserWebSocket(userId) {
let socket = new SockJS('http://localhost:5000/fellowGenius');
this.ws = Stomp.over(socket);
let that = this;
this.ws.connect(
{},
(frame) => {
that.ws.subscribe('/user/Notifications/' +userId, (message) => {
console.log("user subscribed");
});
},
(error) => {
alert('STOMP error ' + error);
}
);
}
Now once i have subscribed to my channel . I want to send a trigger to client which is initiated by backend itself so i run a code in my java service.
My relevant java code is:
#SendTo("/user/Notifications/{userId}")
public String sendMeetingNotificationWebSocket(#DestinationVariable String userId) {
return "hello";
}
my websocket configurations are:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer{
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/fellowGenius").setAllowedOrigins("*").addInterceptors(new HttpSessionHandshakeInterceptor()).withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/inbox/","/user/Notifications/");
}
}
But the problem is that even i can see one web socket connected in my spring boot console.
But i don't get a response from the function on the client side.
Please help me with this problem.
Scenario:
Client(angular js 1.4) will call Rest endpoint to get data, the server(spring boot) will process the list of files and will return accurate data. To process the list of files, the server will take time depending on the number of files. so I have implements STOMP notification as to send a notification to the client saying "List of files have been processed and here is the bunch of files(result)".
Issue:
Stomp connection is established successfully and client also gets subscribed, but when the server publishes the events, client is not able to receive.
Below is my code snippet:
WebSocketConfig.java
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic"); // Enables a simple in-memory broker
}
}
SocketController.java
#Controller
public class SocketController {
#SendTo("/topic/public")
public String sendMessage() {
LOGGER.info("====> chatMessage()");
return "List updated successfully";
}
}
main.js
connect() {
var socket = new SockJS('/ws');
var stompClient = Stomp.over(socket);
event.preventDefault();
console.log("socket: ", socket);
console.log("stompClient: ", stompClient);
stompClient.connect({}, function (frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/public', function (payload) {
console.log("payload: ", payload);
var message = JSON.parse(payload.body);
console.log("message: ", message);
});
}, function (error) {
console.log("onError() called");
console.log("error: ",error);
});
};
Scripts used:
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
connect() method of main.js is called on button click. Probably there is an issue regarding scope I guess.
Use SimpMessagingTemplate to convert and send messages to the specific topic.
#Controller
public class SocketController {
private static final Logger LOGGER = LoggerFactory.getLogger(SocketController.class);
#Autowired private SimpMessagingTemplate template;
public void sendMessage(String message) {
LOGGER.info("====> sendMessage:");
this.template.convertAndSend("/topic/public", message);
}
}
The Spring framework support tcp connection as well , i wrote code below to setup a simple socket server , i am confused about adding below futures to my socket server :
authorizing clients based on a unique identifier ( for example a client secret received from client, maybe using TCP Connection Events )
send a message directly to specific client (based on identifier)
broadcast a message
UPDATE :
Config.sendMessage added to send message to single client
Config.broadCast added to broadcast message
authorizeIncomingConnection to authorize clients , accept or reject connections
tcpConnections static filed added to keep tcpEvent sources
Questions !
is using tcpConnections HashMap good idea ?!
is the authorization method i implemented a good one ?!
Main.java
#SpringBootApplication
public class Main {
public static void main(final String[] args) {
SpringApplication.run(Main.class, args);
}
}
Config.java
#EnableIntegration
#IntegrationComponentScan
#Configuration
public class Config implements ApplicationListener<TcpConnectionEvent> {
private static final Logger LOGGER = Logger.getLogger(Config.class.getName());
#Bean
public AbstractServerConnectionFactory AbstractServerConnectionFactory() {
return new TcpNetServerConnectionFactory(8181);
}
#Bean
public TcpInboundGateway TcpInboundGateway(AbstractServerConnectionFactory connectionFactory) {
TcpInboundGateway inGate = new TcpInboundGateway();
inGate.setConnectionFactory(connectionFactory);
inGate.setRequestChannel(getMessageChannel());
return inGate;
}
#Bean
public MessageChannel getMessageChannel() {
return new DirectChannel();
}
#MessageEndpoint
public class Echo {
#Transformer(inputChannel = "getMessageChannel")
public String convert(byte[] bytes) throws Exception {
return new String(bytes);
}
}
private static ConcurrentHashMap<String, TcpConnection> tcpConnections = new ConcurrentHashMap<>();
#Override
public void onApplicationEvent(TcpConnectionEvent tcpEvent) {
TcpConnection source = (TcpConnection) tcpEvent.getSource();
if (tcpEvent instanceof TcpConnectionOpenEvent) {
LOGGER.info("Socket Opened " + source.getConnectionId());
tcpConnections.put(tcpEvent.getConnectionId(), source);
if (!authorizeIncomingConnection(source.getSocketInfo())) {
LOGGER.warn("Socket Rejected " + source.getConnectionId());
source.close();
}
} else if (tcpEvent instanceof TcpConnectionCloseEvent) {
LOGGER.info("Socket Closed " + source.getConnectionId());
tcpConnections.remove(source.getConnectionId());
}
}
private boolean authorizeIncomingConnection(SocketInfo socketInfo) {
//Authorization Logic , Like Ip,Mac Address WhiteList or anyThing else !
return (System.currentTimeMillis() / 1000) % 2 == 0;
}
public static String broadCast(String message) {
Set<String> connectionIds = tcpConnections.keySet();
int successCounter = 0;
int FailureCounter = 0;
for (String connectionId : connectionIds) {
try {
sendMessage(connectionId, message);
successCounter++;
} catch (Exception e) {
FailureCounter++;
}
}
return "BroadCast Result , Success : " + successCounter + " Failure : " + FailureCounter;
}
public static void sendMessage(String connectionId, final String message) throws Exception {
tcpConnections.get(connectionId).send(new Message<String>() {
#Override
public String getPayload() {
return message;
}
#Override
public MessageHeaders getHeaders() {
return null;
}
});
}
}
MainController.java
#Controller
public class MainController {
#RequestMapping("/notify/{connectionId}/{message}")
#ResponseBody
public String home(#PathVariable String connectionId, #PathVariable String message) {
try {
Config.sendMessage(connectionId, message);
return "Client Notified !";
} catch (Exception e) {
return "Failed To Notify Client , cause : \n " + e.toString();
}
}
#RequestMapping("/broadCast/{message}")
#ResponseBody
public String home(#PathVariable String message) {
return Config.broadCast(message);
}
}
Usage :
Socket Request/Response Mode
notify single client
http://localhost:8080/notify/{connectionId}/{message}
broadCast
http://localhost:8080/broadCast/{message}
The TcpConnectionOpenEvent contains a connectionId property. Each message coming from that client will have the same property in the IpHeaders.CONNECTION_ID message header.
Add a custom router that keeps track of the logged-on state of each connection.
Lookup the connection id and if not authenticated, route to a challenge/response subflow.
When authenticated, route to the normal flow.
To use arbitrary messaging (rather than request/response) use a TcpReceivingChannelAdapter and TcpSendingMessageHandler instead of an inbound gateway. Both configured to use the same connection factory. For each message sent to the message handler, add the IpHeaders.CONNECTION_ID header to target the specific client.
To broadcast, send a message for each connection id.
I just started with Atmosphere for a simple chat application. I downloaded an example with java. This app is sending messages to all clients how can I send a message to a particular client. i think I am able to get uuid. Please someone guide me in right direction.
#Config
#ManagedService(path = "/chat", atmosphereConfig = MAX_INACTIVE + "=9990000")
public class Chat {
private final Logger logger = LoggerFactory.getLogger(Chat.class);
#Inject
private BroadcasterFactory factory;
#Heartbeat
public void onHeartbeat(final AtmosphereResourceEvent event) {
logger.trace("Heartbeat send by {}", event.getResource());
}
#Ready
public void onReady(final AtmosphereResource r) {
logger.info("Browser {} connected", r.uuid());
if(null!=factory && null!=factory.getClass()){
logger.info("BroadcasterFactory used {}", factory.getClass().getName());
}
}
#Disconnect
public void onDisconnect(AtmosphereResourceEvent event) {
if (event.isCancelled()) {
logger.info("Browser {} unexpectedly disconnected", event.getResource().uuid());
} else if (event.isClosedByClient()) {
logger.info("Browser {} closed the connection", event.getResource().uuid());
}
}
#org.atmosphere.config.service.Message(encoders = {JacksonEncoder.class}, decoders = {JacksonDecoder.class})
#DeliverTo(DeliverTo.DELIVER_TO.BROADCASTER)
public Message onMessage(Message message) throws IOException {
logger.info("{} just send {}", message.getAuthor(), message.getMessage());
return message;
}
}
Your Java class is incomplete.
Firstly, there is a missing variable which identify each chat room in your path :
#ManagedService(path = "/chat/{chatRoomId}", atmosphereConfig = MAX_INACTIVE + "=9990000")
public class Chat {
#PathParam("chatRoomId")
private String chatRoomId;
[...]
}
But, you can send all messages to only one socke connected.
Secondly, where is your script JS file to send and receive websocket message ?
This script JS file must contains these methods :
request.onOpen = function(request, response) {
};
request.onTransportFailure = function(request, response) {
};
request.onMessage = function(request, response) {
};
request.onClose = function(request, response) {
};
request.onError= function(request, response) {
};
request.onReconnect = function(request, response) {
};
The most important is to declare the structure of your request :
var socket = atmosphere;
var subSocket;
var transport = 'websocket';
var request = {
url: document.location.toString() + 'chat' + chatRoomId,
contentType : "application/json",
logLevel : 'debug',
transport : transport ,
trackMessageLength : true,
reconnectInterval : 5000
};
I have an JMS listener, and I have to take the message, manipulate it and then redirect it to an page using websocket.
Well, I’m just confused about the configuration, I have configured the WebSocketConfig:
#Configuration
#EnableWebSocketMessageBroker
#EnableScheduling
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws/firstep/").withSockJS();
registry.addEndpoint("/ws/secondep/").withSockJS();
}
And this it should be correct, Then my webpage is:
var socket = new SockJS("/myapp-web/api/ws/secondep/",undefined,options);
var stompClient = Stomp.over(socket);
stompClient.connect({
company : "xxx"
}, function(frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/register', function(message){
console.log('message: ' + message);
});
stompClient.subscribe('/topic/update', function(message){
console.log('message: ' + message);
});
And the connection works.
Now On my jms listener I tried to send a message in this way:
public class ImporterListener implements MessageListener {
Logger logger = LoggerFactory.getLogger(ImporterListner.class);
#SendTo("/topic/register")
private String TestMessage() {
return "TestMessage";
}
#Override
public void onMessage(Message message) {
logger.info("Request on message");
if (message instanceof MapMessage) {
MapMessage t = (MapMessage) message;
TestMessage(); //<--- have to send the message here
But it doesn’t work.
The questions are:
How to send a message?
Where do I have to specify the end point (secondep) when I send a message?
Thank you! any help is appreciated!
TestMessage(); //<--- have to send the message here
No, it has't to send, because you use method from the same class, but #SendTo makes your ImporterListener as proxy and Advice will work on method only from another component.
You should inject this:
#Autowired
#Qualifier("brokerMessagingTemplate")
private MessageSendingOperations brokerMessagingTemplate;
And send a message using that:
brokerMessagingTemplate.convertAndSend("/topic/register", "TestMessage");
Where do I have to specify the end point (secondep) when I send a message?
It is for the #MessageMapping("/ws/secondep") on some POJO method to receive message from clients. It isn't for the sending part.