Track wireless sessions with J2ME/MIDP

Learn three ways to maintain client state information in mobile commerce applications

Client state information proves vital for e-commerce applications that employ user authentication and transactions. In those applications, the server must individually respond to each user according to her previous actions. For example, when a user places an item into a shopping cart, the server must remember and associate that item with that particular user. When the same user selects Checkout later, the server must respond with a list of the merchandise she previously selected.

However, the most widely used Internet e-commerce protocol, HTTP, is stateless. Each HTTP connection is independent and knows nothing about other connections. Standard HTTP connections do not remember client state information. Most developers solve this problem by requiring the client to embed some unique session identification information (usually a session ID string) in each HTTP connection. E-commerce Web servers can then organize individual HTTP connections into sessions according to the session identification. It usually works like so:

  1. When a new session's first connection is made, the server generates a new session ID and sends it back to the client
  2. The client stores that session ID and attaches it to every subsequent HTTP connection so the server knows all connections belong to the same session

That technique requires cooperation between the client and server software. The client and server must exchange session identification information with a previously agreed upon format. The industry has developed two de facto standards for exchanging such session information.

One approach transmits small pieces of text, called cookies, through HTTP connection headers. The other approach attaches a session ID string to the end of each request URL, a technique also known as URL rewriting.

Most Web browsers and desktop HTTP applications support both session-tracking methods. However, the HTTP session support in wireless Java platforms is far from smooth. In the main Java platform designed for cell phones and low-powered PDAs—the Mobile Information Device Profile (MIDP), a Java 2 Platform, Micro Edition (J2ME)-based technology—the HttpConnection object supports neither cookie nor URL rewriting out of the box. Considering the importance of session tracking in e-commerce applications, if we want J2ME/MIDP to be a serious mobile commerce platform, we must equip it with session-aware HTTP connections.

In this article, we discuss how to implement session tracking in MIDP applications with both cookies and URL rewriting. We will also discuss a new way to track sessions by enveloping session information in XML documents. The XML method is unique to wireless applications. (Note: We assume you have basic knowledge of MIDP programming. If you need a refresher, please refer to Resources.)

As we mentioned earlier, session tracking requires a joint effort from both the client and the server. In this article, we give examples in the context of Java application servers, but you can easily apply the same techniques to other servers.

Our example is a simple Web visit counter. It uses HTTP sessions to track each user and reports how many times the user connects to the URL. We give framework implementations of this simple Web counter using the above three session-tracking methods. We created a test MIDlet and a test JSP (JavaServer Page) for each method. You can download Java and JSP source code that accompany this article from Resources. Figure 1 shows the sample application's MIDlets."

Figure 1. Example application's start screen

Cookies

Cookies are pieces of NAME=VALUE format text embedded in HTTP headers. Netscape originally proposed the cookie concept as an "HTTP State Management Mechanism;" the specification was published in a request for comments, RFC 2109, in 1997. (Note: RFC 2695 defines the current cookie specification.) Since cookies reside in HTTP headers, they are transparent to applications and users, and thus the most widely used HTTP session-tracking technique.

The server assigns new cookies to a client through the HTTP header set-cookie. One HTTP connection can have multiple set-cookie headers and hence allows the server to set multiple cookies simultaneously. The set-cookie header takes the format:

set-cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure

The first NAME=VALUE is the cookie itself and is required. Each cookie can have many optionally specified attributes, such as expiration time, domain, and path. The domain attribute states the cookie's valid domains. It proves especially important since it protects user privacy and reduces the chances for conflicting cookies. For example, a cookie set by yahoo.com is not supposed to be sent out when the user visits amazon.com later. If no domain attribute is specified, a cookie's domain defaults to the host name of the server that sets it.

The client program sends out the valid cookies to each URL it connects to. The client sends cookies back to the server in the HTTP header named cookies:

cookie: NAME1=VALUE1; NAME1=VALUE2; ...

A client can send multiple cookies in one connection header by delimiting them using semicolons.

Server side

All major Web server vendors support cookie-based HTTP sessions. In this article, we discuss examples in the Java Web server context.

The HttpSession interface in any Java 2 Platform, Enterprise Edition (J2EE)-compatible Java servlet/JSP container knows how to handle sessions through cookies. To use cookies in our example, we must first ensure that cookie support is not disabled in the configure files. Our example JSP, CookieSession.jsp, uses cookies to track sessions and update visit counters. As you will see later, an HttpSession object can track sessions using both cookies and URL rewriting, but by default, it uses cookies. So, in our example, if the client's incoming connection sends a valid cookie, the code below returns an existing session object:

HttpSession sess = request.getSession(true);

Otherwise, the server creates a new session object and sends a new cookie to the client.

The server invalidates sessions after a period of inactivity. That expiration time can be set by server administrators in configure files or by servlets at runtime through the sess.setMaxInactiveInterval() method. We can then associate server-side objects containing client state information with the HttpSession object:

sess.setAttribute("Count", count);

We can retrieve the above session state information

count

object later as long as the session remains valid:

String count = (String) sess.getAttribute("Count");

If no object associates with attribute Count (for example, at the session's beginning), the above statement returns a null value, and we need to start a new counter:

if ( count == null ) {
  count = "0";
}

MIDP cookie support framework

Supporting cookies on the server side is easy; supporting them in MIDP applications proves more challenging.

We try to make cookie support as transparent as possible; so we borrow a technique from Sun's Smart Ticket J2ME/J2EE demo implementation to design cookie-aware connection objects. RMSCookieConnector is a decorator class for the standard MIDP Connector class. It maintains a MIDP record management system (RMS) record store to store all cookies. When RMSCookieConnector.open() is called, that method completes the following steps:

  1. It calls Connector.open() to create a standard MIDP HttpConnection object:

    HttpConnection c = (HttpConnection) Connector.open(url);
    
  2. It iterates through the record-store records and fetches the valid cookies. We discuss this step's details in the next section.
  3. It assembles the valid cookies into a semicolon-delimited string and sets the string into the HttpConnection object's cookie header:

    c.setRequestProperty( "cookie", cookieStr );
    
  4. RMSCookieConnector.open() then wraps the above standard HttpConnection object with a cookie-aware HttpConnection type object implemented by class HttpRMSCookieConnection and returns the HttpRMSCookieConnection instance:

    HttpConnection sc = new HttpRMSCookieConnection(c);
    return sc;
    

The HttpRMSCookieConnection class implements the HttpConnection interface by acting as a decorator for a standard MIDP HttpConnection implementation object wrapped inside a HttpRMSCookieConnection object. Most methods required by the HttpConnection interface in the decorator class pass directly to the wrapped object. But HttpRMSCookieConnection overrides the openInputStream() and openDataInputStream() methods to process the set-cookie fields before returning the stream object. The cookie is simply the string before the first semicolon in set-cookie headers. The code snippet below shows how to retrieve cookies:

int k = 0;
while (c.getHeaderFieldKey(k) != null) {
  String key = c.getHeaderFieldKey(k);
  String value = c.getHeaderField(k);
  if (key.equals("set-cookie")) {
    // Parse the header and get the cookie.
    int j = value.indexOf(";");
    String cValue = value.substring(0, j);
    // Write the cookie into the cookie store.
    // ... ...
  }
  k++;
}

This framework hides the cookie-handling process from the application developers. To get a new cookie-aware HttpConnection type object, the application just needs to call the following:

HttpConnection conn = (HttpConnection) RMSCookieConnector.open( url );

Then we can open input/output streams to communicate with the server. For more details, please refer to the source code of MIDlet CookieFetch, which in turn calls functions from the Utils helper class.

Store cookies in an RMS record store

The RMSCookieConnector class stores cookies in an RMS record store. An RMS record store is accessed by its name, rather than reference, and can persist between soft resets, or reboots. That ensures an old session remains valid when a user later returns to an application after previously quitting it. Considering most people use their mobile information devices for many different tasks simultaneously, cookie persistence is a desired feature.

Remember, RFC 2109 allows us to separate cookies for different sites by examining the domain attribute associated with each cookie. However, implementing the full specifications in RFC 2109 requires rather complex string parsing and proves quite expensive in terms of memory footprint and CPU usage. Instead of being fully specification-compliant, we decided to use a simpler approach: We simply discard all the attributes information accompanying the set-cookie header and associate each cookie with the real host it comes from. We derive the host name from a call to HttpConnection.getHost(). When we connect to that host again, only cookies from the same host will be used.

We store these cookies and their host names in an RMS record store. An RMS record store only has a one-dimensional structure with sequentially ordered data fields. Starting from field number zero, we store cookies in even-numbered fields and associated host names in the odd numbered fields that directly follow each cookie field. The cookie storage and retrieval code is illustrated below:

// Get cookies from the connection and store them with host names.
static void getCookie(HttpConnection c) throws IOException {
  RecordStore rs = RecordStore.openRecordStore(cookieStoreName, true);
  // "While" loop to iterate through headers and get cookies
  // in to cValue strings.
  /* Start loop. */
    // Write the cookie into the cookie store.
    int newID = rs.addRecord(cValue.getBytes(), 0, cValue.length());
    // We set the domain default to the current server.
    String dValue = c.getHost();
    if ( dValue == null ) {
      // If there is no valid domain,
      // we do not keep the cookie.
        rs.deleteRecord(newID);
    } else {
      // All upper case for easy comparison in the future.
      dValue = dValue.toUpperCase();
      // Write the domain into the cookie store.
      rs.addRecord(dValue.getBytes(), 0, dValue.length());
    }
  /* End loop. */
  rs.closeRecordStore();
  return;
}
// Fetch cookies from record store and set into the connection header.
static void addCookie(HttpConnection c, String url) throws Exception {
  String domain; 
  // Chunk of code to parse domain from input url.
  
  StringBuffer buff = new StringBuffer();;
  RecordStore rs = RecordStore.openRecordStore(cookieStoreName, true);
  RecordEnumeration re = rs.enumerateRecords(null, null, false);
  String cookie = "", cookieDomain = "";
  // Iterate through the cookie record store and find cookies
  // with domain matching the current URL.
  //
  // isCookie is used to tell whether the current record is
  // a cookie or an associated domain.
  boolean isCookie = true;
  while ( re.hasNextElement() ) {
    if ( isCookie ) {
      cookie = new String(re.nextRecord());
    } else {
      cookieDomain = new String(re.nextRecord());
      // Cookies are valid for sub-domains.
      if ( domain.endsWith( cookieDomain ) ) {
        buff.append( cookie );
        buff.append("; ");
      }
    }
    isCookie = !isCookie;
  }
  rs.closeRecordStore();
 
  // If we do have cookies to send, set the composed string into
  // "cookie" header.
  String cookieStr = buff.toString();
  if ( cookieStr == null || cookieStr.equals("") ) {
    // Ignore.
  } else {
    c.setRequestProperty( "cookie", cookieStr );
  }
  return;
}
1 2 3 Page 1
Page 1 of 3