

With usage of APR or NIO APIs as the basis of its connectors, Tomcat is able to provide a number of extensions over the regular blocking IO as provided with support for the Servlet API.
IMPORTANT NOTE: Usage of these features requires using the APR or NIO HTTP connectors. The classic java.io HTTP connector and the AJP connectors do not support them.
Comet support allows a servlet to process IO asynchronously, receiving events when data is available for reading on the connection (rather than always using a blocking read), and writing data back on connections asynchronously (most likely responding to some event raised from some other source).
Servlets which implement the org.apache.catalina.comet.CometProcessor
interface will have their event method invoked rather than the usual service
method, according to the event which occurred. The event object gives
access to the usual request and response objects, which may be used in the
usual way. The main difference is that those objects remain valid and fully
functional at any time between processing of the BEGIN event until processing
an END or ERROR event.
The following event types exist:
There are some event subtypes which allow finer processing of events (note: some of these events require usage of the org.apache.catalina.valves.CometConnectionManagerValve valve):
As described above, the typical lifecycle of a Comet request will consist in a series of events such as: BEGIN -> READ -> READ -> READ -> ERROR/TIMEOUT. At any time, the servlet may end processing of the request by using the close method of the event object.
Similar to regular filters, a filter chain is invoked when comet events are processed. These filters should implement the CometFilter interface (which works in the same way as the regular Filter interface), and should be declared and mapped in the deployment descriptor in the same way as a regular filter. The filter chain when processing an event will only include filters which match all the usual mapping rules, and also implement the CometFiler interface.
The following pseudo code servlet implements asynchronous chat functionality using the API described above:
public class ChatServlet
extends HttpServlet implements CometProcessor {
protected ArrayList<HttpServletResponse> connections =
new ArrayList<HttpServletResponse>();
protected MessageSender messageSender = null;
public void init() throws ServletException {
messageSender = new MessageSender();
Thread messageSenderThread =
new Thread(messageSender, "MessageSender[" + getServletContext().getContextPath() + "]");
messageSenderThread.setDaemon(true);
messageSenderThread.start();
}
public void destroy() {
connections.clear();
messageSender.stop();
messageSender = null;
}
/**
* Process the given Comet event.
*
* @param event The Comet event that will be processed
* @throws IOException
* @throws ServletException
*/
public void event(CometEvent event)
throws IOException, ServletException {
HttpServletRequest request = event.getHttpServletRequest();
HttpServletResponse response = event.getHttpServletResponse();
if (event.getEventType() == CometEvent.EventType.BEGIN) {
log("Begin for session: " + request.getSession(true).getId());
PrintWriter writer = response.getWriter();
writer.println("<!DOCTYPE html>");
writer.println("<head><title>JSP Chat</title></head><body>");
writer.flush();
synchronized(connections) {
connections.add(response);
}
} else if (event.getEventType() == CometEvent.EventType.ERROR) {
log("Error for session: " + request.getSession(true).getId());
synchronized(connections) {
connections.remove(response);
}
event.close();
} else if (event.getEventType() == CometEvent.EventType.END) {
log("End for session: " + request.getSession(true).getId());
synchronized(connections) {
connections.remove(response);
}
PrintWriter writer = response.getWriter();
writer.println("</body></html>");
event.close();
} else if (event.getEventType() == CometEvent.EventType.READ) {
InputStream is = request.getInputStream();
byte[] buf = new byte[512];
do {
int n = is.read(buf); //can throw an IOException
if (n > 0) {
log("Read " + n + " bytes: " + new String(buf, 0, n)
+ " for session: " + request.getSession(true).getId());
} else if (n < 0) {
error(event, request, response);
return;
}
} while (is.available() > 0);
}
}
public class MessageSender implements Runnable {
protected boolean running = true;
protected ArrayList<String> messages = new ArrayList<String>();
public MessageSender() {
}
public void stop() {
running = false;
}
/**
* Add message for sending.
*/
public void send(String user, String message) {
synchronized (messages) {
messages.add("[" + user + "]: " + message);
messages.notify();
}
}
public void run() {
while (running) {
if (messages.size() == 0) {
try {
synchronized (messages) {
messages.wait();
}
} catch (InterruptedException e) {
// Ignore
}
}
synchronized (connections) {
String[] pendingMessages = null;
synchronized (messages) {
pendingMessages = messages.toArray(new String[0]);
messages.clear();
}
// Send any pending message on all the open connections
for (int i = 0; i < connections.size(); i++) {
try {
PrintWriter writer = connections.get(i).getWriter();
for (int j = 0; j < pendingMessages.length; j++) {
writer.println(pendingMessages[j] + "<br>");
}
writer.flush();
} catch (IOException e) {
log("IOExeption sending message", e);
}
}
}
}
}
}
}If you are using the NIO connector, you can set individual timeouts for your different comet connections. To set a timeout, simply set a request attribute like the following code shows:
CometEvent event.... event.setTimeout(30*1000);or
event.getHttpServletRequest().setAttribute("org.apache.tomcat.comet.timeout", new Integer(30 * 1000));
This sets the timeout to 30 seconds.
Important note: in order to set this timeout, it has to be done on the BEGIN event.
The default value is soTimeout
If you are using the APR connector, all Comet connections will have the same timeout value. It is soTimeout*50
When APR or NIO is enabled, Tomcat supports using sendfile to send large static files.
These writes, as soon as the system load increases, will be performed
asynchronously in the most efficient way. Instead of sending a large response using
blocking writes, it is possible to write content to a static file, and write it
using a sendfile code. A caching valve could take advantage of this to cache the
response data in a file rather than store it in memory. Sendfile support is
available if the request attribute org.apache.tomcat.sendfile.support
is set to Boolean.TRUE.
Any servlet can instruct Tomcat to perform a sendfile call by setting the appropriate request attributes. It is also necessary to correctly set the content length for the response. When using sendfile, it is best to ensure that neither the request or response have been wrapped, since as the response body will be sent later by the connector itself, it cannot be filtered. Other than setting the 3 needed request attributes, the servlet should not send any response data, but it may use any method which will result in modifying the response header (like setting cookies).
org.apache.tomcat.sendfile.filename: Canonical filename of the file which will be sent as
a Stringorg.apache.tomcat.sendfile.start: Start offset as a Longorg.apache.tomcat.sendfile.end: End offset as a LongIn addition to setting these parameters it is necessary to set the content-length header. Tomcat will not do that for you, since you may have already written data to the output stream.
Note that the use of sendfile will disable any compression that Tomcat may otherwise have performed on the response.