|
This chapter shows how to use features and techniques that have not been
explained and used in the example Servlets of the previous chapters. The
examples in this chapter are often only fragments of Java source code and not
necessarily complete Servlets.
If a Servlet's service method (or one of the do... methods for subclasses of
HttpServlet) cannot be implemented in a thread-safe manner, the Servlet can declare
that it implements the javax.servlet.SingleThreadModel [API
2.0] 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: 9: 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: 8: 9: } 10: }
If you need to manage a lot of temporary data for a request, it is cumbersome
to pass references to a lot of objects to all methods. This also makes it more
difficult to introduce new data because you have to change all method
signatures to include a reference to the new object.
One way to solve this problem is implementing the SingleThreadModel interface as shown in
the
previous
section. This, however, can be inefficient if the server does not create a
Servlet pool to allow concurrent access to multiple instances of the Servlet.
A usually more efficient solution makes use of a Command object:
1: import java.io.*; 2: import javax.servlet.*; 3: import javax.servlet.http.*; 4: 5: public class CommandServlet extends HttpServlet 6: { 7: interface Command 8: { 9: public void execute() 10: throws ServletException, IOException; 11: } 12: 13: protected void doGet(HttpServletRequest req, 14: HttpServletResponse res) 15: throws ServletException, IOException 16: { 17: final PrintWriter out = res.getWriter(); 18: 19: Command c = new Command(){ 20: 21: private boolean lineBreak = true; 22: 23: public void execute() 24: throws ServletException, IOException 25: { 26: out.println("<HTML><BODY>"); 27: printLink("foo.html"); 28: printLink("bar.html"); 29: out.println("</BODY></HTML>"); 30: } 31: 32: private void printLink(String s) 33: throws IOException 34: { 35: out.print("<A href=\"" + s + "\">" + s + "</A>"); 36: if(lineBreak) out.print("<BR>"); 37: } 38: }; 39: 40: c.execute(); 41: } 42: }
The nested Command interface provides an execute method which can be implemented
by a sub-type to contain the code that should be executed for a request. This
is done by the anonymous class which is defined in the Servlet's doGet method
(lines 19 to 38).
For each request, the Servlet creates an instance of the anonymous class (line
19) and calls its execute method (line 40). The class has access to all
final variables of the containing doGet method and can also define
own variables (like lineBreak in line 21).
The implementation of the execute method contains the code that would normally
be placed into doGet. The execute method can now call the printLink method without
explicitly passing the variables out and lineBreak to it.
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.
|