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