2.5 Forward Versus Redirect

It's often necessary for more than one component to share control of a request. For example, one servlet may be responsible for authenticating and authorizing a client, while it's the job of a different servlet to retrieve some data for the user. The sharing of a request can be accomplished in several different ways.

There are important differences between how a web container processes a forward request versus how it processes a redirect. The Struts front controller servlet, discussed in Chapter 1, will always perform one or the other for a typical request, so it's important that you understand these differences and the impact that each mechanism will have on your application.

2.5.1 Using a Redirect

When the sendRedirect( ) method is invoked, it causes the web container to return to the browser a response indicating that a new URL should be requested. Because the browser issues a completely new request, any objects that are stored as request attributes before the redirect occurs will be lost. This is one of the biggest differences between a forward and redirect. Figure 2-5 illustrates why this occurs.

Figure 2-5. A redirect causes the browser to issue a new request

figs/jstr_0205.gif

Because of the extra round trip that occurs, a redirect is slower than a forward. Example 2-1 provides an example servlet that performs a redirect for a JSP page called result.jsp when a request for the servlet is issued.

Example 2-1. A Java servlet that performs a redirect when it receives a request
package com.oreilly.struts.chapter2examples;
 
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.RequestDispatcher;
 
/**
 * Perform a redirect for the page "redirect.jsp"
 */
public class RedirectServlet extends HttpServlet {
 
  public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException{
    redirect( request, response );
  }
 
  public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException{
    redirect( request, response );
  }
  /**
   * Set a few URL parameters and objects for the request to see what happens
   * to them during a redirect.
   */
  protected void redirect(HttpServletRequest req,HttpServletResponse resp)
    throws ServletException, IOException{
    log( "A request arrived for " + req.getServletPath(  ) );
 
    // Put some objects into request scope
    req.setAttribute( "firstName", "John" );
    req.setAttribute( "lastName", "Doe" );
 
    String contextPath = req.getContextPath(  );
    String redirectStr = contextPath + "/result.jsp?username=foo&password=bar";
 
    log( "redirecting to " + redirectStr );
 
    // Always call the encodeRedirectURL(  ) method when perfoming a redirect
    resp.sendRedirect( resp.encodeRedirectURL(redirectStr) );
  }
}

When the servlet in Example 2-1 receives either a GET or POST request, it calls the redirect( ) method and passes to it the HttpServletRequest and HttpServletResponse objects. The method sets two String objects into the request, to demonstrate that they will not be available after the redirect, and it creates a String that will become the URL for which the client is told to make a new request. After the encodeRedirectURL( ) method is called and passed the redirect string, the sendRedirect( ) method is invoked on the response object.

All URLs passed to the sendRedirect( ) method should be run through the encodeRedirectURL( ) method, so that the session ID can be included if the browser doesn't support cookies and session tracking needs to occur. The Struts framework performs this step automatically in the RequestProcessor during normal action processing.

The JSP page to which the servlet will redirect is shown in Example 2-2.

Example 2-2. The result.jsp page
<html>
 <head>
  <title>Struts Redirect/Forward Example</title>
 </head>
 
<body>
 <img src="images\tomcat-power.gif">
 <br>
 <%
  String firstName = (String)request.getAttribute( "firstName" );
  if ( firstName == null ){
    firstName = "Not found in request";
  }
 
 
  String lastName = (String)request.getAttribute( "lastName" );
  if ( lastName == null ){
    lastName = "Not found in request";
  }
  %>
 
  <b>First Name:</b> <%=firstName%><br>
  <b>Last Name:</b> <%=lastName%><br>
 
</body>
</html> 

When you enter the URL for this servlet (http://localhost:8080/servlet/com.oreilly.struts.chapter2examples.RedirectServlet) in a browser, the browser output will look like Figure 2-6.

Figure 2-6. The output page when the RedirectServlet is called

figs/jstr_0206.gif

Notice that the first name and last name arguments that were set in the servlet were not found in the request. This is because a second request actually was issued for this page. Fortunately, we can peek behind the scenes and observe what's taking place.

The URL parameters username and password were included in this example just to illustrate that they are still present after a redirect occurs. You should never add confidential information such as passwords to the query string.

Let's look at the HTTP response that comes back to the client when the RedirectServlet is requested. We can view the response using a standard Telnet session. An HTTP connection uses a simple network socket to communicate with the server, so we can partially simulate the interaction between a browser and a server using the Telnet application. You can establish a connection to a web server using Telnet by connecting to the port on which the server is listening (usually port 80 or, in the case of Tomcat, 8080):

telnet localhost 8080

You can do this from the command line, whether you're using a DOS shell or Unix. The Telnet session will alert you if it's unable to connect to a server with the specified hostname and port number.

If a connection is established, the Telnet session will sit and wait for you to enter the HTTP request information. The only information you are required to enter is the first line of the request, which tells the server what resource the client wants. Each line in the request is a text line, separated by a newline character. The request header ends with a blank line, so you actually have to press Enter twice to let Telnet know you are done with the request. Figure 2-7 shows the HTTP response message Tomcat returns when a request is made for the RedirectServlet resource.

Figure 2-7. Telnet can be used to inspect the HTTP response headers

figs/jstr_0207.gif

In Figure 2-7, the first line of the request issues a GET and a path for a resource. The path is the portion of the HTTP request that comes after the hostname and includes the preceding "/" character. The "HTTP/1.0" string at the end of the GET request is the HTTP version protocol.

HTTP Version 1.1 added a number of optimizations over 1.0. However, there are additional request headers that must be included in an HTTP 1.1 request, which would make this example more complicated than necessary. Therefore, 1.0 was used to issue the request.

Everything after the first line is a response from the server. The entire HTML output at the bottom comes from Tomcat; it indicates that the original request has performed a redirect and that the client should request a new URL.

If you look at the Location response header, which is the third line in Figure 2-7, you can see that the server has informed the client what the URL for the new request should be. Any URL parameters attached to the original request will be present in the new request. When the browser issues the new request, these parameters will be sent along with it. This technique of using Telnet provides a simple way of interacting with a web server and viewing what responses it sends back to the client.

Hopefully, you now have a better understanding of how a redirect works. It should also be clear why any objects placed into the original request are not available to the redirected resource. This will become very important later, when we discuss the Struts framework.

Notice that in Example 2-1, the redirect string didn't explicitly contain the hostname and port that the client needed to use for the new request. It's the job of the servlet container to translate the relative URL to a fully qualified URL for transmission back to the client.

2.5.2 Using a Forward

A forward differs from a redirect in several ways. When you invoke a forward for a request, the request is sent to another resource on the server, without the client being informed that a different resource is going to process the request. This process occurs completely within the web container, and the client is never the wiser. Unlike with a redirect, objects can be stored in the request and passed along for the next resource to use. Figure 2-8 illustrates the steps that take place when a request is forwarded.

Figure 2-8. When a forward occurs, the client is not notified

figs/jstr_0208.gif

Because a forward takes place completely on the server and there is no communication with the client, the performance is better than with a redirect. However, there are some differences in how a forward deals with relative URLs. Example 2-3 should make this clearer.

Example 2-3. A Java servlet that performs a forward when it receives a request
package com.oreilly.struts.chapter2examples;
 
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.RequestDispatcher;
/**
 * Perform a forward to the page "redirect.jsp"
 */
public class ForwardServlet extends HttpServlet {
 
  public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException{
    forward( request, response );
  }
 
  public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException{
    forward( request, response );
  }
  /**
   * Set a few URL parameters and objects for the request to see what happens
   * to them during a forward.
   */
  protected void forward(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException{
    log( "A request arrived for " + req.getServletPath(  ) );
 
    // Put some objects into request scope
    req.setAttribute( "firstName", "John" );
    req.setAttribute( "lastName", "Doe" );
 
    String redirectStr = "/result.jsp?username=foo&password=bar";
 
    this.log( "forwarding to " + redirectStr );
    RequestDispatcher dispatcher = req.getRequestDispatcher( redirectStr );
    dispatcher.forward( req, resp );
  }
}

When the servlet in Example 2-3 receives either a GET or POST request, it calls the forward( ) method and passes the HttpServletRequest and HttpServletResponse objects to it. As with the redirect example, two String objects are set into the request. However, in contrast with the redirect example, the objects will be available after the forward. Next, the servlet creates a redirect path to which the request will be passed. A RequestDispatcher is created, and the forward( ) method is invoked on it. We'll use the same JSP page from Example 2-2. In the browser, we enter a URL like http://localhost:8080/servlet/com.oreilly.struts.chapter2examples.ForwardServlet. The browser output should look similar to Figure 2-9.

Figure 2-9. The output page when the ForwardServlet is called

figs/jstr_0209.gif

There are two things to note about Figure 2-9. The first is that the first and last name fields have values. This is because the objects were placed into the request before the forward occurred, and result.jsp was able to retrieve the values and use them in the page.

The second is the URL in the address bar. Notice that the URL back in Figure 2-6 showed the new URL, whereas it didn't change in Figure 2-9. This shows further that the client is not aware that a forward occurred—the browser was not notified that a different servlet handled the request.

The forward() method of the RequestDispatcher class can be called only when no output has been committed to the client. Writing something to the response object and then calling forward() may result in an IllegalStateException being thrown by the container.

2.5.3 Which Is Best for Struts?

It's difficult to decree whether Struts applications should use redirects or forwards, as we haven't discussed the framework yet. However, there are some key points about using each approach. Both mechanisms have their pros and cons within Struts and web applications in general. For many situations, a forward is recommended over a redirect, because objects that are stored in the request scope are easily available to the presentation components. In fact, using a forward is the default for the Struts framework. Another advantage of forwards is that they're much more efficient, as the client is not required to issue a new request.

There are some situations, however, where a redirect is necessary or preferred over a forward. When forwards are used, the URLs are not always consistent with the state of the client application. If the browser's Refresh button is pressed, an incorrect request may occur. This problem will be explored further in Chapter 5.

If you are using relative paths for images and other resources in your JSP pages, forwards can be problematic. Because the browser has no indication that a forward has occurred, the relative paths will be relative to the initial servlet, not to the JSP to which it was forwarded. As you'll see in Chapter 8, the Struts framework provides a JSP custom tag that can help to alleviate the hassle associated with this behavior.