Spring Websocket : send notification to subscribed client without any request - java

Im writing back front java code spring 2.2.5. The front is connected to the back via a websocket. I want to send notifications to the client without request sent by client only connection and subscrition events are received by the server.
I tried some solotion based on SimpMessagingTemplate but when i'm trying to send back to the client using this interface (without request) the data is null pointer.
Controller code :
private SimpMessagingTemplate messagingTemplate;
#Autowired
public void WebSocketController(SimpMessagingTemplate messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
#RequestMapping(value = "/post/message", method = RequestMethod.POST)
public void PostMessage(#PathVariable String message) {
this.messagingTemplate.convertAndSend("/topic/myDest", message);
}
With the following config code i intercept connect and subscribe events, is there a way to save client parameter in order to send back notifications to it.
#Override
public void configureClientInboundChannel(ChannelRegistration registration){
registration.interceptors(new ChannelInterceptor() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if(StompCommand.CONNECT.equals(accessor.getCommand())){
String sessionId = accessor.getSessionId();
System.out.println("Connect " + sessionId);
} else if(StompCommand.SUBSCRIBE.equals(accessor.getCommand())){
System.out.println("Subscribe ");
} else if(StompCommand.SEND.equals(accessor.getCommand())){
System.out.println("Send message " );
} else if(StompCommand.DISCONNECT.equals(accessor.getCommand())){
System.out.println("Exit ");
} else {
}
return message;
}
});
}

I finally find the solution: In my previous PreSend interceptor, i can save all subscribed clients :
xxxx.Channels.add(channel);
In xxx class : Channels is defined as :
public final ArrayList<MessageChannel> Channels = new ArrayList<MessageChannel>();
In Post function i created a SimpMessagingTemplate instance for each channel :
for (int i = 0; i < Channels.size(); i++) {
MessageChannel channel = Channels.get(i);
SimpMessagingTemplate messagingTemplate = new SimpMessagingTemplate(channel);
messagingTemplate.convertAndSend("/topic/myDest", data.toString().getBytes());
}
And the message is correctly sent.

Related

Unable to receive the UDP response from device

I'm trying to send the UDP request and receive the response. Spring Integration has the appropriate instruments for such kind of task: UnicastSendingMessageHandler and UnicastReceivingChannelAdapter. I configured it in the following way
#Bean
public MessageChannel requestChannel() {
return new DirectChannel();
}
#Bean
#ServiceActivator(inputChannel = "requestChannel")
public UnicastSendingMessageHandler unicastSendingMessageHandler() {
UnicastSendingMessageHandler unicastSendingMessageHandler = new UnicastSendingMessageHandler("239.255.255.250", 1982);
return unicastSendingMessageHandler;
}
#Bean
public UnicastReceivingChannelAdapter unicastReceivingChannelAdapter() {
UnicastReceivingChannelAdapter unicastReceivingChannelAdapter = new UnicastReceivingChannelAdapter(8080);
unicastReceivingChannelAdapter.setOutputChannelName("nullChannel");
return unicastReceivingChannelAdapter;
}
How I send a message (I'm using sendDiscoveryMessage() wherever I want):
#Service
public class DiscoveryService {
private static final String DISCOVERY_MESSAGE = "M-SEARCH * HTTP/1.1\r\n"
+ "HOST: 239.255.255.250:1982\r\n"
+ "MAN: \"ssdp:discover\"\r\n"
+ "ST: wifi_bulb";
private final MessageChannel requestChannel;
public DiscoveryService(final MessageChannel requestChannel) {
this.requestChannel = requestChannel;
}
public void sendDiscoveryMessage() {
requestChannel.send(new GenericMessage<>(DISCOVERY_MESSAGE));
}
}
At this point, I can check the packets via WireShark and ensure that Datagram was sent and the appropriate response was sent too.
The only question is how to receive this response. As far as I understand reading the documentation, I need the method annotated with #ServiceActivator. But I don't understand where (which channel) I should receive the response (in order to correctly specify #ServiceActivator(inputChannel="")). Also, I'm not sure about #ServiceActivator(inputChannel = "requestChannel") I put for UnicastSendingMessageHandler bean.
I tried to create the following method(assuming that the response will come to the same channel):
#ServiceActivator(inputChannel = "requestChannel")
public void receiveResponse(Message<String> response) {
System.out.println(response);
}
but it actually intercepts my own request message (seems logical to me, because I send the request to requestChannel).
So I don't understand how many channels I need (maybe I need 1 for request and 1 for response) and how to create #ServiceActivator to catch the response.
unicastReceivingChannelAdapter.setOutputChannelName("nullChannel");
You are sending the result to nullChannel which is like /dev/null on Unix; you are discarding it.
Use #ServiceActivator(inputChannel = "replyChannel") and
unicastReceivingChannelAdapter.setOutputChannelName("replyChannel");

How to Implement RabbitMQ consumer part(receiver ) inside the Spring boot API

I am going to do send my DATA toRabbitMq producer(message sender) and get responsible data from RabbitMq consumer(message receiver). producer part is working fine .now my problem is how to implement consumer part (receiver part) in side the Spring boot API. .Below is My spring boot API and i written ProducerAndConsumer one class.
ProducerAndConsumer.class
#Component
public class ProducerAndConsumer {
#Autowired
private RabbitTemplate rabbitTemplate;
//MessageProducer part (send part)
public boolean sendMessage(String message) {
rabbitTemplate.convertAndSend(RobbitMqConfig.ROUTING_KEY, message);
System.out.println("Is listener returned ::: ==========="+rabbitTemplate.isReturnListener());
return rabbitTemplate.isReturnListener();
}
//Consumer part (receiver part)
#RabbitListener(queues = RobbitMqConfig.QUEUE_NAME1)
public void receiveMessage ( final Message message){
System.out.println("Received message====Receiver=====" + message.getPayload());
}
}
API part
#PostMapping(value = {"/sendFilesName"})
public ResponseEntity<?> sendFilesName(#RequestBody SendFileNameRequest sendFileNameRequest, HttpServletRequest request) throws ParseException {
System.out.println("FileNameArray="+sendFileNameRequest.getFileNameArray());
if(sendFileNameRequest.getFileNameArray().size()!=0) {
List<String> message = sendFileNameRequest.getFileNameArray();
**//see here i send my message array data**
if(producerAndConsumer.sendMessage(message.toString())){
**//here i want implement my receiver part how to?**
return ResponseEntity.ok(new ApiResponse(true, "fileName List sent successfully", "",true));
}else {
return ResponseEntity.ok(new ApiResponse(false, "fileName List sent Fails", "",true));
}
}else {
return ResponseEntity.ok(new ApiResponse(false, "fileName List not present ", "",true));
}
}
The routing algorithm behind a direct exchange is simple - a message goes to the queues whose binding key exactly matches the routing key of the message.
spring amqp
Note: Check the routing key and queues binded using rabbitmq admin console to figure out whats going on or share the rabbitmq configuration.

Spring data Redis Pub/Sub and Websockets: recieving messages

I am working with Spring Boot and Redis server for my web application. When a client sends a post, a message is sent to Redis on channel "chat". This works well. For recieving messages I created the class RedisMessageSubscriber, which recieves messages from Redis.
All clients have subscribed the STOMP Websocket client "topic/test".
My question is: How can I redirect the message I recieved in RedisMessageSubscriber to the client Websocket channel?
My first try below didn't worked, because the controller sent a message from the queue to the client before the message received from Redis.
DwittrController.class (Messages are sent to clients here):
#MessageMapping("/newPost")
#SendTo("/topic/test")
public Message message(ClientMessage message) {
log.debug("a message from client was recieved");
userRepository.sendMessage(message.getMessage());
String redisMessage = RedisMessageSubscriber.messageList.get(0);
RedisMessageSubscriber.messageList.remove(0);
return new Message("New post from " + redisMessage + " is available.");
}
UserRepositoryImpl.class:
public void sendMessage(String message) {
stringRedisTemplate.convertAndSend("chat", message);
}
RedisMessageSubscriber.class:
#Component
public class RedisMessageSubscriber implements MessageListener {
private final Logger log = LoggerFactory.getLogger(this.getClass());
public static List<String> messageList = new ArrayList<String>();
public void onMessage(Message message, byte[] pattern) {
messageList.add(message.toString());
log.debug("Message received: " + message.toString());
}
}
In your RedisMessageSubscriber class you can autowire a SimpMessagingTemplate. Code example is not precise, but you get the idea.
#Component
public class RedisMessageSubscriber implements MessageListener {
private final Logger log = LoggerFactory.getLogger(this.getClass());
public static List<String> messageList = new ArrayList<String>();
#Autowired
SimpMessagingTemplate messagingTemplate;
public void onMessage(Message message, byte[] pattern) {
messageList.add(message.toString());
log.debug("Message received: " + message.toString());
messagingTemplate.convertAndSend( "/topic/test", message);
}
}

XMPP server in java does not receive all messages from GCM server

I've implemented XMPP server using Smack library, my server gets messages from Google Cloud Messaging server (now it is Firebase), but the problem is when I send one by one message from android to gcm server, my XMPP server receives only first message and the second is intercepted, (I can see only notification that there was a message
<message id="gQaM0-6"><gcm xmlns="google:mobile:data">{"message_type":"ack","message_id":"0","to":"eVtypIWW7Q8:APA91bH5oU0AC3zyuCAWVYkMzoGQeIiGe71c2BL4lE5uFHRfB3iPXtD-qIJDmJZ3ySsPDi0VhkKl0Cz3XZG7rWa1Ca7pX9yQqzWSMXBiGK4SEO4Q-Owfr45E_VBJMrXqsSziuJhek"}</gcm></message>
but I don't have data in this
and first message I get in method void processPacket(Packet packet)
here is the full code of XMPP server:
public class XMPPServer implements PacketListener {
private static XMPPServer sInstance = null;
private XMPPConnection connection;
private ConnectionConfiguration config;
private String mApiKey = null;
private String mProjectId = null;
private boolean mDebuggable = false;
private String fcmServerUsername = null;
public static XMPPServer getInstance() {
if (sInstance == null) {
throw new IllegalStateException("You have to prepare the client first");
}
return sInstance;
}
public static XMPPServer prepareClient(String projectId, String apiKey, boolean debuggable) {
synchronized (XMPPServer.class) {
if (sInstance == null) {
sInstance = new XMPPServer(projectId, apiKey, debuggable);
}
}
return sInstance;
}
private XMPPServer(String projectId, String apiKey, boolean debuggable) {
this();
mApiKey = apiKey;
mProjectId = projectId;
mDebuggable = debuggable;
fcmServerUsername = mProjectId + "#" + Util.FCM_SERVER_CONNECTION;
}
private XMPPServer() {
// Add GcmPacketExtension
ProviderManager.getInstance().addExtensionProvider(Util.FCM_ELEMENT_NAME, Util.FCM_NAMESPACE,
new PacketExtensionProvider() {
#Override
public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
String json = parser.nextText();
GcmPacketExtension packet = new GcmPacketExtension(json);
return packet;
}
});
}
/**
* Connects to FCM Cloud Connection Server using the supplied credentials
*/
public void connect() throws XMPPException {
config = new ConnectionConfiguration(Util.FCM_SERVER, Util.FCM_PORT);
config.setSecurityMode(SecurityMode.enabled);
config.setReconnectionAllowed(true);
config.setSocketFactory(SSLSocketFactory.getDefault());
// Launch a window with info about packets sent and received
config.setDebuggerEnabled(mDebuggable);
connection = new XMPPConnection(config);
connection.connect();
connection.addConnectionListener(new ConnectionListener() {
//a few overrided methods
});
// Handle incoming packets (the class implements the PacketListener)
connection.addPacketListener(this, new PacketTypeFilter(Message.class));
// Second message without data I get in this method (1)
connection.addPacketWriterInterceptor(new PacketInterceptor() {
#Override
public void interceptPacket(Packet packet) {
System.out.println("INTERCEPT PACKAGE: " + packet.toXML());
}
}, new PacketTypeFilter(Message.class));
connection.login(fcmServerUsername, mApiKey);
}
/**
* Normal message with my data I get in this method (2)
*/
#SuppressWarnings("unchecked")
#Override
public void processPacket(Packet packet) {
Message incomingMessage = (Message) packet;
GcmPacketExtension gcmPacket = (GcmPacketExtension) incomingMessage.getExtension(Util.FCM_NAMESPACE);
String json = gcmPacket.getJson();
System.out.println("Message : " + json);
}
There is almost the whole code, the most important part I marked with (1) and (2), (use search to find quickly)
Why can I receive only first message with my data ?
And why does the second message go to PacketInterceptor (mark (1) ) ?
If you're using Firebase Cloud Messaging(FCM), check if your app server is connected to the following endpoints:
// Production
fcm-xmpp.googleapis.com:5235
// Testing
fcm-xmpp.googleapis.com:5236
In addition to that, you may want to also check Downstream messages wherein it was mentioned that once the XMPP connection is established, CCS and your server use normal XMPP <message> stanzas to send JSON-encoded messages back and forth. The body of the <message> must be:
<gcm xmlns:google:mobile:data>
JSON payload
</gcm>
Also, note of the exceptions in JSON payload for regular FCM messages. Visit the given links for more information.
These related SO posts might also help:
FCM receive message Issue
Send FCM messages from server side to android device

spring tcp socket , authorizing clients and handle pending response

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.

Categories

Resources