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
Related
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.
I've build a prototype lambda function that can send automated push notifications by querying my database based on the rules stored in Firebase. This function is scheduled to run everyday. By this function below, I am calling the Messaging object
private void sentAutomatedMessages(List<String> tokens, CardAbandonmentRule rule) {
for (String token : tokens) {
//Create Messaging object for every user that fits in this user
Messaging msgHandler = new Messaging(rule.getTitle(), rule.getMessage(), token);
try {
msgHandler.handleSingleDevicePush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
The class definition and the methods for sending push notifications =>
public class Messaging {
private static final String PROJECT_ID = "<project_id>";
private static final String BASE_URL = "https://fcm.googleapis.com";
private static final String FCM_SEND_ENDPOINT = "/v1/projects/" + PROJECT_ID + "/messages:send";
private static final String MESSAGING_SCOPE = "https://www.googleapis.com/auth/firebase.messaging";
private static final String[] SCOPES = {MESSAGING_SCOPE};
private String title;
private String message;
private String token;
public Messaging(String title, String message, String token) {
this.title = title;
this.message = message;
this.token = token; // <FCM_token>
}
/**
* Retrieve a valid access token that can be use to authorize requests to the FCM REST
* API.
*
* #return Access token.
* #throws IOException
*/
private static String getAccessToken() throws IOException {
GoogleCredential googleCredential = GoogleCredential
.fromStream(new FileInputStream("<firebase_private_key.json>"))
.createScoped(Arrays.asList(SCOPES));
googleCredential.refreshToken();
return googleCredential.getAccessToken();
}
/**
* Create HttpURLConnection that can be used for both retrieving and publishing.
*
* #return Base HttpURLConnection.
* #throws IOException
*/
private static HttpURLConnection getConnection() throws IOException {
// [START use_access_token]
URL url = new URL(BASE_URL + FCM_SEND_ENDPOINT);
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
String accessToken = getAccessToken();
System.out.println(accessToken);
httpURLConnection.setRequestProperty("Authorization", "Bearer " + accessToken);
httpURLConnection.setRequestProperty("Content-Type", "application/json; UTF-8");
return httpURLConnection;
// [END use_access_token]
}
/**
* Construct the body of a notification message request.
*
* #return JSON of notification message.
*/
private JsonObject buildNotificationMessage() {
JsonObject jNotification = new JsonObject();
jNotification.addProperty("title", this.title);
jNotification.addProperty("body", this.message);
JsonObject jMessage = new JsonObject();
jMessage.add("notification", jNotification);
jMessage.addProperty("token", this.token);
JsonObject jFcm = new JsonObject();
jFcm.add("message", jMessage);
return jFcm;
}
/**
* Send request to FCM message using HTTP.
*
* #param fcmMessage Body of the HTTP request.
* #throws IOException
*/
private static void sendtoSingleDevice(JsonObject fcmMessage) throws IOException {
HttpURLConnection connection = getConnection();
connection.setDoOutput(true);
DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
outputStream.writeBytes(fcmMessage.toString());
outputStream.flush();
outputStream.close();
int responseCode = connection.getResponseCode();
if (responseCode == 200) {
String response = inputstreamToString(connection.getInputStream());
System.out.println("Message sent to Firebase for delivery, response:");
System.out.println(response);
} else {
System.out.println("Unable to send message to Firebase:");
String response = inputstreamToString(connection.getErrorStream());
System.out.println(response);
}
}
/**
* Public method to send Push Notification
*
* #throws IOException
*/
public void handleSingleDevicePush() throws IOException {
JsonObject notificationMessage = buildNotificationMessage();
sendtoSingleDevice(notificationMessage);
}
After I run the buildNotificationMessage(), the object is formed like example below.
// Example Notification Message to send over HTTP
{
"message": {
"notification": {
"title": "title",
"body": "body"
},
"token": "<FCM_token>"
}
}
The response is =>
{ "name": "projects/<project_id>/messages/1542324302450893"}
I have to develop a dashboard for listing the sent messages, open rate and analytics. However, I need some guidance.
1 - What can I do with this name given as a response from the FCM REST API ? I didn't see anything in the documentation for getting the details of messages.
2 - Is there a better way for sending bulk messages for multiple unique FCM token ? I see some stuff about device groups but Firebase says it's for a different purpose.
Typically, "group" refers a set of different devices that belong to a single user.
Thanks
After I've contacted with Firebase Support, they recommend me to use BigQuery by Google to see datasets for firebase messaging functionality.
After you enable the BigQuery integration in Firebase settings, you just need to go to BigQuery console.
A query like down below, will give you details for given message.
SELECT *
FROM `<project_name>.firebase_messaging.data`
WHERE
_PARTITIONTIME = TIMESTAMP('<date as YYYY-MM-DD>')
AND message_id = '<your message id>'
AND instance_id = '<your instance id>'
ORDER BY event_timestamp;
Link to see more examples and read about the BigQuery integration for FCM =>
Understanding the message delivery
I am trying to send a message. Connection with firebase xmpp server is done. I am using latest smack 4.2.
public myClient(String senderId, String serverKey) throws IOException, NotConnectedException, InterruptedException {
Roster.setRosterLoadedAtLoginDefault(false);
Jid jid ;
jid = JidCreate.from(HOST);
final XMPPTCPConnectionConfiguration conf = XMPPTCPConnectionConfiguration.builder()
.setCompressionEnabled(false)
.setSendPresence(false)
.setConnectTimeout(10000)
.setHost(HOST)
.setDebuggerEnabled(false)
.setPort(PORT)
.setXmppDomain((DomainBareJid) jid)
.setSocketFactory(SSLSocketFactory.getDefault())
.setUsernameAndPassword(senderId + "#gcm.googleapis.com", serverKey)
.build();
this.conn = new MyXMPPTCPConnection(conf);
try {
conn.connect();
conn.login();
} catch (XMPPException | InterruptedException | SmackException e) {
throw new IOException(e);
}
Roster roster = Roster.getInstanceFor(conn);
Collection<RosterEntry> entries = roster.getEntries();
for (RosterEntry entry : entries) {
System.out.println(entry);
}
Entity jEntity ;
Jid jid1 = JidCreate.from("text");
EntityBareJid jid21 = JidCreate.entityBareFrom("ak#gcm.googleapis.com");
Chat chat = ChatManager.getInstanceFor(conn)
.createChat(jid21);
Message newMessage = new Message();
String t = "hello this is a test message";
newMessage.setBody(t.toString());
/* ((Object) chat).send(newMessage);*/
chat.sendMessage(newMessage);
StanzaFilter filter = new AndFilter(new StanzaTypeFilter(Message.class));
//PacketCollector myCollector = conn2.createPacketCollector(filter);
// Normally, you'd do something with the collector, like wait for new packets.
StanzaListener myListener = new StanzaListener() {
#Override
public void processPacket(Stanza packet) throws SmackException.NotConnectedException {
System.out.println("packet = [" + packet + "]");
}
};
conn.addAsyncStanzaListener(myListener, filter);
while (true) {
}
//conn2.disconnect();
}
When I am running this method got the following error
packet = [hello
this is a test
message25667738-6dd1-4c74-a64b-2fdbac74339bInvalidJson :
MissingPayload]
Jul 26, 2017 12:52:46 PM
org.jivesoftware.smack.tcp.XMPPTCPConnection$PacketReader parsePackets
INFO: MyXMPPTCPConnection[438277974557#gcm.googleapis.com/41CA77F0]
(0) received closing element. Server wants to terminate the
connection, calling disconnect()
Please review and suggest.
Thanks in advance.
The FCM XMPP Server uses JSON syntax. So you need to use the systax as described for FCM payload in this documentation. Since XMPP uses XML, you need to embed the JSON payload in an xml node with the name gcm as follows:
<gcm xmlns:google:mobile:data>
JSON payload
</gcm>
For e.g, your JSON payload may look like this:
{
"to":"APA91bEXJvW4jUdxxxxx-xxxxxxxx",
"data":{"message":"Hello"},
"priority":"high"
}
For complete details on using the XMPP Connection Server using GCM/FCM, take a look at this documentation.
Using Smack you can create a class with the GcmPacketExtension to create the gcm element with the json payload.
I am trying to set up basic message broker on Spring framework, using a recipe I found here
Author claims it has worked well, but I am unable to receive messages on client, though no visible errors were found.
Goal:
What I am trying to do is basically the same - a client connects to server and requests some async operation. After operation completes the client should receive an event. Important note: client is not authenticated by Spring, but an event from async back-end part of the message broker contains his login, so I assumed it would be enough to store concurrent map of Login-SessionId pairs for sending messages directly to particular session.
Client code:
//app.js
var stompClient = null;
var subscription = '/user/queue/response';
//invoked after I hit "connect" button
function connect() {
//reading from input text form
var agentId = $("#agentId").val();
var socket = new SockJS('localhost:5555/cti');
stompClient = Stomp.over(socket);
stompClient.connect({'Login':agentId}, function (frame) {
setConnected(true);
console.log('Connected to subscription');
stompClient.subscribe(subscription, function (response) {
console.log(response);
});
});
}
//invoked after I hit "send" button
function send() {
var cmd_str = $("#cmd").val();
var cmd = {
'command':cmd_str
};
console.log("sending message...");
stompClient.send("/app/request", {}, JSON.stringify(cmd));
console.log("message sent");
}
Here is my configuration.
//message broker configuration
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
/** queue prefix for SUBSCRIPTION (FROM server to CLIENT) */
config.enableSimpleBroker("/topic");
/** queue prefix for SENDING messages (FROM client TO server) */
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/cti")
.setAllowedOrigins("*")
.withSockJS();
}
}
Now, after basic config I should implement an application event handler to provide session-related information on client connect.
//application listener
#Service
public class STOMPConnectEventListener implements ApplicationListener<SessionConnectEvent> {
#Autowired
//this is basically a concurrent map for storing pairs "sessionId - login"
WebAgentSessionRegistry webAgentSessionRegistry;
#Override
public void onApplicationEvent(SessionConnectEvent event) {
StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage());
String agentId = sha.getNativeHeader("Login").get(0);
String sessionId = sha.getSessionId();
/** add new session to registry */
webAgentSessionRegistry.addSession(agentId,sessionId);
//debug: show connected to stdout
webAgentSessionRegistry.show();
}
}
All good so far. After I run my spring webapp in IDE and connected my "clients" from two browser tabs I got this in IDE console:
session_id / agent_id
-----------------------------
|kecpp1vt|user1|
|10g5e10n|user2|
-----------------------------
Okay, now let's try to implement message mechanics.
//STOMPController
#Controller
public class STOMPController {
#Autowired
//our registry we have already set up earlier
WebAgentSessionRegistry webAgentSessionRegistry;
#Autowired
//a helper service which I will post below
MessageSender sender;
#MessageMapping("/request")
public void handleRequestMessage() throws InterruptedException {
Map<String,String> params = new HashMap(1);
params.put("test","test");
//a custom object for event, not really relevant
EventMessage msg = new EventMessage("TEST",params);
//send to user2 (just for the sake of it)
String s_id = webAgentSessionRegistry.getSessionId("user2");
System.out.println("Sending message to user2. Target session: "+s_id);
sender.sendEventToClient(msg,s_id);
System.out.println("Message sent");
}
}
A service to send messages from any part of the application:
//MessageSender
#Service
public class MessageSender implements IMessageSender{
#Autowired
WebAgentSessionRegistry webAgentSessionRegistry;
#Autowired
SimpMessageSendingOperations messageTemplate;
private String qName = "/queue/response";
private MessageHeaders createHeaders(String sessionId) {
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
return headerAccessor.getMessageHeaders();
}
#Override
public void sendEventToClient(EventMessage event,String sessionId) {
messageTemplate.convertAndSendToUser(sessionId,qName,event,createHeaders(sessionId));
}
}
Now, let's try to test it. I run my IDE, opened Chrome and created 2 tabs form which I connected to server. User1 and User2. Result console:
session_id / agent_id
-----------------------------
|kecpp1vt|user1|
|10g5e10n|user2|
-----------------------------
Sending message to user2. Target session: 10g5e10n
Message sent
But, as I mentioned in the beginning - user2 got absolutely nothing, though he is connected and subscribed to "/user/queue/response". No errors either.
A question is, where exactly I am missing the point? I have read many articles on the subject, but to no avail.
SPR-11309 says it's possible and should work. Maybe, id-s aren't actual session id-s?
And well maybe someone knows how to monitor if the message actually has been sent, not dropped by internal Spring mechanics?
SOLUTION UPDATE:
A misconfigured bit:
//WebSocketConfig.java:
....
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
/** queue prefix for SUBSCRIPTION (FROM server to CLIENT) */
// + parameter "/queue"
config.enableSimpleBroker("/topic","/queue");
/** queue prefix for SENDING messages (FROM client TO server) */
config.setApplicationDestinationPrefixes("/app");
}
....
I've spent a day debugging internal spring mechanics to find out where exactly it goes wrong:
//AbstractBrokerMessageHandler.java:
....
protected boolean checkDestinationPrefix(String destination) {
if ((destination == null) || CollectionUtils.isEmpty(this.destinationPrefixes)) {
return true;
}
for (String prefix : this.destinationPrefixes) {
if (destination.startsWith(prefix)) {
//guess what? this.destinationPrefixes contains only "/topic". Surprise, surprise
return true;
}
}
return false;
}
....
Although I have to admit I still think the documentation mentioned that user personal queues aren't to be configured explicitly cause they "already there". Maybe I just got it wrong.
Overall it looks good, but could you change from
config.enableSimpleBroker("/topic");
to
config.enableSimpleBroker("/queue");
... and see if this works? Hope this help.
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.