Implementing Servlets

Now we’re going to take a leap from the client side over to the server side, to write Java applications for web servers. The Java servlet API is a framework for writing servlets, which are application components for a web server or other type of server; just as applets are application components for a web browser.

The servlet APIs live in the javax.servlet package, which is a standard Java API extension, not part of the core Java APIs. In this book we haven’t talked about many standard extension packages, but this is one is particularly important. (It should probably be a core API.) You’ll want to grab the latest Java Servlet Development Kit (JSDK) from http://java.sun.com/products/servlet.

The servlet APIs are useless without a server on which to run them, so you’ll also want to find an implementation of the servlet environment for your favorite web server: Netscape, Apache, or whatever. We won’t try to anticipate which environment you have in this book, so the details about how to install your servlets and invoke them will be up to you. But it should be pretty easy.

Why Servlets?

Why would we want to use Java on the server side, as opposed to a scripting language, such as Perl? The simplest answer to that question is: for the same reasons that you would use Java anywhere else. Servlets simply let you write in Java and derive all of the benefits of Java and the virtual machine environment on the server side. (You also have the limitations of Java.) Historically, servlets had speed advantages over CGI programs written in scripting languages or even in C/C++. That is because servlets execute in a multithreaded way within one instance of a virtual machine. Older CGI applications required the server to start a separate process, “pipe” data to it, and then receive the response as a stream as well. Speed is still a factor, but a more important reason for using Java is that Java makes writing large applications much more manageable. Java isn’t as easy to use as a scripting language, but it’s much easier to come back to your program next year and add a new feature, and it’s a lot better at scaling to large applications.

Writing server code with servlets allows you to access all of the standard Java APIs within the virtual machine while your servlets are handling requests. This means that your Java server code can access “live” database connections, or communicate with other network services that have already been established. This kind of behavior has been hacked into other CGI environments, but it has always been there in Java in a robust and natural way.

The Servlet Life Cycle

The Servlet API is very simple, almost exactly paralleling the Applet API. There are three life-cycle methods, init( ), service( ), and destroy( ), along with a couple of methods for getting configuration parameters and servlet info. Before a servlet is used for the first time, it is initialized by the server through its init( ) method. Thereafter the servlet spends its time handling service( ) requests and doing its job until (presumably) the server is shut down and the servlet’s destroy( ) method is called, giving it an opportunity to clean up.

The service( ) method of a servlet accepts two parameters: a servlet "request” object and a servlet “response” object. These provide tools for reading the client request and generating output; we’ll talk about them in detail in the examples.

By default, servlets are expected to handle multithreaded requests; that is, the servlet’s service methods may be invoked by many threads at the same time. This means that you cannot store client-related data in instance variables of your servlet object. (Of course, you can store general data related to the servlet’s operation, as long as it does not change on a per-request basis.) Per-client state information can be stored in a client “session” object, which persists across client requests. We’ll talk about that in detail later.

If for some reason you have developed a servlet that cannot support multi-threaded access, you can tell the runtime system this by implementing the flag interface SingleThreadModel . It has no methods, serving only to indicate that the servlet should be invoked in a single-threaded manner.

HTTP (Web) Servlets

There are actually two packages of interest in the Servlet API. The first is the javax.servlet package, which contains the most general servlet APIs. The second important package is javax.servlet.http , which contains APIs specific to servlets that handle HTTP requests for web servers. In the rest of this section, we are going to discuss servlets pretty much as if all servlets were HTTP-related. Although you can write servlets for other protocols, that’s not what we’re currently interested in.

The primary tool provided by the javax.servlet.http package is the HttpServlet base class. This is an abstract servlet that provides some basic implementation related to handling an HTTP request. In particular, it overrides the generic servlet service( ) request and breaks it out into several HTTP-related methods for you, including: doGet( ) , doPost( ), doPut( ), and doDelete( ). The default service( ) method examines the request to determine what kind it is and dispatches it to one of these methods, so you can override one or more of them to implement the corresponding web server behavior.

doGet( ) and doPost( ) correspond to the standard HTTP GET and POST operations. GET is the standard request for the object at a specified URL: e. g., a file or document. POST is the method by which a client sends data to the server. HTML forms are the most common example of users of POST.

To round these out, HttpServlet provides the doPut( ) and doDelete( ) methods. These methods correspond to a poorly supported part of the HTTP protocol, meant to provide a way to upload and remove files. doPut( ) is supposed to be like POST but with different semantics; doDelete( ) would be its opposite.

HttpServlet also implements three other HTTP related methods for you: doHead( ) , doTrace( ), and doOptions( ). You don’t normally need to override these methods. doHead( ) implements the HTTP HEAD request, which asks for the headers of a GET request without the body. (HttpServlet implements this by performing the GET and then sending only the headers). doTrace( ) and doOptions( ) implement other features of HTTP that allow for debugging and for simple client/server capabilities negotiation. Again, you generally shouldn’t need to override these.

Along with HttpServlet, javax.servlet.http also includes subclasses of the ServletRequest and ServletResponse objects, namely: HttpServletRequest and HttpServletResponse . These provide (respectively) the input and output streams needed to read and write client data. They also provide the APIs for getting or setting HTTP header information and, as we’ll see, client session information. Rather than document these dryly, we’ll just show them in the context of some examples. As usual, we’ll start with the simplest example possible.

The HelloClient Servlet

Here’s our “Hello World” of servlet land, HelloClient:

//file: HelloClient.java
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.http.*;

public class HelloClient extends HttpServlet { 

    public void doGet(HttpServletRequest request,
                      HttpServletResponse response) 
        throws ServletException, IOException {

        // must come first
        response.setContentType("text/html");
        PrintWriter out = response.getWriter( );

        out.println( 
            "<html><head><title>Hello Client</title></head><body>"
            + "<h1> Hello Client </h1>"
            + "</body></html>" );
        out.close( );
    }
}

HelloClient extends the base HttpServlet class and overrides the doGet( ) method to handle simple requests. In this case, we want to respond to any GET request by sending back a one-line HTML document that says “Hello Client”. We get the output writer from our HttpServletResponse parameter using the getWriter( ) method and print the message to it. Then we close the stream to indicate that we are done generating output.

Content Types

Before fetching the output stream and writing to it, however, it’s very important that we specify what kind of output we are sending by calling the response parameter’s setContentType( ) method.

In this case, we set the content type to text/html, which is the proper MIME type for an HTML document. But in general, it’s possible for a servlet to generate any kind of data, including sound, video, or some other kind of text. If we were writing a generic FileServlet that serves files like a regular web server, we might inspect the filename extension and determine the MIME type from that, or from direct inspection of the data.

The content type is used in the Content-Type: header of the server’s HTTP response, which tells the client what to expect even before it starts reading the result. This is how your web browser is able to prompt you with the “Save File” dialog when you click on a zip archive or executable program. When the content type string is used in its full form to specify the character encoding (for example, text/html; charset=ISO-8859-1) that information is also used by the servlet engine to set the character encoding of the PrintWriter output stream. So you should call the setContentType( ) method before fetching the writer with the getWriter( ) method.

Servlet Parameters

Our first example shows how to accept a basic request. You can imagine how we might do arbitrary processing, database queries, etc., to generate an interesting response. Of course, to do anything really useful we are going to have to get some information from the user. Fortunately, the servlet engine handles this for us, interpreting both GET- and POST-encoded form information from the client and providing it to us through the simple getParameter( ) method of the servlet request.

GET, POST, and the “extra path”

There are essentially two ways to pass information from your web browser to a servlet or CGI program. The most general is to “post” it, which means that your client encodes the information and sends it as a stream to the program, which decodes it. Posting can be used to upload large amounts of form data or other data, including files. The other way is to somehow encode the information in the URL of your client’s request. The primary way to do this is to use GET-style encoding of parameters in the URL string. In this case the web browser will append the parameters to the end of the URL string in an encoded way and the server will decode them and pass them to the application.

As we described earlier in this chapter, GET-style encoding says that you take the parameters and append them to the URL in a name/value fashion, with the first parameter preceded by a question mark (?) and the rest separated by ampersands (&). The entire string is expected to be URL-encoded: any special characters (like spaces, ?, and & in the string) are specially encoded.

A less sophisticated form of encoding data in the URL is called extra path. This simply means that when the server has located your servlet or CGI program as the target of a URL, it takes any remaining path components of the URL string and simply hands it over as an extra part of the URL. For example, consider these URLs:

http://www.myserver.example/servlets/MyServlet
http://www.myserver.example/servlets/MyServlet/foo/bar

Suppose the server maps the first URL to the servlet called MyServlet. When subsequently given the second URL, the server would still invoke MyServlet, but would consider /foo/bar to be “extra path” that could be retrieved through the servlet request getExtraPath( ) method.

Both GET and POST encoding can be used with HTML forms on the client, simply by specifying get or post in the action attribute of the form tag. The browser handles the encoding; on the server side, the servlet engine will handle the decoding.

Which one to use?

To users, the primary difference between GET and POST is that they can see the GET information in the encoded URL shown in their web browser. This can be useful because the user can cut and paste that URL (the result of a search for example) and mail it to a friend or bookmark it for future reference. POST information is never visible to the user and ceases to exist after it’s sent to the server. This behavior goes along with the protocol’s perspective that GET and POST are intended to have different semantics. By definition, the result of a GET operation is not supposed to have any side effects. That is, it’s not supposed to cause the server to perform any consequential operations (such as making an e-commerce purchase). In theory, that’s the job of POST. That’s why your web browser warns you about “re-posting form data” if you hit reload on a page that was the result of a form posting.

The extra path method is not useful for form data, but would be useful if you wanted to make a servlet that retrieves files or handles a range of URLs not driven by forms.

The ShowParameters Servlet

Our first example didn’t do anything interesting. This example prints the values of any parameters that were received. We’ll start by handling GET requests and then make some trivial modifications to handle POST as well. Here’s the code:

//file: ShowParameters.java
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import java.util.Enumeration;

public class ShowParameters extends HttpServlet { 

    public void doGet(HttpServletRequest request,
                      HttpServletResponse response) 
      throws ServletException, IOException {
        showRequestParameters( request, response );
    }

    void showRequestParameters(HttpServletRequest request,
                               HttpServletResponse response)
      throws IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter( );

        out.println(
          "<html><head><title>Show Parameters</title></head><body>"
          + "<h1>Parameters</h1><ul>");

        for ( Enumeration e=request.getParameterNames( );
              e.hasMoreElements( ); ) {
            String name = (String)e.nextElement( );
            String value = request.getParameter( name );
            if (! value.equals("") )
                out.println("<li>"+ name +" = "+ value );
        }

        out.close( );
    }
}

There’s not much new here. As in the first example, we override the doGet( ) method. Here, we delegate the request to a helper method that we’ve created, called showRequestParameters( ). All this method does is enumerate the parameters using the request object’s getParameterNames( ) method and print the names and values. (To make it pretty, we’ve listed them in an HTML list by prepending each with an <li> tag.)

As it stands, our servlet would respond to any URL that contains a GET request. Let’s round it out by adding our own form to the output and also accommodating POST method requests. To accept posts, we simply override the doPost( ) method. The implementation of doPost( ) could simply call our showRequest-Parameters( ) method as well, but we can make it simpler still. The API lets us treat GET and POST requests interchangeably, because the servlet engine handles the decoding of request parameters. So we simply delegate the doPost( ) operation to doGet( ).

Add the following method to the example:

public void doPost( HttpServletRequest request,
                    HttpServletResponse response) 
  throws ServletException, IOException {
    doGet( request, response );
}

Now let’s add an HTML form to the output. The form lets the user fill in some parameters and submit them to the servlet. Add this line to the showRequestParameters( ) method before the call to out.close( ):

out.println(
  "</ul><p><form method=\"POST\" action=\"" 
  + request.getRequestURI( ) + "\">"
  + "Field 1 <input name=\"Field 1\" size=20><br>"
  + "Field 2 <input name=\"Field 2\" size=20><br>"
  + "<br><input type=\"submit\" value=\"Submit\"></form>"
);

The form’s action attribute is the URL of our servlet, so that it will get the data. We use the getRequestURI( ) method to ask for the location of our servlet. For the method attribute we’ve specified a POST operation; but you can try changing the operation to GET to see both styles.

So far, we haven’t done anything that you couldn’t do easily with your average CGI script. Next, we’ll show some more interesting stuff: how to manage a user session.

User Session Management

One of the nicest features of the servlet API is that it provides a simple mechanism for managing a user session. By a session, we mean that the servlet can maintain information over multiple pages and through multiple transactions as navigated by the user. Providing continuity through a series of web pages is important in many kinds of applications, like providing a login process or tracking purchases in a shopping cart. In a sense, session data takes the place of instance data in your servlet object. It lets you store data between invocations of your service methods.

Session tracking is supported by the servlet engine; you don’t have to worry about the details of how it’s accomplished. It’s done in one of two ways: using client-side cookies , or URL rewriting . Client-side cookies are a standard HTTP mechanism for getting the client web browser to cooperate in storing some state information for you. A cookie is basically just a name/value attribute that is issued by the server, stored on the client, and returned by the client whenever it is accessing a certain group of URLs on a specified server. First, we’ll talk about cookies that live only for the duration of a typical user session (although it is possible to use cookies to store state across multiple user visits).

URL rewriting appends the session tracking information to the URL, using GET-style encoding or extra path information. The term “rewriting” applies because the server rewrites the URL before it is seen by the client and absorbs the extra information before it is passed back to the servlet.

To the servlet programmer, the state information is made available through an HttpSession object, which acts like a hashtable for storing whatever objects you would like to carry through the session. The objects stay on the server side; a special identifier is sent to the client through a cookie or URL rewriting. On the way back, the identifier is mapped to a session and the session is associated with the servlet again.

The ShowSession servlet

Here’s a simple servlet that shows how to store some string information in a session.

//file: ShowSession.java
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import java.util.Enumeration;

public class ShowSession extends HttpServlet { 

    public void doPost( HttpServletRequest request,
                        HttpServletResponse response) 
      throws ServletException, IOException {
        doGet( request, response );
    }
    
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response) 
      throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter( );

        out.println(
          "<html><head><title>Show Session</title></head><body>");
        HttpSession session = request.getSession( );
        
        out.println("<h1>In this session:</h1><ul>");
        String [] names = session.getValueNames( );
        for (int i=0; i< names.length; i++)
            out.println(
              "<li>"+names[i]+" = "+session.getValue( names[i] ));

        // add new name-value to session
        String name = request.getParameter("Name");
        if ( name != null ) {
            String value = request.getParameter("Value");
            session.putValue( name, value );
        }

        out.println(
          "</ul><p><hr><h1>Add String</h1>"
          + "<form method=\"POST\" action=\""
          + request.getRequestURI( ) +"\">"
          + "Name: <input name=\"Name\" size=20><br>"
          + "Value: <input name=\"Value\" size=20><br>"
          + "<br><input type=\"submit\" value=\"Submit\"></form>"
        );

        out.close( );
    }
}

When you invoke the servlet, you are presented with a form that prompts you to enter a name and a value. The value string is stored in a session object under the name provided. Each time the servlet is called, it outputs the list of all data items associated with the session. You will see the session grow as each item is added (in this case, until you restart your web browser or the server).

The basic mechanics are much like our ShowParameters servlet. Our doGet( ) method generates the form, which refers back to our servlet via a POST method. We override doPost( ) to delegate back to our doGet( ) method, allowing it to handle everything. Once in doGet( ), we attempt to fetch the user session object from the request parameter using getSession( ). The HttpSession object supplied by the request functions like a hashtable. There is a putValue( ) method, which takes a string name and an Object argument and a corresponding getValue( ) method. In our example, we use the getValueNames( ) method to enumerate the values currently stored in the session and print them.

By default, getSession( ) creates a session if one does not yet exist. If you want to test for a session or explicitly control when one is created, you can call the overloaded version getSession(false), which does not automatically create a new session. This method returns null if there is no session yet.

The ShoppingCart servlet

Next, we’ll build on the previous example to make a servlet that could be used as part of an online store. ShoppingCart lets users choose items and add them to their basket until check-out time:

//file: ShoppingCart.java
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import java.util.Enumeration;

public class ShoppingCart extends HttpServlet { 
    // from your database 
    String [] items = new String [] { 
        "Chocolate Covered Crickets", "Raspberry Roaches",
        "Buttery Butterflies", "Chicken Flavored Chicklets(tm)" };

    public void doPost( HttpServletRequest request,
                        HttpServletResponse response)
      throws IOException, ServletException {
        doGet( request, response ); 
    }

    public void doGet( HttpServletRequest request,
                       HttpServletResponse response) 
      throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter( );

        // get or create the session information
        HttpSession session = request.getSession( );
        int [] purchases = (int [])session.getValue("purchases");
        if ( purchases == null ) {
            purchases = new int [ items.length ];
            session.putValue( "purchases", purchases );
        }

        out.println( "<html><head><title>Shopping Cart</title>"
                     + "</title></head><body><p>" );
        if ( request.getParameter("checkout") != null )
            out.println("<h1>Thanks for ordering!</h1>");
        else  {
            if ( request.getParameter("add") != null ) {
                addPurchases( request, purchases ); 
                out.println(
                    "<h1>Purchase added.  Please continue</h1>");
            } else {
                if ( request.getParameter("clear") != null )
                    for (int i=0; i<purchases.length; i++)
                         purchases[i] = 0;
                out.println("<h1>Please Select Your Items!</h1>");
            }
            doForm( out, purchases, request.getRequestURI( ) );
        }
        showPurchases( out, purchases );
        out.close( );
    }

    void addPurchases( HttpServletRequest request,
                       int [] purchases ) {
        for (int i=0; i<items.length; i++) {
            String added = (String)request.getParameter( items[i] );
            if ( !added.equals("") )
                purchases[i] += Integer.parseInt( added );
        }
    }

    void doForm( PrintWriter out, int [] purchases,
                String requestURI) {
        out.println( "<form method=POST action="+ requestURI +">" );
    
        for(int i=0; i< items.length; i++)
            out.println( "Quantity <input name=\"" + items[i]
              + "\" value=0 size=3> of: " + items[i] + "<br>");
        out.println(
          "<p><input type=submit name=add value=\"Add To Cart\">" 
          + "<input type=submit name=checkout value=\"Check Out\">" 
          + "<input type=submit name=clear value=\"Clear Cart\">"
          + "</form>" );
    }

    void showPurchases( PrintWriter out, int [] purchases )
        throws IOException {

        out.println("<hr><h2>Your Shopping Basket</h2>");
        for (int i=0; i<items.length; i++)
            if ( purchases[i] != 0 )
                out.println( purchases[i] +"  "+ items[i] +"<br>" );
    }
}

First we should point out that ShoppingCart has some instance data: a String array that holds a list of products. We’re making the assumption that the product selection is the same for all customers. If it’s not, we’d have to generate the product list on the fly or put it in the session for the user.

Next, we see the same basic pattern as in our previous servlets, with doPost( ) delegating to doGet( ) and doGet( ) generating the body of the output and a form for gathering new data. Here we’ve broken down the work using a few helper methods: doForm( ), addPurchases( ) and showPurchases( ). Our shopping cart form has three submit buttons: one for adding items to the cart, one for check-out, and one for clearing the cart. In each case we show the user what his or her purchases are. Depending on the button chosen in the form, we either add new purchases, clear the list, or simply show the results as a check out window.

The form is generated by our doForm( ) method, using the list of items for sale. As in the other examples, we supply our servlet’s address as the target of the form. Next, we have placed an integer array called purchases into the user session. Each element in purchases holds a count of the number of each item the user wants to buy. We create the array after retrieving the session simply by asking the session for it. If this is a new session and the array hasn’t been created, getValue( ) gives us a null array that we can then populate. Since we generate our form using the names from the items array, it’s easy for addPurchases( ) to check for each name using getParameter( ), and increment the purchases array for the number of items requested.[39] Finally, showPurchases( ) simply loops over the purchases array and prints the name and quantity for each item that the user has purchased.

Cookies

In our previous examples, a session lived only until you shut down your web browser or the server. You can do more long-lived kinds of user tracking or identification by managing cookies explicitly. You can send a cookie to the client by creating a javax.servlet.http.Cookie object and adding it to the servlet response using the addCookie( ) method. Later you can retrieve the cookie information from the servlet request and use it to look up persistent information in a database. The following servlet sends a “Learning Java” cookie to your web browser and displays it when you return to the page:

//file: CookieCutter.java
import java.io.*;
import java.text.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class CookieCutter extends HttpServlet {

    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
      throws IOException, ServletException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter( );

        if ( request.getParameter("setcookie") != null ) {
            Cookie cookie = new Cookie("Learningjava", "Cookies!");
            cookie.setMaxAge(3600);
            response.addCookie(cookie);
            out.println("<html><body><h1>Cookie Set...</h1>");
        } else {
            out.println("<html><body>");
            Cookie[] cookies = request.getCookies( );
            if ( cookies.length == 0 )
                out.println("<h1>No cookies found...</h1>");
            else
                for (int i = 0; i < cookies.length; i++)
                    out.print("<h1>Name: "+ cookies[i].getName( )
                              + "<br>"
                              + "Value: " + cookies[i].getValue( )
                              + "</h1>" );
            out.println("<p><a href=\""+ request.getRequestURI( )
              +"?setcookie=true\">"
              +"Reset the Learning Java cookie.</a>");
        }
        out.println("</body></html>");
        out.close( );
    }
}

This example simply enumerates the cookies supplied by the request object using the getCookies( ) method, and prints their names and values. We provide a GET-style link that points back to our servlet with a parameter setcookie, indicating that we should set the cookie. In that case, we create a Cookie object using the specified name and value and add it to the response with the addCookie( ) method. We set the maximum age of the cookie to 3600 seconds, so it will remain in a web browser for one hour before being discarded. You can specify an arbitrary time period here, or a negative time period to indicate that the cookie should not be stored persistently on the client.

Two other methods of Cookie are of interest: setDomain( ) and setPath( ). These allow you to specify the domain name and path component that limits the servers to which the client will send the cookie. (If you’re writing some kind of purchase applet for L.L. Bean, you don’t want clients sending your cookies over to Eddie Bauer.) The default domain is the domain of the server sending the cookie. (You may not be able to specify other domains for security reasons.) The path parameter defaults to the base URL of the servlet, but you can specify a wider (or narrower) range of URLs on the server by setting this parameter manually.



[39] We also test for the value being equal to the null string, because some web browsers send empty strings for all field values.

Get Learning Java now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.