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