Previous Section  < Day Day Up >  Next Section

4.3 Servlets, Filters, and Listeners

All new Java web-tier technologies, such as JSP, JSF, and portlets, are defined on top of the API that started it all: the Servlet API. A servlet is a Java class that processes a request from a client and produces a response. Most implementations of the JSF specification use a servlet as the entry-point for the page requests, and some JSF-based applications may also include a few servlets and other classes defined by the servlet specification, such as listeners and filters.

The Servlet API is general enough to allow servlets to deal with any request/response-based protocol, but it's almost exclusively used with HTTP. The API consists of two packages: the javax.servlet package contains classes and interfaces that are protocol-independent, while the javax.servlet.http package provides HTTP-specific extensions and utility classes.

For HTTP processing, one class and two interfaces make up the bulk of the API:


javax.servlet.http.HttpServlet

This is the base class for most servlets. It contains empty default implementations of two methods that can be overridden by a subclass for initialization and release of internal resources used by a servlet: init() and destroy( ). The init() method is called once before the servlet is asked to process its first request, and the destroy() method is called just before the servlet is taken out of service.

The base class also provides default implementations of the request processing methods, one for each HTTP request type: doGet( ), doPost(), doDelete( ), doHead(), doOptions( ), doPut(), and doTrace( ). Most subclasses override the doGet() and doPost() methods (or at least one of them) to handle the corresponding HTTP request types, but rely on the default implementations for the other request types.


javax.servlet.http.HttpServletRequest

This interface represents the HTTP request, with methods for reading request parameters, headers, and the request URL. Other methods let you add, read, and remove application data—called request attributes—associated with the request. This mechanism comes in handy when the servlet delegates some of the processing (e.g., the rendering of the response) to another application class, as I describe later.


javax.servlet.http.HttpServletResponse

This interface represents the HTTP response sent back to the client. It provides methods for setting response headers and obtaining a stream for the response body.

The web container loads the servlet class—either when the container is started or when the first request for the servlet is received—and calls its methods, converting raw HTTP request messages into HttpServletRequest objects and HttpServletResponse objects into raw HTTP response messages. It's important to realize that the container creates only one instance of each servlet. This means that the servlet must be thread safe—able to handle multiple requests at the same time, each executing as a separate thread through the servlet code.

4.3.1 Using Request Data

Example 4-1 shows a servlet that uses many of the HttpServletRequest methods.

Example 4-1. Using HttpServletRequest methods
import java.io.IOException;

import java.io.PrintWriter;

import java.util.Iterator;

import java.util.Map;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

  

public class HelloYou extends HttpServlet {

    public void doGet(HttpServletRequest request, 

        HttpServletResponse response) 

        throws ServletException, IOException {

  

        String name = request.getParameter("name");

        if (name == null) {

            name = "you";

        }

        response.setContentType("text/html");

        PrintWriter out = response.getWriter( );

        out.println("<html><body>");

        out.println("<h1>Hello " + name + "</h1>");

  

        out.println("I see that:<ul>");

        String userAgent = request.getHeader("User-Agent");

        out.println("<li>your browser is: " + userAgent);

        String requestURI = request.getRequestURI( );

        out.println("<li>the URI for this page is: " +

            requestURI);

        String contextPath = request.getContextPath( );

        out.println("<li>the context path for this app is" +

            contextPath);

        String servletPath = request.getServletPath( );

        out.println("<li>this servlet is mapped to: " + 

            servletPath);

        String pathInfo = request.getPathInfo( );

        out.println("<li>the remaining path is: " + pathInfo);

        Map parameters = request.getParameterMap( );

        out.println("<li>you sent the following params:<ul>");

        Iterator i = parameters.keySet( ).iterator( );

        while (i.hasNext( )) {

            String paramName = (String) i.next( );

            out.println("<li><b>" + paramName + "</b>:");

            String[] paramValues = 

                (String[]) parameters.get(paramName);

            for (int j = 0; j < paramValues.length; j++) {

                if (j != 0) {

                    out.print(", ");

                }

                out.print(paramValues[j]);

            }

        }

        out.println("</ul></ul></body></html>");

    }

}

The HelloYou class in Example 4-1 extends the HttpServlet class, which makes it a valid servlet. It implements the doGet() method to process GET requests.

The doGet() method uses the getParameter() method to get a single value for a request parameter named name. For a request with multiple parameters of the same name, you can use a method called getParameterValues() instead. It returns a String array with all values. Further down in the example, note that you can also use the getParameterMap( ) to get a Map containing all parameters in the request. Each key is a String with the parameter name, and the values are String arrays with all values for the parameter. The getParameterMap( ) method was added in the Servlet 2.3 API. It comes in handy when you need to pass all parameters around or process all parameters without knowing their names. All these parameter access methods work the same for both GET and POST requests.

Example 4-1 also shows you how to use the getHeader() method for reading request header values, the getRequestURI( ) method for getting the complete request URI, and various getXXXPath() methods for getting different parts of the URI path.

To write all the information received from the request back to the browser, the doGet() gets a PrintWriter from the HttpServletResponse object by calling the getWriter() method. It then generates the response body simply by writing content to the writer.

4.3.2 Compiling, Installing, and Running a Servlet

To compile a servlet, you must first ensure that you have the JAR file containing all Servlet API classes in the CLASSPATH environment variable. The JAR file is distributed with all web containers. Tomcat 5 includes it in a file called servlet-api.jar, located in the common/lib directory. On a Windows platform, you include the JAR file in the CLASSPATH like this (assuming Tomcat is installed in C:\Jakarta\jakarta-tomcat-5):

C:/> set CLASSPATH=C:\Jakarta\jakarta-tomcat-5\common\lib\servlet-api.jar; %CLASSPATH%

You can then compile the HelloYou servlet from Example 4-1 with the javac command, like this:

C:/> javac HelloYou.java

To make the servlet available to the container, you can place the resulting class file in the WEB-INF/classes directory for the example application:

C:/> copy HelloYou.class C:\Jakarta\jakarta-tomcat-5\webapps\jsfbook\WEB-INF\classes

The container looks automatically for classes in the WEB-INF/classes directory structure, so you can use this directory for all application class files. The HelloYou servlet is part of the default package, so it goes in the WEB-INF/classes directory itself. If you use another package, say com.mycompany, you must put the class file in a directory under WEB-INF/classes that mirrors the package structure, as described earlier. Alternatively, you can package the class files in a JAR file (see the Java SDK documents for details) and place the JAR file in the WEB-INF/lib directory. The internal structure of the JAR file must also mirror the package structure for all your classes.

Next, you must tell the container that it should invoke your servlet when it recieves a request for a specific URL. You do this with <servlet> and <servlet-mapping> elements in the application deployment descriptor (WEB-INF/web.xml) file:

  ...

  <servlet>

    <servlet-name>helloYou</servlet-name>

    <servlet-class>HelloYou</servlet-class>

  </servlet>

  

  <servlet-mapping>

    <servlet-name>helloYou</servlet-name>

    <url-pattern>/hello/*</url-pattern>

  </servlet-mapping>

  ...

The <servlet> element gives the servlet class a unique name, and the <servlet-mapping> element links a URL pattern to the named servlet. Here I use a pattern that says that all requests with a path starting with /hello/ followed by any other characters (the "*" is a wildcard character) should be served by the servlet named helloYou. Appendix F contains a complete reference for the deployment descriptor format.

After compiling and installing the HelloYou servlet, and mapping it to the /helloYou/* URL pattern in the deployment descriptor, you can test it with this URL, assuming the context path for the application is /jsfbook:

http://localhost:8080/jsfbook/helloYou/extra?name=Mike&a=1&a=2

The result should be similar to that shown in Figure 4-5.

Figure 4-5. Response generated by the HelloYou servlet
figs/Jsf_0405.gif

4.3.3 Generating Responses of Different Types

Besides the request object, the container passes an object that implements the HttpServletResponse interface as an argument to the doGet() and doPost() methods. This interface defines methods for getting a writer or stream for the response body. It also defines methods for setting the response status code and headers. Example 4-2 contains the code for a servlet that uses some of these methods.

Example 4-2. Using HttpServletResponse methods
import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.io.PrintWriter;

import javax.servlet.ServletContext;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

  

public class HelloMIME extends HttpServlet {

    private static final int TEXT_TYPE = 0;

    private static final int IMAGE_TYPE = 1;

  

    public void doGet(HttpServletRequest request, 

        HttpServletResponse response) 

        throws ServletException, IOException {

  

        String greeting = "Hello World!";

        int majorType = TEXT_TYPE;

        String type = request.getParameter("type");

        if ("plain".equals(type)) {

            response.setContentType("text/plain");

        }

        else if ("html".equals(type)) {

            response.setContentType("text/html");

            greeting = "<html><body><h1>" + greeting +

                "</h1></body></html>";

        }

        else if ("image".equals(type)) {

            response.setContentType("image/gif");

            majorType = IMAGE_TYPE;

        }

        else {

            response.sendError(HttpServletResponse.SC_BAD_REQUEST, 

                "Please specify a valid response type");

            return;

        }

  

        if (majorType == TEXT_TYPE) {

            PrintWriter out = response.getWriter( );

            out.println(greeting);

        }

        else {

            OutputStream os = response.getOutputStream( );

            ServletContext application = getServletContext( );

            InputStream is = 

                application.getResourceAsStream("/ora.gif");

            copyStream(is, os);

        }

    }

  

    private void copyStream(InputStream in, OutputStream out) 

            throws IOException {

        int bytes;

        byte[] b = new byte[4096];

        

        while ((bytes = in.read(b, 0, b.length)) != -1) {

            out.write(b, 0, bytes);

            out.flush( );

        }

    }

}

In Example 4-2, a request parameter named type is used to choose between a plain text, an HTML, or a GIF response. The response must include the Content-Type header to tell the browser what type of content the response body contains. The servlet used the setContentType() method to set this header. The method takes the MIME type for the content as its single argument. The HttpServletResponse interface contains a number of methods like this for setting specific response headers. For headers not covered by specific methods, you can use the setHeader() method.

If no type or an invalid type is specified, the servlet in Example 4-2 returns an error response using the sendError( ) method. This method takes two arguments: the HTTP response status code and a short message to be used as part of the response body. If you prefer to use the container's default message for the status code, you can use another version of the sendError() method that omits the message argument.

With the content type setting out of the way, it's time to generate the response body. For a body containing either plain text or a markup language such as HTML or XML, you acquire a PrintWriter for the response by calling the getWriter() method and just write the text to it. For a binary body, such as an image, you must use an OutputStream instead, which is exactly what the getOutputStream() method provides. When the type parameter has the value image, I use this method to grab the stream and write the content of a GIF file to it.

The way the GIF file is accessed in Example 4-2 is the recommended way to access any application file:

ServletContext application = getServletContext( );

InputStream is = application.getResourceAsStream("/ora.gif");

The getServletContext() method returns a reference to the ServletContext instance for this servlet. As you may recall, a ServletContext instance represents a web application and provides access to various shared application resources. The getResourceAsStream() method takes the context-relative path to a file resource as its argument and returns an InputStream. The Servlet API contains methods that let you open a file using the standard Java File class as well, but there's no guarantee that this will work in all containers. A container may serve the application files directly from a compressed WAR file, from a database, or any other way that it sees fit. Using a File object in such a container doesn't work, but using the getResourceAsStream() method does, because the container is responsible for providing the stream no matter how it stores the application data.

4.3.4 Filters and Listeners

The servlet specification defines two component types beside servlets: filters and listeners. These compont types are also often used in a JSF-based application.

4.3.4.1 Filters

A filter is a component that can intercept a request targeted for a servlet, JSP page, or static page, as well as the response before it's sent to the client. This makes it easy to centralize tasks that apply to all requests, such as access control, logging, and charging for the content or the services offered by the application. A filter has full access to the body and headers of the request and response, so it can also perform various transformations. One example is compressing the response body if the Accept-Encoding request header indicates that the client can handle a compressed response.

A filter can be applied to either a specific servlet or to all requests matching a URL pattern, such as URLs starting with the same path elements or having the same extension. Jason Hunter's JavaWorld article about filters, http://www.javaworld.com/javaworld/jw-06-2001/jw-0622-filters.html, is a good introduction to how to develop various types of filters, such as filters for measuring processing time, click and clickstreams monitoring, response compression, and file uploading.

4.3.4.2 Listeners

Listeners allow your application to react to certain events. Starting with Version 2.3 of the servlet specification, there are listener types for servlet context, session and request lifecycle events ("created" and "destroyed" events), session attribute events ("added" and "removed" events), as well as for session activation and passivation events (used by a container that temporarily saves session state to disk or migrates a session to another server).

All these listener types follow the standard Java event model. In other words, a listener is a class that implements one or more listener interfaces. The interfaces define methods that correspond to events. The listener class is registered with the container when the application starts, and the container then calls the event methods at the appropriate times.

    Previous Section  < Day Day Up >  Next Section