Richard G Baldwin (512) 223-4758, baldwin@austin.cc.tx.us, http://www2.austin.cc.tx.us/baldwin/

Servlets, Session Tracking Using Cookies

Java Programming, Lecture Notes # 693, Revised 3/17/99.

Preface

Introduction

The Java Cookie Class

Sample Program

Interesting Code Fragments

Program Listing


Preface

Students in Prof. Baldwin's Advanced Java Programming classes at ACC will be responsible for knowing and understanding all of the material in this lesson beginning with the spring semester of 1999.

This lesson was originally written on March 17, 1999.

The sample servlet in this lesson was tested using Win95, Netscape Navigator, the JDK 1.2 download package from JavaSoft, the JSDK 2.0 download package from JavaSoft, and the servletrunner program that is included in the JSDK. The sample program was tested using servletrunner both from the same machine, and from a different machine on the network.

Introduction

This is one of a series of lessons designed to show you some of the ways to implement session tracking using servlets. The purpose of the sample program in this lesson is to illustrate session tracking through the use of cookies.

Cookies are information stored on the client machine by the browser. Under certain conditions, this information is sent to the HTTP server whenever a request is sent from the client to the server

Cookies are widely used by various server-side programming techniques for the purpose of implementing session tracking. Java provides classes and methods designed to allow you to use cookies in your servlets.

By default, cookies are sent only to the host that caused them to be saved. Methods can be invoked to set attributes for each cookie that determine which servers will receive the cookie. For example, the setDomain() method can be used to specify a domain pattern indicating which servers should receive a cookie.

By default, cookies are sent to the page that set the cookie and to all the pages in that directory or under that directory. This also can be modified by invoking the setPath() method on the cookie.

Also by default, a cookie expires when the browser exits. The setMaxAge() method can be invoked to override the default and specify the maximum age of the cookie in seconds. The cookie will expire when the maximum age has been reached. The parameter to this method is an int so the maximum age of a cookie can be a very large number of seconds.

The setComment() method can be used to provide a comment with the cookie. The browser may elect to make this information available to the user.

The constructor sets the name and value of a cookie when the Cookie object is instantiated. The setValue() method can be used to assign a new value to the cookie after it is instantiated.

There are a few other attributes that can be set as well. You can read about them in the JavaSoft JSDK documentation.

CAUTION: The user can disable cookies in most, and perhaps all browsers. My Netscape Navigator browser provides the following options regarding cookies:

In addition, my browser can be set to warn me before accepting a cookie.

Therefore, unless you can be certain that all of your clients will operate with cookies enabled, the use of cookies for session tracking may not be satisfactory in all cases.

The Java Cookie Class

The Cookie class represents a cookie that can be used for session management with the HTTP protocol.

Cookies are used to cause user agents such as web browsers to store small amounts of state associated with a user's web browsing activities.

Common applications for cookies include:

Each cookie has a name and a single value. As mentioned earlier, it may have optional attributes, including:

Servers assign cookies, using fields added to HTTP response headers.

In the Java API, cookies are saved one at a time into such HTTP response headers, using the addCookie() method.

Browsers are expected to support twenty cookies per host, of at least four kilobytes each.

Cookies are passed from the browser to the server using fields added to HTTP request headers.

In the Java API, HTTP cookies are retrieved using the getCookies() method. This method returns all of the cookies found in the request sent by the client.

A description of the constructor for a Cookie object is shown below.

public Cookie(String name, String value)

Defines a cookie with an initial name/value pair. The name must be an HTTP/1.1 "token" value; alphanumeric ASCII strings work. Names starting with a "$" character are reserved by RFC 2109.

Parameters:

  • name - name of the cookie
  • value - value of the cookie

 

Sample Program

The name of the servlet is Servlet09. Each time the servlet is invoked, it displays an html form on the client screen. The form contains:

The first time the servlet is invoked, it creates a unique session ID and stores it in a cookie on the browser. This session ID is not used for any purpose in this sample program. It is included in the servlet simply to show how to create and save a unique session ID.

Each time the servlet is invoked, it creates a cookie containing the field value submitted by the user and stores it on the browser. Netscape Navigator will only store about 20 cookies. Beyond this number, older cookies are deleted when new cookies are added.

When the servlet is invoked, it gets all of the cookie information stored on the browser and displays that information on the client screen. The display should include the unique session ID and all of the field values submitted by the user during that session.

By default, the cookie information is deleted when the browser is exited. As discussed earlier, other possibilities regarding the maximum age of the cookie are available as well. It is important to note that a session doesn't end just because the user visits another page. If the user returns to the Servlet09 servlet without exiting the browser, the cookies written by Servlet09 continue to exist on the browser and the session continues.

Interesting Code Fragments

The first fragment shows the beginning of the controlling class and the beginning of the doGet() method. You have seen this before, so I won't discuss it further.

public class Servlet09 extends HttpServlet{
  public void doGet(HttpServletRequest req, 
                    HttpServletResponse res)
                      throws ServletException, IOException{

The next fragment constructs a unique session ID that is later written into a cookie on the browser.

The first step is to get a String representation of a UID object. According to JavaSoft, the UID class is an "Abstraction for creating identifiers that are unique with respect to the host on which it is generated."

    String uid = new java.rmi.server.UID().toString();

    String sessionID = java.net.URLEncoder.encode(uid);

The second step is to invoke the encode() method of the URLEncoder class to convert the string into a MIME format called the "x-www-form-urlencoded" format. This ensures that the identifier can be reliably transmitted between the server and the client and that it will contain only those characters that are acceptable for saving in a cookie (see documentation on the setValue() method for more information).

To convert the String, each character is examined and modified as follows:

A typical session identifier created by this process might appear as follows:

1cce64%3Ad69878ccf0%3A-7ff9

The next fragment uses the getCookies() method of the incoming HttpServletRequest object to get and save the cookies submitted by the browser. The values of the cookies will be displayed later.

    Cookie[] cookies = req.getCookies();

As in previous sample programs, the next fragment gets and saves the field value submitted by the client.

    String name = req.getParameter("firstName");

The next step establishes the type of output. You have seen this many times before, so I won't comment further on this step.

    res.setContentType("text/html");

Because cookies are sent to the browser using HTTP headers, they should be added to the response before you send any content.

If no cookies were submitted by the client with the request, this is interpreted by this program to be the beginning of the session. A new Cookie object is instantiated containing the session ID value created above along with the name of the cookie: sessionID.

Then the cookie is sent to the client's browser by invoking the addCookie() method on the outgoing HttpServletResponse object.

    if(cookies == null){
      Cookie newCookie = new Cookie("sessionID",sessionID);
      res.addCookie(newCookie);
    }//end if

The next fragment gets an output stream. You have seen this many times before, so I won't comment further.

    PrintWriter out = res.getWriter();

The next fragment instantiates a cookie containing the field value submitted by the client and sends that cookie back to the browser for storage.

Unless delineated by path information, each cookie needs a unique name in addition to its value. Assuming that successive invocations of the servlet will be separated in time by at least one millisecond, unique names can be created by getting the current date and time in milliseconds. That mechanism for creating unique cookie names was used in this sample program. The getTime() method of the Date class returns the date and time in milliseconds represented by a Date object.

    if(name != null){
      String cookieName = "" + new Date().getTime();
      Cookie newCookie = new Cookie(cookieName, name);
      res.addCookie(newCookie);
    }//end if

If you compile and run this program with Netscape Navigator, you will probably notice that after about 20 cookies are added, Navigator starts deleting the oldest cookie when each new cookie is added.

Any scheme for session tracking that uses cookies must take this limitation into account.

Perhaps more important, any scheme that implements session tracking using cookies must take into account the fact that the user can disable the storage of cookies on most browsers. As near as I can tell, the servlet receives no warning that cookie processing has been disabled on the browser and that cookies being sent to the browser are not being saved.

(However, some web applications warn me to enable cookies so there must be some way to detect that cookies are disabled.)

The next fragment contains code that is very similar to code discussed in a previous lesson, so I won't discuss it further here. The fragment is being included here simply for continuity.

    //Construct an HTML form and send it back to the client
    out.println("<HTML>");
    out.println("<HEAD><TITLE>Servlet09</TITLE></HEAD>");
    out.println("<BODY>");
        
    //Substitute the name of your server or localhost in
    // place of baldwin in the following statement.
    out.println("<FORM METHOD=GET ACTION="
           + "\"http://baldwin:8080/servlet/Servlet09\">");
    out.println("Enter a name and press the button<P>");
    out.println("Name: <INPUT TYPE=TEXT NAME="
                                    + "\"firstName\"><P>");
    out.println("<INPUT TYPE=submit VALUE="
                                     + "\"Submit Name\">");
    out.println(
      "<BR><BR>Your session ID and list of names is:<BR>");
    if(name == null){
      out.println("Empty<BR>");
    }//end if

The next fragment uses the getValue() method of the Cookie class to get and display the values of each of the cookies saved earlier in the array of cookies. The first value displayed should be the session ID that was stored in the first cookie saved. The remaining items in the list should be the field input values previously submitted by the user each time the servlet was invoked.

    if(cookies != null){
      for(int i = 0; I < cookies.length; i++){
        out.println(cookies[i].getValue() + "<BR>");
      }//end for loop
    }//end if

The next fragment displays the field value submitted by the user for the current invocation of the servlet. This value is displayed at the end of the list.

    if(name != null){
      out.println(name + "<BR>");
    }//end if

A text version of a typical screen output following several invocations of the servlet is shown below. Note that the graphic input text field and the graphic button have been replaced by <comments>.

Enter a name and press the button

Name: <Input Field Appears Here>

<Submit Button Appears Here>

Your session ID and list of names is:
1cce64%3Ad69878ccf0%3A-8000
Tom
Dick
Harry
Bill
Sue
Mary
Alice

The remainder of the code is typical of what you have seen before and can be viewed in the complete listing of the servlet in the next section.

Program Listing

A complete listing of the program follows.

/*File Servlet09.java, Copyright 1999, R.G.Baldwin
Rev 3/17/99
The purpose of this program is to illustrate session
tracking through the use of cookies.

Each time the servlet is invoked, it displays an html
form on the client screen.  The form contains:
  An input field for submitting a name
  A submit button
  A list of previously submitted names
  
The first time the servlet is invoked, it creates a
unique session ID and stores it in a cookie on the browser.

Each time the servlet is invoked, it creates a cookie
containing the name submitted by the user and stores it
on the browser.  Netscape will only store about 20 cookies
before beginning to delete the older cookies.

The servlet was tested using the JDK 1.2 download package
from JavaSoft along with the Java Servlet Development Kit
(JSDK) 2.0 from JavaSoft. All tests were performed 
under Win95 using a Netscape browser.

The servlet was tested using the servletrunner program that
is included in the JSDK with the servletrunner program 
running on the same machine and also with the servletrunner
program running on a different machine on the network. 


**********************************************************/
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class Servlet09 extends HttpServlet{
  public void doGet(HttpServletRequest req, 
                    HttpServletResponse res)
                      throws ServletException, IOException{
                        
    //Get a unique ID to be used to construct a session ID
    String uid = new java.rmi.server.UID().toString();
    //Encode any special characters that may be in the uid
    // to construct the session ID
    String sessionID = java.net.URLEncoder.encode(uid);

    //Get and save the cookies submitted with the request
    Cookie[] cookies = req.getCookies();
    
    //Get the submitted name for the current GET request
    String name = req.getParameter("firstName");

    //Establish the type of output
    res.setContentType("text/html");
    
    //If no cookies were submitted with the request, 
    // create and add a cookie containing the session ID
    if(cookies == null){
      Cookie newCookie = new Cookie("sessionID",sessionID);
      res.addCookie(newCookie);
    }//end if
    
    //Get an output stream
    PrintWriter out = res.getWriter();

    //Add a cookie with the new name value.  Use the
    // current date/time in milliseconds as a unique
    // name for the cookie.  Note that Netscape will
    // save only about 20 cookies before deleting the
    // older cookies.
    if(name != null){
      String cookieName = "" + new Date().getTime();
      Cookie newCookie = new Cookie(cookieName, name);
      res.addCookie(newCookie);
    }//end if
    
    //Construct an HTML form and send it back to the client
    out.println("<HTML>");
    out.println("<HEAD><TITLE>Servlet09</TITLE></HEAD>");
    out.println("<BODY>");
        
    //Substitute the name of your server or localhost in
    // place of baldwin in the following statement.
    out.println("<FORM METHOD=GET ACTION="
           + "\"http://baldwin:8080/servlet/Servlet09\">");
    out.println("Enter a name and press the button<P>");
    out.println("Name: <INPUT TYPE=TEXT NAME="
                                    + "\"firstName\"><P>");
    out.println("<INPUT TYPE=submit VALUE="
                                     + "\"Submit Name\">");
    out.println(
      "<BR><BR>Your session ID and list of names is:<BR>");
    if(name == null){
      out.println("Empty<BR>");
    }//end if 
    
    //Display the session ID and the values of the
    // cookies that have been saved.
    if(cookies != null){
      for(int i = 0; i < cookies.length; i++){
        out.println(cookies[i].getValue() + "<BR>");
      }//end for loop
    }//end if

    //Display name submitted with current GET request
    if(name != null){
      out.println(name + "<BR>");
    }//end if

    out.println("</BODY></HTML>");
      
  }//end doGet()
}//end class Servlet09

-end-