Begin
In this article, I'll show you how to build some simple Comet-style WEB applications using a variety of Java technologies. Readers should have some knowledge of Java Servlet, Ajax, and JavaScript. We'll look at some of the features that support Comet in Tomcat and Jetty, so we need to use the latest versions of both products. This article uses Tomcat 6.0.14 and Jetty 6.1.14. You will also need a JDK that supports Java 5 or later. This article uses JDK 1.5.0-16. In addition, you need to look at the pre-release version of Jetty 7 because it implements the Servlet 3.0 gauge Fan, we will study the specification in this article.
Understanding Comet
You may have heard of Comet because it has recently received a certain amount of attention. Comet is also sometimes called reverse Ajax or server-side push technology (server-side push). The idea is simple: push data directly from the server to the browser without waiting for the data to be requested by the browser. It sounds simple, but if you're familiar with WEB applications, especially the HTTP protocol, then you know it's not easy. Implementing Comet-style WEB applications, while ensuring scalability on browsers and servers, is only possible in recent years. Later in this article, we'll look at how some popular Java WEB servers support Scalable Comet architectures, but first let's see why we're creating Comet applications and the common design patterns that we use to implement them.
motives for using Comet
There is no doubt about the success of the HTTP protocol. It is the basis for much of the information exchange on the Internet. However, it has some limitations. In particular, it is a stateless, one-way protocol. The request is sent to the WEB server, the server processes the request and returns a response-nothing more. The request must be sent by the client, and the server can only send data in the response to the request. This can at least affect the usefulness of many types of WEB applications. A typical example is a chat program. There are also examples, such as game scores, stock quotes or email programs.
These limitations of HTTP are also the reason why it has been successful. The request/response cycle makes it the classic model that each connection uses a thread. This approach has tremendous scalability as long as it is able to service requests quickly. You can handle a large number of requests per second, and you can handle a large amount of users with only a few servers. This is ideal for many classic WEB applications, such as content management systems, search applications, e-commerce sites, and so on. In either of these WEB applications, the server provides the data requested by the user, then closes the connection and releases the thread so that it can serve other requests. If there may still be interaction after the initial data is provided, the connection is kept open so that the thread cannot be released and the server cannot serve many users.
However, if you want to respond to the request and send the initial data, still interact with the user? In the early days of the Web, this was often implemented using meta refreshes. This automatically instructs the browser to reload the page after a specified number of seconds, thereby supporting a rudimentary polling (polling). This is not only a bad user experience, but usually very inefficient. What if there are no new data to display on the page? The same page has to be rendered again. If there are few changes to the page, and most of the page does not change? Again, you have to request and get everything on the page, whether it's necessary or not.
The invention and popularity of Ajax have changed the situation. The server can now communicate asynchronously, so you do not have to request the entire page again. An incremental update is now available. Simply use XMLHttpRequest to poll the server. This technique is often referred to as Comet. There are some variants of this technology, each of which has different performance and scalability. Let's take a look at these different styles of Comet.
Comet Style
The advent of Ajax makes Comet possible. The one-way nature of HTTP can be effectively circumvented. There are actually some different ways to get around this. As you may have guessed, the easiest way to support Comet is polling (poll). Use XMLHttpRequest to make calls to the server, after returning, wait a fixed amount of time (usually using JavaScript's settimeout function), and then call again. This is a very common technique. For example, most webmail applications use this technique to display e-mail messages when they arrive.
This technique has both advantages and disadvantages. In this case, you expect to return the response quickly, just like any other Ajax request. There must be a pause between requests. Otherwise, continuous requests can burst the server, and in this case obviously not scalable. This pause causes the application to generate a delay. The longer the pause, the more time the new data on the server will take to reach the client. If you shorten the pause time, you will again face the risk of flooding the server. But on the other hand, this is obviously the simplest way to achieve Comet.
It should now be noted that many people think that polling does not belong to Comet. Instead, they think Comet is a solution to the limitations of polling. The most common "real" Comet technology is a variant of polling, a long poll (length polling). The main difference between polling and long polling is how long the server takes to respond. Long polling usually keeps the connection for a long time-usually for a few seconds, but it can be a minute or even longer. When an event occurs on the server, the response is sent and then closed, and polling starts again immediately.
The advantage of long polling relative to general polling is that once the data is available, it is sent from the server to the client immediately. The request may wait for a long time, without any data return, but once the new data is available, it will be sent to the client immediately. So there is no delay. If you've ever used a web-based chat program, or any program that claims to be "real time," it's probably using this technique.
Long polling has a variant that is the third style of Comet. This is often referred to as a stream (streaming). In this style, the server pushes data back to the client, but does not close the connection. The connection remains open until it expires and causes the request to be issued again. The XMLHttpRequest specification shows that you can check whether the value of ReadyState is 3 or receiving (instead of 4 or Loaded) and get the data that is "flowing out" from the server. As with long polling, there is no delay in this way. When the data on the server is ready, the data is sent to the client. Another advantage of this approach is that you can significantly reduce the requests sent to the server, thereby avoiding the overhead and latency associated with setting up a server connection. Unfortunately, XMLHttpRequest has many different implementations in different browsers. This technology can only be reliably used in newer versions of Mozilla Firefox. For Internet Explorer or Safari, you still need to use long polling.
At this point, you might think that long polling and streaming have a big problem. The request needs to exist on the server for a longer period of time. This breaks the model of using one thread per request because the thread used for one request has not been released. Worse still, the thread is idle until the data is sent back. This is clearly not scalable. Fortunately, there are many ways that modern Java Web servers can solve this problem.
The Comet in Java
Many WEB servers are now built in Java. One reason is that Java has a rich local threading model. So it's very simple to implement a typical model for each thread that connects to each one. This model is not suitable for Comet, but Java has a solution as well. In order to effectively handle Comet, non-blocking Io,java is required to provide non-blocking IO through its NIO library. The two most popular open source servers Apache Tomcat and Jetty use NIO to increase non-blocking io, which supports Comet. However, the implementation in both of these servers are not the same. Let's look at Tomcat and Jetty support for Comet.
Tomcat and Comet
For Apache Tomcat, there are two main things you need to do to use Comet. First, the configuration file server for Tomcat is required. XML is slightly modified. The more typical synchronous IO connectors are enabled by default. Now just switch it to an asynchronous version, as shown in Listing 1.
Listing 1. Modify Tomcat's Server.xml
<!--this are the usual Connector, comment it out and add the NIO one-->
<!--Connector uriencoding= "Utf-8 "connectiontimeout=" 20000 "port=" 8084 "protocol=" http/1.1 "redirectport="
8443 "/-->
<connector connectiontimeout= "20000" port= "8080" protocol= "Org.apache".
coyote.http11.Http11NioProtocol "redirectport=" 8443 "/>
Servlet. This is obviously a Tomcat-specific interface. Listing 2 shows an example of this.
Listing 2. Tomcat Comet servlet
public class Tomcatweatherservlet extends HttpServlet implements Cometprocessor {private Messagesender Messagesender =
Null
private static final Integer TIMEOUT = 60 * 1000;
@Override public void Destroy () {messagesender.stop ();
Messagesender = null;
@Override public void Init () throws servletexception {Messagesender = new Messagesender (); Thread messagesenderthread = new Thread (Messagesender, "messagesender[" + getservletcontext (). Getcontextpath () + "]")
;
Messagesenderthread.setdaemon (TRUE);
Messagesenderthread.start (); The public void event (final cometevent event) throws IOException, servletexception {httpservletrequest request = event.
Gethttpservletrequest ();
HttpServletResponse response = Event.gethttpservletresponse (); if (event.geteventtype () = = CometEvent.EventType.BEGIN) {request.setattribute ("org.apache.tomcat.comet.timeout"),
TIMEOUT);
Log ("Begin for session:" + Request.getsession (True). GetId ()); Messagesender.setconnection (response);
Weatherman weatherman = new Weatherman (95118, 32408);
New Thread (weatherman). Start (); else if (event.geteventtype () = = CometEvent.EventType.ERROR) {log ("ERROR for session:" + Request.getsession (true). GE
TId ());
Event.close (); else if (event.geteventtype () = = CometEvent.EventType.END) {log ("End for session:" + Request.getsession (True). GetId (
));
Event.close (); else if (event.geteventtype () = = CometEvent.EventType.READ) {throw new Unsupportedoperationexception ("This servlet does
Es not accept data ");
} } }
The Cometprocessor interface requires that the event method be implemented. This is a lifecycle method for Comet interaction. Tomcat will be invoked using a different cometevent instance. By examining the EventType of cometevent, you can determine which stage of the life cycle you are in. The BEGIN event occurs the first time the request is passed in. The READ event indicates that the data is being sent and is required only if the request is POST. The request terminates when an end or ERROR event is encountered.
In the example in Listing 2, the Servlet uses a Messagesender class to send data. An instance of this class was created in its own thread in the servlet's Init method and destroyed in the Servlet's Destroy method. Listing 3 shows the Messagesender.
Listing 3. Messagesender
Private class Messagesender implements Runnable {Protected Boolean running = true;
Protected final ArrayList messages = new ArrayList ();
private Servletresponse Connection;
Private synchronized void SetConnection (Servletresponse connection) {this.connection = connection;
Notify ();
public void Send (String message) {synchronized (messages) {messages.add (message);
Log ("message Added #messages =" + messages.size ());
Messages.notify (); } public void Run () {while (running) {if (messages.size () = 0) {try {synchronized (messages) {MESSAG
Es.wait ();
The catch (Interruptedexception e) {//Ignore}} string[] pendingmessages = null;
Synchronized (messages) {pendingmessages = Messages.toarray (new string[0]);
Messages.clear ();
try {if (connection = = null) {try{synchronized (this) {wait ();
} catch (Interruptedexception e) {//Ignore}} printwriter writer = Connection.getwriter (); For (int j = 0; J < Pendingmessages.length;
J + +) {Final String forecast = pendingmessages[j] + "";
WRITER.PRINTLN (forecast);
Log ("Writing:" + forecast);
} writer.flush ();
Writer.close ();
connection = null;
Log ("Closing connection");
catch (IOException e) {log ("Ioexeption sending message", e);
} } } }
This class is basically boilerplate code and has no direct relationship with Comet. However, there are two points to note. This class contains a Servletresponse object. Looking back at the event method in Listing 2, when the event is BEGIN, the response object is passed into the messagesender. In the Messagesender run method, it uses Servletresponse to send data back to the client. Note that once all queued messages have been sent, it closes the connection. This enables long polling. If you want to implement streaming-style Comet, you need to keep the connection open, but still refresh the data.
Looking back at Listing 2, you can see that a weatherman class was created. It is this class that uses Messagesender to send data back to the client. This class uses the Yahoo RSS feed to get weather information for different regions and sends that information to the client. This is a specially designed example for simulating a data source that sends data asynchronously. Listing 4 shows the code for it.
Listing 4. Weatherman
Private class weatherman implements runnable{private final List zipcodes;
Private final String Yahoo_weather = "http://weather.yahooapis.com/forecastrss?p=";
Public weatherman (Integer ... zips) {zipcodes = new ArrayList (zips.length);
for (Integer zip:zips) {try {zipcodes.add (new URL (Yahoo_weather + Zip));
catch (Exception e) {//dont add it if it sucks}}} public void Run () {int i = 0;
while (I >= 0) {int j = i% zipcodes.size ();
Syndfeedinput input = new Syndfeedinput ();
try {syndfeed feed = input.build (New InputStreamReader (Zipcodes.get (j). OpenStream ()));
Syndentry entry = (syndentry) feed.getentries (). get (0);
Messagesender.send (entrytohtml (entry));
Thread.Sleep (30000L);
catch (Exception e) {//Just Eat it, eat it} i++;
} private String entrytohtml (Syndentry entry) {StringBuilder html = new StringBuilder ("");
Html.append (Entry.gettitle ());
Html.append (""); Html.append (entry.geTdescription (). GetValue ());
return html.tostring ();
} }
This class uses the Project Rome library to parse RSS feeds from Yahoo Weather. This is a useful library if you need to generate or use an RSS or Atom feed. In addition, there is only one place in this code that is noteworthy: it produces another thread that sends weather data every 30 seconds. Finally, let's look at one more place: The client code that uses the Servlet. In this case, a simple JSP plus a small amount of JavaScript is enough. Listing 5 shows the code.
Listing 5. Client Comet Code
"Http://www.w3.org/TR/html4/loose.dtd" >
var request = new XMLHttpRequest ();
Request.open ("Get", url, True);
Request.setrequestheader ("Content-type", "APPLICATION/X-JAVASCRIPT; ");
Request.onreadystatechange = function () {
if (request.readystate = 4) {
if (Request.status = 200) {
if (request.responsetext) {
document.getElementById ("forecasts"). InnerHTML = C14/>request.responsetext;
}
Go
();
}
};
request.send (NULL);
}
The code simply starts a long poll when the user clicks the Go button. Note that it uses the XMLHttpRequest object directly, so this will not work in Internet Explorer 6. You may need to use an AJAX library to troubleshoot browser variance issues. In addition, the only thing to be aware of is the callback function, or the closure created for the requested onreadystatechange function. The function pastes the new data from the server, and then calls the Go function again.
Now, we've seen what a simple Comet app looks like on Tomcat. There are two things that are closely related to Tomcat: one is to configure its connectors, and the other is to implement a Tomcat-specific interface in the Servlet. You might want to know how difficult it is to "migrate" the code to Jetty. Let's take a look at the problem next.
Jetty and Comet
The Jetty server uses slightly different techniques to support the scalable implementation of Comet. Jetty supports a programming structure called continuations. The idea is simple. The request is paused first and then continues at some point in the future. The expiration of a specified time, or the occurrence of a meaningful event, can cause the request to continue. When a request is paused, its thread is freed.
You can create org.mortbay.util.ajax.Continuation for any httpservletrequest using the Jetty Org.mortbay.util.ajax.ContinuationSupport class An instance of. This method differs greatly from Comet. However, continuations can be used to implement logically equivalent Comet. Listing 6 shows the code after the weather servlet "ported" to Jetty in Listing 2.
Listing 6. Jetty Comet servlet
public class Jettyweatherservlet extends HttpServlet {private Messagesender messagesender = null;
private static final Integer TIMEOUT = 5 * 1000; public void begin (HttpServletRequest request, httpservletresponse response) throws IOException, Servletexception {req
Uest.setattribute ("Org.apache.tomcat.comet", boolean.true);
Request.setattribute ("Org.apache.tomcat.comet.timeout", timeout);
Messagesender.setconnection (response);
Weatherman weatherman = new Weatherman (95118, 32408);
New Thread (weatherman). Start (); } public void End (HttpServletRequest request, httpservletresponse response) throws IOException, Servletexception {s
Ynchronized (Request) {Request.removeattribute ("Org.apache.tomcat.comet");
Continuation continuation = Continuationsupport.getcontinuation (request, request);
if (continuation.ispending ()) {continuation.resume (); }} public void error (HttpServletRequest request, httpservletresponse response) throws IoexcEption, Servletexception {end (request, response);
public Boolean read (HttpServletRequest request, httpservletresponse response) throws IOException, Servletexception {
throw new Unsupportedoperationexception (); @Override protected void Service (HttpServletRequest request, httpservletresponse response) throws IOException, Ser vletexception {synchronized (request) {Continuation continuation = Continuationsupport.getcontinuation (Request, R
Equest);
if (!continuation.ispending ()) {Begin (Request, response);
The integer timeout = (integer) request.getattribute ("Org.apache.tomcat.comet.timeout");
Boolean resumed = Continuation.suspend (Timeout = = null? 10000:timeout.intvalue ());
if (!resumed) {error (request, response); }} public void settimeout (HttpServletRequest request, httpservletresponse response, int timeout) throws Ioexcep tion, servletexception, unsupportedoperationexception {request.setattribute ("ORG.APACHE.TOMCAT.comet.timeout ", New Integer (timeout));
} }
The most important thing to note here is that the structure is very similar to the TOMCAT version of the code. The begin, read, end, and error methods match the same events in Tomcat. The Servlet's service method is overwritten to create a continuation and suspend the request the first time it is entered, until the timeout has expired, or the event that caused it to restart occurs. The Init and destroy methods are not shown above because they are the same as the Tomcat version. The servlet uses the same messagesender as Tomcat. Therefore, no modification is required. Note how the Begin method creates a weatherman instance. The use of this class is exactly the same as in the Tomcat version. Even the client code is the same. Only the servlet has changes. Although the servlet changes quite a bit, it is still one by one corresponding to the event model in Tomcat.
Hope this is enough to inspire. Although the exact same code cannot run in both Tomcat and Jetty, it is very similar. Of course, Java EE attracts people with portability. Most code that runs in Tomcat can run in Jetty without modification, and vice versa. It is not surprising, therefore, that the next version of the Java Servlet specification includes the standardization of Asynchronous request processing (the underlying technology behind Comet). Let's take a look at this specification: Servlet 3.0 specification.
Servlet 3.0 Specification
Here, we do not delve into the full details of the servlet 3.0 specification, only to see what the Comet servlet might look like if it was running in the Servlet 3.0 container. Notice the word "may". The specification has published a public preview, but there is no final version at the time of writing this article. As a result, listing 7 shows an implementation that complies with the common preview specification.
Listing 7. Servlet 3.0 Comet
@WebServlet (Asyncsupported=true, asynctimeout=5000) public
class Weatherservlet extends HttpServlet {
Private Messagesender Messagesender;
Init and destroy are the same as other
@Override
protected void doget (HttpServletRequest request, HTTPSERVL Etresponse response)
throws Servletexception, IOException {asynccontext
async = Request.startasync (Request, R Esponse);
Messagesender.setconnection (async);
Weatherman weatherman = new Weatherman (95118, 32444);
Async.start (weatherman);
}
}
Happily, this version is much simpler. In all fairness, if you do not follow the Tomcat event model, you can have a similar implementation in Jetty. This event model seems reasonable and can easily be implemented in containers other than Tomcat (such as Jetty), except that there are no relevant criteria.
Looking back at Listing 7, note that its annotation declares that it supports asynchronous processing and sets the timeout time. The Startasync method is a new method on the HttpServletRequest that returns an instance of the new Javax.servlet.AsyncContext class. Note that Messagesender now passes the Asyncontext reference instead of the Servletresponse reference. Here, you should not turn off the response, but call the complete method on the Asynccontext instance. It should also be noted that weatherman is passed directly to the start method of the Asynccontext instance. This will start a new thread in the current ServletContext.
Furthermore, although there are significant differences compared to Tomcat or Jetty, it is not too difficult to modify the same style of programming to handle the API proposed by the Servlet 3.0 specification. It should also be noted that Jetty 7 was designed to implement Servlet 3.0 and is currently in beta state. However, at the time of writing this article, it has not yet implemented the latest version of the specification.
Conclusion
Comet-style Web applications can bring new interactivity to the Web. It poses some complex challenges for achieving these features on a large scale. However, the leading Java Web server is providing mature and stable technology for the implementation of Comet. In this article, you see the different points and similarities of the current style Comet on Tomcat and Jetty, and the standardization of the Servlet 3.0 specification that is in progress. Tomcat and Jetty make it possible to build today's scalable Comet applications and identify future upgrades to the Servlet 3.0 standardization.