Home > Uncategorized > Writing a Twitter like application using Grizzly Comet part 1: The Servlet

Writing a Twitter like application using Grizzly Comet part 1: The Servlet

Twitter is more and more popular and I’ve decided to write my own Twitter using Grizzly Comet. The result is amazing: 150 lines of Java code and an amazing grizzly transformed into a bird!

IMG_0126.JPG

Grizzly Comet is a framework build on top of the Grizzly. With Grizzly Comet, you can create powerful asynchronous application. Grizzly Comet support:

  • Asynchronous read and write: read or write without blocking, waiting for an images to be fully uploaded or written. Instead, let the framework notify you when an async event is ready. This is particularly useful when your application needs to upload files.
  • Long Polling: open a connection from the browser and wait for the server to push data only when available. That means the connection stay suspended until an event happens on the server side.
  • Streaming: Same as long polling, but never resume the connection. When an event happens, leave the connection suspended “forever”.
  • Grouping of suspended connections: You can group suspended connection, and manipulate a groups instead of single connection. That makes building a Grizzly Comet application quite simple.
  • Filtering/Aggregating/Throttling events per connection or group. Under high load, it might be better to aggregate events instead of sending them one by one (might be faster). Event can also be filtered or throttled before they get written on a suspended connection. When pushing event to a group, it is always important to make sure the push operations is not a bottleneck. Grizzly Comet ships with such mechanism, significantly improving performance of your asynchronous application

OK Enough charabia! Let’s demonstrate the power of the framework by re-writing Twitter and make it a full real time and asynchronous application. No need to refresh the page anymore. In this first part, I will explain the server side…the client will comes next, but get ready, I’m far from an expert with JavaScript🙂. But first, let’s see what the end results will looks like…all of this using a single Servlet!

MainScreen.png

Now let’s deep dive into the monster’s Comet API

CometEngine: The CometEngine is the entry point to the framework. The first steps when writing a Grizzly Comet application is to first create a ‘group’ or ‘topic’ object that can be used to suspend, share, filter, throttle, aggregate and resume connections:

    /**
     * Create a {@link CometContext}
     * @param id - The topic associated with the {@link CometContext}
     * @return {@link CometContext}
     */
    private CometContext createCometContext(String id){
        CometEngine cometEngine = CometEngine.getEngine();
        CometContext ctx = cometEngine.register(id);
        ctx.setExpirationDelay(FIVE_MINUTES_TIMEOUT);
        return ctx;
    }

In the above, we grab the static instance of CometEngine and call register(id) to create a CometContext, an object representing suspended connection based on a topic. Note that CometContext can be created from everywhere like EJB, POJo, etc. This is quite important if you are planning to push events from non web components

CometContext: The most important object of the Grizzly Comet Framework. A CometContext represents a group of suspended connections. From a CometContext you can push events, define your own mechanism of filtering/aggregating/throttling, suspend and resume connection:

                // Create a CometContext based on this session id.
                twitterContext = 
                        createCometContext(sessionId);
                
                // Create and register a CometHandler.
                ReflectorCometHandler handler = new ReflectorCometHandler
                        (true,startingMessage,endingMessage);
                
                handler.attach(response.getWriter());
                twitterContext.addCometHandler(handler);
                
                // Keep a reference to us so we can be updated directly.
                twitterContext.addAttribute("twitterHandler", handler);                

In the code above, we first create a CometContext, then add a ReflectorCometHandler (more on this below) and add some attributes. Invoking addCometHandler(handler) automatically suspend the connection.

CometHandler: This interface represents your suspended connection. Defining a CometHandler allow a web application to handle the lifecycle of a suspended connection. Events are pushed to a CometHandler as soon as they occurs. Event like when the connection get suspended (onInitialize), when server event are pushed (onEvent), when your application decide to resume the connection (onTerminate) or when the client close a suspended connection, or the connection was idle for X times (onInterrupt). The most important method is the onEvent, where you are usually define what you will do with the event, e.g. store it, write it, discard it etc. As a very simple example (this is the one used by this Twitter application), Grizzly Comet ships with the RefectorCometHandler, which does nothing except writing all messages it gets:

    /**
     * Write {@link CometEvent#attachment} and resume the connection if 
     * {@link ReflectorCometHandler#useStreaming} is false
     * @param event
     * @throws java.io.IOException
     */
    public synchronized void onEvent(CometEvent event) throws IOException {
        try {
            if (event.getType() != CometEvent.READ) {
                printWriter.println(event.attachment());
                printWriter.flush();
                
                if (!useStreaming){
                    event.getCometContext().resumeCometHandler(this);
                }
            }
        } catch (Throwable t) {
            throw new IOException(t.getMessage());
        }
    }

But how CometHandler gets invoked? CometHandler gets invoked when an application invoke CometContext.notify(CometEvent). As an example, let’s take explain how a chat application works. First, users (browsers) enter a chat room, waiting for message. As soon as they enter the chatroom, a Grizzly Comet implementation will invoke CometContext.addCometHandler(). That means those connections are suspended, waiting for events. If a user enter some message, the way to share (or push) that information back to the users is by invoking CometContext.notify(“Salut”). Automatically the CometHandler.onEvent() will be called, and if the ReflectorCometHandler described above is used, then the message will be directly written to the suspended connection. In effect, that operation will push back the message to the client. You can always filter messages before they reach CometHandler (details in part III)

So, four simple steps:

  • CometEngine.register(“topic|group”) to create the group
  • CometHandler ch = new CometHandler() to prepare for suspending the request/response
  • CometContext.addCometListener(CometHandler) to suspend the connection
  • CometContext.notify(Message) to push message to the group or CometContext.resumeCometHandler(ch) to resume the connection, or in short to commit the response

And now, Twitter Twitter Twitter

Let’s first define what the application will do:

  • First, the user will sign in.
  • Welcome.png

  • Next, the user is now able to micro blog (add updates and push them to peoples registered (followers) to receives such updates).
  • MainScreen-JF.png

  • User can follow another user’s micro blogs/updates by entering their name inside the ‘follow field’
  • AlexeyFollowingJan.png

  • To be smarter than Twitter, we will allow the user to gets update from the person the follow directly on the screen
  • JanUpdates.png

  • To make the application more cool that the original Twitter.com, we will allow users to move their blogs/updates on the screen, via the JMaki Comet Extension

Moving.png

So let’s go steps by steps on how we can build a Twitter like application. For Twitter, we will use a single TwitterServlet, and we will define the basic as:

    /**
     * Grab an instance of {@link ServletContext}
     * @param config
     * @throws javax.servlet.ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        servletContext = config.getServletContext();
    }

    /**
     * Same as {@link TwitterServlet#doPost}
     * 
     * @param request
     * @param response
     * @throws javax.servlet.ServletException
     * @throws java.io.IOException
     */
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doPost(request, response);
    }

All the logic happens inside the doPost.

First, sign in Twitter:

I will talk in more details in part II about how the client works, so let’s just describe the basic. Below is the code that allow a user to sign in

   login: function() {
      var name = $F('login-name');
      if(! name.length > 0) {
	 $('system-message').style.color = 'red';
	 $('login-name').focus();
	 return;
      }
      $('system-message').style.color = '#2d2b3d';
      $('system-message').innerHTML = '';

      $('login-button').disabled = true;
      $('login-form').style.display = 'none';
      $('message-form').style.display = '';
      $('follower').style.display = '';

      var query =
	 'action=login' +
	 '&name=' + encodeURI($F('login-name'));
      new Ajax.Request(app.url, {
	 postBody: query,
	 onSuccess: function() {
	    $('message').focus();
	 }
      });
   },

On the server side, this is as simple as:

    /**
     * Based on the {@link HttpServletRequest#getParameter} action value, decide
     * if the connection needs to be suspended (when the user logs in) or if the 
     * {@link CometContext} needs to be updated (by the user or by its follower.
     * 
     * There is one {@link CometContext} per suspended connection, representing 
     * the user account. When one user B request to follow user A, the {@link CometHandler}
     * associated with user B's {@link CometContext} is also added to user A
     * {@link CometContext}. Hence when user A push message ({@link CometContext.notify()}
     * all {@link CometHandler} gets the {@link CometEvent}, which means user B
     * will be updated when user A update its micro blog.
     * 
     * The suspended connection on the client side is multiplexed, e.g. 
     * messages sent by the server are not only for a single component, but
     * shared amongs several components. The client side include a message board
     * that is updated by notifying the owner of the {@link CometContext}. This
     * is achieved by calling {@link CometContext.notify(CometEvent,CometHandler)}
     * 
     * @param request
     * @param response
     * @throws javax.servlet.ServletException
     * @throws java.io.IOException
     */
    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        String action = request.getParameter("action");                               
        String sessionId = request.getSession().getId();
        HttpSession session = request.getSession();
        CometContext twitterContext = (CometContext) session.getAttribute(sessionId);
        if (action != null) {

In short, we grab the action value from the doPost, and also we look for a CometContext. The CometContext here represents the user blogs (or update), and all CometHandlers added to this “user” CometContext will receive updates when the user micro blogs/updates his page. Of course on the first request the CometContext is null as their is no suspended connections. On the client/broser side, the first action sent is ‘start’ (when the page is loaded), and this is where we will suspend the connection:

            } else if ("start".equals(action)) {
                String message = "{ message : 'Welcome'}";              
                response.setContentType("text/html");
                String callback = request.getParameter("callback");
                if (callback == null) {
                    callback = "alert";
                }
                            
                response.getWriter().println("<script id='comet_" + counter++ + "'>" 
                        + "window.parent." + callback + "(" + message + ");</script>");

                // Create a CometContext based on this session id.
                twitterContext = 
                        createCometContext(sessionId);
                
                // Create and register a CometHandler.
                ReflectorCometHandler handler = new ReflectorCometHandler
                        (true,startingMessage,endingMessage);
                
                handler.attach(response.getWriter());
                twitterContext.addCometHandler(handler);
                
                // Keep a reference to us so we can be updated directly.
                twitterContext.addAttribute("twitterHandler", handler);
                
                session.setAttribute("handler", handler);
                session.setAttribute(sessionId, twitterContext);
                return;

The most important code above is when we create the CometContext (based on the session id), then create a ReflectorCometHandler that gets invoked when it is time to write back to the browser updates made to the CometContext. The CometContext as described above. Next, we suspend the connection/response by invoking addCometHandler. We use the session to store the CometContext and also the CometHandler representing the user suspended connection. As soon as the user sign in:

            /*
             * Notify the submitter, via its CometHandler, that it has just logged in.
             */
            if ("login".equals(action)) {
                response.setContentType("text/plain");
                response.setHeader("Cache-Control", "private");
                response.setHeader("Pragma", "no-cache");
                response.setCharacterEncoding("UTF-8");
                               
                String name = request.getParameter("name");              
                if (name == null) {
                    logger.severe("Name cannot be null");
                    return;
                }
                
                session.setAttribute("name", name);              
                CometHandler ch = (CometHandler)session.getAttribute("handler");
                twitterContext.notify(BEGIN_SCRIPT_TAG
                        + toJsonp("Welcome back", name) 
                        + END_SCRIPT_TAG, CometEvent.NOTIFY, ch);
                
                // Store the CometContext associated with this user so
                // we can retrieve it for supporting follower.
                servletContext.setAttribute(name, twitterContext);

From the user’s name, we push our first message back to this user using the suspended connection. As we noted above, we push the message using the CometContext.notify, which in turn will invoke the ReflectorCometHandler.onEvent(), which will write the message back to the client. Now the user is ready to micro blog/updates.

Time to micro blog using Grizzzly!!!!!

As soon as the user update his status, the client sent:

            } else if ("post".equals(action)) {
                String message = request.getParameter("message");
                String callback = request.getParameter("callback");
                
                if (message == null) {
                    logger.severe("Message cannot be null");
                    return;
                }
                
                if (callback == null) {
                    callback = "alert";
                }

                if (twitterContext != null){
                    // Notify other registered CometHandler.
                    twitterContext.notify("<script id='comet_" + counter++ + "'>" 
                            + "window.parent." + callback + "(" + message + ");</script>");
                }
                response.getWriter().println("ok");
                return;

This looks like way to simple, right? For any update, we just need to get the CometContext and invoke notify on it, and BINGO, all our followers will be updated in REAL-TIME!. What is a follower? A follower is another users that want to get updated when another user enter a new micro blog. Followers register themselves by entering the name of the user they want to follow:

            } else if ("following".equals(action)) {
                response.setContentType("text/html");
                String message = request.getParameter("message");
                String name = (String)session.getAttribute("name");
                
                // Retrive the user CometContext.
                CometContext followerContext 
                        = (CometContext) servletContext.getAttribute(message);
    
                                
                CometHandler ch = (CometHandler)session.getAttribute("handler");
                if (followerContext == null){
                  twitterContext.notify(BEGIN_SCRIPT_TAG
                        + toJsonp("Invalid Twitter user ", message) 
                        + END_SCRIPT_TAG, CometEvent.NOTIFY, ch);
                  return;
                }

                followerContext.addCometHandler(ch, true);
                
                twitterContext.notify(BEGIN_SCRIPT_TAG
                        + toJsonp("You are now following ", message) 
                        + END_SCRIPT_TAG, CometEvent.NOTIFY, ch);
                
                CometHandler twitterHandler = 
                        (CometHandler)followerContext.getAttribute("twitterHandler");
                followerContext.notify(BEGIN_SCRIPT_TAG
                        + toJsonp(name, " is now following " + message)
                        + END_SCRIPT_TAG, CometEvent.NOTIFY, twitterHandler);               
                return;
            }

The ‘name’ is the user which want to follow another user, which is represented by the ‘message’. First, we get the CometContext representing the user we want to follow, and add our CometHandler to it. That means that every time the followerContext ill be updated, we will also get the update via our CometHandler. Too cool!. We also update our CometContext by pushing a message telling our follower that we are now following a new user. We also update the followerContext by pushing a message saying we are now following that user.

THAT’S IT!!!

Yes, that the only steps you have to write in order to build a Twitter like application using Grizzly Comet. One thing to remember is that everything happens using a CometContext. From that object, you can notify/filter/aggregate/ etc. a set of suspended connection represented by a CometHandler. In the current example I’m using the ReflectorCometHandler, but you can write your own by implementing the CometHandler interface. Now ready to try it? Two possibilities. For development, I recommend you use the embedded Grizzly Comet Server and just do

% java - jar grizzly-comet-webserver-1.9.0.jar -p 8080 -a \
./grizzly-twitter.war com.sun.grizzly.samples.twitter.TwitterServlet

For production or if you need to uses Eclipse or Netbeans, download GlassFish v3 and deploy the grizzly-twitter.war application like any other web application. The application and source can be downloaded here. If you want to improve it or contribute, you are welcome to join the Grizzly community!

OK next time I will explain in details how the client works (at least I will try :-)). Please post your questions on users@grizzly.dev.java.net so the Grizzly community can help and respond🙂

_uacct = “UA-3111670-1”;
urchinTracker();

technorati:

Categories: Uncategorized
  1. Pablo
    October 25, 2010 at 1:10 pm

    Hi,
    Your project is excelent.
    I wanna do a page that can read from a SQL server messages and display it instant like twitter, do you know how can I do this?
    Thanks in Advance.
    Pablo.

  1. July 20, 2010 at 5:50 am

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: