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

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

2.3 Session Tracking [API 2.0]

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 user 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 a ServletOutputStream or PrintWriter 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 item can be put into the shopping cart by simply 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:

 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.

[API 2.1] Beginning with version 2.1 of the Servlet API, the abbrevation URL is consistently spelled in upper case in all method names. The old method names have been deprecated but are still supported in 2.1. The method encodeUrl which is used in the shopping cart Servlet should be replaced by encodeURL when writing a 2.1-compliant Servlet.

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