Novocode
Software & Networking
Contents
 NetForge 0.40 Manual:   Servlet Essentials 


Stefan Zeiger

Servlet Essentials

Version 1.0.2

June 23, 1998

Abstract

This document explains Servlets and provides a step-by-step tutorial for writing HTTP Servlets with complete source code for the example Servlets. The tutorial and the "Advanced Topics" chapter cover all facets of Servlet programming from a simple "Hello World" Servlet to advanced Servlet features like session tracking and Cookies. There are also appendices on compiling and running Servlets. The reader is assumed to have some basic knowledge of HTML and Java programming.

Table of Contents:

1. An Invitation to Servlets
1.1 What is a Servlet?
1.2 Servlets vs CGI
1.3 The Basic Servlet Architecture
1.4 HTTP

2. Servlets Step by Step
2.1 Hello World!
2.2 A Form Processing Servlet
2.3 Session Tracking

3. Advanced Topics
3.1 Not Thread-Safe Servlets
3.2 Client-Side Data with Cookies
3.3 Inter-Servlet Communication
3.4 Creating Images with a Servlet
3.5 User Authentication via Sessions
3.6 Servlets and Hyperlinks

Appendix
A. Compiling Servlets
B. Running the Example Servlets with NetForge
C. Servlet Resources on the Web

1.  An Invitation to Servlets

This first chapter answers the question "What is a Servlet?", shows typical uses for Servlets, compares Servlets to CGI programs and explains the basics of the Servlet architecture and the Servlet lifecycle. It also gives a quick introduction to HTTP and its implementation in the HttpServlet class.

1.1  What is a Servlet?

Servlets are modules of Java code that run in a server application (hence the name "Servlets", similar to "Applets" on the client side) to answer client requests. Servlets are not tied to a specific client-server protocol but they are most commonly used with HTTP and the word "Servlet" is often used in the meaning of "HTTP Servlet".

Servlets make use of the Java standard extension classes in the packages javax.servlet (the basic Servlet framework) and javax.servlet.http (extensions of the Servlet framework for Servlets that answer HTTP requests). Since Servlets are written in the highly portable Java language and follow a standard framework, they provide a means to create sophisticated server extensions in a server and operating system independent way.

Typical uses for HTTP Servlets include:

1.2  Servlets vs CGI

The traditional way of adding functionality to a Web Server is the
Common Gateway Interface (CGI), a language-independant interface that allows a server to start an external process which gets information about a request through environment variables, the command line and its standard input stream and writes response data to its standard output stream. Each request is answered in a separate process by a separate instance of the CGI program, or CGI script (as it is often called because CGI programs are usually written in interpreted languages like Perl).

Servlets have several advantages over CGI:

1.3  The Basic Servlet Architecture

A Servlet, in its most general form, is an instance of a class which implements the javax.servlet.Servlet interface. Most Servlets, however, extend one of the standard implementations of that interface, namely javax.servlet.GenericServlet and javax.servlet.http.HttpServlet. In this tutorial we'll be discussing only HTTP Servlets which extend the javax.servlet.http.HttpServlet class.

In order to initialize a Servlet, a server application loads the Servlet class (and probably other classes which are referenced by the Servlet) and creates an instance by calling the no-args constructor. Then it calls the Servlet's init(ServletConfig config) method. The Servlet should performe one-time setup procedures in this method and store the ServletConfig object so that it can be retrieved later by calling the Servlet's getServletConfig() method. This is handled by GenericServlet. Servlets which extend GenericServlet (or its subclass HttpServlet) should call super.init(config) at the beginning of the init method to make use of this feature. The ServletConfig object contains Servlet parameters and a reference to the Servlet's ServletContext. The init method is guaranteed to be called only once during the Servlet's lifecycle. It does not need to be thread-safe because the service method will not be called until the call to init returns.

When the Servlet is initialized, its service(ServletRequest req, ServletResponse res) method is called for every request to the Servlet. The method is called concurrently (i.e. multiple threads may call this method at the same time) so it should be implemented in a thread-safe manner. Techniques for ensuring that the service method is not called concurrently, for the cases where this is not possible, are described in section 3.1.

When the Servlet needs to be unloaded (e.g. because a new version should be loaded or the server is shutting down) the destroy() method is called. There may still be threads that execute the service method when destroy is called, so destroy has to be thread-safe. All resources which were allocated in init should be released in destroy. This method is guaranteed to be called only once during the Servlet's lifecycle.

A typical Servlet lifecycle looks like this:

1.4  HTTP

Before we can start writing the first Servlet, we need to know some basics of HTTP ("HyperText Transfer Protocol"), the protocol which is used by a WWW client (e.g. a browser) to send a request to a Web Server.

HTTP is a request-response oriented protocol. An HTTP request consists of a request method, a URI, header fields and a body (which can be empty). An HTTP response contains a result code and again header fields and a body.

The service method of HttpServlet dispatches a request to different Java methods for different HTTP request methods. It recognizes the standard HTTP/1.1 methods and should not be overridden in subclasses unless you need to implement additional methods. The recognized methods are GET, HEAD, PUT, POST, DELETE, OPTIONS and TRACE. Other methods are answered with a Bad Request HTTP error. An HTTP method XXX is dispatched to a Java method doXxx, e.g. GET -> doGet. All these methods expect the parameters "(HttpServletRequest req, HttpServletResponse res)". The methods doOptions and doTrace have suitable default implementations and are usually not overridden. The HEAD method (which is supposed to return the same header lines that a GET method would return, but doesn't include a body) is performed by calling doGet and ignoring any output that is written by this method. That leaves us with the methods doGet, doPut, doPost and doDelete whose default implementations in HttpServlet return a Bad Request HTTP error. A subclass of HttpServlet overrides one or more of these methods to provide a meaningful implementation.

The request data is passed to all methods through the first argument of type HttpServletRequest (which is a subclass of the more general ServletRequest class). The response can be created with methods of the second argument of type HttpServletResponse (a subclass of ServletResponse).

When you request a URL in a Web Browser, the GET method is used for the request. A GET request does not have a body (i.e. the body is empty). The response should contain a body with the response data and header fields which describe the body (especially Content-Type and Content-Encoding). When you send an HTML form, either GET or POST can be used. With a GET request the parameters are encoded in the URL, with a POST request they are transmited in the body. HTML editors and upload tools use PUT requests to upload resources to a Web Server and DELETE requests to delete resources.

2.  Servlets Step by Step

This chapter acts as a Servlet tutorial. You will learn how to use important techniques for Servlet development by writing some typical Servlets, ranging from very simple to rather complex.

2.1  Hello World!

This section shows how to
  • use the framework that makes up a simple Servlet
  • write a Servlet that provides static content (i.e. it produces the same output every time it is called by a client)

We start our venture into Servlet programming with the well-known "Hello World" example, this time named more suitably "Hello Client":

HelloClientServlet.java

 1:  import java.io.*;
 2:  import javax.servlet.*;
 3:  import javax.servlet.http.*;
 4:  
 5:  public class HelloClientServlet extends HttpServlet
 6:  {
 7:    protected void doGet(HttpServletRequest req,
 8:                         HttpServletResponse res)
 9:              throws ServletException, IOException
10:    {
11:      res.setContentType("text/html");
12:      PrintWriter out = res.getWriter();
13:      out.println("<HTML><HEAD><TITLE>Hello Client!</TITLE>"+
14:                  "</HEAD><BODY>Hello Client!</BODY></HTML>");
15:      out.close();
16:    }
17:  
18:    public String getServletInfo()
19:    {
20:      return "HelloClientServlet 1.0 by Stefan Zeiger";
21:    }
22:  }

When you compile this Servlet and run it by requesting a URL which is assigned to it in a Web Browser it produces the following output:

Let's have a look at how the Servlet works.

2.2  A Form Processing Servlet

This section shows how to
  • process form data
  • manage persistent data
  • use init parameters

The next Servlet that we are going to write provides a user interface to a mailing list through HTML forms. A user should be able to enter an email address in a text field and press a button to subscribe to the list or another button to unsubscribe.

The Servlet consists of two major parts: Data managment and client interaction.

Data management

The data management is rather straight-forward for an experienced Java programmer. We use a java.lang.Vector object which contains the email addresses as Strings. Since a Servlet can have data which persists between requests we load the address list only once, when the Servlet is initialized, and save it every time it has been changed by a request. An alternative approach would be keeping the list in memory while the Servlet is active and writing it to disk in the destroy method. This would avoid the overhead of saving the address list after every change but is less fail-safe. If for some reason the address file can't be written to disk or the server crashes and cannot destroy the Servlet, all changes to the list will be lost even though the users who submitted the requests to change the list received positive responses.

The following parts of the Servlet are related to data management:


  8:    private Vector addresses;
  9:    private String filename;
 10:  
 11:    public void init(ServletConfig config) throws ServletException
 12:    {
 13:      super.init(config);
 14:      filename = config.getInitParameter("addressfile");
 15:      if(filename == null)
 16:        throw new UnavailableException(this,
 17:                                       "The \"addressfile\" property "+
 18:                                       "must be set to a file name");
 19:      try
 20:      {
 21:        ObjectInputStream in =
 22:          new ObjectInputStream(new FileInputStream(filename));
 23:        addresses = (Vector)in.readObject();
 24:        in.close();
 25:      }
 26:      catch(FileNotFoundException e) { addresses = new Vector(); }
 27:      catch(Exception e)
 28:      {
 29:        throw new UnavailableException(this,
 30:                                       "Error reading address file: "+e);
 31:      }
 32:    }

104:    private synchronized boolean subscribe(String email) throws IOException
105:    {
106:      if(addresses.contains(email)) return false;
107:      addresses.addElement(email);
108:      save();
109:      return true;
110:    }
111:  
112:    private synchronized boolean unsubscribe(String email) throws IOException
113:    {
114:      if(!addresses.removeElement(email)) return false;
115:      save();
116:      return true;
117:    }
118:  
119:    private void save() throws IOException
120:    {
121:      ObjectOutputStream out =
122:        new ObjectOutputStream(new FileOutputStream(filename));
123:      out.writeObject(addresses);
124:      out.close();
125:    }

In init we first call super.init(config) to leave the ServletConfig management to the superclass (HttpServlet), then we get the name of the address file from an init parameter (which is set up in the Web Server configuration). If the parameter is not available the Servlet throws a javax.servlet.UnavailableException (a subclass of javax.servlet.ServletException) which indicates that a Servlet is temporarily (if a duration is specified) or permanently (as in this case) unavailable. Finally, the init method deserializes the address file or creates an empty Vector if the address file does not exist yet. All exceptions that occur during the deserialization are transformed into UnavailableExceptions.

The methods subscribe and unsubscribe are used to (un-)subscribe an address. They save the address list if it was modified by calling save() and return a boolean success value. Note that these methods are both synchronized (on the Servlet object) to ensure the integrity of the address list, both, in memory and on disk.

The save method serializes the address list to the address file on disk which can be read in again by init when the Servlet is restarted.

Client interaction

The client interaction is handled by two of the standard HttpServlet methods, doGet and doPost.

As usual, the Servlet extends javax.http.servlet.HttpServlet and overrides getServletInfo to provide a short notice. At last, here is the full source code of the ListManagerServlet:

ListManagerServlet.java

  1:  import java.util.Vector;
  2:  import java.io.*;
  3:  import javax.servlet.*;
  4:  import javax.servlet.http.*;
  5:  
  6:  public class ListManagerServlet extends HttpServlet
  7:  {
  8:    private Vector addresses;
  9:    private String filename;
 10:  
 11:    public void init(ServletConfig config) throws ServletException
 12:    {
 13:      super.init(config);
 14:      filename = config.getInitParameter("addressfile");
 15:      if(filename == null)
 16:        throw new UnavailableException(this,
 17:                                       "The \"addressfile\" property "+
 18:                                       "must be set to a file name");
 19:      try
 20:      {
 21:        ObjectInputStream in =
 22:          new ObjectInputStream(new FileInputStream(filename));
 23:        addresses = (Vector)in.readObject();
 24:        in.close();
 25:      }
 26:      catch(FileNotFoundException e) { addresses = new Vector(); }
 27:      catch(Exception e)
 28:      {
 29:        throw new UnavailableException(this,
 30:                                       "Error reading address file: "+e);
 31:      }
 32:    }
 33:  
 34:    protected void doGet(HttpServletRequest req,
 35:                         HttpServletResponse res)
 36:              throws ServletException, IOException
 37:    {
 38:      res.setContentType("text/html");
 39:      res.setHeader("pragma", "no-cache");
 40:      PrintWriter out = res.getWriter();
 41:      out.print("<HTML><HEAD><TITLE>List Manager</TITLE></HEAD>");
 42:      out.print("<BODY><H3>Members:</H3><UL>");
 43:      for(int i=0; i<addresses.size(); i++)
 44:        out.print("<LI>" + addresses.elementAt(i));
 45:      out.print("</UL><HR><FORM METHOD=POST>");
 46:      out.print("Enter your email address: <INPUT TYPE=TEXT NAME=email><BR>");
 47:      out.print("<INPUT TYPE=SUBMIT NAME=action VALUE=subscribe>");
 48:      out.print("<INPUT TYPE=SUBMIT NAME=action VALUE=unsubscribe>");
 49:      out.print("</FORM></BODY></HTML>");
 50:      out.close();
 51:    }
 52:  
 53:    protected void doPost(HttpServletRequest req,
 54:                          HttpServletResponse res)
 55:              throws ServletException, IOException
 56:    {
 57:      String email = req.getParameter("email");
 58:      String msg;
 59:      if(email == null)
 60:      {
 61:        res.sendError(res.SC_BAD_REQUEST,
 62:                      "No email address specified.");
 63:        return;
 64:      }
 65:      if(req.getParameter("action").equals("subscribe"))
 66:      {
 67:        if(subscribe(email))
 68:          msg = "Address " + email + " has been subscribed.";
 69:        else
 70:        {
 71:          res.sendError(res.SC_BAD_REQUEST,
 72:                        "Address " + email + " was already subscribed.");
 73:          return;
 74:        }
 75:      }
 76:      else
 77:      {
 78:        if(unsubscribe(email))
 79:          msg = "Address " + email + " has been removed.";
 80:        else
 81:        {
 82:          res.sendError(res.SC_BAD_REQUEST,
 83:                        "Address " + email + " was not subscribed.");
 84:          return;
 85:        }
 86:      }
 87:  
 88:      res.setContentType("text/html");
 89:      res.setHeader("pragma", "no-cache");
 90:      PrintWriter out = res.getWriter();
 91:      out.print("<HTML><HEAD><TITLE>List Manager</TITLE></HEAD><BODY>");
 92:      out.print(msg);
 93:      out.print("<HR><A HREF=\"");
 94:      out.print(req.getRequestURI());
 95:      out.print("\">Show the list</A></BODY></HTML>");
 96:      out.close();
 97:    }
 98:  
 99:    public String getServletInfo()
100:    {
101:      return "ListManagerServlet 1.0 by Stefan Zeiger";
102:    }
103:  
104:    private synchronized boolean subscribe(String email) throws IOException
105:    {
106:      if(addresses.contains(email)) return false;
107:      addresses.addElement(email);
108:      save();
109:      return true;
110:    }
111:  
112:    private synchronized boolean unsubscribe(String email) throws IOException
113:    {
114:      if(!addresses.removeElement(email)) return false;
115:      save();
116:      return true;
117:    }
118:  
119:    private void save() throws IOException
120:    {
121:      ObjectOutputStream out =
122:        new ObjectOutputStream(new FileOutputStream(filename));
123:      out.writeObject(addresses);
124:      out.close();
125:    }
126:  }

2.3  Session Tracking

This section shows how to
  • use Session Tracking capabilities

Session Tracking allows a Servlet to associate a request with a user. A session can extend across requests and connections of the stateless HTTP. Sessions can be maintained in two ways:

  1. By using Cookies. A Cookie is a string (in this case that string is the session ID) which is sent to a client to start a session. If the client wants to continue the session it sends back the Cookie with subsequent requests. This is the most common way to implement session tracking.

  2. By rewriting URLs. All links and redirections which are created by a Servlet have to be encoded to include the session ID. This is a less elegant solution (both, for Servlet implementors and users) because the session cannot be maintained by requesting a well-known URL oder selecting a URL which was created in a different (or no) session. It also does not allow the use of static pages. All HTML pages which are sent within a session have to be created dynamically. Our next Servlet manages a virtual shopping cart. Users can add various items to their shopping cart via HTML forms. The shopping cart contents are stored on the server and each users gets his own shopping cart which is selected automatically whenever he makes a request to the Servlet.

    In the simplified version that we implement in class ShoppingCartServlet there are only two kinds of items, named FOO and BAR. By pressing a button in an HTML form a single FOO or BAR item can be put into the shopping cart. There's another button to see the current contents of the shopping cart and a button to order the selected items, thus clearing the shopping cart.

    The first version of the Servlet, called ShoppingCartServlet, which works with Cookie-style sessions only, consists of the two standard methods, doGet and doPost:

    • A form with the buttons is created by the Servlet's doGet method.

      
       7:    protected void doGet(HttpServletRequest req, HttpServletResponse res)
       8:              throws ServletException, IOException
       9:    {
      10:      res.setContentType("text/html");
      11:      PrintWriter out = res.getWriter();
      12:      out.print("<HTML><HEAD><TITLE>Online Shop</TITLE>;"+
      13:                "</HEAD><BODY><FORM METHOD=POST>;"+
      14:                "<INPUT TYPE=SUBMIT NAME=foo VALUE="+
      15:                "\"Put a FOO into the shopping cart\">"+
      16:                "<INPUT TYPE=SUBMIT NAME=bar VALUE="+
      17:                "\"Put a BAR into the shopping cart\">"+
      18:                "<INPUT TYPE=SUBMIT NAME=see VALUE="+
      19:                "\"See the shopping cart contents\">"+
      20:                "<INPUT TYPE=SUBMIT NAME=buy VALUE="+
      21:                "\"Buy the shopping cart contents\">"+
      22:                "</FORM></BODY></HTML>");
      23:      out.close();
      24:    }
      

      Alternatively, it could also come from a static HTML page.

    • The doPost method processes the form data which is sent by a client in response to the form created by doGet.

      
      26:    protected void doPost(HttpServletRequest req, HttpServletResponse res)
      27:              throws ServletException, IOException
      28:    {
      29:      String msg;
      30:  
      31:      HttpSession session = req.getSession(true);
      32:      if(session.isNew())
      33:      {
      34:        session.putValue("foo", new int[] { 0 });
      35:        session.putValue("bar", new int[] { 0 });
      36:      }
      37:  
      38:      int[] foo = (int[])session.getValue("foo");
      39:      int[] bar = (int[])session.getValue("bar");
      40:  
      41:      if(req.getParameter("foo") != null)
      42:      {
      43:        foo[0]++;
      44:        msg = "Bought a FOO. You now have "+foo[0]+".";
      45:      }
      46:      else if(req.getParameter("bar") != null)
      47:      {
      48:        bar[0]++;
      49:        msg = "Bought a BAR. You now have "+bar[0]+".";
      50:      }
      51:      else if(req.getParameter("buy") != null)
      52:      {
      53:        session.invalidate();
      54:        msg = "Your order for "+foo[0]+" FOOs and "+bar[0]+
      55:          " BARs has been accepted. Your shopping cart is empty now.";
      56:      }
      57:      else
      58:      {
      59:        msg = "You have "+foo[0]+" FOOs and "+bar[0]+
      60:          " BARs in your shopping cart.";
      61:      }
      62:  
      63:      res.setContentType("text/html");
      64:      res.setHeader("pragma", "no-cache");
      65:      PrintWriter out = res.getWriter();
      66:      out.print("<HTML><HEAD><TITLE>Shopping Cart</TITLE></HEAD><BODY>");
      67:      out.print(msg);
      68:      out.print("<HR><A HREF=\"");
      69:      out.print(req.getRequestURI());
      70:      out.print("\">Back to the shop</A></BODY></HTML>");
      71:      out.close();
      72:    }
      

      First we get the HttpSession object which is associated with the request by calling req.getSession. The argument true forces the creation of a new session if the request doesn't contain a valid session key.

      Note: Although getSession is a method of HttpServletRequest and not of HttpServletResponse it may modify the response header and therefore needs to be called before an OutputStream or Writer is requested.

      If the session is indeed new (determined by calling HttpSession's isNew() method) we add some custom data to the session: Two counters, one for the FOOs and one for the BARs in the shopping cart. The session object can be used like a Dictionary. That means we can only add Objects, not instances of primitive types like int. We could use an instance of java.lang.Integer for each counter, but these objects are immutable which makes incrementing inefficient and difficult to implement. Instead we use an array of int (int[]) with only one element as a mutable wrapper object. The element is initialized to 0.

      Next we retrieve the values for "foo" and "bar" from the session, no matter if they were just added or carried over from a previous request.

      In the ListManagerServlet both buttons had the same name but different values so we could use getParameter to retrieve the value from the request and then do a string compare to the possible values. This time we use a different approach which can be implemented more efficiently. All buttons have different names and we can find out which button was used to submit the form by checking which name has a non-null value.

      A new FOO or BAR can be put into the shopping cart simply by incrementing the counter in the array. Note that the array does not need to be put back into the session because it has not changed itself, only the contents have been modified.

      When the user chooses to buy the contents of the shopping cart we call session.invalidate() to delete the session on the server side and tell the client to remove the session ID Cookie. The session data is lost and when a new POST request is made to the Servlet, a new session will be created.

      The rest of the doPost method is basically the same as in the ListManagerServlet.

    Here is the full source code of the ShoppingCartServlet:

    ShoppingCartServlet.java
    
     1:  import java.io.*;
     2:  import javax.servlet.*;
     3:  import javax.servlet.http.*;
     4:  
     5:  public class ShoppingCartServlet extends HttpServlet
     6:  {
     7:    protected void doGet(HttpServletRequest req, HttpServletResponse res)
     8:              throws ServletException, IOException
     9:    {
    10:      res.setContentType("text/html");
    11:      PrintWriter out = res.getWriter();
    12:      out.print("<HTML><HEAD><TITLE>Online Shop</TITLE>;"+
    13:                "</HEAD><BODY><FORM METHOD=POST>;"+
    14:                "<INPUT TYPE=SUBMIT NAME=foo VALUE="+
    15:                "\"Put a FOO into the shopping cart\">"+
    16:                "<INPUT TYPE=SUBMIT NAME=bar VALUE="+
    17:                "\"Put a BAR into the shopping cart\">"+
    18:                "<INPUT TYPE=SUBMIT NAME=see VALUE="+
    19:                "\"See the shopping cart contents\">"+
    20:                "<INPUT TYPE=SUBMIT NAME=buy VALUE="+
    21:                "\"Buy the shopping cart contents\">"+
    22:                "</FORM></BODY></HTML>");
    23:      out.close();
    24:    }
    25:  
    26:    protected void doPost(HttpServletRequest req, HttpServletResponse res)
    27:              throws ServletException, IOException
    28:    {
    29:      String msg;
    30:  
    31:      HttpSession session = req.getSession(true);
    32:      if(session.isNew())
    33:      {
    34:        session.putValue("foo", new int[] { 0 });
    35:        session.putValue("bar", new int[] { 0 });
    36:      }
    37:  
    38:      int[] foo = (int[])session.getValue("foo");
    39:      int[] bar = (int[])session.getValue("bar");
    40:  
    41:      if(req.getParameter("foo") != null)
    42:      {
    43:        foo[0]++;
    44:        msg = "Bought a FOO. You now have "+foo[0]+".";
    45:      }
    46:      else if(req.getParameter("bar") != null)
    47:      {
    48:        bar[0]++;
    49:        msg = "Bought a BAR. You now have "+bar[0]+".";
    50:      }
    51:      else if(req.getParameter("buy") != null)
    52:      {
    53:        session.invalidate();
    54:        msg = "Your order for "+foo[0]+" FOOs and "+bar[0]+
    55:          " BARs has been accepted. Your shopping cart is empty now.";
    56:      }
    57:      else
    58:      {
    59:        msg = "You have "+foo[0]+" FOOs and "+bar[0]+
    60:          " BARs in your shopping cart.";
    61:      }
    62:  
    63:      res.setContentType("text/html");
    64:      res.setHeader("pragma", "no-cache");
    65:      PrintWriter out = res.getWriter();
    66:      out.print("<HTML><HEAD><TITLE>Shopping Cart</TITLE></HEAD><BODY>");
    67:      out.print(msg);
    68:      out.print("<HR><A HREF=\"");
    69:      out.print(req.getRequestURI());
    70:      out.print("\">Back to the shop</A></BODY></HTML>");
    71:      out.close();
    72:    }
    73:  
    74:    public String getServletInfo()
    75:    {
    76:      return "ShoppingCartServlet 1.0 by Stefan Zeiger";
    77:    }
    78:  }
    

    Note that req.getSession is called for every POST request. It is important that the session object is requested on a regular basis because the Web Server may set a session timeout. Requesting the session ensures that the session's time-to-live is reset. The only reason for not calling req.getSession in the doGet method of this version of the Servlet is to make the response to doGet cachable.

    Adding Support for URL Rewriting

    To make the Servlet usable with URL rewriting (for clients without Cookie support or with Cookie support turned off) we have to make some modifications.

    The formerly static "Online Shop" page which is created by doGet needs to be modified to include an ACTION URL which contains an encoded session ID in the HTML form. This is done with the encodeUrl method of HttpServletResponse. We also need to call req.getSession to keep the session alive. The additional code to check for a new session can be avoided by using getSession(false). If there is no session or an invalid session we do not force the creation of a new session. This is deferred until the doPost method is called. Finally, the response has to be marked as not cachable by calling res.setHeader("pragma", "no-cache"), as usual.

    These changes lead us to the following revised implementation of doGet:

    
    protected void doGet(HttpServletRequest req, HttpServletResponse res)
              throws ServletException, IOException
    {
      HttpSession session = req.getSession(false);
      res.setContentType("text/html");
      res.setHeader("pragma", "no-cache");
      PrintWriter out = res.getWriter();
      out.print("<HTML><HEAD><TITLE>Online Shop</TITLE>;"+
                "</HEAD><BODY><FORM METHOD=POST ACTION=;"+
      out.print(res.encodeUrl(req.getRequestURI()));
      out.print("><INPUT TYPE=SUBMIT NAME=foo VALUE="+
                "\"Put a FOO into the shopping cart\">"+
                "<INPUT TYPE=SUBMIT NAME=bar VALUE="+
                "\"Put a BAR into the shopping cart\">"+
                "<INPUT TYPE=SUBMIT NAME=see VALUE="+
                "\"See the shopping cart contents\">"+
                "<INPUT TYPE=SUBMIT NAME=buy VALUE="+
                "\"Buy the shopping cart contents\">"+
                "</FORM></BODY></HTML>");
      out.close();
    }
    

    The doPost method requires only a minor change. The Servlet's URI which is used to link back to the "Online Shop" page needs to be encoded with res.encodeUrl, as in the doGet method.

    The full source code of the revised Shopping Cart Servlet is available as ShoppingCartServlet2.java.

    3.  Advanced Topics

    This chapter shows how to use features and techniques that have not been explained and used in the example Servlets of the previous chapter.

    3.1  Not Thread-Safe Servlets

    If a Servlet's service method (or one of the doXxx methods for subclasses of HttpServlet) cannot be implemented in a thread-safe manner, the Servlet can declare that it implements the javax.servlet.SingleThreadModel interface which guarantees that the service method is not called concurrently. This interface does not contain any methods but simply acts as a flag which indicates that the Servlet is not thread-safe:

    
     1:  public class NotThreadSafeServlet extends HttpServlet
     2:         implements SingleThreadModel
     3:  {
     4:    protected void doGet(HttpServletRequest req,
     5:                         HttpServletResponse res)
     6:              throws ServletException, IOException
     7:    {
     8:      // This method is called from HttpServlet's service method.
     9:      // Thus it will never be called concurrently.
    10:    }
    11:  }
    

    If a server gets concurrent requests for such a Servlet it could e.g. serialize the calls or create multiple instances of the Servlet which form a Servlet pool. The SingleThreadModel interface was added in JSDK 2.0. Not thread-safe Servlets which have to run with JSDK 1.0 can use the synchronized keyword to ensure that a method does not run concurrently:

    
     1:  public class NotThreadSafeServlet extends HttpServlet
     2:  {
     3:    protected synchronized void doGet(HttpServletRequest req,
     4:                                      HttpServletResponse res)
     5:              throws ServletException, IOException
     6:    {
     7:      // This method is synchronized on the Servlet object.
     8:      // Thus it will never be called concurrently.
     9:    }
    10:  }
    

    3.2  Client-Side Data with Cookies

    We've already used Cookies indirectly through Sessions. While session data is stored on the server side and only an index to the server-side data (the session ID) is transmitted to the client, Cookies can also be used directly to store data on the client side.

    The Servlet API provides the class javax.servlet.http.Cookie for a convenient object-oriented representation of Cookies so you don't need to compose and decompose Cookie and Set-Cookie HTTP headers yourself.

    Even if you don't care where the data is stored it is sometimes useful to manipulate Cookies directly via the Cookie class to get more control over Cookie parameters like Domain and Path, e.g. to share Cookies between different Servlets or even servers.

    Example. Imagine an authentication Servlet which receives a username and password from a login form via doPost and verifies them against a central authentication database. It then computes an authentiation string (e.g. a Base64-encoded "user:password" combination, as used by HTTP Basic Authentication). This string is now put into a Cookie and sent back to the client:

    
    Cookie authCookie = new Cookie("xyz-Auth", credentials);
    authCookie.setVersion(1);
    authCookie.setDomain(".xyz.com");
    res.addCookie(authCookie);
    

    The Cookie's domain is set to ".xyz.com" so it will be sent to all hosts in domain "xyz.com" like "a.xyz.com" and "foo.xyz.com" (but not "c.d.xyz.com"). Note that the Domain attribute is supported by RFC2109-style Cookies (version 1) but not by old Netscape Cookies (version 0, the default for newly created Cookie objects).

    All Web Servers on hosts in xyz.com are running an instance of another Servlet which serves protected data after verifying the authentication credentials:

    
    boolean verified = false;
    Cookie[] cookies = req.getCookies();
    
    for(int i=0; i<cookies.length; i++)
    {
      String n = cookies[i].getName(),
             d = cookies[i].getDomain();
      if(n != null && n.equals("xyz-Auth") &&
         d != null && d.equals(".xyz.com"))
      {
        String credentials = cookies[i].getValue();
        verfied = verifyCredentials(credentials);
        break;
      }
    }
    
    if(!verified)
    {
      res.sendRedirect(...);
      return;
    }
    

    The credentials are retrieved from the Cookie and verified by the authentication database. If the credentials are invalid or missing the client is redirected to the login page on the authentication server, otherwise the protected content is returned.

    3.3  Inter-Servlet Communication

    Servlets are not alone in a Web Server. They have access to other Servlets in the same Servlet Context (usually a Servlet directory), represented by an instance of javax.servlet.ServletContext. The ServletContext is available through the ServletConfig object's getServletContext method.

    A Servlet can get a list of all other Servlets in the Servlet Context by calling getServletNames on the ServletContext object. A Servlet for a known name (probably obtained through getServletNames) is returned by getServlet. Note that this method can throw a ServletException because it may need to load and initialize the requested Servlet if this was not already done.

    After obtaining the reference to another Servlet that Servlet's methods can be called. Methods which are not declared in javax.servlet.Servlet but in a subclass thereof can be called by casting the returned object to the required class.

    Note that in Java a class is not only defined by the class name but also by the ClassLoader by which it was loaded. Web servers usually load each Servlet by a different class loaders. This is necessary to reload Servlets on the fly because single classes cannot be replaced in the running JVM. Only a ClassLoader with all loaded classes can be replaced.

    This means that classes which are loaded by a Servlet class loader cannot be used for inter-Servlet communication. A class literal FooServlet (as used in a type cast like "FooServlet foo = (FooServlet)context.getServlet("FooServlet")") which is used in class BarServlet is different from the class literal FooServlet as used in FooServlet itself.

    A way to overcome this problem is using a superclass or an interface which is loaded by the system loader and thus shared by all Servlets. In a Web Server which is written in Java those classes are usually located in the class path (as defined by the CLASSPATH environment variable).

    Example. Servlet FooServlet wants to call the method public void bar() of Servlet BarServlet. Both Servlets should be reloadable so the Servlet classes cannot be loaded by the system loader. Instead we define an interface BarInterface which defines the callable method and is loaded by the system loader. BarServlet implements this interface. The Servlets are placed into the Servlet directory and the interface into a directory in the class path.

    
     1:  public class FooServlet extends HttpServlet
     2:  {
     3:    protected void doGet(HttpServletRequest req,
     4:                         HttpServletResponse res)
     5:              throws ServletException, IOException
     6:    {
     7:      ...
     8:      ServletContext context = getServletConfig().getServletContext();
     9:      BarInterface bar = (BarInterface)context.getServlet("BarServlet");
    10:      bar.bar();
    11:      ..
    12:    }
    13:  
    14:    ...
    15:  }
    
    
    1: public interface BarInterface 2: { 3: public void bar(); 4: }
    1: public class BarServlet extends HttpServlet implements BarInterface 2: { 3: public void bar() 4: { 5: System.err.println(""bar() called""); 6: } 7: 8: ... 9: }

    3.4  Creating Images with a Servlet

    A Servlet can not only return HTML pages but any kind of text or binary data. Apart from creating dynamic HTML pages and returning data which is stored on disk another common use for Servlets is creating dynamic images. The images can use any format for which an encoder in Java is available and which the Web Clients understand.

    A rather inefficient but common and easy to create image format is XPM. XPM stores uncompressed pixel data in an ASCII file which can also be included in a C program. Here is a simple XPM image:

    x.xpm
    
     1:  /* XPM */
     2:  static char * x_xpm[] = {
     3:  "17 13 2 1",
     4:  " <TAB>c #000000",
     5:  "X<TAB>c #FFFFFF",
     6:  "XXXXX       XXXXX",
     7:  " XXXXX     XXXXX ",
     8:  "  XXXXX   XXXXX  ",
     9:  "   XXXXX XXXXX   ",
    10:  "    XXXXXXXXX    ",
    11:  "     XXXXXXX     ",
    12:  "      XXXXX      ",
    13:  "     XXXXXXX     ",
    14:  "    XXXXXXXXX    ",
    15:  "   XXXXX XXXXX   ",
    16:  "  XXXXX   XXXXX  ",
    17:  " XXXXX     XXXXX ",
    18:  "XXXXX       XXXXX"};
    

    The numbers in line 3 are the image width, image height, number of colors and characters per pixel. Lines 4 and 5 define the colors for the two characters " " (space) and "X". The rest of the array contains the image data, one character per pixel.

    The following Servlet dynamically creates an XPM image like this one. You can specify a "color" attribute in the URL for the "X" (e.g. http://localhost:22722/servlet/XpmServlet?COLOR=FF0000 for a red "X"). If the attribute is omitted the default "FFFFFF" (white) is used.

    XpmServlet.java
    
     1:  import java.io.*;
     2:  import javax.servlet.*;
     3:  import javax.servlet.http.*;
     4:  
     5:  public class XpmServlet extends HttpServlet
     6:  {
     7:    protected void doGet(HttpServletRequest req,
     8:                         HttpServletResponse res)
     9:              throws ServletException, IOException
    10:    {
    11:      String color = req.getParameter("color");
    12:      if(color == null) color = "FFFFFF";
    13:  
    14:      res.setContentType("image/x-pixmap");
    15:      res.setHeader("pragma", "no-cache");
    16:  
    17:      ServletOutputStream out = res.getOutputStream();
    18:      out.print(";/* XPM */\n"+
    19:                ";static char * hello_xpm[] = {\n"+
    20:                ";\"17 13 2 1\",\n"+
    21:                ";\" \tc #000000\",\n"+
    22:                ";\"X\tc #"+color+"\",\n"+
    23:                ";\"XXXXX       XXXXX\",\n"+
    24:                ";\" XXXXX     XXXXX \",\n"+
    25:                ";\"  XXXXX   XXXXX  \",\n"+
    26:                ";\"   XXXXX XXXXX   \",\n"+
    27:                ";\"    XXXXXXXXX    \",\n"+
    28:                ";\"     XXXXXXX     \",\n"+
    29:                ";\"      XXXXX      \",\n"+
    30:                ";\"     XXXXXXX     \",\n"+
    31:                ";\"    XXXXXXXXX    \",\n"+
    32:                ";\"   XXXXX XXXXX   \",\n"+
    33:                ";\"  XXXXX   XXXXX  \",\n"+
    34:                ";\" XXXXX     XXXXX \",\n"+
    35:                "\"XXXXX       XXXXX\"};\n");
    36:      out.close();
    37:    }
    38:  }
    

    After retrieving the "color" parameter, the content type ("image/x-pixmap" for XPM images) and the usual "pragma: no-cache" header are set. Then a ServletOutputStream is requested for writing the response body. We have to use getOutputStream instead of getWriter because we are writing image (binary) data and not text (even though XPM is using a text format). The ServletOutputStream's print(String) method converts the Unicode String to ISO-8859-1 (an 8-bit superset of the 7-bit ASCII format which is used by XPM).

    3.5  User Authentication via Sessions

    We've already seen session that were created implicitly in the ShoppingCartServlet. The sessions were created as needed and only used to identify an anonymous user.

    Sessions can also be used for authentication. In contrast to HTTP Basic Authentication a session can be invalidated which enables users to log out without quitting the Web Browser (which is required with Basic Authentication because there is no way to force a browser to delete the authentication credentials).

    The following SessionAuthServlet shows how to do authentication with a Servlet. The doPost method processes requests to log in or out. sendPage is called by both, doGet and doPost.

    SessionAuthServlet.java
    
     1:  import java.io.*;
     2:  import javax.servlet.*;
     3:  import javax.servlet.http.*;
     4:  
     5:  public final class SessionAuthServlet extends HttpServlet
     6:  {
     7:    protected void doGet(HttpServletRequest req, HttpServletResponse res)
     8:              throws ServletException, IOException
     9:    {
    10:      sendPage(req, res, req.getSession(false));
    11:    }
    12:  
    13:    protected void doPost(HttpServletRequest req, HttpServletResponse res)
    14:              throws ServletException, IOException
    15:    {
    16:      if(req.getParameter("login") != null)
    17:      {
    18:        HttpSession session = req.getSession(true);
    19:        String name = req.getParameter("name");
    20:        if(name == null || name.length()==0) name = "Anonymous";
    21:        session.putValue("name", name);
    22:        sendPage(req, res, session);
    23:      }
    24:      else
    25:      {
    26:        HttpSession session = req.getSession(false);
    27:        if(session != null) session.invalidate();
    28:        sendPage(req, res, null);
    29:      }
    30:    }
    31:  
    32:    private void sendPage(HttpServletRequest req, HttpServletResponse res,
    33:                          HttpSession session)
    34:            throws ServletException, IOException
    35:    {
    36:      res.setContentType("text/html");
    37:      res.setHeader("pragma", "no-cache");
    38:      PrintWriter o = res.getWriter();
    39:      o.print("<HTML><HEAD><TITLE>SessionAuthServlet</TITLE></HEAD><BODY>");
    40:      if(session == null)
    41:        o.print("<FORM METHOD=POST>Please enter your name: "+
    42:                "<INPUT TYPE=TEXT NAME=\"name\">"+
    43:                "<INPUT TYPE=SUBMIT NAME=\"login\" VALUE=\"Log in\">"+
    44:                "</FORM></BODY></HTML>");
    45:      else
    46:        o.print("Hi " + session.getValue("name") +
    47:                "<P><FORM METHOD=POST><INPUT TYPE=SUBMIT NAME=\"logout\" "+
    48:                "VALUE=\"Log out\"></FORM></BODY></HTML>");
    49:      o.close();
    50:    }
    51:  }
    

    3.6  Servlets and Hyperlinks

    When a Servlet creates an HTML form which contains links you have to make sure that you link to the right directory.

    Example. The following document is mounted on a server as /shop/preview/form.html:

    
    <HTML><HEAD><TITLE>Form</TITLE></HEAD><BODY>
    <FORM ACTION="/servlet/PreviewServlet" METHOD=GET>
    ...
    </FORM>
    </BODY></HTML>
    

    There are also product images in the same directory, e.g. /shop/preview/foo.gif, /shop/preview/bar.gif, ...

    The Servlet which is mounted as /servlet/PreviewServlet evaluates the form data and creates an HTML page with IMG tags to the appropriate product images, e.g. <IMG SRC="foo.gif">. But this naive approach does not work. The images are in the same directory as the form but the page is created by the Servlet, so the client sees it as /servlet/PreviewServlet. The base name of this URL is /servlet/, thus the image foo.gif from the above image tag is expected at /servlet/foo.gif and not /shop/preview/foo.gif. If /servlet/ is a pure Servlet directory it can only contain Servlets and no other data (like GIF images). It is therefore not possible to move the images to the Servlet directory (/servlet/). Instead the image tags need to be modified to include the full path to the images, relative to the server root, i.e. <IMG SRC="/shop/preview/foo.gif"> for the foo.gif image.

    Appendix

    A.  Compiling Servlets

    Servlets can be compiled with the JDK (available from
    http://www.javasoft.com) and other Java compilers, just like Applets and Applications. If the Servlet packages javax.servlet and javax.servlet.http are not included with the compiler you have to install them separately by downloading the JSDK (Java Servlet Development Kit) from http://java.sun.com/products/java-server/servlets/index.html#sdk and including the JSDK classes in your CLASSPATH environment variable.

    Example. Compiling the HelloClientServlet on a Unix machine with a JDK installation and the JSDK 2.0 unpacked in /usr/local/JSDK2.0:

    
    $ echo $CLASSPATH
    .:/usr/local/jdk/lib/classes.zip
    $ export CLASSPATH=$CLASSPATH:/usr/local/JSDK2.0/lib/jsdk.jar
    $ echo $CLASSPATH
    .:/usr/local/jdk/lib/classes.zip:/usr/local/JSDK2.0/lib/jsdk.jar
    $ ls HelloClientServlet.*
    HelloClientServlet.java
    $ javac HelloClientServlet.java
    $ ls HelloClientServlet.*
    HelloClientServlet.class HelloClientServlet.java
    $ |
    

    B.  Running the Example Servlets with NetForge

    The Servlets from this tutorial can be tested easily with the
    NetForge Web Server. The server has a preconfigured Servlet directory netforge/servlets/. Compile the servlets and copy the *.class files to netforge/servlets/. When the server is running, the Servlets can be accessed via http://localhost:22722/servlet/ClassName, e.g. http://localhost:22722/servlet/HelloClientServlet for the HelloClientServlet.

    Example. Copying the compiled HelloClientServlet to the NetForge Servlet directory and starting the server on a Unix machine with NetForge installed in /usr/local/netforge/:

    
    $ cp HelloClientServlet.class /usr/local/netforge/servlets/
    $ /usr/local/netforge/bin/netforge -Dnetforge.initialgui=true
    [22 Jun 1998 9:39:05 GMT] <note> Starting up NetForge 0.38
    [22 Jun 1998 9:39:05 GMT] <note> Home directory is "/usr/local/netforge/."
    [22 Jun 1998 9:39:06 GMT] <note> Security manager installed
    [22 Jun 1998 9:39:12 GMT] <note> HTTP server "Example Web" started on port 22722
    [22 Jun 1998 9:39:28 GMT] <note> Local administration GUI opened
    [22 Jun 1998 9:39:28 GMT] <note> Startup completed
    

    The ListManagerServlet requires an init parameter, so it has to be accessed through an alias:

    1. Start NetForge with the argument "-Dnetforge.initialgui=true" (as shown above) to open an administration GUI.

    2. Use the popup menu which is attached to the "/servlet/" object to open a configuration window.

    3. Enter the line "listman ListManagerServlet addressfile=/tmp/addresses" into the "Aliases" field.

    4. Click the "Apply" button to perform the configuration change.

    The ListManagerServlet can now be accessed via http://localhost:22722/servlet/listman. The addresses are written to the file /tmp/addresses.

    See the NetForge manual for details on configuring and running the server.

    C.  Servlet Resources on the Web

    The Servlet home page on the Javasoft website
    http://jserv.javasoft.com/products/java-server/servlets/

    A list of Servlet engines and Servlet-enabled Web Servers
    http://jserv.javasoft.com/products/java-server/servlets/environments.html

    The JSDK download page
    http://java.sun.com/products/java-server/servlets/index.html#sdk

    Sun offers packages for Windows and Solaris/Sparc. The JSDK does not contain any native code so it can be used on other platforms as well. You should download the Solaris version for all platforms other than Windows.


    This document is copyrighted © by Stefan Zeiger. You may make an archival copy for personal use. Apart from that it may not be redistributed in part or whole, in electronical, printed or any other form without the author's prior consent.

    Java is a trademark of Sun Microsystems, Inc. Netscape is a registered trademark of Netscape Communications Corporation. All other trademarks mentioned belong to their respective owners.

    The latest version of this document can be found at http://www.novocode.com/doc/servlet-essentials/.
    Please send feedback to szeiger@novocode.com.