HTTP is a stateless protocol, which means that each request and response pair is a separate conversation. Sometimes, though, you want the server to remember who you are. This can be done with a session. On the server side, a session is just a collection of information. When the client sends an HTTP request to the server, it includes a session ID as part of the request. The server can then look up the corresponding session and have some idea of the identity (or at least the state) of the client.
The most common way to store a session ID on the client side is using HTTP cookies. A cookie is just a little piece of data that is passed from the server to the client in an HTTP response. Most web browsers automatically store cookies and will send them back to the appropriate server when a new request is made.
In the MIDP world, of course, there's no web browser taking care of cookies for you. You have to do it yourself. Fortunately, it's not very complicated.
Network code that maintains a server session ID needs to do two things:
When receiving a response from a server, check for a cookie. If there is a cookie present, save it away for later (perhaps in a member variable). A cookie is just another HTTP response header line. You can check for a cookie by calling getHeaderField() on an HttpConnection object after the request has been sent.
When sending a request to the server, send the session ID cookie if it has been previously received. Again, sending a cookie to the server is just a matter of putting it in the request headers, using HttpConnection's setRequestProperty() method.
Each time you send a request to the server, you will be sending a session ID as a request header. The server uses this session ID to look up a session object that can be used, server side, to do useful stuff like retrieve preferences or maintain a shopping cart.
It's not hard to implement this behavior in a MIDlet. If you have a session ID cookie handy, you should send it when you open up an HTTP connection to the same server, like this:
HttpConnection hc = (HttpConnection)Connector.open(url);
if (mSession != null)
hc.setRequestProperty("cookie", mSession);
This code assumes you have a session ID cookie saved away in the mSession member variable. The first time you contact the server, of course, you won't have a session ID cookie.
Later, when you receive a response from an HTTP request, look for a cookie. If you find one, parse out the session ID and save it away, like this:
InputStream in = hc.openInputStream();
String cookie = hc.getHeaderField("Set-cookie");
if (cookie != null) {
int semicolon = cookie.indexOf(';');
mSession = cookie.substring(0, semicolon);
}
The cookie string needs to be parsed because it comes in two pieces. The first piece is a path that can be used to determine when the cookie should be sent back to the server. The second part contains the session ID—that's the part we parse out and save.
For more information on the different parts of a cookie string and how they are used, see http://www.ietf.org/rfc/rfc2965.txt and http://www.ietf.org/rfc/rfc2109.txt.
Listing 9-4 shows a class, CookieMIDlet, that uses this technique to maintain a session with a server. It has a very bland user interface—just an empty Form with two commands. If you invoke the Send command, the MIDlet sends an HTTP request and receives a response using the cookie handling described earlier.
import java.io.*;
import javax.microedition.io.*;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class CookieMIDlet
extends MIDlet
implements CommandListener, Runnable {
private Display mDisplay;
private Form mForm;
private String mSession;
public void startApp() {
mDisplay = Display.getDisplay(this);
if (mForm == null) {
mForm = new Form("CookieMIDlet");
mForm.addCommand(new Command("Exit", Command.EXIT, 0));
mForm.addCommand(new Command("Send", Command.SCREEN, 0));
mForm.setCommandListener(this);
}
mDisplay.setCurrent(mForm);
}
public void pauseApp() {}
public void destroyApp(boolean unconditional) {}
public void commandAction(Command c, Displayable s) {
if (c.getCommandType() == Command.EXIT) notifyDestroyed();
else {
Form waitForm = new Form("Connecting...");
mDisplay.setCurrent(waitForm);
Thread t = new Thread(this);
t.start();
}
}
public void run() {
String url = getAppProperty("CookieMIDlet-URL");
try {
// Query the server and retrieve the response.
HttpConnection hc = (HttpConnection)Connector.open(url);
if (mSession != null)
hc.setRequestProperty("cookie", mSession);
InputStream in = hc.openInputStream();
String cookie = hc.getHeaderField("Set-cookie");
if (cookie != null) {
int semicolon = cookie.indexOf(';');
mSession = cookie.substring(0, semicolon);
}
int length = (int)hc.getLength();
byte[] raw = new byte[length];
in.read(raw);
String s = new String(raw);
Alert a = new Alert("Response", s, null, null);
a.setTimeout(Alert.FOREVER);
mDisplay.setCurrent(a, mForm);
in.close();
hc.close();
}
catch (IOException ioe) {
Alert a = new Alert("Exception", ioe.toString(), null, null);
a.setTimeout(Alert.FOREVER);
mDisplay.setCurrent(a, mForm);
}
}
}
On the server side, things are much simpler, as you'll see in Listing 9-5. If you're writing Java servlets, you don't even have to worry about cookies. Instead, you just deal with an HttpSession object. The code that follows shows a servlet that interacts with CookieMIDlet; it implements a session-based hit counter. It's been tested on Tomcat 4.0 but should work fine on other servers. Note that you will have to map the URL used by the MIDlet to this servlet class; for details, see an introductory book on servlets or your server's documentation.
import javax.servlet.http.*;
import javax.servlet.*;
import java.io.*;
import java.util.*;
public class CookieServlet extends HttpServlet {
private Map mHitMap = new HashMap();
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession();
String id = session.getId();
int hits = -1;
// Try to retrieve the hits from the map.
Integer hitsInteger = (Integer)mHitMap.get(id);
if (hitsInteger != null)
hits = hitsInteger.intValue();
// Increment and store.
hits++;
mHitMap.put(id, new Integer(hits));
String message = "Hits for this session: " + hits + ".";
response.setContentType("text/plain");
response.setContentLength(message.length());
PrintWriter out = response.getWriter();
out.println(message);
}
}
The servlet retrieves the HttpSession object. Then it pulls out the session ID and uses it as a key into a map of hit counts. After retrieving and incrementing the hit count for the session, the servlet sends it as the response back to the MIDlet. You can start up multiple copies of the emulator and run them simultaneously to see how the hit counts are independent of each other and associated with each session.