Home > Articles > Programming > Java

  • Print
  • + Share This
This chapter is from the book

2.9 Cookies

Cookies are small bits of textual information that a Web server sends to a browser and that the browser later returns unchanged when visiting the same Web site or domain. By letting the server read information it sent the client previously, the site can provide visitors with a number of conveniences such as presenting the site the way the visitor previously customized it or letting identifiable visitors in without their having to reenter a password.

This section discusses how to explicitly set and read cookies from within servlets, and the next section shows how to use the servlet session tracking API (which can use cookies behind the scenes) to keep track of users as they move around to different pages within your site.

Benefits of Cookies

There are four typical ways in which cookies can add value to your site.

Identifying a User During an E-commerce Session

Many online stores use a "shopping cart" metaphor in which the user selects an item, adds it to his shopping cart, then continues shopping. Since the HTTP connection is usually closed after each page is sent, when the user selects a new item to add to the cart, how does the store know that it is the same user who put the previous item in the cart? Persistent (keep-alive) HTTP connections do not solve this problem, since persistent connections generally apply only to requests made very close together in time, as when a browser asks for the images associated with a Web page. Besides, many servers and browsers lack support for persistent connections. Cookies, however, can solve this problem. In fact, this capability is so useful that servlets have an API specifically for session tracking, and servlet authors don't need to manipulate cookies directly to take advantage of it. Session tracking is discussed in Section 2.10.

Avoiding Username and Password

Many large sites require you to register to use their services, but it is inconvenient to remember and enter the username and password each time you visit. Cookies are a good alternative for low-security sites. When a user registers, a cookie containing a unique user ID is sent to him. When the client reconnects at a later date, the user ID is returned automatically, the server looks it up, determines it belongs to a registered user, and permits access without an explicit username and password. The site might also store the user's address, credit card number, and so forth in a database and use the user ID from the cookie as a key to retrieve the data. This approach prevents the user from having to reenter the data each time.

Customizing a Site

Many "portal" sites let you customize the look of the main page. They might let you pick which weather report you want to see, what stock and sports results you care about, how search results should be displayed, and so forth. Since it would be inconvenient for you to have to set up your page each time you visit their site, they use cookies to remember what you wanted. For simple settings, the site could accomplish this customization by storing the page settings directly in the cookies. For more complex customization, however, the site just sends the client a unique identifier and keeps a server-side database that associates identifiers with page settings.

Focusing Advertising

Most advertiser-funded Web sites charge their advertisers much more for displaying "directed" ads than "random" ads. Advertisers are generally willing to pay much more to have their ads shown to people that are known to have some interest in the general product category. For example, if you go to a search engine and do a search on "Java Servlets," the search site can charge an advertiser much more for showing you an ad for a servlet development environment than for an ad for an online travel agent specializing in Indonesia. On the other hand, if the search had been for "Java Hotels," the situation would be reversed. Without cookies, the sites have to show a random ad when you first arrive and haven't yet performed a search, as well as when you search on something that doesn't match any ad categories. With cookies, they can identify your interests by remembering your previous searches.

Some Problems with Cookies

Providing convenience to the user and added value to the site owner is the purpose behind cookies. And despite much misinformation, cookies are not a serious security threat. Cookies are never interpreted or executed in any way and thus cannot be used to insert viruses or attack your system. Furthermore, since browsers generally only accept 20 cookies per site and 300 cookies total, and since browsers can limit each cookie to 4 kilobytes, cookies cannot be used to fill up someone's disk or launch other denial-of-service attacks.

However, even though cookies don't present a serious security threat, they can present a significant threat to privacy. First, some people don't like the fact that search engines can remember that they're the user who usually does searches on certain topics. For example, they might search for job openings or sensitive health data and don't want some banner ad tipping off their coworkers next time they do a search. Even worse, two sites can share data on a user by each loading small images off the same third-party site, where that third party uses cookies and shares the data with both original sites. The doubleclick.net service is the prime example of this technique. (Netscape, however, provides a nice feature that lets you refuse cookies from sites other than that to which you connected, but without disabling cookies altogether.) This trick of associating cookies with images can even be exploited through e-mail if you use an HTML-enabled e-mail reader that "supports" cookies and is associated with a browser. Thus, people could send you e-mail that loads images, attach cookies to those images, then identify you (e-mail address and all) if you subsequently visit their Web site. Boo.

A second privacy problem occurs when sites rely on cookies for overly sensitive data. For example, some of the big online bookstores use cookies to remember users and let you order without reentering much of your personal information. This is not a particular problem since they don't actually display the full credit card number and only let you send books to an address that was specified when you did enter the credit card in full or use the username and password. As a result, someone using your computer (or stealing your cookie file) could do no more harm than sending a big book order to your address, where the order could be refused. However, other companies might not be so careful, and an attacker who gained access to someone's computer or cookie file could get online access to valuable personal information. Even worse, incompetent sites might embed credit card or other sensitive information directly in the cookies themselves, rather than using innocuous identifiers that are only linked to real users on the server. This is dangerous, since most users don't view leaving their computer unattended in their office as being tantamount to leaving their credit card sitting on their desk.

The point of this discussion is twofold. First, due to real and perceived privacy problems, some users turn off cookies. So, even when you use cookies to give added value to a site, your site shouldn't depend on them. Second, as the author of servlets that use cookies, you should be careful not to use cookies for particularly sensitive information, since this would open users up to risks if somebody accessed their computer or cookie files.

The Servlet Cookie API

To send cookies to the client, a servlet should create one or more cookies with designated names and values with new Cookie(name, value), set any optional attributes with cookie.setXxx (readable later by cookie.getXxx), and insert the cookies into the response headers with response.addCookie(cookie). To read incoming cookies, a servlet should call request.getCookies, which returns an array of Cookie objects corresponding to the cookies the browser has associated with your site (null if there are no cookies in the request). In most cases, the servlet loops down this array until it finds the one whose name (getName) matches the name it had in mind, then calls getValue on that Cookie to see the value associated with that name. Each of these topics is discussed in more detail in the following sections.

Creating Cookies

You create a cookie by calling the Cookie constructor, which takes two strings: the cookie name and the cookie value. Neither the name nor the value should contain white space or any of the following characters:

 [ ] ( ) = , " / ? @ : ;

If you want the browser to store the cookie on disk instead of just keeping it in memory, use setMaxAge to specify how long (in seconds) the cookie should be valid.

Placing Cookies in the Response Headers

The cookie is inserted into a Set-Cookie HTTP response header by means of the addCookie method of HttpServletResponse. The method is called addCookie, not setCookie, because any previously specified Set-Cookie headers are left alone and a new header is set. Here's an example:

Cookie userCookie = new Cookie("user", "uid1234");
userCookie.setMaxAge(60*60*24*365); // Store cookie for 1 year
response.addCookie(userCookie);

Reading Cookies from the Client

To send cookies to the client, you create a Cookie, then use addCookie to send a Set-Cookie HTTP response header. To read the cookies that come back from the client, you call getCookies on the HttpServletRequest. This call returns an array of Cookie objects corresponding to the values that came in on the Cookie HTTP request header. If the request contains no cookies, getCookies should return null. However, Tomcat 3.x returns a zero-length array instead.

Core Warning

In Tomcat 3.x, calls to request.getCookies return a zero-length array instead of null when there are no cookies in the request. Tomcat 4 and most commercial servers properly return null.

Once you have this array, you typically loop down it, calling getName on each Cookie until you find one matching the name you have in mind. You then call getValue on the matching Cookie and finish with some processing specific to the resultant value. This is such a common process that, at the end of this section, I present two utilities that simplify retrieving a cookie or cookie value that matches a designated cookie name.

Using Cookie Attributes

Before adding the cookie to the outgoing headers, you can set various characteristics of the cookie by using one of the following setXxx methods, where Xxx is the name of the attribute you want to specify. Each setXxx method has a corresponding getXxx method to retrieve the attribute value. Except for name and value, the cookie attributes apply only to outgoing cookies from the server to the client; they aren't set on cookies that come from the browser to the server. So, don't expect these attributes to be available in the cookies you get by means of request.getCookies.

public String getComment()
public void setComment(String comment)

These methods look up or specify a comment associated with the cookie. With version 0 cookies (see the upcoming entry on getVersion and setVersion), the comment is used purely for informational purposes on the server; it is not sent to the client.

public String getDomain()
public void setDomain(String domainPattern)

These methods get or set the domain to which the cookie applies. Normally, the browser returns cookies only to the same hostname that sent them. You can use setDomain method to instruct the browser to return them to other hosts within the same domain. To prevent servers from setting cookies that apply to hosts outside their domain, the specified domain must meet the following two requirements: It must start with a dot (e.g., .prenhall.com); it must contain two dots for noncountry domains like .com, .edu, and .gov; and it must contain three dots for country domains like .co.uk and .edu.es. For instance, cookies sent from a servlet at bali.vaca_tions.com would not normally get returned by the browser to pages at mexico.vaca_tions.com. If the site wanted this to happen, the servlets could specify cookie.setDomain(".vacations.com").

public int getMaxAge()
public void setMaxAge(int lifetime)

These methods tell how much time (in seconds) should elapse before the cookie expires. A negative value, which is the default, indicates that the cookie will last only for the current session (i.e., until the user quits the browser) and will not be stored on disk. See the LongLivedCookie class (Listing 2.18), which defines a subclass of Cookie with a maximum age automatically set one year in the future. Specifying a value of 0 instructs the browser to delete the cookie.

public String getName()
public void setName(String cookieName)

This pair of methods gets or sets the name of the cookie. The name and the value are the two pieces you virtually always care about. However, since the name is supplied to the Cookie constructor, you rarely need to call setName. On the other hand, getName is used on almost every cookie received on the server. Since the getCookies method of HttpServletRequest returns an array of Cookie objects, a common practice is to loop down the array, calling getName until you have a particular name, then to check the value with getValue. For an encapsulation of this process, see the getCookieValue method shown in Listing 2.17.

public String getPath()
public void setPath(String path)

These methods get or set the path to which the cookie applies. If you don't specify a path, the browser returns the cookie only to URLs in or below the directory containing the page that sent the cookie. For example, if the server sent the cookie from http://ecommerce.site.com/toys/ specials.html, the browser would send the cookie back when connecting to http:// ecommerce.site.com/toys/bikes/beginners.html, but not to http://ecommerce.site.com/cds/classical.html. The setPath method can specify something more general. For example, someCookie.setPath("/") specifies that all pages on the server should receive the cookie. The path specified must include the current page; that is, you may specify a more general path than the default, but not a more specific one. So, for example, a servlet at http://host/store/cust-service/request could specify a path of /store/ (since /store/ includes /store/cust-service/) but not a path of /store/cust-service/returns/ (since this directory does not include/store/cust-service/).

public boolean getSecure()
public void setSecure(boolean secureFlag)

This pair of methods gets or sets the boolean value indicating whether the cookie should only be sent over encrypted (i.e., SSL) connections. The default is false; the cookie should apply to all connections.

public String getValue()
public void setValue(String cookieValue)

The getValue method looks up the value associated with the cookie; the setValue method specifies it. Again, the name and the value are the two parts of a cookie that you almost always care about, although in a few cases, a name is used as a boolean flag and its value is ignored (i.e., the existence of a cookie with the designated name is all that matters).

public int getVersion()
public void setVersion(int version)

These methods get and set the cookie protocol version the cookie complies with. Version 0, the default, follows the original Netscape specification (http://www.netscape.com/newsref/std/cookie_spec.html). Version 1, not yet widely supported, adheres to RFC 2109 (retrieve RFCs from the archive sites listed at http://www.rfc-editor.org/).

Examples of Setting and Reading Cookies

Listing 2.15 and Figure 2–13 show the SetCookies servlet, a servlet that sets six cookies. Three have the default expiration date, meaning that they should apply only until the user next restarts the browser. The other three use setMaxAge to stipulate that they should apply for the next hour, regardless of whether the user restarts the browser or reboots the computer to initiate a new browsing session.

Figure 2-13Figure 2–13 Result of SetCookies servlet.


Listing 2.16 shows a servlet that creates a table of all the cookies sent to it in the request. Figure 2–14 shows this servlet immediately after the SetCookies servlet is visited. Figure 2–15 shows it within an hour of when SetCookies is visited but when the browser has been closed and restarted. Figure 2–16 shows it within an hour of when SetCookies is visited but when the browser has been closed and restarted.

Figure 2-14Figure 2–14 Result of visiting the ShowCookies servlet within an hour of visiting SetCookies (same browser session).


Figure 2-15Figure 2–15 Result of visiting the ShowCookies servlet within an hour of visiting SetCookies (different browser session).


Figure 2-16Figure 2–16 Result of visiting the ShowCookies servlet more than an hour after visiting SetCookies (different browser session).


Listing 2.15 SetCookies.java

package moreservlets;

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

/** Sets six cookies: three that apply only to the current
 * session (regardless of how long that session lasts)
 * and three that persist for an hour (regardless of
 * whether the browser is restarted).
 */
public class SetCookies extends HttpServlet {
 public void doGet(HttpServletRequest request,
          HttpServletResponse response)
   throws ServletException, IOException {
  for(int i=0; i<3; i++) {
   // Default maxAge is -1, indicating cookie
   // applies only to current browsing session.
   Cookie cookie = new Cookie("Session-Cookie-" + i,
                 "Cookie-Value-S" + i);
   response.addCookie(cookie);
   cookie = new Cookie("Persistent-Cookie-" + i,
             "Cookie-Value-P" + i);
   // Cookie is valid for an hour, regardless of whether
   // user quits browser, reboots computer, or whatever.
   cookie.setMaxAge(3600);
   response.addCookie(cookie);
  } 
  response.setContentType("text/html");
  PrintWriter out = response.getWriter();
  String title = "Setting Cookies";
  out.println
   (ServletUtilities.headWithTitle(title) +
    "<BODY BGCOLOR=\"#FDF5E6\">\n" +
    "<H1 ALIGN=\"CENTER\">" + title + "</H1>\n" +
    "There are six cookies associated with this page.\n" +
    "To see them, visit the\n" +
    "<A HREF=\"/servlet/moreservlets.ShowCookies\">\n" +
    "<CODE>ShowCookies</CODE> servlet</A>.\n" +
    "<P>\n" +
    "Three of the cookies are associated only with the\n" +
    "current session, while three are persistent.\n" +
    "Quit the browser, restart, and return to the\n" +
    "<CODE>ShowCookies</CODE> servlet to verify that\n" +
    "the three long-lived ones persist across sessions.\n" +
    "</BODY></HTML>");
 }
}

Listing 2.16 ShowCookies.java

package moreservlets;

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

/** Creates a table of the cookies associated with
 * the current page.
 */

public class ShowCookies extends HttpServlet {
 public void doGet(HttpServletRequest request,
          HttpServletResponse response)
   throws ServletException, IOException {
  response.setContentType("text/html");
  PrintWriter out = response.getWriter();
  String title = "Active Cookies";
out.println(ServletUtilities.headWithTitle(title) +
        "<BODY BGCOLOR=\"#FDF5E6\">\n" +
        "<H1 ALIGN=\"CENTER\">" + title + "</H1>\n" +
        "<TABLE BORDER=1 ALIGN=\"CENTER\">\n" +
        "<TR BGCOLOR=\"#FFAD00\">\n" +
        " <TH>Cookie Name\n" +
        " <TH>Cookie Value");
  Cookie[] cookies = request.getCookies();
  if (cookies == null) {
   out.println("<TR><TH COLSPAN=2>No cookies");
  } else {
   Cookie cookie;
   for(int i=0; i<cookies.length; i++) {
    cookie = cookies[i];
    out.println("<TR>\n" +
          " <TD>" + cookie.getName() + "\n" +
          " <TD>" + cookie.getValue());
   }
  }
  out.println("</TABLE></BODY></HTML>");
 }
}

Basic Cookie Utilities

This section presents some simple but useful utilities for dealing with cookies.

Finding Cookies with Specified Names

Listing 2.17 shows a section of ServletUtilities.java that simplifies the retrieval of a cookie or cookie value, given a cookie name. The getCookieValue method loops through the array of available Cookie objects, returning the value of any Cookie whose name matches the input. If there is no match, the designated default value is returned. So, for example, our typical approach for dealing with cookies is as follows:

Cookie[] cookies = request.getCookies();
String color =  ServletUtilities.getCookieValue(cookies, "color", "black");
String font =
 ServletUtilities.getCookieValue(cookies, "font", "Arial");

The getCookie method also loops through the array comparing names but returns the actual Cookie object instead of just the value. That method is for cases when you want to do something with the Cookie other than just read its value.

Listing 2.17 ServletUtilities.java

package moreservlets;

import javax.servlet.*;
import javax.servlet.http.*;

public class ServletUtilities {
 // Other parts of ServletUtilities shown elsewhere.


 /** Given an array of Cookies, a name, and a default value,
  * this method tries to find the value of the cookie with
  * the given name. If there is no cookie matching the name
  * in the array, then the default value is returned instead.
  */

public static String getCookieValue(Cookie[] cookies,
                   String cookieName,
                   String defaultValue) {
  if (cookies != null) {
   for(int i=0; i<cookies.length; i++) {
    Cookie cookie = cookies[i];
    if (cookieName.equals(cookie.getName()))
     return(cookie.getValue());
   }
  }
  return(defaultValue);
 }

 /** Given an array of cookies and a name, this method tries
  * to find and return the cookie from the array that has
  * the given name. If there is no cookie matching the name
  * in the array, null is returned.
  */
 
 public static Cookie getCookie(Cookie[] cookies,
                 String cookieName) {
  if (cookies != null) {
   for(int i=0; i<cookies.length; i++) {
    Cookie cookie = cookies[i];
    if (cookieName.equals(cookie.getName()))
     return(cookie);
   }
  }
  return(null);
 }
}

Creating Long-Lived Cookies

Listing 2.18 shows a small class that you can use instead of Cookie if you want your cookie to automatically persist for a year when the client quits the browser. For an example of the use of this class, see the customized search engine interface of Section 8.6 of Core Servlets and JavaServer Pages (available in PDF at http://www.moreservlets.com).

Listing 2.18 LongLivedCookie.java

package moreservlets;

import javax.servlet.http.*;

/** Cookie that persists 1 year. Default Cookie doesn't
 * persist past current session.
 */

public class LongLivedCookie extends Cookie {
 public static final int SECONDS_PER_YEAR = 60*60*24*365;
 
 public LongLivedCookie(String name, String value) {
  super(name, value);
  setMaxAge(SECONDS_PER_YEAR);
 }
}
  • + Share This
  • 🔖 Save To Your Account