Wednesday, October 3, 2012

Push notification to Web client


Due to the nature of the HTTP protocol pushing data to the client isn't a trivial thing. The flow of web communications is request-response, meaning that the server has a hard time sending data if there isn't a request from the client.
Server notifications where the client doesn't need to bee constantly asking if there are new events (polling) already exists in mobile OS, HTML5 Web Socket API and Server Sent Event. But until there is a higher implementation of this standard among the different browsers, developers still have to come up with some elaborated schemes to provide real time updates without constant page refresh.
This post presents a server notification approach in AJAX and using a Java Servlet environment.

There are multiple ways to accomplish this notification paradigm using AJAX but we'll be focusing on a low level solution based on long pulling.

Long Pulling

Long pulling, aka COMET, it's a very easy concept to understand but difficult to implement.

When a browser makes a request to a server, a HTTP connection is created between both. Normally the server replies immediately with data, for instance html content, and the connection closes. But if we could hold that request and only reply when there was something new then we would have a notification system in place.

For example, let's assume we wanted to track a football match and the user should be notified when a new game event happened. Now, we could periodically request the game state to the server, aka polling, or we could just ask once and the reply would only be sent when there was a new event, aka long pulling. This has two inconvenients, first how can the server successfully hold the request keeping the connection alive and what about request timeouts.

Holding the connection (sleep or wait):

The easiest method to keep the connection alive is by simply putting a sleep(time) within a while. This way you could hang the request while there isn't anything new and keep checking from time to time. This is a possibility but I personally prefer using .wait() because you don't need to set a time and you synchronize over an object and not a thread.

How wait() works:

What wait does is hold the thread execution until another thread sends a notification to resume. This is kinda what we want, the thread in charge of the HTTP request waits until the server has a new notification.
As sleep, wait also needs to be within a while loop because there can be uncontrollable interrupts that break a thread's waiting state. Another requirement of wait is the present thread owning the object on which wait is evoked. This basically means that only the calling thread has access to the object at the time of evocation, in other words wait is inside a synchronize block.

Notification Architecture Example:

Going back to the football example the following diagram illustrates a possible architecture of the notification system:

The "Thread Match Watcher" is responsible for monitoring the game status and every time there is an event, like goal or penalty, it will add to the events queue and notify every request thread that is waiting for new events. This notification is done by invoking notif.notifyAll().
Each client request has an event_id associated with it, which if it's the first request will start with 0, and if there is a queue entry on index = event_id then it will be immediately returned along with the remaining ones. The event_id will be incremented to match last_event_returned_index+1 and the client will make a consecutive request. If there aren't new events the Thread responsible for the request will invoke notif.wait() and waits on the notif object until a notification comes.

Requests timeouts won't be any trouble because we just need to issue a new request from the client, using the same event_id so we don't miss anything.

Obviously things might bot be as simple as this due to the issue of allocating a thread to a request...if there are thousands of threads waiting for notifications then resource management might became a problem. This can be resolved by setting a timeout variable that is set to true every 30 mins and invoking a notifyAll() on the notif object. This way all the request will be dropped and only the still active clients will re-request the new events.

Pseudo code:


/* request Thread: if there wasn't any new event */
synchronized(notif)
{
     while(!timeIsUp)
    {
        notif.wait();
        /*this will run on notif.notifyAll() or uncontrollable interrupt*/
        if(event_id<=eventsQueue.size()-1 && eventsQueue.get(event_id)!=null)
        {
             /* write response*/
             resp.getWriter().print(eventQueue.get(event_id));
             timeIsUp = true;
        }
    }
}

/* watcher Thread */
while(gameIsRunning)
{
        GameEvent event= getGameEvent();
        eventQueue.add(event);
        synchronized(notif){
           notif.notifyAll();
        }
}

1 comment: