Android, iOS and desktop browser clients are currently polling a PHP-backend (utilizing PostgreSQL database on CentOS Linux) every few seconds.
I would like to replace the polling by using standalone Jetty Websocket Server to notify clients, that new data is available for pickup at the backend.
So in the custom WebSocketListener
I authenticate connected clients and store them in a ConcurrentHashMap<String,Session>
:
public class MyListener implements WebSocketListener
{
private Session mSession;
@Override
public void onWebSocketConnect(Session session) {
mSession = session;
}
@Override
public void onWebSocketText(String message) {
if (mSession != null && mSession.isOpen()) {
// 1. validate client id and password
// 2. store client id and session into Map
}
}
My question: How to notify the connected (via websockets) clients?
I.e. in the PHP-scripts I would like to run a lightweight program java -jar MyNotify.jar client-1234
to tell the Jetty standalone server:
Hey, there is new data available for the
client-1234
at the database!Please send it a short message over websockets by calling
MyMap.get("client-1234").getRemote().sendString("hey", null);
You have to put your
ConcurrentHashMap<String,Session> sessionMap.
into public static field on custom javax.servlet.ServletContextEvent. Field should be initialized on event
@Override
public void contextInitialized(ServletContextEvent ctx) {
Then anywhere in you app, you can access this static field in normal way (using dot syntax).
Because contextInitialized is fired before any servlets or websockets methods (get, put, onMessage), map will be there. Also being concurrent map, it should have no duplicate id's inside.
Of course, you need also strategy for cleaning up the session map. To sum up, you have to build up your system together with events from javax.servlet API.
Similar example:
package example;
import java.io.FileNotFoundException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.*;
/**
* Application lifecycle events. Handles:
* <ul>
* <li>start, shutdown of application
* <li>start, stop of session
* </ul>
*
* @author mitjag
*
*/
public class AppInit implements HttpSessionListener, ServletContextListener {
public static final Logger log = Logger.getLogger(AppInit.class.getCanonicalName());
public static final Map<String, HttpSession> SESSION_MAP = new ConcurrentHashMap<String, HttpSession>(); /* access AppInit.SESSION_MAP from anywhere in your app*/
@Override
public void contextInitialized(ServletContextEvent ctx) {}
@Override
public void sessionCreated(HttpSessionEvent arg0) {
// With this trick we maintain the list of sessionid's together with corresponding session
// It is used to grab the session if you have the valid session id
final String sid = arg0.getSession().getId();
log.info("SESSION CREATED with id " + arg0.getSession().getId());
SESSION_MAP.put(sid, arg0.getSession());
}
/**
* Called on session invalidation (manual or session timeout trigger, defined in web.xml (session-timeout)).
* @see javax.servlet.http.HttpSessionListener#sessionDestroyed(javax.servlet.http.HttpSessionEvent)
*/
@Override
public void sessionDestroyed(HttpSessionEvent arg0) {
// remove session from our list (see method: sessionCreated)
final String sid = arg0.getSession().getId();
SESSION_MAP.remove(sid);
}
@Override
public void contextDestroyed(ServletContextEvent arg0) {
}
}