novocode.com
About Novocode.com  |  Contact  |  Site Map  |  Search

Need more information? Shop for books about Servlets at Amazon.com:

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.

[API 2.1] Version 2.1 of the Servlet API offers a no-args init method which is called by GenericServlet's init(ServletConfig) method. By using this new method you don't have to worry about passing the ServletConfig object to the superclass yourself.

Note that even though code that uses the no-args init method can be compiled without problems using the JSDK 1.0 or 2.0 interface classes and run in a 1.0 or 2.0 compliant web server, the initialization code will never be executed in such an environment.

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.

  • The doGet method replies to GET requests by sending an HTML page which contains the list of the currently subscribed addresses and the form that is used to subscribe or unsubscribe an address:

     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: }
    

    The response content type is again set to text/html and the response is marked as not cacheable to proxy servers and clients (because it is dynamically created) by setting an HTTP header "pragma: no-cache". The form asks the client to use the POST method for submitting form data.

    Here is a typical output by this method:

A browser window showing the "Members" page
  • The doPost method receives the submitted form data, updates the address list and sends back a confirmation page:

     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:    }
    

    First the form parameters "email" and "action" are retrieved with the getParameter method of HttpServletRequest. getParameter (and also getParameters and getParameterValues) can be used to retrieve form data from both, POST and GET requests. As an alternative you can use getQueryString for a GET request and getInputStream for a POST request and parse the application/x-www-urlencoded data on your own. Note that you cannot use both ways of getting the request data together in one request.

    Then subscribe or unsubscribe is called. When a user error occurs (i.e. no address or an already subscribed address was entered for subscribe, or a not subscribed address was entered for unsubscribe) res.sendError is used to send back an error response with a Bad Request response code.

    Finally a confirmation page is sent with the usual method. req.getRequestURI() is used to get the URI of the Servlet for a link back to the main page (which is created by doGet).

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:

  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:  }