When the Hyper-Text Transfer Protocol (HTTP) was developed, its designers chose to create a protocol which does not maintain a persistent connection. Each request made by a Web browser, for an image, an HTML page, or other Web object, is made via a new connection. It would have been handy if Web browsers established a single connection, through which multiple requests could be made. Indeed, this feature has been introduced in HTTP 1.1, with the keep-alive feature. This feature comes too late though for server-side programmers, as support for legacy browsers and Web servers must be maintained. Without an ongoing session, however, it is difficult for developers to maintain state across HTTP requests. This ability is critical though for all but the simplest of server-side applications. A shopping cart, for example, needs to maintain a list of items and have this list associated with a specific user.
How can state be maintained across HTTP requests?
Developers are an inventive bunch and quickly found solutions. The simplest of all was to encode hidden parameters inside an HTML form, such as the username and type of transaction being made. Though not evident to the user, additional state information has been added to the form, which will be passed when the form is submitted. If every form included a hidden field indicating the user, then it would be possible to track user actions across HTTP requests. Of course, additional information can also be included, such as the type of operation being performed, as well as the focus of the operation. The following HTML code illustrates the use of hidden fields:
<form action="/servlet/order" method=post>
<input type="hidden" name=userid value="5532211">
<input type="hidden" name=operation value="add">
<input type="hidden" name=product_id value="4432A">
Product ID : 4432A
Quantity : <input type="quantity" value=1>
<input type=submit>
</form>
A variation on this theme is to use hyperlinks, which generate HTTP GET requests. Often a hyperlink is better than an HTML form, as most users are more comfortable with hyperlinks than buttons. Parameters can be added to the end of each URL, so that state information is passed when the next connection is made. For example, every hyperlink on the page could contain a userID code to allow user activity to be tracked. This information can be fetched by a servlet using javax.servlet.http.HttpServletRequest.getParameter(String)
just as a standard CGI parameter would.
<a href="/servlet/shopping?userID=5532211>View Shopping Cart</a>
Whether a hyperlink or an HTML form is used, this state information must be included every time a request is made, so that subsequent requests can gain access to it. If your servlet misses a URL or a hidden variable is missed from a form, the state information is lost. Such solutions are crude but effective. They do the job but are an inelegant and overly complex way of maintaining state across HTTP requests. A better way of maintaining state information is to use persistent client state cookies.
Did you say cookies?
To solve the problem of maintaining persistence across HTTP connections, Netscape Communications developed a specification for "cookies". Cookies are state objects stored by a Web browser (or other HTTP client) and can be used by server-side applications to store and retrieve information. Cookies can be created by servlets (or CGI scripts) and sent to the browser. For every subsequent request made by the browser, the cookie is sent as part of the HTTP request. This allows server-side applications to access state information, without the effort of encoding it in a hyperlink or HTML form. Figure 1 shows an example transaction between a server-side application and a browser where a cookie is stored and then returned.
Figure 1. A cookie sent to an HTTP client is returned on subsequent requests.
To understand how cookies work, let’s consider the analogy of a bank. When signing up for an account, most customers will be given a card that contains identification information. Every time the customer uses an ATM, he or she presents the card. State information (the user’s account number) persists across each transaction, even though transactions may be days, weeks, or even months apart. Cookies are no different — the state information issued by a server-side application is presented later, when the next HTTP request is made.
Each cookie is named and contains a single value. The cookie is effectively a mapping between a single key and a single value. If developers need to store multiple values, they can use more than one cookie or can encode multiple values using some sort of separator character for the cookie data. Remember, however, that the number of cookies per domain name varies from browser to browser. Netscape imposes a limit of 20 cookies per domain, and a maximum size of 4096 bytes per cookie. As a general rule, cookies should only contain small pieces of data (to identify a user or a session). You have the option of using an identifier stored in a cookie to look up larger pieces of data stored server-side.
Storing cookies from a Java servlet
Support for cookies has been included in the Servlet API and provides an extremely easy interface for storing and retrieving cookies. Cookies are represented by the
javax.servlet.http.Cookie |
// Create a new cookie
Cookie myCookie = new Cookie ( "accountID" , "212994234");
Servlets that wish to set cookies must add their cookie to the response sent back to the browser.
HttpServletResponse |
addCookie(Cookie) |
public void doGet(HttpServletRequest req, HttpServletResponse res)
{
// Store state information in browser cookies
res.addCookie (new Cookie ("thecounter", "1");
// Additional servlet code would go here…..
}
Reading cookies from a Java servlet
Accessing stored cookies from a servlet is also easy. Cookies are sent each time a request is made, so that if a cookie is already stored in the browser, it can be accessed by invoking the
Cookies[] getCookies() |
javax.servlet.http.HttpServletRequest |
null |
// Get cookie array from HttpServletRequest
Cookie[] cookieArray = request.getCookies();
// Guard statement to check for missing cookies
if (cookieArray != null)
{
// Print a list of all cookies sent by browser
for (int i =0; i< cookieArray.length; i++)
{
Cookie c = cookieArray[i];
pout.println ("Name : " + c.getName());
pout.println ("Value: " + c.getValue());
}
}
else
pout.println ("No cookies present, or browser does not support cookies");
Putting cookies to work
To demonstrate cookies in action, let’s look at a simple example that tracks the number of times a user has visited a site. Though no unique identifier is to be stored as a cookie, the number of visits made by the user will persist over time. This shows that state has been maintained across connections.
We start by checking for the presence of any cookies, and then a specific cookie named "count." If the cookie exists, we read the value and then display it to the user. Since the idea of a counter is to increase on every visit, we increment the counter value and send it back to the user as a cookie. As you reload the page, the cookie is incremented, and state information persists across connections. Note too that, by default, the cookie will persist only while the browser remains open, unless a specific time period is assigned to the cookie (see Listing 1).
Listing 1 (See page 2)
Summary
Cookies provide an easy mechanism for maintaining state information across HTTP requests. The Servlet API provides an easy-to-use implementation of cookies, which requires a minimal amount of code. In the second part of this article (State and session tracking with Java servlets Part 2: Securing data), we’ll cover more-advanced uses of cookies and enhanced session tracking features offered by the Servlet API.
About the author
David Reilly is a software engineer and freelance technical writer living in Australia. A Sun Certified Java 1.1 Programmer, his research interests include the Java programming language and networking & distributed systems. He can be reached via e-mail at java@davidreilly.com.
Listing 1 Putting cookies to work by David Reilly. |
||
|