11.2 Sharing Control

Java Servlet Programming, 2nd Edition > 11. Servlet Collaboration > 11.2 Sharing Control

 
< BACKCONTINUE >

11.2 Sharing Control

For more dynamic collaboration, servlets can share control of the request. First, a servlet can forward an entire request, doing some preliminary processing and then passing off the request to another component. Second, a servlet can include in its response a bit of content generated by another component, essentially creating a programmatic server-side include. Conceptually, if you think of the resulting page like a screen, a forward gives another servlet full control of the screen, while an include injects only a section of content into the screen at some point.

This delegation ability gives servlets more flexibility and allows for better abstraction. Using delegation, a servlet can construct its response as a collection of content generated by various web server components. This functionality is especially important to JavaServer Pages, where it often happens that one servlet preprocesses a request, then hands off the request to a JSP page for completion (see Chapter 18).

11.2.1 Getting a Request Dispatcher

To support request delegation, Servlet API 2.1 introduced the javax.servlet.RequestDispatcher interface. A servlet gets a RequestDispatcher instance using the getRequestDispatcher( ) method on its request object. This method returns a RequestDispatcher that can dispatch to the component (servlet, JSP, static file, etc.) found at the given URI path:

public RequestDispatcher ServletRequest.getRequestDispatcher(String path)

The provided path may be relative, although it cannot extend outside the current servlet context. You can use the getContext( ) method as discussed in the previous section for dispatching outside the current context. There's no way to dispatch to a context on another server. If the path begins with a / it is interpreted as relative to the current context root. If the path contains a query string, the parameters are added to the beginning of the receiving component's parameter set. The method returns null if the servlet container cannot return a RequestDispatcher for any reason.

Curiously, there's a method by the same name in the ServletContext class:

public RequestDispatcher ServletContext.getRequestDispatcher(String path)

The difference here is that the version in ServletContext (introduced in API 2.1) accepts only absolute URLs (beginning with a slash) while the version in ServletRequest (introduced in API 2.2) accepts both absolute URLs and relative URLs. Consequently, there's no reason to use the method in ServletContext. It exists only for historical reasons and can be considered deprecated although officially it's not.

It's also possible to get a RequestDispatcher for a resource specified by name instead of by path, using getNamedDispatcher( ) in ServletContext:

public RequestDispatcher ServletContext.getNamedDispatcher(String name)

This allows dispatching to resources that are not necessarily publicly available. Servlets (and JSP pages also) may be given names via the web application deployment descriptor, as discussed in Chapter 3. The method returns null if the context cannot return a dispatcher for any reason.

RequestDispatcher has two methods, forward( ) and include( ). The forward( ) method hands off the entire request to the delegate. The include( ) method adds the delegate's output to the calling servlet's response but leaves the calling servlet in control.

11.2.2 Dispatching a Forward

The forward( ) method forwards a request from a servlet to another resource on the server. The method allows one servlet to do preliminary processing of a request and another resource to generate the response. Unlike a sendRedirect( ), a forward( ) operates entirely within the server, and the client cannot tell the forward occurred. Information can be passed to the delegate using an attached query string or using request attributes set with the setAttribute( ) method. Example 11-3 demonstrates a servlet that performs a search, then forwards the search results to another page for rendering.

Example 11-3. A Search Engine Backend
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class SearchLogic extends HttpServlet {   public void doGet(HttpServletRequest req, HttpServletResponse res)                                throws ServletException, IOException {     // We don't set the content type or get a writer     // Get the string to search for     String search = req.getParameter("search");     // Calculate the URLs containing the string     String[] results = getResults(search);     // Specify the results as a request attribute     req.setAttribute("results", results);     // Forward to a display page     String display = "/servlet/SearchView";     RequestDispatcher dispatcher = req.getRequestDispatcher(display);     dispatcher.forward(req, res);   }   // In real use this method would call actual search engine logic   // and return more information about each result than a URL   String[] getResults(String search) {     return new String[] { "http://www.abc.com",                           "http://www.xyz.com" };   } }

This servlet's job is to be the brains of an online search engine. It does a search for the text given by the search parameter, then stores the resulting URLs into a results attribute of the request. Then the servlet forwards the request to a display component for rendering. Here that's hard coded as /servlet/SearchView, but the path could be made to change depending on user preferences for language, site colors, beginner or advanced display, and so on.

The rules a forwarding servlet must follow are relatively strict:

  • It may set headers and the status code but may not send any response body to the client (that's the job for an include). Consequently, the forward( ) must be called before the response has been committed.

  • If the response already has been committed, the forward( ) call throws an IllegalStateException.

  • If the response has not been committed but there's content within the response buffer, the buffer is automatically cleared as part of the forward.

  • In addition, you can't get creative by substituting new request and response objects. The forward( ) method must be called with the same request and response objects as were passed to the calling servlet's service method, and the forward( ) must be called from within the same handler thread.

The receiving component may be written as any other component, as Example 11-4 demonstrates.

Example 11-4. A Search Engine Frontend
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class SearchView extends HttpServlet {   public void doGet(HttpServletRequest req, HttpServletResponse res)                                throws ServletException, IOException {     res.setContentType("text/plain");     PrintWriter out = res.getWriter();     // Get the search results from a request attribute     String[] results = (String[]) req.getAttribute("results");     if (results == null) {       out.println("No results.");       out.println("Did you accidentally access this servlet directly?");     }     else {       out.println("Results:");       for (int i = 0; i < results.length; i++) {         out.println(results[i]);       }     }     out.println();     out.println("Request URI: " + req.getRequestURI());     out.println("Context Path: " + req.getContextPath());     out.println("Servlet Path: " + req.getServletPath());     out.println("Path Info: " + req.getPathInfo());     out.println("Query String: " + req.getQueryString());   } }

When you try this servlet yourself, notice the path information printed by the servlet has been adjusted. It doesn't match the original request information; instead, it matches the path used to obtain the dispatcher:

Request URI: /servlet/SearchView Context Path: Servlet Path: /servlet/SearchView Path Info: null Query String: null

The reason for this path adjustment is that, if you forward to a servlet, that servlet should get the same path info as if it were invoked directly; it has full control over the request from this point. That's also why it's important that the response buffer is flushed before it's invoked and that the response has not been committed.

11.2.2.1 Dispatching by name

One problem with dispatching by path is that if the target component is available to server components on some path, it's also available to clients at the same path. For safety you may consider making a component not publicly available (by disabling the /servlet invoker logic, for example) and dispatching requests by name instead of path. The getNamedDispatcher( ) method makes this possible. Example 11-3 could be rewritten with dispatch logic as follows:

// Forward to a display page String display = "searchView"; RequestDispatcher dispatcher = req.getNamedDispatcher(display); dispatcher.forward(req, res);

One thing to be aware of with getNamedDispatcher( ) is that, because there's no URI path used, no query strings may be appended and there's no path adjustment.

11.2.3 Forward Versus Redirect

Is it better to use forward( ) or sendRedirect( )? Both methods have their uses. forward( ) works best when one component must perform business logic and share the results with another component. sendRedirect( ) works best when the client should be redirected from one page to another. It's tempting to use forward( ) instead of sendRedirect( ) for simple redirect duties, because a forward( ) operates within the server and executes faster than a sendRedirect( ) which requires round-trip communication with the client. Unfortunately, that causes problems with relative URL handling, as demonstrated by Example 11-5, a servlet that forwards a request to the site home page.

Example 11-5. Go Directly to Home, Do Not Pass Go
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class HomePageForward extends HttpServlet {   public void doGet(HttpServletRequest req, HttpServletResponse res)                                throws ServletException, IOException {     RequestDispatcher dispatcher = req.getRequestDispatcher("/index.html");     dispatcher.forward(req, res);   } }

If you use Tomcat to access this servlet at the URL http://localhost:8080/servlet/HomePageForward, you'll notice all images within the page are broken. The reason is that the page contains <IMG> tags with relative paths to the image files, and while a sendRedirect( ) would have given the client notice that the file was served from the document root directory, the forward( ) gave the client no such notice, and all relative URLs end up broken. The sendRedirect( ) method also makes it easier to dispatch to resources within other contexts, because no getContext( ) lookup is required. Our advice is to use sendRedirect( ) whenever possible and to use forward( ) only when required.

11.2.4 Dispatching an Include

The RequestDispatcher 's include( ) method includes the content of a resource into the current response. It enables what could be considered a programmatic server-side include. It's different than a forward( ) because the calling servlet retains control of the response and may include content both before and after the included content. Example 11-6 demonstrates a servlet that includes a catalog item display into the current response.

Example 11-6. Including a Catalog Item
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class NileBooks extends HttpServlet {   public void doGet(HttpServletRequest req, HttpServletResponse res)                                throws ServletException, IOException {     res.setContentType("text/html");     PrintWriter out = res.getWriter();     out.println("<HTML><HEAD><TITLE>Welcome to Nile</TITLE></HEAD>");     out.println("<BODY>");     // Show an item in an online catalog     out.println("Feast your eyes on this beauty:");     RequestDispatcher dispatcher =       req.getRequestDispatcher("/servlet/NileItem?item=0596000405");     dispatcher.include(req, res);     out.println("And, since I like you, it's 20% off!");     out.println("</BODY></HTML>");   } }

As with forward( ), information can be passed to the called resource using an attached query string or using request attributes set with the setAttribute( ) method. Using attributes instead of parameters gives you the ability to pass objects instead of simple strings, as shown in Example 11-7.

Example 11-7. Including Several Catalog Items
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class NileBooks extends HttpServlet {   public void doGet(HttpServletRequest req, HttpServletResponse res)                                throws ServletException, IOException {     res.setContentType("text/html");     PrintWriter out = res.getWriter();     out.println("<HTML><HEAD><TITLE>Welcome to Nile</TITLE></HEAD>");     out.println("<BODY>");     // Show items in an online catalog     RequestDispatcher dispatcher =       req.getRequestDispatcher("/servlet/NileItem");     out.println("Feast your eyes on this beauty:");     req.setAttribute("item", Book.getBook("0596000405"));     dispatcher.include(req, res);     // Remove the "item" attribute after use     req.removeAttribute("item");     out.println("Or how about this one:");     req.setAttribute("item", Book.getBook("0395282659"));     dispatcher.include(req, res);     out.println("And, since I like you, they're all 20% off!");     out.println("</BODY></HTML>");   } } // A simple Book class class Book {   String isbn;   String title;   String author;   private static Book JSERVLET =     new Book("0596000405", "Java Servlet Programming", "Hunter");   private static Book HOBBIT =     new Book("0395282659", "The Hobbit", "Tolkien");   // Here we simulate a database lookup   public static Book getBook(String isbn) {     if (JSERVLET.getISBN().equals(isbn)) {       return JSERVLET;     }     else if (HOBBIT.getISBN().equals(isbn)) {       return HOBBIT;     }     else {       return null;     }   }   private Book(String isbn, String title, String author) {     this.isbn = isbn;     this.title = title;     this.author = author;   }   public String getISBN() {     return isbn;   }   public String getTitle() {     return title;   }   public String getAuthor() {     return author;   } }

Here instead of passing just an ISBN, the servlet passes a complete Book object. In production use, the book information would come from an ISBN lookup on a database. Here we simply hard coded a pair of books.

The NileItem servlet can receive the item request attribute by calling req.getAttribute("item"), as shown in Example 11-8.

Example 11-8. Displaying a Catalog Item
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class NileItem extends HttpServlet {   public void doGet(HttpServletRequest req, HttpServletResponse res)                                throws ServletException, IOException {     // We do not set the content type     PrintWriter out = res.getWriter();     Book book = (Book) req.getAttribute("item");     out.println("<BR>");     if (book != null) {       out.println("<I>" + book.getTitle() + "</I>");       out.println(" by " + book.getAuthor());     }     else {       out.println("<I>No book record found</I>");     }     out.println("<BR>");   } }

An included servlet may have its content placed anywhere in a page, and because of that it has no ability to change the status code or HTTP headers sent in the response. Any attempt to make a change is ignored.

Unlike a forward( ), the path elements and parameters of the request remain unchanged from the caller's. If the included component requires access to its own path elements and parameters, it may retrieve them using the following server-assigned request attributes:

javax.servlet.include.request_uri javax.servlet.include.context_path javax.servlet.include.servlet_path javax.servlet.include.path_info javax.servlet.include.query_string

The request and response parameters must be the same objects as were passed to the calling servlet's service method, and the include( ) must be called from within the same handler thread.

The included resource must use an output mechanism that matches the caller's. If the caller uses a PrintWriter, then the included resource must use a PrintWriter. If the caller uses an OutputStream, then the included resource must use an OutputStream. If the mechanisms don't match, the included servlet throws an IllegalStateException. If you can, make sure to use the PrintWriter for all text output, and this won't be a problem.


Last updated on 3/20/2003
Java Servlet Programming, 2nd Edition, © 2001 O'Reilly

< BACKCONTINUE >


Java servlet programming
Java Servlet Programming (Java Series)
ISBN: 0596000405
EAN: 2147483647
Year: 2000
Pages: 223

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net