Search This Blog

Tuesday, 12 April 2016

Reverse AJAX - The Comet Technique

In my series on Reverse AJAX, I covered Polling and Piggybacking. We saw the pros and cons of either technique. In this post I shall take a look at a third technique - Comet.
According to wikipedia:
Comet is a web application model in which a long-held HTTP request allows a 
web server to push data to a browser, without the browser explicitly requesting it.
In this approach:
  1. Client sends a request to the server.
  2. When server has some information to send back, it will return the information as a response to this request. If there is no data for a certain amount of time, the request times out.
  3. For the client, the request ends either with a 200 OK (and some data) or a time -out error. In either case, client will initiate another request to the server.
Thus with Comet, web servers can send the data to the client without having to explicitly request it. One of the requirements on server side is the ability to keep the client request on till it has some data to send the client. The server should be able to suspend the client request for use when needed.
Prior to servlet 3.x, Servlets did not have the ability to suspend requests. So most container providers built their own implementations to support Comet behavior. For e.g. Tomcat provided the CometProcessor interface which needs to be implemented by Servlets.
public void event(CometEvent event)
This method is the on where all the magic happens. There is an example here. JBoss and Jetty servers among others also provide similar techniques.
With Servlet 3.x async support however, there is a uniform way to do this across the various containers.
The first step was to create an asynchronous endpoint at the server.
@SuppressWarnings("serial")
public class CometPushServlet extends HttpServlet {
    
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    System.out.println("Request received for a check at " + new Date());
    AsyncContext asyncContext = req.startAsync();
    //Save for later processing
    CometEventNotifier.suspendRequestForLaterProcessing(asyncContext);
    
  }
}
As seen here, we call the startAsync method on the request.
Puts this request into asynchronous mode, and initializes its AsyncContext with the original 
(unwrapped) ServletRequest and ServletResponse objects.
Calling this method will cause committal of the associated response to be delayed until 
AsyncContext#complete is called on the returned AsyncContext, or the asynchronous operation 
has timed out.
Every request that arrives here is converted into an asynchronous one (creating the AsyncContext) and then passed to the CometEventNotifier class.
public class CometEventNotifier implements ServletContextListener {
  
  // A Queue to hold all the AyncContexts
  private static final BlockingQueue<AsyncContext> queue = new LinkedBlockingQueue<>();

  //using a separate thread to avoid blocking the main guy
  private Thread workerThread;
  
  public static void suspendRequestForLaterProcessing( AsyncContext asyncContext) {
    queue.add(asyncContext);
  }
  
  @Override
  public void contextDestroyed(ServletContextEvent arg0) {
    workerThread.interrupt();// no point in running it any further
    }

  @Override
  public void contextInitialized(ServletContextEvent arg0) {
    
    workerThread = new Thread(new Runnable() {
      @Override
      public void run() {
        
        while (true) { //Runs on an infinite loop
          try {
            //Check if there is any event
            int eventC = EventUtils.getEvents();
            if(eventC==0) {
              //sleep for 2 seconds
              Thread.sleep(2000);
              continue;
            }
            //Inform all waiting requests about the invite
            AsyncContext context;
            while ((context = queue.poll()) != null) {
              try {
                ServletResponse response = context.getResponse();
                response.setContentType(" application/xml");
                PrintWriter out = response.getWriter();
                out.printf("<data><eventC>" + eventC +"</eventC></data>");
                out.flush();
                out.close();
              } catch (Exception e) {
                throw new RuntimeException(e.getMessage());
              } finally {
                context.complete();
              }
            }
          } catch (InterruptedException e) {
            //skip
            return;
          }
        }
      }
    });
    //Start the Thread
    workerThread.start();
  }

}
Several Points of note in this code:
  1. The class is an instance of ServletContextListener. However on initialization it simply transfers control to a worker thread responsible for event processing.
  2. The Thread runs an infinite loop. Every few seconds it checks if any events have been generated. In case of events, for each of  AsyncContext, it retrieves the ServletRequest and ServletResponse.
  3. It then writes the output on the response stream.
  4. Processing is completed by calling the complete() method - this indicates that response associated with the AsyncContext can be closed. In case the operation had timed out, its the container's responsibility to call the complete() method.
The configurations in web.xml is as follows:
<servlet>
      <servlet-name>CometPushServlet</servlet-name>
      <servlet-class>com.app.web.CometPushServlet</servlet-class>
      <async-supported>true</async-supported>
   </servlet>
   <servlet-mapping>
      <servlet-name>CometPushServlet</servlet-name>
      <url-pattern>/comet</url-pattern>
   </servlet-mapping>

   <listener>
      <description> Responsible for completing all long requests</description>
      <listener-class>com.app.web.CometEventNotifier</listener-class>
   </listener>
As seen, there is a special async-supported tag that must be added - this must be present in all servlets and filters through which async requests pass through. Last is the client which makes the call:
<html>
<head>
<script>
  var rec =0;
  
  var cometCheck = function() {
      rec= rec+1;
      if (window.XMLHttpRequest) {
          // code for IE7+, Firefox, Chrome, Opera, Safari
          xmlhttp = new XMLHttpRequest();
      } else { // code for IE6, IE5
          xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
      }
      xmlhttp.onreadystatechange = function() {
          if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {  
              var xmlDoc = xmlhttp.responseXML;
              var eventCount = xmlDoc.getElementsByTagName("eventC")[0].childNodes[0].nodeValue
              document.getElementById("eventHighlighter").innerHTML = " Call No " + rec + " : <b>"
                  + eventCount
                  + " Event Invites received ! Goto Events page to review. </b>";
             // processing complete - restart the connection
                cometCheck();
          }
      }
      xmlhttp.open("GET", "comet", true);
      xmlhttp.timeout = 4000;
      xmlhttp.ontimeout = function () {
       alert("TimedOut");
       //In case of a timeout - restart the connection
       cometCheck(); 
    }
      xmlhttp.send();
  };

cometCheck();
  
</script>

</head>

<body>
     <h3>Hello, This is a sample page</h3>
      <br />
      <br /> Is your session active ??
      <div id="sessionActve"></div>
      <div id="eventHighlighter"></div>
</body>
</html>
As seen here, the java script includes a method that opens a connection to our comet servlet. On completion it updates the UI and then reopens the connection. If there was a timeout, than the client will reopen a different connection.
There are several advantages to this technique. Unlike polling we aren't sending several requests to the server. At any given time there is just one request open at the server, which is processed only when data needs to be sent back.
Unlike the piggyback technique, the client receives fast updates when an event occurs.

Reference and Guide: http://www.ibm.com/developerworks/library/wa-reverseajax1/

19 comments:

  1. This is really a great post. Thank you for taking time to provide us some of the useful and exclusive information with us. Keep
    on blogging!
    Java training in Chennai

    ReplyDelete
  2. Another great articles and very interesting to read, thanks for sharing that wonderful useful information, given programming coding was very excellent and easily observe all provided information.

    Android Training in Chennai

    ReplyDelete
  3. Great and useful article. Creating content regularly is very tough. Your points are motivated me to move on.


    SEO Company in Chennai

    ReplyDelete
  4. Wow amazing i saw the article with execution models you had posted. It was such informative. Really its a wonderful article. Thank you for sharing and please keep update like this type of article because i want to learn more relevant to this topic.

    Digital Marketing For Small Business in Chennai

    ReplyDelete
  5. Bluehost is definitely the best hosting provider for any hosting plans you might require.

    ReplyDelete
  6. Thank you for sharing such a nice and interesting blog with us. Hope it might be much useful for us. keep on updating...!!
    seo company in india
    digital marketing company in india
    seo company in chennai
    digital marketing company in chennai

    ReplyDelete
  7. A very nice guide. I will definitely follow these tips. Thank you for sharing such detailed article. I am learning a lot from you.

    Best Dentists In Chennai

    Smile Designing Dental Clinic In Chennai

    ReplyDelete
  8. I am regular visitor of this blog .I am working as blog reviewer in a private press and I saw many useful posts here. Sure, I will give best ratings for this blog .Keep posting best posts like this to get top reviews and ratings from blog reviewers and people .And I am thankful for this valuable post.
    Marine Colleges in Chennai | Mechanical Colleges in Chennai | ECE Colleges in Chennai

    ReplyDelete
  9. This is my first visit to your blog, your post made productive reading, thank you. dot net training in chennai

    ReplyDelete
  10. I must thank you for the efforts you have put in penning this site. I am hoping to check out the same high-grade content by you later on as well. In truth, your creative writing abilities has inspired me to get my own, personal blog now..
    Office Interiors in Chennai
    Home Interior Decorators in Chennai

    ReplyDelete
  11. Informative article, just what I was looking for.seo services chennai

    ReplyDelete
  12. Its fantatic explaintion lot of information gather it...nice article....
    seo company in Chennai

    ReplyDelete

  13. Its a wonderful post and very helpful, thanks for all this information. You are including better information regarding this topic in an effective way.Thank you so much

    Personal Installment Loans
    Payday Cash Advance loan
    Title Car loan
    Cash Advance Loan

    ReplyDelete
  14. Interesting article to read.. thanks a lot for sharing

    hadoop training | big data training

    ReplyDelete
  15. nice blog. thanks for sharing valuable information. It’s really good.Any one looking software courses nareshit is the best offline and online software training institute in Hyderabad and Chennai.

    ReplyDelete
  16. Its a wonderful post and very helpful, thanks for all this information. You are including better information regarding this topic in an effective way.Thank you so much

    Installment Loans Near Me
    Title loans Near Me
    Cash Advances Near Me

    ReplyDelete
  17. Really it was an awesome article...very interesting to read..You have provided an nice article....Thanks for sharing..
    Android Training in Chennai
    Ios Training in Chennai

    ReplyDelete