[ Team LiB ] Previous Section Next Section

Recipe 3.7 Mapping Requests to a Controller and Preserving Servlet Mappings

Problem

You want to map all requests to a single controller servlet, while preserving the servlet mappings for other servlets in a secure manner.

Solution

Use security-constraint elements in web.xml to prevent web users from making requests to the noncontroller servlets.

Discussion

What if the controller servlet that receives all requests wants to conditionally forward the request along to another servlet for specialized processing? If all of the other servlet mappings are removed from web.xml and the invoker-style URL pattern (/servlet/*) is mapped to the controller servlet itself, even the controller servlet is prevented from forwarding a request to another servlet! How can you get around these restrictions?

A solution is to retain the individual servlet mappings in web.xml. Then you can use security-constraint elements to prevent web users from making requests to these noncontroller servlets. When the controller servlet wants to forward a request to another servlet, it uses an object that implements the javax.servlet.RequestDispatcher interface. RequestDispatchers are not restricted from forwarding requests (using the RequestDispatcher.forward(request, response) method) to URL patterns that are specified by security-constraint elements. Example 3-10 shows a servlet named Controller that uses a RequestDispatcher to forward a request to another servlet.

Recipe 3.9 describes how to protect servlets from receiving any web-user requests with the security-constraint element, so I won't repeat that information here.

Example 3-10. Using RequestDispatcher to forward a request
import javax.servlet.*;
import javax.servlet.http.*;

public class Controller extends HttpServlet {
       
  public void doGet(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, java.io.IOException {
    
    RequestDispatcher dispatcher = null;
    String param = request.getParameter("go");

    if (param == null)
      throw new ServletException("Missing parameter in Controller.");
    else if (param.equals("weather"))
      dispatcher = request.getRequestDispatcher("/weather");
    else if (param.equals("maps"))
      dispatcher = request.getRequestDispatcher("/maps");
    else
      throw new ServletException(
        "Improper parameter passed to Controller.");

    //if we get this far, dispatch the request to the correct URL
    if (dispatcher != null)
      dispatcher.forward(request,response);
    else
      throw new ServletException(
        "Controller received a null dispatcher from request object.");
  }
}

The servlet checks the go parameter for its value. A request to this servlet might look like:

http://localhost:8080/home?go=weather

In this example, the Controller servlet is mapped to receive all web requests to the "home" web application. In other words, the controller's servlet-mapping in web.xml has a url-pattern of /*.

Based on the go parameter value, Controller creates a RequestDispatcher object with a different specified URL for forwarding. The servlet gets a RequestDispatcher object first by calling the request object's getRequestDispatcher(String path) method. The path parameter can be relative to the context root of the web application, as it is here, but it cannot extend beyond the current servlet context. Suppose the URL pattern /weather is mapped to the registered servlet name "Weather":

<servlet-mapping>
    <servlet-name>Weather</servlet-name>
    <url-pattern>/weather</url-pattern>
</servlet-mapping>

In this case, the path passed to the getRequestDispatcher( ) method looks like getRequestDispatcher("/weather"). If the go parameter is either wrong or missing, the Controller throws a ServletException with an appropriate message. The Weather servlet, though, cannot be accessed by web users directly because it is restricted by a security-constraint element—but the RequestDispatcher.forward(request,response) method is not limited by these constraints.

You can also use the javax.servlet.ServletContext.getNamedDispatcher(String name) method to get a RequestDispatcher object for forwarding. Using this method, you do not have to include any servlet-mapping elements for the target servlet. The getNamedDispatcher( ) method takes as its parameter the registered name of the servlet in web.xml. Example 3-11 shows the prior servlet example altered to use getNamedDispatcher("Weather"), using the weather servlet's registered name instead.

Example 3-11. Using getNamedDispatcher( ) to forward a request
import javax.servlet.*;
import javax.servlet.http.*;

public class Controller extends HttpServlet {
       
    public void doGet(HttpServletRequest request, 
        HttpServletResponse response) 
         throws ServletException, java.io.IOException {

        RequestDispatcher dispatcher = null;
        String param = request.getParameter("go");

             if (param == null)
                 throw new 
                     ServletException("Missing parameter in Controller.");
             else if (param.equals("weather"))
                 dispatcher = getServletContext( ).
                     getNamedDispatcher("Weather");
             else if (param.equals("maps"))
                 dispatcher = getServletContext( ).
                     getNamedDispatcher("Maps");
            else
                throw new ServletException(
                    "Improper parameter passed to Controller.");

         /*check for a null dispatcher, then 
            dispatch the request to the correct URL*/
        if (dispatcher != null)
            dispatcher.forward(request,response);
        else
            throw new ServletException(
              "Controller received a null dispatcher.");
    }
}

The doGet( ) method has been changed to use a RequestDispatcher received from the ServletContext.getNamedDispatcher(String registered-servlet-name) method. Instead of a servlet path, the dispatcher object uses that servlet's registered name ("Weather") from web.xml, as in:

<servlet>
    <servlet-name>Weather</servlet-name>
    <servlet-class>com.jspservletcookbook.Weather
    </servlet-class>
</servlet>

If the ServletContext returns a null dispatcher because someone left out the necessary XML element in web.xml, then doGet( ) throws a ServletException explaining that the dispatcher object is null.

An alternate strategy is to use a listener to check the request before it finds its way to a servlet. Chapter 19 describes how to use a listener to examine an HTTP request.


See Also

Chapter 1 on web.xml; Recipe 3.1-Recipe 3.5; Recipe 3.8; Chapter 19 on using a listener to examine the request; Chapter 11 of the Servlet v2.3 and 2.4 specifications on mapping requests to servlets; the Core J2EE Blueprints page: http://java.sun.com/blueprints/corej2eepatterns/Patterns/FrontController.html

    [ Team LiB ] Previous Section Next Section