DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Data Engineering
  3. Databases
  4. Servlet 3.0 Async API or Atmosphere? A Simple Comparison

Servlet 3.0 Async API or Atmosphere? A Simple Comparison

Jean-Francois Arcand user avatar by
Jean-Francois Arcand
·
Nov. 09, 09 · Interview
Like (0)
Save
Tweet
Share
16.54K Views

Join the DZone community and get the full member experience.

Join For Free

One the comments I'm getting about Atmosphere is why should I use the framework instead of waiting for Servlet 3.0 Async API. Well, it simple: much simpler, works with any existing Java WebServer (including Google App Engine!), and will auto-detect the Servlet 3.0 Async API if you deploy your application on a WebServer that support it.

To make a fair comparison, let's write the hello world of Comet, a Chat application and compare the server side code. Without technical details, let's just drop the entire server code. First, the Servlet 3.0 version (can probably be optimized a little):

 

  package web.servlet.async_request_war;
   
   import java.io.IOException;
   import java.io.PrintWriter;
   import java.util.Queue;
   import java.util.concurrent.ConcurrentLinkedQueue;
   import java.util.concurrent.BlockingQueue;
   import java.util.concurrent.LinkedBlockingQueue;
   
  import javax.servlet.AsyncContext;
  import javax.servlet.AsyncEvent;
  import javax.servlet.AsyncListener;
  import javax.servlet.ServletConfig;
  import javax.servlet.ServletException;
  import javax.servlet.annotation.WebServlet;
  import javax.servlet.http.HttpServlet;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  
  @WebServlet(urlPatterns = {"/chat"}, asyncSupported = true)
  public class AjaxCometServlet extends HttpServlet {
  
      private static final Queue<AsyncContext> queue = new ConcurrentLinkedQueue<AsyncContext>();
      private static final BlockingQueue<String> messageQueue = new LinkedBlockingQueue<String>();
      private static final String BEGIN_SCRIPT_TAG = "<script type='text/javascript'>\n";
      private static final String END_SCRIPT_TAG = "</script>\n";
      private static final long serialVersionUID = -2919167206889576860L;
      private Thread notifierThread = null;
  
      @Override
      public void init(ServletConfig config) throws ServletException {
          Runnable notifierRunnable = new Runnable() {
              public void run() {
                  boolean done = false;
                  while (!done) {
                      String cMessage = null;
                      try {
                          cMessage = messageQueue.take();
                          for (AsyncContext ac : queue) {
                              try {
                                  PrintWriter acWriter = ac.getResponse().getWriter();
                                  acWriter.println(cMessage);
                                  acWriter.flush();
                              } catch(IOException ex) {
                                  System.out.println(ex);
                                  queue.remove(ac);
                              }
                          }
                      } catch(InterruptedException iex) {
                          done = true;
                          System.out.println(iex);
                      }
                  }
              }
          };
          notifierThread = new Thread(notifierRunnable);
          notifierThread.start();
      }
  
      @Override
      protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
          res.setContentType("text/html");
          res.setHeader("Cache-Control", "private");
          res.setHeader("Pragma", "no-cache");
          
          PrintWriter writer = res.getWriter();
          // for IE
          writer.println("<!-- Comet is a programming technique that enables web servers to send data to the client without having any need for the client to request it. -->\
    n");
          writer.flush();
  
          req.setAsyncTimeout(10 * 60 * 1000);
          final AsyncContext ac = req.startAsync();
          queue.add(ac);
          req.addAsyncListener(new AsyncListener() {
              public void onComplete(AsyncEvent event) throws IOException {
                  queue.remove(ac);
              }
  
              public void onTimeout(AsyncEvent event) throws IOException {
                  queue.remove(ac);
              }
          });
      }
  
      @Override
      @SuppressWarnings("unchecked")
      protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
          res.setContentType("text/plain");
          res.setHeader("Cache-Control", "private");
          res.setHeader("Pragma", "no-cache");
 
          req.setCharacterEncoding("UTF-8");
          String action = req.getParameter("action");
          String name = req.getParameter("name");
  
          if ("login".equals(action)) {
              String cMessage = BEGIN_SCRIPT_TAG + toJsonp("System Message", name + " has joined.") + END_SCRIPT_TAG;
             notify(cMessage);
 
             res.getWriter().println("success");
         } else if ("post".equals(action)) {
            String message = req.getParameter("message");
             String cMessage = BEGIN_SCRIPT_TAG + toJsonp(name, message) + END_SCRIPT_TAG;
             notify(cMessage);
 
             res.getWriter().println("success");
         } else {
             res.sendError(422, "Unprocessable Entity");
         }
     }
 
     @Override
     public void destroy() {
         queue.clear();
         notifierThread.interrupt();
     }
 
     private void notify(String cMessage) throws IOException {
         try {
             messageQueue.put(cMessage);
         } catch(Exception ex) {
             throw new IOException(ex);
         }
     }
 
     private String escape(String orig) {
         StringBuffer buffer = new StringBuffer(orig.length());
 
         for (int i = 0; i < orig.length(); i++) {
             char c = orig.charAt(i);
             switch (c) {
             case '\b':
                 buffer.append("\\b");
                 break;
             case '\f':
                buffer.append("\\f");
                 break;
             case '\n':
                 buffer.append("<br />");
                 break;
             case '\r':
                 // ignore                 break;
            case '\t':
                 buffer.append("\\t");
                 break;
             case '\'':
                 buffer.append("\\'");
                 break;
             case '\"':
                 buffer.append("\\\"");
                 break;
            case '\\':
                 buffer.append("\\\\");
                 break;
             case '<':
                 buffer.append("<");
                 break;
             case '>':
                 buffer.append(">");
                 break;
             case '&':
                buffer.append("&");
                 break;
             default:
                 buffer.append(c);
             }
         }
 
         return buffer.toString();
     }
 
     private String toJsonp(String name, String message) {
         return "window.parent.app.update({ name: \"" + escape(name) + "\", message: \"" + escape(message) + "\" });\n";
     }
 }

 

OK now with Atmosphere , the same code consist of:

 

  package org.atmosphere.samples.chat.resources;
   
   import javax.ws.rs.Consumes;
   import javax.ws.rs.GET;
   import javax.ws.rs.POST;
   import javax.ws.rs.Path;
   import javax.ws.rs.Produces;
   import javax.ws.rs.WebApplicationException;
   import javax.ws.rs.core.MultivaluedMap;
   import org.atmosphere.annotation.Broadcast;
   import org.atmosphere.annotation.Schedule;
   import org.atmosphere.annotation.Suspend;
  import org.atmosphere.util.XSSHtmlFilter;
  
  @Path("/")
  public class ResourceChat {
  
     @Suspend
     @GET
      @Produces("text/html;charset=ISO-8859-1")
      public String suspend() {
          return "";
      }
  
     @Broadcast({XSSHtmlFilter.class, JsonpFilter.class})
     @Consumes("application/x-www-form-urlencoded")
      @POST
      @Produces("text/html;charset=ISO-8859-1")
     public String publishMessage(MultivaluedMap form) {
          String action = form.getFirst("action");
          String name = form.getFirst("name");
  
         if ("login".equals(action)) {
              return ("System Message" + "__" + name + " has joined.");
          } else if ("post".equals(action)) {
              return name + "__" + form.getFirst("message");
          } else {
              throw new WebApplicationException(422);
          }
      }
  }

 

OK so what's the deal? What's makes Atmosphere so easy? The Servlet 3.0 new Async API offers:

  • Method to suspend a response, HttpServletRequest.startAsync()
  • Method to resume a response: AsyncContext.complete()
Atmosphere offers:
  • Annotation to suspend: @Suspend
  • Annotation or resume: @Resume
  • Annotation to broadcast (or push) events to the set of suspended responses: @Broadcast
  • Annotation to filter and serialize broadcasted events using BroadcasterFilter (XSSHtmlFilter.class, JsonpFilter.class)
  • Build it support for all browser implementation incompatible implementation (ex: no need to output comments like in the Servlet 3.0 sample (line 69)). Atmosphere will workaround all those issues for you.
With Servlet 3.0 Async API, the missing part is how you share information with suspended responses. In the current chat sample, you need to creates your own Thread/Queue in order to broadcast events to your set of suspended responses (line 32 to 56). This is not a big deal, but you will need to do something like that for all your Servlet 3.0 Async based applications...or use a Framework that do it for you!.

 

Still not convinced? Well, you can write your Atmosphere applications today and not have to wait for Servlet.3.0 implementation (OK easy plug for my other project: GlassFish v3 supports it pretty well!). Why? Atmosphere always auto-detected the best asynchronous API when you deploy your application. It always try first to look up the 3.0 Async API. If it fails, it will try to find WebServer's native API like Grizzly Comet (GlassFish), CometProcessor (Tomcat), Continuation (Jetty), HttpEventServlet (JBossWeb), AsyncServlet (WebLogic), Google App Engine (Google). Finally, it will fallback to use a blocking I/O Thread to emulate support for asynchronous events.

But you don't want to use Java? Fine, try the Atmosphere Grails Plug In, or Atmosphere in PrimesFaces if you like JSF, or use Scala:

 

   package org.atmosphere.samples.scala.chat
   
   import javax.ws.rs.{GET, POST, Path, Produces, WebApplicationException, Consumes}
   import javax.ws.rs.core.MultivaluedMap
   import org.atmosphere.annotation.{Broadcast, Suspend}
   import org.atmosphere.util.XSSHtmlFilter
   
   @Path("/chat")
   class Chat {
  
      @Suspend
      @GET
      @Produces(Array("text/html;charset=ISO-8859-1"))
      def suspend() = {
          ""
      }
  
      @Broadcast(Array(classOf[XSSHtmlFilter],classOf[JsonpFilter]))
      @Consumes(Array("application/x-www-form-urlencoded"))
      @POST
      @Produces(Array("text/html;charset=ISO-8859-1"))
      def publishMessage(form: MultivaluedMap[String, String]) = {
          val action = form.getFirst("action")
          val name = form.getFirst("name")
  
          val result: String = if ("login".equals(action)) "System Message" + "__" + name + " has joined."
              else if ("post".equals(action)) name + "__" + form.getFirst("message")
               else throw new WebApplicationException(422)
  
          result
      }
  
  
  }

 

Echec et Mat!

 

Now, I can understand you already have an existing application and just want to update it with suspend/resume/broadcast functionality, without having to re-write it completely. Fine, let's just use the Atmosphere's Meteor API:

 

   package org.atmosphere.samples.chat;
   
   import java.io.IOException;
   import java.util.LinkedList;
   import java.util.List;
   import javax.servlet.http.HttpServlet;
   import javax.servlet.http.HttpServletRequest;
   import javax.servlet.http.HttpServletResponse;
   import org.atmosphere.cpr.BroadcastFilter;
  import org.atmosphere.cpr.Meteor;
  import org.atmosphere.util.XSSHtmlFilter;
  
  public class MeteorChat extends HttpServlet {
 
      private final List list;
  
      public MeteorChat() {
          list = new LinkedList();
          list.add(new XSSHtmlFilter());
          list.add(new JsonpFilter());
      }
  
      @Override
      public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
          Meteor m = Meteor.build(req, list, null);
  
          req.getSession().setAttribute("meteor", m);
  
          res.setContentType("text/html;charset=ISO-8859-1");
          res.addHeader("Cache-Control", "private");
          res.addHeader("Pragma", "no-cache");
  
         m.suspend(-1);
          m.broadcast(req.getServerName() + "__has suspended a connection from " + req.getRemoteAddr());
      }
  
      public void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException {
        Meteor m = (Meteor)req.getSession().getAttribute("meteor");
          res.setCharacterEncoding("UTF-8");
         String action = req.getParameterValues("action")[0];
          String name = req.getParameterValues("name")[0];
  
          if ("login".equals(action)) {
              req.getSession().setAttribute("name", name);
              m.broadcast("System Message from " + req.getServerName() + "__" + name + " has joined.");
              res.getWriter().write("success");
              res.getWriter().flush();
          } else if ("post".equals(action)) {
              String message = req.getParameterValues("message")[0];
              m.broadcast(name + "__" + message);
              res.getWriter().write("success");
              res.getWriter().flush();
          } else {
              res.setStatus(422);
  
              res.getWriter().write("success");
              res.getWriter().flush();
         }
      }
  }

 

Servlet 3.0 Async API is Game Over! Finally I must admit that Servlet 3.0 Async API have asynchronous dispatcher you can use to forward request asynchronously:

 

    public void doGet(HttpServletRequest req, HttpServletResponse res)
            throws IOException, ServletException {

        final AsyncContext ac = req.startAsync();
        final String target = req.getParameter("target");

        Timer asyncTimer = new Timer("AsyncTimer", true);
        asyncTimer.schedule(
            new TimerTask() {
                @Override
                public void run() {
                    ac.dispatch(target);
                }
            },
        5000);
    }

 

With Atmosphere, the same code will works but your application will only works when deployed on Servlet 3.0 WebServer. Instead, you can implement the same functionality using Broadcast's delayed broadcast API and still have a portable application without limiting you with Servlet 3.0 Async API...that's something I will talk in my next blog!

For any questions or to download Atmosphere, go to our main site and use our Nabble forum (no subscription needed) or follow us on Twitter and tweet your questions there!

API Atmosphere (architecture and spatial design) Comparison (grammar)

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Stop Using Spring Profiles Per Environment
  • Introduction to Container Orchestration
  • A Gentle Introduction to Kubernetes
  • Container Security: Don't Let Your Guard Down

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: