I have a websocket endpoint as
#ServerEndpoint("/tweets")
public class TweetStreamServer {
private static final Logger LOGGER = LoggerFactory.getLogger(TweetStreamServer.class);
#OnMessage
public void tweets(final String message, final Session session) throws IOException, InterruptedException {
System.out.println("session id:" + session.getId() + ", search term: " + message);
final Client twitterClient = TwitterHoseBird.getInstance(message);
while (!session.getOpenSessions().isEmpty()) {
for (final Session s : session.getOpenSessions()) {
if (twitterClient.isDone()) {
System.out.println("Twitter Client Done, waiting ...");
}
s.getBasicRemote().sendText(TwitterHoseBird.getMsgQueue().take());
}
}
}
}
I deploy this on WildFly 8.1.0 Final. Then I open multiple tabs on Chrome, Safari and run the following
var connection = new WebSocket('ws://127.0.0.1:8080/tweetstream-1.0-SNAPSHOT/tweets');
connection.onopen = function () {
connection.send('germany');
};
connection.onerror = function (error) {
console.log('WebSocket Error ' + error);
};
connection.onmessage = function (e) {
console.log('Server: ' + e.data);
};
connection.onclose = function (e) {
console.log('closing session');
};
Then all the tabs start receiving data from server.
Then when I do connection.close(); on one of the tabs, only that connection breaks while all the other tabs are still receiving the data
But if I close one of the tabs (in any browser), all the sessions that were open in all the other tabs close session with closing session message
Question
- Is it not a valid use case that if user closes a tab in one browser, all the other tabs should still receive the data?
- Do you see any bug/issue with what I am doing?
- How can I fix this issue?
Thanks
Instead of using
s.getBasicRemote().sendText(TwitterHoseBird.getMsgQueue().take());
change it to
s.getAsyncRemote().sendText(TwitterHoseBird.getMsgQueue().take());
and everything else would just workout fine
Related
I am using ngWebsocket for listening user actions and update all users page according to current action not just page that who send action.
And I make a end point in java who catch all actions and send message all open sessions. but when i testing, end point find sessions and send message to all of them but message just come to person who send action.
my java code like
#OnMessage
public String onMessage(Session session, String message) { Gson gson = new Gson();
SocketMessage sm = gson.fromJson(message, new SocketMessage().getClass());
if (sm.getEvent().equals("teklif")) {
Set<Session> openSessions = session.getOpenSessions();
for (Session openSession : openSessions) {
try {
openSession.getBasicRemote().sendText("{\"event\":\"teklif\",\"data\":" + sm.getData() + "}");
} catch (Exception ioe) {
System.out.println(ioe.getMessage());
}
}
}
return message;
}`
when i debug Set<Session> openSessions = session.getOpenSessions(); it show me two session and send message to all remote. And I listen in my controller
$rootScope.ws.$on('teklif', function (data) { console.log(data);
});
it is shown only person who emit the message
note : I send message like this -->$rootScope.ws.$emit('teklif', data.content);
How can I make this socket that all user listen all actions ?
Thanks in advance.
Your are using Session.getOpenSessions(). The Javadoc states:
Return a copy of the Set of all the open web socket sessions that
represent connections to the same endpoint to which this session
represents a connection. The Set includes the session this method is
called on. These sessions may not still be open at any point after the
return of this method. For example, iterating over the set at a later
time may yield one or more closed sessions. Developers should use
session.isOpen() to check.
So it does not give you the set of all client sessions connected to your endpoint.
Instead you need to keep track of all session connected to your endpoint for yourself and iterate over that set. Here is an example.
I found my problem what is it .
#OnMessage
public String onMessage(Session session, String message) {
Gson gson = new Gson();
SocketMessage sm = gson.fromJson(message, new SocketMessage().getClass());
if (sm.getEvent().equals("teklif")) {
//SoncketTestMessage fromJson = gson.fromJson(test.getData(), SoncketTestMessage.class);
Set<Session> openSessions = session.getOpenSessions();
for (Session openSession : openSessions) {
try {
SocketResponse rsp = new SocketResponse();
rsp.setEvent("teklif");
rsp.setData(gson.toJson(sm.getData()));
//openSession.getBasicRemote().sendText("{\"event\":\"teklif\",\"data\":" + sm.getData() + "}");
openSession.getBasicRemote().sendText(gson.toJson(rsp, SocketResponse.class));
} catch (Exception ioe) {
System.out.println(ioe.getMessage());
}
}
}
return null;
}
i made a mistake at
openSession.getBasicRemote().sendText("{\"event\":\"teklif\",\"data\":" + sm.getData() + "}");
i just changed sm.getData and send right json format then it send to all user.
It send just to owner before because of that function return message and it is at right format and only owner get the return. Now all user are getting the message.
I'm newbie to the web-socket programming...
I have the following JavaScript client code:
var connection = new WebSocket('ws://localhost:8080/OmegaThings/registerdevice');
connection.onopen = function () {
console.log("Socket has been opened state = " + connection.readyState);
connection.send('Ping'); // Send the message 'Ping' to the server
connection.send('Websocket client');
};
console.log("Socket has been opened state = " + connection.readyState);
connection.send('finish');
// Log errors
connection.onerror = function (error) {
console.log('WebSocket Error ' + error);
};
// Log messages from the server
connection.onmessage = function (e) {
console.log('Server: ' + e.data);
};
Java endpoint:
#ServerEndpoint("/registerdevice")
public class RegisterDeviceEndPoint
{
private static final Logger LOG = Logger.getLogger(RegisterDeviceEndPoint.class.getName());
#OnOpen
public void connectionOpened()
{
LOG.log(Level.INFO, "******************connection opened**************");
}
#OnMessage
public synchronized void processMessage(Session session, String message)
{
LOG.log(Level.INFO, "received message: {0}", message);
}
#OnClose
public void connectionClosed()
{
LOG.log(Level.INFO, "connection closed");
}
}
on the firefox console I got the following output:
"Socket has been opened state = 1"
InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable
"Socket has been opened state = 0"
on the GlassFish server log I got "ping" and "Websocket client", but the connection closed after onopen event exit(not sure), thus, the last word "finish" doesn't appear on the log and the error occurs.
I want to know if my code is correct?
What causes the error? javascript code, GlassFish server configuration or the java endpoint code?
Try to change the glassfish 8080 port, eg: 8887, or make sure Your antivirus/other application are not using port 80, I previously had experience where my server websocket was blocked by antivirus which using port 80.
I use websockets and Glassfish. I call start() funcion on load page. When start function contains alert it sends the message to the server but when I don't put the alert it doesn't work. I can't figure out why.
java script
function start() {
alert('a'); //this alert
webSocket.send('start_server');
}
function onMessage(event) {
document.getElementById('messages').innerHTML
+= event.data;
}
server side
#OnMessage
public void onMessage(String message, Session session)
throws IOException, InterruptedException {
System.out.println("Message recieved");
session.getBasicRemote().sendText(message);
}
#OnOpen
public void onOpen() {
System.out.println("Client connected");
}
Where do you initialize the web-socket on the client side?
The alert suspends execution until you click ok - so i guess your order of execution is wrong (initialize the websocket before webSocket.send)
You may send data after the WebSocket#open event happend.
var connection = new WebSocket(...)
connection.onopen = function () {
connection.send('Ping');
};
Source: html5rocks
This question is Extension of my previous question on this SO question "How to connect XMPP bosh server using java smack library?"
I am using Java as server side language. I have successfully implement xmpp BOSH connection using smach-jbosh thanks to #Deuteu for helping me to achieve this, so far I have modify jbosh's BOSHClient.java file and added two getter method for extracting RID and SID.
Now I have RID and SID on my app server (I am using Apache Tomcat). I need to pass this credential to Strophe (web client) so that it can attach to connection.
Here I have some doubt.
When to disconnect bosh Connection establish from the app server? before passing sid, rid and jid to strophe or after passing sid, rid and jid to strophe?
As per my observation during implementation for the same, I have observed that once bosh connection from the app server has been disconnected, session is expired and SID and RID is no longer useful!!!
I have implemented this logic (Establishing bosh connection and Extracting sid and rid) on a Servlet, here once response has been send from Servlet, Thread will get expired and end BOSH connection will get terminated, so I am not able perform `Attach()` on strophe as session is expired.
Can somebody help me with that problem?
I believe #fpsColton's answer is correct - I'm just added extra info for clarity. As requested on linked thread here is the code changes I made on this - note: I only added the parts where I've labelled "DH"
In BOSHConnection:
// DH: function to preserve current api
public void login(String username, String password, String resource)
throws XMPPException {
login(username, password, resource, false);
}
// DH: Most of this is existing login function, but added prebind parameter
// to allow leaving function after all required pre-bind steps done and before
// presence stanza gets sent (sent from attach in XMPP client)
public void login(String username, String password, String resource, boolean preBind)
throws XMPPException {
if (!isConnected()) {
throw new IllegalStateException("Not connected to server.");
}
if (authenticated) {
throw new IllegalStateException("Already logged in to server.");
}
// Do partial version of nameprep on the username.
username = username.toLowerCase().trim();
String response;
if (config.isSASLAuthenticationEnabled()
&& saslAuthentication.hasNonAnonymousAuthentication()) {
// Authenticate using SASL
if (password != null) {
response = saslAuthentication.authenticate(username, password, resource);
} else {
response = saslAuthentication.authenticate(username, resource, config.getCallbackHandler());
}
} else {
// Authenticate using Non-SASL
response = new NonSASLAuthentication(this).authenticate(username, password, resource);
}
// Indicate that we're now authenticated.
authenticated = true;
anonymous = false;
// DH: Prebind only requires connect and authenticate
if (preBind) {
return;
}
// Set the user.
if (response != null) {
this.user = response;
// Update the serviceName with the one returned by the server
config.setServiceName(StringUtils.parseServer(response));
} else {
this.user = username + "#" + getServiceName();
if (resource != null) {
this.user += "/" + resource;
}
}
// Create the roster if it is not a reconnection.
if (this.roster == null) {
this.roster = new Roster(this);
}
if (config.isRosterLoadedAtLogin()) {
this.roster.reload();
}
// Set presence to online.
if (config.isSendPresence()) {
sendPacket(new Presence(Presence.Type.available));
}
// Stores the autentication for future reconnection
config.setLoginInfo(username, password, resource);
// If debugging is enabled, change the the debug window title to include
// the
// name we are now logged-in as.l
if (config.isDebuggerEnabled() && debugger != null) {
debugger.userHasLogged(user);
}
}
and
// DH
#Override
public void disconnect() {
client.close();
}
then my Client-side (Web Server) wrapper class - for connecting from within JSP is:
Note: This is proving code rather than production - so there's some stuff in here you may not want.
public class SmackBoshConnector {
private String sessionID = null;
private String authID = null;
private Long requestID = 0L;
private String packetID = null;
private boolean connected = false;
public boolean connect(String userName, String password, String host, int port, final String xmppService) {
boolean success = false;
try {
Enumeration<SaslClientFactory> saslFacts = Sasl.getSaslClientFactories();
if (!saslFacts.hasMoreElements()) {
System.out.println("Sasl Provider not pre-loaded");
int added = Security.addProvider(new com.sun.security.sasl.Provider());
if (added == -1) {
System.out.println("Sasl Provider could not be loaded");
System.exit(added);
}
else {
System.out.println("Sasl Provider added");
}
}
BOSHConfiguration config = new BOSHConfiguration(false, host, port, "/http-bind/", xmppService);
BOSHConnection connection = new BOSHConnection(config);
PacketListener sndListener = new PacketListener() {
#Override
public void processPacket(Packet packet) {
SmackBoshConnector.this.packetID = packet.getPacketID();
System.out.println("Send PacketId["+packetID+"] to["+packet.toXML()+"]");
}
};
PacketListener rcvListener = new PacketListener() {
#Override
public void processPacket(Packet packet) {
SmackBoshConnector.this.packetID = packet.getPacketID();
System.out.println("Rcvd PacketId["+packetID+"] to["+packet.toXML()+"]");
}
};
PacketFilter packetFilter = new PacketFilter() {
#Override
public boolean accept(Packet packet) {
return true;
}
};
connection.addPacketSendingListener(sndListener, packetFilter);
connection.addPacketListener(rcvListener, packetFilter);
connection.connect();
// login with pre-bind only
connection.login(userName, password, "", true);
authID = connection.getConnectionID();
BOSHClient client = connection.getClient();
sessionID = client.getSid();
requestID = client.getRid();
System.out.println("Connected ["+authID+"] sid["+sessionID+"] rid["+requestID+"]");
success = true;
connected = true;
try {
Thread.yield();
Thread.sleep(500);
}
catch (InterruptedException e) {
// Ignore
}
finally {
connection.disconnect();
}
} catch (XMPPException ex) {
Logger.getLogger(SmackBoshConnector.class.getName()).log(Level.SEVERE, null, ex);
}
return success;
}
public boolean isConnected() {
return connected;
}
public String getSessionID() {
return sessionID;
}
public String getAuthID() {
return authID;
}
public String getRequestIDAsString() {
return Long.toString(requestID);
}
public String getNextRequestIDAsString() {
return Long.toString(requestID+1);
}
public static void main(String[] args) {
SmackBoshConnector bc = new SmackBoshConnector();
bc.connect("dazed", "i3ji44mj7k2qt14djct0t5o709", "192.168.2.15", 5280, "my.xmppservice.com");
}
}
I confess that I'm don't fully remember why I put the Thread.yield and Thread.sleep(1/2 sec) in here - I think - as you can see with added PacketListener - the lower level functions return after sending data and before getting a response back from the server - and if you disconnect before the server has sent it's response then it (also) causes it to clean up the session and things won't work. However it may be that, as #fpsColton says, this dicsonnect() isn't actually required.
Edit: I now remember a bit more about whay I included sleep() and yield(). I noticed that Smack library includes sleep() in several places, including XMPPConnection.shutdown() as per source. Plus in terms of yield() I had problems in my environment (Java in Oracle Database - probably untypical) when it wasn't included - as per Smack Forum Thread.
Good luck.
After you have created a BOSH session with smack and have extracted the SID+RID values, you need to pass them to Strophe's attach() and from here on out you need to let strophe deal with this connection. Once Strophe has attached, you do not want your server to be doing anything to the connection at all.
If your server side code sends any messages at all to the connection manager after strophe has attached, it's likely that it will send a invalid RID which will cause your session to terminate.
Again, once the session has been established and is usable by strophe, do not attempt to continue using it from the server side. After your server side bosh client completes authentication and you've passed the SID+RID to the page, just destroy the server side connection object, don't attempt to disconnect or anything as this will end your session.
The thing you need to remember is, unlike traditional XMPP connections over TCP, BOSH clients do NOT maintain a persistent connection to the server (this is why we use BOSH in web applications). So there is nothing to disconnect. The persistent connection is actually between the XMPP server and the BOSH connection manager, it's not something you need to deal with. So when you call disconnect from your server side BOSH client, you're telling the connection manager to end the session and close it's connection to the XMPP server, which completely defeats the purpose of creating the session in the first place.
Here is my Java endpoint
#ServerEndpoint("/tweets")
public class TweetStreamServer {
private static final Logger LOGGER = LoggerFactory.getLogger(TweetStreamServer.class);
#OnMessage
public void tweets(final String message, final Session session) throws IOException, InterruptedException {
System.out.println("session id:" + session.getId() + ", search term: " + message);
final Client twitterClient = TwitterHoseBird.getInstance(message);
while (!session.getOpenSessions().isEmpty()) {
for (final Session s : session.getOpenSessions()) {
if (twitterClient.isDone()) {
System.out.println("Twitter Client Done, waiting ...");
}
s.getBasicRemote().sendText(TwitterHoseBird.getMsgQueue().take());
}
}
}
#OnClose
public void onClose(Session session, CloseReason reason) throws IOException {
LOGGER.warn("closing session: {}, remaining session: {}", session.getId(), session.getOpenSessions().size());
}
On Chrome, I do the following in 4 different tabs
var connection = new WebSocket('ws://127.0.0.1:8080/tweetstream-1.0-SNAPSHOT/tweets');
connection.onopen = function () {
connection.send('germany');
};
connection.onerror = function (error) {
console.log('WebSocket Error ' + error);
};
connection.onmessage = function (e) {
console.log('Server: ' + e.data);
};
connection.onclose = function (e) {
console.log('closing session');
};
All the tabs then receive data from Twitter.
If I do connection.close(); on one of the tab, only that tab closes connection, but other 3 still receive the data.
However, if I close the tab, it closes connections in all other tabs by logging closing session
What is the issue here?
On server, when I close tab, I see log as
[0m[33m09:24:53,342 WARN [com.self.tweetstream.TweetStreamServer] (default task-16) closing session: tulDjtq4Lx8s1Zo3mA2mwX8B, remaining session: 1
How can I make other connections live, even if any tab is closed?