Delayed disconnect mechanism for WebSockets - java

I'm trying to add a "delayed disconnect" mechanism to a WebSocket chat I'm developing. What this means is that if the user is disconnected, but reconnects within a certain time limit - I'm going to use 30 seconds as an example - the disconnection is ignored. The reason for this is a proof-of-concept for if the user briefly loses their connection - e.g. a mobile user entering a lift.
I've decided to use cookies for this. The logic I've figured out is that when a WebSocket is opened, it also opens a HttpSession. From that, I can check to see if a cookie with a particular id exists. If it does, then they are not treated as a new user. However, for this I need to be able to set the expiry time of the cookie for 30 seconds after the socket has been closed.
I already know that Cookie.setMaxAge() would do that, but when I tried this inside the OnClose() method on the server, the server threw a NullPointerException. That's not really a surprise, since I was obviously trying to access the user session after it had been closed.
So, is there a way to do this?
Update 16th of Feb I've decided to try resetting the cookie entirely when a message is sent. This partly works, since the cookies are generated and added to the HttpSession, but upon reconnecting the server thinks the user is entirely new. So, I think my problem is that the cookie isn't being sent to the user.
Update 2 After reading this question, I've moved the cookie generation into a configuration class that is called on a successful handshake. If the request does not have a cookie, it is treated as an entirely new connection, and logs that to the System console as proof of concept. One thing I've had to do was to extend the lifetime of the cookie at the start: currently, it's 10 minutes as a ballpark figure. If I can't find out how to do exactly what I said up above, I'll go with this.
Update 19th of February I've ditched cookies altogether. See my solution.

I solved this by ditching cookies altogether. I've just shown the methods in the relevant classes; if this isn't enough, I will edit my answer to include the full code.
Inside the configuration class, I get the x-forwarded-for header of the request. This matches the IP address of the client, especially since my backend server is behind a proxy. If the user's IP address is in a list of users, their connection is "refreshed"; otherwise, they are added to the list. Upon disconnection, for whatever reason, the user is marked as disconnected.
A separate ConnectionMonitor class implements the Runnable interface and runs every 10 seconds, and checks to see if any clients have been disconnected for more than 30 seconds. If they have been, then they are removed from the list of users.
MyConfigClass.modifyHandshake()
#Override
public void modifyHandshake(ServerEndpointConfig config,
HandshakeRequest request,
HandshakeResponse response)
{
HttpSession theSession = (HttpSession) request.getHttpSession();
config.getUserProperties().put(HttpSession.class.getName(), theSession);
String ID = request.getHeaders().get("x-forwarded-for").get(0);
if (ChatroomServerEndpoint.users.containsKey(ID))
{
// if this user isn't new, add them back onto the list
User oldUser = ChatroomServerEndpoint.users.get(ID);
System.out.println("An old user with " + ID + " has returned.");
ChatroomServerEndpoint.users.remove(oldUser);
ChatroomServerEndpoint.users.put(ID, oldUser);
oldUser.toggleConnection(true);
System.out.println(oldUser + ", " + ChatroomServerEndpoint.users.size() );
}
else
{
// add a new user to the list
System.out.println("A new user with ID " + ID + " has arrived!");
User newUser = new User(ID);
ChatroomServerEndpoint.users.put(ID, newUser);
System.out.println(newUser + ", " + ChatroomServerEndpoint.users.size() );
}
// put this ID into the configuration for proof of concept
config.getUserProperties().put("newUser", ID);
}
ConnectionMonitor.updateUsers() runs in a separate thread.
void updateUsers()
{
for(String id : ChatroomServerEndpoint.users.keySet())
{
User theUser = ChatroomServerEndpoint.users.get(id);
if (theUser.getStatus() == User.Connection.DISCONNECTED)
{
// get the time at which the user disconnected
Calendar disconnectDate = theUser.getdisconnectionDate();
// Calendar.getTime.getTime returns milliseconds,
// so, multiply maxDisconnectTime by 1000 to see if the user has expired
if (theDate.getTime().getTime() - disconnectDate.getTime().getTime()
>= maxDisconnectTime * 1000 )
{
System.out.println(id + " has timed out");
ChatroomServerEndpoint.users.remove(id);
}
}
}
}
User
public class User {
// the ID is the user's IP address
private String id;
// connection status
public enum Connection
{
CONNECTED,
DISCONNECTED
}
private Connection status;
// the time of disconnection
private Calendar disconnectionDate;
// each user needs a WebSocket Session to be able to send and receive messages
private Session userSession;
/**
* #return the id of this user
*/
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
/**
* #return connection status
*/
public Connection getStatus() {
return status;
}
public void setStatus(Connection status) {
this.status = status;
}
public Calendar getdisconnectionDate() {
return disconnectionDate;
}
public void setdisconnectionDate(Calendar disconnectionDate) {
this.disconnectionDate = disconnectionDate;
}
/**
* #return the userSession
*/
public Session getUserSession() {
return userSession;
}
/**
* #param userSession the userSession to set
*/
public void setUserSession(Session userSession) {
this.userSession = userSession;
}
/**
* #param newID the new ID of the user
*/
public User (String newID)
{
this.id = newID;
this.status = Connection.CONNECTED;
}
/**
* Toggles the connection
* #param toggle - if true, the user is connected
*/
public void toggleConnection(boolean toggle)
{
if (toggle == false)
{
status = Connection.DISCONNECTED;
disconnectionDate = Calendar.getInstance();
}
else
{
status = Connection.CONNECTED;
disconnectionDate = Calendar.getInstance();
disconnectionDate.add(Calendar.HOUR, 1); // give an extra hour to prevent them being disconnected too soon
}
}
}

Related

How to close Telegram poll (that was created by bot) after few days and manually

#Scheduled(cron = "* * * * * *")
private void sendFriPoll(){
SendPoll sendPoll = new PollService().getPoll(String.valueOf(chatIdPoll), "friday");
sendPoll.enableNotification();
sendSchPoll(sendPoll);
}
private void sendSchPoll(SendPoll sendPoll){
sendPoll.setChatId(String.valueOf(chatIdPoll));
try {
if (!isPollExist){
execute(sendPoll);
//isPollExist = true;
}
} catch (TelegramApiException e) {
System.out.println(e.getMessage());
}
}
How to close Telegram poll after few days if poll was created by bot?
Related with close_date max value is 600 sec.
#Override
public void onUpdateReceived(Update update)
Does not track updated from bot.
I have tried get ".getMessageId()" but I need new instance of "Update" and as I said "onUpdateReceived" does not tracked message from bot.
Also I tried send command for bot by bot.
I expect that poll will automatically closed after 2 days or I can close it with command /stop (using .getMessageId())

AutoTrigger a method on Server using Timer - Performance

I have a webservice deployed on a weblogic server. Whenever any client instance hits that webservice, I have to store the clients details and send mail to the client every 24 hours until the client takes the required action.
So in order to send mail every 24 hours, I've written a timer code, that should call the method to send mail every 24 hours.
My question is : Since the code is on server, And there can be 100s of clients that will hit my webservice and start their own timer instance. So does having these multiple timer instance(say 100 or more) affect the server performance?
If yes, please suggest an alternative code I can use to trigger mails sending every 24 hours after the webservice is hit.
Update - My Code
public static final Map<String, Timer> userDeactivationPendingMap = new HashMap();
#GET
#Path("/check-deactivation-status/{username}")
#Produces(MediaType.APPLICATION_JSON)
public String checkDeactivationStatus(#PathParam("username") final String username) {
String returnJsonString = "Email has been sent to IProc team to take further actions on it.";
Timer timer = new Timer();
TimerTask dailyTask = new TimerTask() {
#Override
public void run() {
hitEmailSendingApi(username, "abc#test.com");
}
};
// schedule the task to run starting now and then every day...
timer.schedule(dailyTask, 0l, 1000 * 60 * 60 * 24);
userDeactivationPendingMap.put(username, timer);
return returnJsonString;
}
#Path("/initializeUserDeactivationRequest") #GET
#Produces(MediaType.TEXT_HTML)
public String deactivateUser(#QueryParam("username") String username) {
//code to deactivate user
//Cancelling timer
Timer timer = userDeactivationPendingMap.get(username);
timer.cancel();
return "<h4>User Deactivated Successfully!!<h4>";
}
private void hitEmailSendingApi(String username, String iprocTeamMailAddress) {
String domain = RequestFilter.getRequest().getRequestURL().toString();
String contextPath = RequestFilter.getRequest().getContextPath();
String serverURL = domain.substring(0, domain.indexOf(contextPath));
String servletPath = RequestFilter.getRequest().getServletPath();
serverURL = serverURL + contextPath + servletPath;
EmailSender.sendEmail(iprocTeamMailAddress, "Action Required: Clear User's Pending Requisitions for Account Deactivation", "Hi Team"
+ ",\n\n A new request for user deactivation has been raised by " + username + ".\n\n"
+ "Once all the requisitions are cleared, please press on below link for deactivating the user.\n"
+ serverURL + "/deactivate-account/initializeUserDeactivationRequest?username=" + (username));
}

AngularJS ngWebscoket not send message to all user

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.

When to disconnect bosh connection establish from app server to use prebinding in strophe?

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.

Does Smack work well in Java EE

Does Smack function properly in Java EE?? I am having issues with presence.
I get the credentials from the login form via doPost method..I can able to successfully authenticate as well as connection.getRoster() also works.Next I want to show only users who are online so when I get the presence of user,presence object stores default value "unavailable" for all users even when they are available!!
The whole chat app works without flaw in a normal java class without any change..
String userName = request.getParameter("username");
String password = request.getParameter("password");
HttpSession session=request.getSession();
session.setAttribute("username", userName);
SmackAPIGtalkServlet gtalk = new SmackAPIGtalkServlet();
ConnectionConfiguration config = new ConnectionConfiguration(
"talk.google.com", 5222, "gmail.com");
connection = new XMPPConnection(config);
config.setSASLAuthenticationEnabled(false);
try {
connection.connect();
} catch (XMPPException e) {
e.printStackTrace();
}
try {
connection.login(userName, password);
} catch (XMPPException e) {
e.printStackTrace();
}
System.out.println(connection.isAuthenticated());
boolean status = connection.isAuthenticated();
if (status == true) {
gtalk.displayOnlineBuddyList();
response.sendRedirect("Roster.jsp");
}
else
{
response.sendRedirect("Failed.jsp");
}
}
public void displayOnlineBuddyList() {
Roster roster = connection.getRoster();
Collection<RosterEntry> entries = roster.getEntries();
int count1 = 0;
int count2 = 0;
for (RosterEntry r : entries) {
Presence presence = roster.getPresence(r.getUser());
if (presence.getType() == Presence.Type.unavailable) {
// System.out.println(user + "is offline");
count1++;
} else {
System.out.println(name+user + "is online");
count2++;
}
}
roster.addRosterListener(new RosterListener() {
// Ignored events public void entriesAdded(Collection<String>
// addresses) {}
public void entriesDeleted(Collection<String> addresses) {
}
public void entriesUpdated(Collection<String> addresses) {
}
public void presenceChanged(Presence presence) {
System.out.println("Presence changed: " + presence.getFrom()
+ " " + presence);
}
#Override
public void entriesAdded(Collection<String> arg0) {
// TODO Auto-generated method stub
}
});
}
I am stuck with this and not able to get the code working with servlets..Can anyone help me out??
Will Smack work inside of Java EE, yes and no.
Smack will work inside of a web container, but since it creates its own threads it will NOT work inside of an EJB container. So it will work depending on where you are running it.
To understand some of your issues, you have to understand that the lifecycle of your objects in a servlet is tied to the request/response cycle of each request. This is not the same as a standard java app where the objects will typically live as long as you need them to, since you control their lifecycle.
For example, in the code you have shown, you create the connection for each request (I assume, since not all the code is shown). Therefore registering listeners against that connection will be pointless since it will pass out of scope as soon as you leave the method, and eventually get garbage collected. You will have to maintain the connections outside of the scope of the servlet requests for this to work, otherwise you will be opening and closing connections for each request.
XMPP is completely asynchronous by nature whereas servlet requests are synchronous. You have to put some effort in to making them work together, so don't expect code that works in a standalone app to simply work in this environment.
You have to implement the RosterListener interface in which you have to override the presenceChanged method in that you can get the presence of the users.
It works for me.
When you are getting the rosters of GTalk all will have status as unavailable.
But after sometime their presence changes and the presence can be get from the presenceChanged method in the RosterListner but for that you have to implement the RosterListener's presenceChnaged method.
And ya it works well in Java EE, Android as well as WAP.
Does Smack function properly in Java EE?? I am having issues with presence. I get the credentials from the login form via doPost method..I can able to successfully authenticate as well as connection.getRoster() also works.Next I want to show only users who are online so when I get the presence of user,presence object stores default value "unavailable" for all users even when they are available!! here my code
<%
Roster rst = roster;
rst.addRosterListener(new RosterListener() {
public void entriesAdded(final Collection args) {}
public void entriesDeleted(final Collection<String> addresses) {}
public void entriesUpdated(final Collection<String> addresses) {}
public void presenceChanged(final Presence presence) {
final Presence prsence1 = presence;
prsenceChanged(prsence1);
if (prsence1.isAvailable()) {
System.out.println("Is Available: " + presence.isAvailable());
}
}
});
%>
<%!void prsenceChanged(Presence presence){ if(null != presence){%>
<script language="javascript">
alert("hai");
</script>

Categories