Contents


Web servers and Socket.IO

Using various web containers, APIs, and abstraction libraries

Comments

Content series:

This content is part # of # in the series: Reverse Ajax, Part 3

Stay tuned for additional content in this series.

This content is part of the series:Reverse Ajax, Part 3

Stay tuned for additional content in this series.

Today's users expect fast, dynamic applications accessible from the web. This series shows you how to develop event-driven web applications using Reverse Ajax techniques. Part 1 introduced Reverse Ajax, polling, streaming, Comet, and long polling. You learned how Comet using HTTP long polling is the best way to reliably implement Reverse Ajax, since all browsers now provide support. Part 2 showed how to implement Reverse Ajax using WebSockets. Code examples helped illustrate WebSockets, FlashSockets, constraints on the server side, request-scoped services, and pausing long-lived requests.

In this article, delve into details about using Comet and WebSockets in your web application for different web containers and APIs (Servlet 3.0 and Jetty Continuations). Learn to transparently use Comet and WebSockets by using abstraction libraries, such as Socket.IO. Socket.IO uses feature detection to decide if the connection will be established with WebSocket, AJAX long polling, Flash, and so on.

You can download the source code used in this article.

Prerequisites

Ideally, to get the most out of this article, you should know JavaScript and Java. The example created in this article was built using Google Guice, a dependency injection framework written in Java. To follow along with this article, you should be familiar with the concepts of a dependency injection framework, such as Guice, Spring, or Pico.

To run the sample in this article, you will also need the latest version of Maven and the JDK (see Related topics).

Server solutions for Comet and WebSocket

You learned in Part 1 that Comet (long-polling or streaming) requires that the server is able to pause a request and resume or complete it after a potentially long delay. Part 2 described how servers need to use the non-blocking I/O features to handle a lot of connections and that they only use threads to serve requests (thread-per-request model). You also learned that the usage of WebSocket is server-dependent and that not all servers support WebSockets.

This section shows you how to use Comet and WebSockets, if applicable, on the Jetty, Tomcat, and Grizzly web servers. The source code provided with this article contains a sample chat web application for Jetty and Tomcat. This section also discusses the supported APIs for the following application servers: Jboss, Glassfish, and WebSphere.

Jetty

Jetty is a web server supporting the Java Servlet specification 3.0, WebSockets, and many other integration specifications. Jetty is:

  • Powerful and flexible
  • Easily embedded
  • Supports virtual hosts, session clustering, and a lot of features that can be easily configured through Java code or XML
  • Used for the Google App Engine's hosting service

The core Jetty project is hosted by the Eclipse Foundation.

Since Version 6, Jetty includes an asynchronous API called Jetty Continuations, which allows a request to be paused and resumed later. Table 1 shows the map of the supporting specification and API for the major Jetty version families.

Table 1. Jetty versions and support
SupportsJetty 6Jetty 7Jetty 8
Non-blocking I/OXXX
Servlet 2.5XXX
Servlet 3.0XX
Jetty Continuations (Comet)XXX
WebSocketsXX

To implement Reverse Ajax with Comet, you can use Jetty's Continuation API, as shown in Listing 1:

Listing 1. Jetty Continuation API for Comet
// Pausing a request from a servlet's method (get, post, ...):

protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException { 

Continuation continuation = ContinuationSupport.getContinuation(req); 
// optionally set a timeout to avoid suspending requests for too long 
continuation.setTimeout(0); 
// suspend the request 
continuation.suspend(); 
// then hold a reference for future usage from another thread 
continuations.offer(continuation); 

}

// Then, from another thread which wants to send an event to the client:

while (!continuations.isEmpty()) { 
    Continuation continuation = continuations.poll(); 
    HttpServletResponse response = 
        (HttpServletResponse) continuation.getServletResponse(); 
    // write to the response 
    continuation.complete(); 
}

The complete web application is in the source code that comes with this article. The Jetty Continuations are bundled in a JAR archive. You have to put this JAR file in your web application's WEB-INF/lib folder to be able to use Jetty's Comet features. The Jetty Continuations will work on Jetty 6, 7, and 8.

Beginning with Jetty 7, you also have access to the WebSockets feature. Place Jetty's WebSocket JAR file in your web application's WEB-INF/lib folder to gain access to Jetty's WebSocket API, as shown in Listing 2:

Listing 2. Jetty's WebSocket API
// Implement the  doWebSocketConnect and returns an implementation of
//  WebSocket:

public final class ReverseAjaxServlet extends WebSocketServlet { 
    @Override 
    protected WebSocket doWebSocketConnect(HttpServletRequest request,
                                           String protocol) { 
        return [...] 
    } 
}

// Sample implementation of  WebSocket:

class Endpoint implements WebSocket { 
    Outbound outbound; 
    public void onConnect(Outbound outbound) { 
        this.outbound = outbound;    
    } 
    public void onMessage(byte opcode, String data) { 
        outbound.sendMessage("Echo: " + data);
        if("close".equals(data)) 
             outbound.disconnect();
    } 
    public void onFragment(boolean more, byte opcode, 
                           byte[] data, int offset, int length) { 
    } 
    public void onMessage(byte opcode, byte[] data, 
                          int offset, int length) { 
        onMessage(opcode, new String(data, offset, length)); 
    } 
    public void onDisconnect() { 
        outbound = null; 
    } 
}

In the downloadable source code, there is a chat sample in the jetty-websocket folder that demonstrates how to use Jetty's WebSocket API.

Tomcat

Tomcat is probably the most widely known web server. It has been used for many years and was integrated as a web container into early versions of the Jboss application server. Tomcat was also used as the reference implementation for the servlet specification. It was discontinued in servlet API 2.5 when people began to look at alternatives based on non-blocking I/O (such as Jetty). Table 2 shows the supported specifications and API for the two latest Tomcat version families.

Table 2. Tomcat support
SupportsTomcat 6Tomcat 7
Non-blocking I/OXX
Servlet 2.5XX
Servlet 3.0X
Advanced I/O (Comet)XX
WebSockets

As shown in Table 2, Tomcat does not support WebSockets; it has an equivalent of Jetty's Continuations called Advanced I/O to support Comet. Advanced I/O is much more of a low-level wrapper around NIO than a good API to facilitate Comet usage. It is poorly documented, and there are few examples of applications using this API. Listing 3 shows a servlet example used to suspend and resume requests in a chat web application. You can find the complete web application in the source code with this article.

Listing 3. Tomcat API for Comet
public final class ChatServlet extends HttpServlet 
                               implements CometProcessor { 

    private final BlockingQueue<CometEvent> events = 
         new LinkedBlockingQueue<CometEvent>(); 

    public void event(CometEvent evt) 
        throws IOException, ServletException { 

        HttpServletRequest request = evt.getHttpServletRequest(); 
        String user = 
            (String) request.getSession().getAttribute("user"); 
        switch (evt.getEventType()) { 
            case BEGIN: { 
                if ("GET".equals(request.getMethod())) { 
                    evt.setTimeout(Integer.MAX_VALUE); 
                    events.offer(evt); 
                } else { 
                    String message = request.getParameter("message"); 
                    if ("/disconnect".equals(message)) { 
                        broadcast(user + " disconnected"); 
                        request.getSession().removeAttribute("user"); 
                        events.remove(evt); 
                    } else if (message != null) { 
                        broadcast("[" + user + "]" + message); 
                    } 
                    evt.close(); 
                } 
            } 
        } 
    } 

    void broadcast(String message) throws IOException { 
        Queue<CometEvent> q = new LinkedList<CometEvent>(); 
        events.drainTo(q); 
        while (!q.isEmpty()) { 
            CometEvent event = q.poll(); 
            HttpServletResponse resp = event.getHttpServletResponse();
            resp.setStatus(HttpServletResponse.SC_OK);
            resp.setContentType("text/html");
            resp.getWriter().write(message);
            event.close(); 
        } 
    } 
}

In Tomcat, an asynchronous servlet must implement a CometProcessor. For asynchronous servlets, Tomcat does not call the standard HTTP methods (doGet, doPost, and so on). Instead, an event is sent to the event(CometdEvent) method. When a request first arrives, the example checks to see if it is a GET in order to suspend it; evt.close() is not called. If it is a POST, it means the user sent a message, so it is broadcast to the other CometEvent, and evt.close() is called to complete the post request. On the client side, broadcasting will make all long polling requests to complete the sent message, and another long polling request is immediately sent to receive the next events.

Grizzly and Glassfish

Grizzly is not a web container, but more of an NIO framework that helps developers build scalable applications. It was developed as part of the Glassfish project, but it can also be used on a standalone or embedded basis. Grizzly provides components to act as an HTTP/HTTPS server and components for Bayeux Protocol, Servlet, HttpService OSGi, and Comet, among others. Grizzly supports WebSockets and is used in Glassfish to provide Comet and WebSocket support.

Glassfish, Oracle's Application Server, is the reference implementation of the J2EE 6 specifications. Glassfish is a complete suite, like WebSphere and Jboss, using Grizzly for NIO, WebSocket, and Comet support. Its modular architecture, based on OSGI, makes it really flexible for changing components. Table 3 shows Glassfish support for Comet and WebSockets.

Table 3. Glassfish support
SupportsGlassfish 2Glassfish 3
Non-blocking I/OXX
Servlet 2.5XX
Servlet 3.0X
CometXX
WebSocketsX

Grizzly usage is not trivial, as it's intended to be used embedded or directly from Java code. It is widely used as a framework to support Comet and WebSockets that can be embedded in a larger application, such as Glassfish, which provides web deployment capabilities and the Servlet specification API.

See Related topics for links to examples of WebSockets and Comet in Grizzly or Glassfish. Because Glassfish uses Grizzly, the examples should work on both. The WebSocket API is very similar to the one in Jetty, but the Comet API is more complex.

Jboss

Jboss is an application server built on top of Tomcat. It has supported Comet and NIO since Version 5. Jboss 7 is still in development, but is included in Table 4 below.

Table 4. Jboss support
SupportsJboss 5Jboss 6Jboss 7
Non-blocking I/OXXX
Servlet 2.5XXX
Servlet 3.0XX
CometXXX
WebSockets

WebSphere

WebSphere is an IBM application server. Support of the Servlet 3 API (containing the standardized asynchronous API for Comet) was added in Version 8 of WebSphere (see Related topics to read the announcement).

Table 5. WebSphere support
SupportsWebSphere 8
Non-blocking I/OX
Servlet 2.5X
Servlet 3.0X
CometX
WebSockets

What about having common APIs?

Each server brings its own native API for Comet and WebSocket. As you might guess, writing a portable web application can be difficult. The Servlet 3.0 Specification includes additional methods to suspend and resume a request later, allowing all web containers supporting the Servlet 3.0 Specification to support the Comet long-polling requests.

The Jetty team provides a library called Jetty Continuation, which is independent from the Jetty container. The Jetty Continuation library is clever enough to detect the container or specification available. If running on a Jetty server, the native Jetty API will be used. If running on a container supporting the Servlet 3.0 Specification, this common API will be used. Otherwise, a non-scalable implementation is used.

Regarding WebSockets, there is no standard yet in Java, and thus you need to use the container vendor API within your web application if you want to use WebSockets.

Table 6 summarizes the technologies supported by the various servers.

Table 6. Technologies supported by servers
ContainerCometWebSocket
Jetty 6Jetty ContinuationsN/A
Jetty 7Servlet 3.0
Jetty Continuations
Native Jetty API
Jetty 8Servlet 3.0
Jetty Continuations
Native Jetty API
Tomcat 6Advanced I/ON/A
Tomcat 7Servlet 3.0
Advanced I/O
Jetty Continuations
N/A
Glassfish 2Native Grizzly APIN/A
Glassfish 3Servlet 3.0
Native Grizzly API
Jetty Continuations
Native Grizzly API
Jboss 5Native Jboss APIN/A
Jboss 6Servlet 3.0
Native Jboss API
Jetty Continuations
N/A
Jboss 7Servlet 3.0
Native Jboss API
Jetty Continuations
N/A
WebSphere 8Servlet 3.0
Jetty Continuations
N/A

There is no obvious solution for WebSockets except to use the container API. As for Comet, each container supporting the Servlet 3.0 Specification supports Comet. The advantage of Jetty Continuations here is to provide Comet support on all of these containers. Thus, some Reverse Ajax libraries (which is discussed in the next section and in the next article in this series) are using Jetty Continuations for their server-side API.

The Jetty Continuation API is shown in the Jetty sample in this article. The Servlet 3.0 Specification was described and used in the two Comet examples in Part 1 of this series.

Abstraction libraries

Considering all of the major APIs (Servlet 3.0 and Jetty Continuations), plus all of the native support on the server side, and two major ways of doing Reverse Ajax on the client side (Comet and WebSocket), writing your own JavaScript and Java code to wire them could be difficult. You must also factor in timeouts, connection failures, acknowledgement, ordering, buffering, and so on.

The rest of this article shows you Socket.IO in action. Part 4 in this series will explore Atmosphere and CometD. These three libraries are all open source, and they all support Comet and WebSocket on many servers.

Socket.IO

Socket.IO is a JavaScript client library that provides a single API, similar to WebSocket, to connect to a remote server to asynchronously send and receive messages. By providing a common API, Socket.IO supports several transports: WebSocket, Flash Sockets, long polling, streaming, forever Iframes, and JSONP polling. Socket.IO detects browser capabilities and tries to choose the best transport available. The Socket.IO library is compatible with nearly all browsers (including old ones, such as IE 5.5) as well as mobile browsers. It also has features such as heartbeats, timeout, disconnection, and error handling.

The Socket.IO website (see Related topics) describes in detail how the library works and which browser and Reverse Ajax technique are used. Basically, Socket.IO uses a communication protocol that enables the client library to communicate with an endpoint on the server side, which can understand the Socket.IO protocol. Socket.IO was first developed for Node JS, a JavaScript engine used to build faster servers. Many projects brought support for other languages, including Java.

Listing 4 shows an example of using the Socket.IO JavaScript library on the client side. The Socket.IO website has documentation and examples.

Listing 4. Socket.IO client library usage
var socket = new io.Socket(document.domain, { 
    resource: 'chat' 
}); 
socket.on('connect', function() { 
    // Socket.IO is connected
}); 
socket.on('disconnect', function(disconnectReason, errorMessage) { 
    // Socket.IO disconnected
}); 
socket.on('message', function(mtype, data, error) { 
    // The server sent an event
});

// Now that the handlers are defined, establish the connection:
socket.connect();

To use the Socket.IO JavaScript library, you will need the corresponding Java part called Socket.IO Java (see Related topics). This project was initially started by the Apache Wave team to bring support for Reverse Ajax to Wave before WebSockets was available. Socket.IO Java was forked and maintained by Ovea (a company specializing in event-driven web development) and then abandoned. Developing the back end to support the Socket.IO client library is complex due to the multiple transports. Part 4 of this series will show how supporting a lot of transports in a client library is not necessary to achieve better scalability and browser support, since long-polling and WebSockets are sufficient. When WebSockets were not available, Socket.IO was indeed a good choice.

Socket.IO Java uses the Jetty Continuation API to suspend and resume requests. It uses the native Jetty WebSockets API for WebSockets support. You can determine which server will work correctly with a web application using Socket.IO Java.

Listing 5, below, shows an example of how to use Socket.IO on the server. You have to define a servlet extending SocketIOServlet and implement a method returning a sort of endpoint representation. The API is very similar to the WebSockets API. The advantage is that this API is used on the server side, independent of the chosen transport on the client side. Socket.IO translates all transport types to the same API on the server side.

Listing 5. Socket.IO Java library used for the chat sample - servlet
public final class ChatServlet extends SocketIOServlet { 
    private final BlockingQueue<Endpoint> endpoints = 
        new LinkedBlockingQueue<Endpoint>(); 
 
    @Override 
    protected SocketIOInbound doSocketIOConnect
                                        (HttpServletRequest request) { 
        String user = 
                (String) request.getSession().getAttribute("user"); 
        return user == null ? null : new Endpoint(this, user, request); 
    } 
 
    void broadcast(String data) { 
        for (Endpoint endpoint : endpoints) { 
            endpoint.send(data); 
        } 
    } 
 
    void add(Endpoint endpoint) { 
        endpoints.offer(endpoint); 
    } 
 
    void remove(Endpoint endpoint) { 
        endpoints.remove(endpoint); 
    } 
}

Listing 6 shows how to return the endpoint.

Listing 6. Socket.IO Java library usage for the chat sample - endpoint
class Endpoint implements SocketIOInbound { 
 
    [...]
 
    private SocketIOOutbound outbound; 
 
    [...]

    @Override 
    public void onConnect(SocketIOOutbound outbound) { 
        this.outbound = outbound; 
        servlet.add(this); 
        servlet.broadcast(user + " connected"); 
    } 
 
    @Override 
    public void onDisconnect(DisconnectReason reason, 
                             String errorMessage) { 
        outbound = null; 
        request.getSession().removeAttribute("user"); 
        servlet.remove(this); 
        servlet.broadcast(user + " disconnected"); 
    } 
 
    @Override 
    public void onMessage(int messageType, String message) { 
        if ("/disconnect".equals(message)) { 
            outbound.close(); 
        } else { 
            servlet.broadcast("[" + user + "] " + message); 
        } 
    } 
 
    void send(String data) { 
        try { 
            if (outbound != null 
    && outbound.getConnectionState() == ConnectionState.CONNECTED) { 
                outbound.sendMessage(data); 
            } 
        } catch (IOException e) { 
            outbound.close(); 
        } 
    } 
 
}

The complete example of Socket.IO is included in the source code in the socketio folder.

Conclusion

All web containers support Comet, and nearly all support WebSockets. Even if the specifications lead to several different native implementations, you can still develop a web application using Comet with the common APIs (Servlet 3.0 or Jetty Continuations). And, even better, you can harness the power of Comet and WebSockets transparently by using libraries such as Socket.IO. Two more libraries, Atmosphere and CometD, will be covered in the next article in this series. All three libraries provide multi-browser support, a fantastic user experience, and the benefits of error handling, an easier API, timeout, and reconnections.


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=754325
ArticleTitle=Reverse Ajax, Part 3: Web servers and Socket.IO
publish-date=08302011