Up until now, every page returned by a servlet has been just that: a page. Always one page with one content type. But why think in such limited terms? Why not have a servlet return several pages, each with a different content type, all in response to the same request? It may be hard to imagine--and sound even harder to implement--but it's actually quite easy using a technique known as server push.
It's called server push because the server sends, or pushes, a sequence of response pages to the client. Compare this to the client pull technique discussed in the last chapter, where it's left to the client to get, or pull, each page from the server. Although the results of each technique are similar to the end user--the appearance of a sequence of pages--the implementation details and the appropriate uses of the two techniques are quite different.
With server push, the socket connection between the client and the server remains open until the last page has been sent. This gives the server the ability to send page updates quickly and to control exactly when those updates are sent. As such, server push is ideal for pages that need frequent updates (such as rudimentary animations) or pages that need server-controlled but somewhat infrequent updates (such as live status updates). Note, however, that server push is not yet supported by Microsoft Internet Explorer, and extended use should be avoided, as it has been found to be harmful to the server's available socket count.
With client pull, the socket connection is broken after every page, so responsibility for page updates falls to the client. The client uses the Refresh header value sent by the server to determine when to perform its update, so client pull is the best choice for pages that require infrequent updates or have updates at known intervals.
Server push can come in handy for limited-length animations and for real-time status updates. For example, consider a servlet that could push the four latest satellite weather maps, creating a rudimentary animation. If you recall the PrimeSearcher servlet from Chapter 3, "The Servlet Life Cycle", think about how we could use server push to notify a limited number of clients immediately as the servlet finds each new prime.
Example 6-12 shows a servlet that uses server push to display a countdown to a rocket launch. It begins by sending a series of pages that count down from 10 to 1. Every page replaces the previous page. When the countdown reaches 0, the servlet sends a picture of a launch. It uses the com.oreilly.servlet.MultipartRe-sponse utility class (shown in Example 6-13) to manage the server push details.
import java.awt.*; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import com.oreilly.servlet.MultipartResponse; import com.oreilly.servlet.ServletUtils; public class Countdown extends HttpServlet { static final String LAUNCH = "/images/launch.gif"; public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { ServletOutputStream out = res.getOutputStream(); // some binary output // Prepare a multipart response MultipartResponse multi = new MultipartResponse(res); // First send a countdown for (int i = 10; i > 0; i--) { multi.startResponse("text/plain"); out.println(i + "..."); multi.endResponse(); try { Thread.sleep(1000); } catch (InterruptedException e) { } } // Then send the launch image multi.startResponse("image/gif"); try { ServletUtils.returnFile(req.getRealPath(LAUNCH), out); } catch (FileNotFoundException e) { throw new ServletException("Could not find file: " + e.getMessage()); } // Don't forget to end the multipart response multi.finish(); } }
The MultipartResponse class hides most of the nasty, dirty details involved in using server push. Feel free to use it in your own servlets. It is easy to use, as you can see from the previous example.
First, create a new MultipartResponse object, passing it the servlet's response object. MultipartResponse uses the response object to fetch the servlet's output stream and to set the response's content type. Then, for each page of content, begin by calling startResponse() and passing in the content type for that page. Send the content for the page by writing to the output stream as usual. A call to endResponse() ends the page and flushes the content, so the client can see it. At this point, you can add a call to sleep(), or some other kind of delay, until the next page is ready for sending. The call to endResponse() is optional, as the startResponse() method knows whether the previous response was ended and ends it if necessary. You should still call endResponse() if there's going to be a delay between the time one response ends and the next begins. This lets the client display the latest response while it is waiting for the next one. Finally, after all the response pages have been sent, a call to the finish() method finishes the multipart response and sends a code telling the client there will be no more responses.
Example 6-13 contains the code for the MultipartResponse class.
public class MultipartResponse { HttpServletResponse res; ServletOutputStream out; boolean endedLastResponse = true; public MultipartResponse(HttpServletResponse response) throws IOException { // Save the response object and output stream res = response; out = res.getOutputStream(); // Set things up res.setContentType("multipart/x-mixed-replace;boundary=End"); out.println(); out.println("--End"); } public void startResponse(String contentType) throws IOException { // End the last response if necessary if (!endedLastResponse) { endResponse(); } // Start the next one out.println("Content-Type: " + contentType); out.println(); endedLastResponse = false; } public void endResponse() throws IOException { // End the last response, and flush so the client sees the content out.println(); out.println("--End"); out.flush(); endedLastResponse = true; } public void finish() throws IOException { out.println("--End--"); out.flush(); } }
Copyright © 2001 O'Reilly & Associates. All rights reserved.