Persistent connections (sometimes called "keep-alive" connections) can be used to optimize the way servlets return content to the client. To understand how this optimization works, you first need to understand how HTTP connections work. We'll keep this at a high level and only go as low as is necessary to explain the basic idea. The details are well covered in Clinton Wong's Web Client Programming (O'Reilly).
When a client, such as a browser, wants to request a web document from a server, it begins by establishing a socket connection to the server. Over this connection, the client makes its request and then receives the server's response. The client indicates it has finished its request by sending a blank line; the server, in turn, indicates that the response is complete by closing the socket connection.
So far, so good. But what if the retrieved page contains <IMG> tags or <APPLET> tags that require the client to retrieve more content from the server? Well, another socket connection is used. If a page contains 10 graphics along with an applet made up of 25 classes, that's 36 connections needed to transfer the page. No wonder some people say WWW stands for the World Wide Wait! This approach is like ordering a pizza, but making a separate phone call for each topping.
A better approach is to use the same socket connection to retrieve more than one piece of a page, something called a persistent connection. The trick with a persistent connection is that the client and server must somehow agree on where the server's response ends and where the client's next request begins. They could try to use a token like a blank line, but what if the response itself contains a blank line? The way persistent connections work is that the server just tells the client how big the response body will be by setting the Content-Length header as part of the response. The client then knows that after that much response body, it has control of the socket again.
Most servers internally manage the Content-Length header for the static files they serve, but do not do the same for the servlets they serve. That's left to the servlets themselves. A servlet can gain the advantages of a persistent connection for its dynamic content by using the setContentLength() method:
public void ServletResponse.setContentLength(int len)
This method sets the length (in bytes) of the content being returned by the server. In an HTTP servlet, the method sets the HTTP Content-Length header. Note that using this method is optional. If you use it, however, your servlets will be able to take advantage of persistent connections when they are available. The client will also be able to display an accurate progress monitor during the download.
If you do call setContentLength(), there are two caveats: a servlet must call this method before sending the response body, and the given length must be exact. If it's off by even one byte, you will have problems.[1] This sounds more difficult than it really is. The trick is for a servlet to use a ByteArrayOutputStream to buffer the output, as shown in Example 5-2.
[1] For example, with the Java Web Server, if a servlet sets the length too short, the server throws an IOException saying there was a "write past end of stream". If a servlet sets the length too long, the client stalls as it waits for the rest of the response.
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class KeepAlive extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/html"); // Set up a PrintStream built around a special output stream ByteArrayOutputStream bytes = new ByteArrayOutputStream(1024); PrintWriter out = new PrintWriter(bytes, true); // true forces flushing out.println("<HTML>"); out.println("<HEAD><TITLE>Hello World</TITLE></HEAD>"); out.println("<BODY>"); out.println("<BIG>Hello World</BIG>"); out.println("</BODY></HTML>"); // Set the content length to the size of the buffer res.setContentLength(bytes.size()); // Send the buffer bytes.writeTo(res.getOutputStream()); } }
Instead of writing to the PrintWriter returned by getWriter(), this servlet writes to a PrintWriter built around a ByteArrayOutputStream. This array grows as necessary to accommodate whatever output the servlet sends. When the servlet is ready to exit, it sets the content length to be the size of the buffer and then sends the contents of the buffer to the client. Notice that the bytes are sent using the byte-oriented ServletOutputStream. With this simple modification, a servlet can take advantage of a persistent connection.
It is important to note that persistent connections come with a price. Buffering all the output and sending it all in one batch requires extra memory, and it may delay the time at which a client begins receiving data. For servlets with short responses, persistent connections make sense, but for servlets with long responses, the memory overhead and delay probably outweigh the benefit of opening fewer connections.
It is also important to note that not all servers and not all clients support persistent connections. That said, it's still appropriate for a servlet to set its content length. This information will be used by those servers that support persistent connections and ignored by the others.
Copyright © 2001 O'Reilly & Associates. All rights reserved.