<< March 2012 | Home | May 2012 >>

Ajax Push with Tomcat 7 and nginx

This is really nothing new, but I couldn't find any comprehensive instruction for what I wanted to accomplish, so I thought I'd write down a note about it here.

Tomcat 7 supports the Servlet 3.0 specification, which includes a standard (Java EE 6) way to handle HTTP requests and responses asynchronously. This is good for "Ajax Push" applications, because you don't have to hardwire your code for a specific servlet container. Of course it has always been possible to write container-independent code for Ajax Push by simply having the request thread sleep until it times out or is woken up by a notification. However this ties up a thread in the operating system for each concurrent request. With an asynchronous servlet, the open TCP socket is the only thing that needs to be stored away until the response is completed, at least in principle.

Some footwork is needed to enable asynchronous servlets. First install the Tomcat native-code APR library. On Ubuntu this is easily done by:
    # apt-get install libtcnative-1 
Then restart Tomcat. It will automatically detect the presence of the library and switch the default connector to APR, and report something like this in the log:
    INFO: Loaded APR based Apache Tomcat Native library 1.1.20. 
Then create a servlet that can use the new functionality:
    @WebServlet(urlPatterns={"/async"}, asyncSupported=true)
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

AsyncContext async = request.startAsync(request, response);
MyAsyncListener listener = new MyAsyncListener(async);
MyNotificationRegistry.add(listener);
async.addListener(listener);
}
}
The class "MyAsyncListener" contains the code that completes the HTTP response, by accessing the ServletResponse stored in the AsyncContext. The event that triggers this will find this MyAsyncListener instance via MyNotificationRegistry. How this works is left as an exercise for the reader :-)

Actually POST is better than GET for this if the async servlet is reachable from the public Internet. The reason is that GET urls can be embedded in other web pages, so it's very easy for a hostile agent to arrange a distributed denial-of-service attack. This is not possible (or at least not as easy) with POST.

But ignoring this for the moment, below is the jQuery code used on the client:
    function longPolling() {
$.ajax({
url: "/webapp/async",
type: "GET",
success: function(response) {
alert("Got a response!");
},
complete: function() {
longPolling();
},
dataType: "json"
});
}
$(document).ready(function() { setTimeout("longPolling()", 500); });
Some details have been omitted here, but this captures the essential mechanism.

The final step is changing the reverse proxy. My standard tool for proxying Tomcat/GlassFish/whatever, for handling SSL certificates, and for serving static content, has for more than 15 years been good old Apache httpd. But it doesn't handle long polling well, since it keeps one thread per request. So now I too have switched to nginx. This was much easier than I had expected, at least for this use-case. Just put this in a suitable configuration file:
    location /webapp {
proxy_pass http://tomcat.example.com:8080/webapp;
proxy_redirect off;
}
That's it!