Caucho Technology
  • resin 4.0
  • server-push servlet


    Resin's server-push (Comet) servlet API enables streaming communication such as reverse AJAX dynamic updates for browser/JavaScript applications. The API encapsulates of the threading and communications issues between the request threads and the rest of the application.

    Demo

    Resin's server-push (Comet) API lets server application push new data to the client as it becomes available. Administration and monitoring applications need to continually update the client when new information becomes available to the server.

    The architecture in the picture uses two HTTP streams to the server application, one for normal client-server requests, and a second unidirectional stream to send updates from the server to the client. In this example, we're using a browser with JavaScript, but the same architecture applies to a more sophisticated Flash monitoring application sending Hessian packets to update the monitoring display.

    Files in this tutorial

    WEB-INF/resin-web.xmlresin-web.xml configuration
    WEB-INF/beans.xmlJava Injection beans.xml marker for class scanning
    WEB-INF/classes/example/TestCometServlet.javaThe Comet servlet.
    WEB-INF/classes/example/TimerService.javaApplication service for timer/comet events.
    WEB-INF/classes/example/CometState.javaThe application's Comet controller state.
    comet.htmlMain HTML page linking comet servlet.

    Example Overview

    The example updates a comet.html page every two seconds with new data. In this case, just an updated counter.

    The components of the Comet/AJAX application look like:

    • Protocol: JavaScript function calls with a trivial argument.
    • Client:
      • View: HTML updated by JavaScript AJAX
      • Controller: call server with an <iframe>
    • Server:
      • Service: TimerService manages the comet connections and wakes them with new data.
      • Servlet: TestCometServlet generates <script> protocol tags from new data from the TimerService on each resume.
      • State: CometState encapsulates both the item's state (the timer count), and the CometController needed to wake the servlet and pass updated data.

    Streaming Protocol: <script> tags

    The comet HTTP stream is a sequence of <script> tags containing JavaScript commands to update the browser's display. Because the browser executes the script as part of its progressive rendering, the user will see the updates immediately without waiting for the entire HTTP request to complete.

    In our example, the packet is a JavaScript comet_update(data) call, which updates the text field with new data. Here's an example of the packet stream:

    Update JavaScript packets
    <script type="text/javascript">
    window.parent.comet_update(1);
    </script>
    
    <!-- 2 second delay -->
    
    <script type="text/javascript">
    window.parent.comet_update(2);
    </script>
    
    <!-- 2 second delay -->
    
    <script type="text/javascript">
    window.parent.comet_update(3);
    </script>
    

    More sophisticated comet applications will use a dynamic-typed protocol to update the client. Browser-based applications could use JSON to update the client and Flash-based applications might use Hessian. In all cases, the protocol must be kept simple and designed for the client's requirements. Design separate, simple protocols for Flash and JavaScript browsers, rather than trying to create some complicated general protocol.

    Browser Client

    The JavaScript command stream updates a parent HTML file which defines the JavaScript commands and launches the Comet servlet request with an <iframe> tag. Our comet_update function finds the HTML tag with id="content" and updates its HTML content with the new data from the server.

    comet.html
    <html>
    <body>
    
    Server Data:
    <span id="content">server data will be shown here</span>
    
    <script type="text/javascript">
    function comet_update(value) {
      document.getElementById('content').innerHTML = value;
    };
    </script>
    
    <iframe src="comet"
            style="width:1px;height:1px;position:absolute;top:-1000px"></iframe>
    
    </body>
    </html>
    

    AsyncContext

    The AsyncContext is the Servlet 3.0 encapsulation of control and communication from the application's service to the Comet servlet. Applications may safely pass the AsyncContext to different threads, wake the servlet with dispatch(), and send data with the request returned by getAttribute.

    In the example, the TimerService passes the updated count to the servlet by calling setAttribute("caucho.count", count), and wakes the servlet by calling dispatch(). When the servlet resumes, it will retrieve the count using request.getAttribute("caucho.count"). Note, applications must only use the thread-safe AsyncContext in other threads. As with other servlets, the ServletRequest, ServletResponse, writers and output stream can only be used by the servlet thread itself, never by any other threads.

    javax.servlet.AsyncContext
    package javax.servlet;
    
    public interface AsyncContext;
    {
      public ServletRequest getRequest();
      public ServletResponse getResponse();
    
      public boolean hasOriginalRequestAndResponse();
    
      public void complete();
      
      public void dispatch();
      public void dispatch(String path);
      public void dispatch(ServletContext context, String path);
    
      public void start(Runnable run);
    }
    

    Comet Servlet

    The comet servlet has three major responsibilities:

    1. Process the initial request (service).
    2. Register the AsyncContext with the service (service).
    3. Send streaming data as it becomes available (resume).

    Like other servlets, only the comet servlet may use the ServletRequest, ServletResponse or any output writer or stream. No other thread may use these servlet objects, and the application must never store these objects in fields or objects accessible by other threads. Even in a comet servlet, the servlet objects are not thread-safe. Other services and threads must use the AsyncContext to communicate with the servlet.

    Process the initial request: our servlet just calls setContentType("text/html"), since it's a trivial example. A real application would do necessary database lookups and possibly send more complicated data to the client.

    Register the AsyncContext: our servlet registers the controller with the timer service by calling addCometState. In general, the application state object will contain the CometController as part of the registration process.

    Send streaming data:. The TimerService will set new data in the "comet.count" attribute and dispatch() the controller. When the servlet executes the resume() method, it will retrieve the data, and send the next packet to the client.

    example/TestCometServlet.java
    package example;
    
    import java.io.*;
    
    import javax.servlet.http.*;
    import javax.servlet.*;
    import javax.inject.Current;
    
    public class TestComet extends GenericCometServlet {
      @Current private TimerService _timerService;
    
      @Override
      public void service(ServletRequest request,
                          ServletResponse response)
        throws IOException, ServletException
      {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
    
        AsyncContext async = request.getAsyncContext();
    
        if (async != null) {
          resume(request, response, async);
          return;
        }
    
        res.setContentType("text/html");
    
        async = request.startAsync();
    
        TestState state = new TestState(async);
    
        _timerService.addCometState(state);
      }
      
      private void resume(ServletRequest request,
                          ServletResponse response,
                          AsyncContext async)
        throws IOException, ServletException
      {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
    
        PrintWriter out = res.getWriter();
    
        String count = req.getAttribute("comet.count");
    
        out.print("<script type='text/javascript'>");
        out.print("comet_update(" + count + ");");
        out.print("</script>");
      }
    }
    

    The connection can close for a number of reasons. The service might call AsyncContext.complete() which will also close the connection. Finally, the client may close the connection itself.

    The sequence of calls for the example looks like the following:

    1. servlet.service() is called for the initial request
    2. _service.addCometState() registers with the TimerService
    3. after the service() completes, Resin suspends the servlet.
    4. The TimerService detects an event, in this case the timer event.
    5. The TimerService calls request.setAttribute() to send new data.
    6. The TimerService calls async.dispatch() to wake the servlet.
    7. servlet.resume() processes the data and sends the next packet.
    8. After the resume() completes, Resin suspends the servlet again and we repeat as after step #3.
    9. After the 10th data, the TimerService calls controller.close(), closing the servlet connection.

    Comet Servlet State Machine

    The sequence of comet servlet calls looks like the following state machine. After the initial request, the servlet spends most of its time suspended, waiting for the TimerService to call complete().

    Demo


    Copyright © 1998-2011 Caucho Technology, Inc. All rights reserved.
    Resin ® is a registered trademark, and Quercustm, Ambertm, and Hessiantm are trademarks of Caucho Technology.