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:
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.
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.
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/
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:
- Client sends a request to the server.
- 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.
- 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.
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:
- The class is an instance of ServletContextListener. However on initialization it simply transfers control to a worker thread responsible for event processing.
- 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.
- It then writes the output on the response stream.
- 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.
<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/
It's very nice blog. I'm so happy to gain some knowledge from here.
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDelete