Session Management

Web servers have no memory on what they send you, they forget who you are as soon as they send a response to you, they do not remember any requests received or any responses sent. But sometimes you need to keep conversational state with the client across multiple requests, a shopping cart is an example of this.

In the coffee application, the business logic in the model simply checks the parameter from the request and gives back an response, nobody remembers anything that went on with this client prior to the current request. Wouldn't be good if the user answers a question and then the web app responds with a new question based on the answer to the previous one.

We need away to track of everything the client has already said during the conversation, not just the answer in the current request. What we need is the servlet to get the request parameters representing the client's choices and save it somewhere. Each time the client answers a question, the advise engine uses all of that client's previous answers to come up with either another question to ask, or the final answer.

You could use a stateful session enterprise Javabean and each time a request comes in you could locate that client's stateful bean, but that's way to much overhead for this application and you need a full JEE 2 server with an EJB Container (we are only using Tomcat).

You could also use a database but this has as much of a runtime performance hit as an enterprise bean (possibly more) and again we are introducing more complexity.

The answer is to use a HttpSession object to hold the conversational state across multiple requests (for the entire session with the client). In fact you would have had to use a HttpSession for the two options above as you still need to match a specific client with a specific database key or session bean ID and HttpSession takes care of that identification.

You are probably thinking how does the Container know who the client is as the HTTP protocol uses stateless connections, the connection exists for only a single request/response. Because the connections don't persist, the Container doesn't recognize that the client is making a second request is the same client from the previous request, basically every request is from a new client from the servers point of view. You could use the clients IP address but this can change as many companies use NAT'ing (IP address translation), so there is no guarantee that the IP address will be the same. You could use HTTPS as the Container can identify the client and associate the session, but most companies only use HTTPS until it really matters (paying for your goods).

What the client needs is a unique session ID, the Container generates a unique session ID and gives it back to the client with the response, the client then will use this session ID with each subsequent request. The Container sees the ID, finds the matching session and associates the session with the request.

So how does this session ID get to the client, the simplest and most common way to exchange the info is via a cookie

Cookies

You have to tell the Container that you want to create or use a session, but the Container takes care of generating the session ID, creating the Cookie object, stuffing the session ID into the cookie and setting the cookie as part of the response. The Container also takes of getting the session ID out of the cookie from the subsequent requests.

Sending a session cookie in the Response

HttpSession session = request.getSession();

Note: All the cookie work happens behind the scenes.

Getting the session ID from the Request

HttpSession session = request.getSession();

Note: yes is the same as sending above

If you wanted to know whether the session already existed or was just created, the getSession() returns a session regardless whether there's a pre-existing session. Since you always get an HttpSession instance back from that method, the only way to know if the session is new is to ask the session

Existing session?

HttpSession session = request.getSession();

if (session.isNew()) {
  out.println("This is a new session");
} else {
  out.println("Welcome back");
}

If you implement any of the four listening interfaces related to sessions you can access the session through the event-handling callback methods

Get the session from the event-handling method public void sessionCreated(HttpSessionEvent event) {

  HttpSession session = event.getSession();
  // event handling code
}

You might want a servlet to use a previously-created session, there is a overloaded getSession(boolean) method for this purpose, if you don't want to create a new session, call getSession(false) and you will get either null or a preexisting HttpSession.

Get pre-existing session

HttpSession session = request.getSession(false);

if (session == null) {
  session = request.getSession();
}

Note: this code is the same as the "Existing session?" one, but more inefficient

URL Rewriting

OK, you are now thinking but some companies turn off cookies, how do we do this now?.

If the client has cookies turned off it ignores the "set-Cookie" response headers, can you have to use URL rewriting. URL rewriting will always work, the client will never prevent it. UR takes the session ID that's in the cookie and sticks it right onto the end of every URL that comes to this app.

A web page where every link has a little bit of extra info (the session ID) tacked onto the end of the URL, the Container simply strips off the extra part of the request URL and uses to find the matching session

If the cookie does not work, the Container will fall back and use URL rewriting, but only if you have done the extra work of encoding all the URLs you send in the response. Default the Container will also use cookies first then URL rewriting, but if you do not explicitly encode your URLs and the client will not accept cookies you don't get to use sessions.

URL rewriting

HttpSession session = request.getSession();

out.println("<html><body>");
out.println("<a href=\"" + response.encodeURL("/CoffeeTest.do") + "\">click me</a>");
out.println("</body></html>");

A really dumb Container will actually send both a cookie and a URL rewrite each time, but a clever Container will first send both a cookie and a URL rewrite with the first response, after that it will know which one to use, remember the only way the Container can recognize that it has seen this client before is if the client sends a session ID.

Sometimes you may want to redirect the client to a different URL but you still want to use the same session ID

URL rewriting and redirect response.encodeRedirectURL("/coffee.do")

You can also do UR rewriting in JSPs using the <c:URL> tag, I will be having a topic on JSPs.

One warning vendors have very specific ways of handling URL rewriting, Tomcat uses a semicolon to append the extra info to the URL, it also adds "jsessionid=" to the URL but other vendors may do this differently so check.

How to get rid of Sessions

Like everything, session objects uses resources, you don't want a session to stick around longer than necessary, HTTP does not have a mechanism to let the server know that's it gone (user decided to leave your site). So how does the Container know when to get rid of your session,

There are a number of other of key methods related to sessions

getCreationTime() Returns the time the session was first created, this can help in finding out how old the session is as you might want to restrict certain sessions to a fixed length of time
getLastAccessedTime() Returns the last time the Container got a request with this session ID (in milliseconds), this can tell when the session last last accessed.
setMaxInactiveInterval() Specifies the maximum time, in seconds that you want to allow between client requests for this session, you can use it to destroy a session after amount of time has passed without the client making any requests for this session
getMaxInactiveInterval() Returns the maximum time, in seconds that is allowed between client requests for this session, use this to find out how long this session can be inactive and still be alive.
invalidate() Ends the session, this includes unbinding all session attributes currently stored in this session, basically kills a session if the client has been inactive or if you KNOW the session is over.

There are three ways a session can die

Configuring session timeout in the DD

<web-app ...>
  <servlet>
    ...
  </servlet>
  <session-config>
    <session-timeout>15</session-timeout>
  </session-config>
</web-app>

Note: the timeout is in minutes, so the example above is 15mins

Setting session timeout for a specific session

session.setMaxInactiveInterval(15*60);

Note: the timeout is in seconds, so you could have set it to 900

Kill a session

session.invalidate();

session.setMaxInactiveInterval(0);

Note: both the above examples invalidate the session, once a session is invalidated you cannot use it any more period.

Cookies other Uses

You can custom cookies for other things besides session state, its no more that a small piece of data (a name/value String pair) exchanged between the client and the server. Normally the cookie disappears are the session finished but you tell a cookie to stay alive even after the browser shuts down. This is how web sites know what you name is when you return to them, your name is stored in the cookie.

The below examples show you how to set and get information from the cookie,

Create a index.html form page <html>
<head>
<title>Hello Web Application</title>
</head>
<body>
  <h1>Hello Web Application</h1>
  <form action="/cookie/setCookie.do" method="POST" >
    <table width="75%">
    <tr>
      <td width="48%">What is your name?</td>
      <td width="52%">
      <input type="text" name="username" />
      </td>
    </tr>
    </table>
<p>
  <input type="submit" name="Submit" value="Submit name" />
  <input type="reset" name="Reset" value="Reset form" />
</p>
</form>
</body>
</html>
Setting the cookie

# Create file called setCookie.java and compile it

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

public class setCookie extends HttpServlet {

  public void doPost( HttpServletRequest request,
                      HttpServletResponse response)
                      throws IOException, ServletException {
    
    response.setContentType("text/html");

    // Get the username from the submitted form page
    String name = request.getParameter("username");

    // Create a new cookie
    Cookie cookie = new Cookie("username", name);

    // Keep it alive on the client for 30 minutes
    cookie.setMaxAge(30*60);

    // Add the cookie as a "Set-Cookie" response header
    response.addCookie(cookie);

    RequestDispatcher view = request.getRequestDispatcher("displayCookie.jsp");
    view.forward(request, response);
  }
}

JSP to render the view from the above servlet

# Create the below file displayCookie.jsp


<html>
<body>
  <a href="displayCookie.do">click here</a>
</body>
</html>

Note: remember you have to perform a servlet mapping within Tomcat to the class below, if you are not sure have a look at my Tomcat Tutorial, the mapping is below

Getting the cookie

# Create file called getCookie.java and compile it

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

public class getCookie extends HttpServlet {

  public void doGet( HttpServletRequest request,
                      HttpServletResponse response)
                      throws IOException, ServletException {

    response.setContentType("text/html");

    PrintWriter out = response.getWriter();

    // Get the cookie from the response
    Cookie[] cookies = request.getCookies();

    // Loop through the cookie array looking for the username
    if ( cookies != null ) {
      for (int i = 0; i < cookies.length; i++) {
        Cookie cookie = cookies[i];
        if (cookie.getName().equals("username")) {
          String userName = cookie.getValue();
          out.println("Hello " + userName);
        }
      }
    }
  }
}

Servlet mapping

# copy below in the WEB-INF/web.xml file

<display-name>Cookie Application</display-name>

<welcome-file-list>
  <welcome-file>index.html</welcome-file>
</welcome-file-list>

<servlet>
  <servlet-name>setCookie</servlet-name>
  <servlet-class>setCookie</servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name>setCookie</servlet-name>
  <url-pattern>/setCookie.do</url-pattern>
</servlet-mapping>

<servlet>
  <servlet-name>getCookie</servlet-name>
  <servlet-class>getCookie</servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name>getCookie</servlet-name>
  <url-pattern>/displayCookie.do</url-pattern>
</servlet-mapping>

 

Milestones for an HttpSession

There are a number of key milestones for a HttpSession objects life, these can be useful when you want to update something like a database for an example when an attribute is changed

Lifecycle The session was created HttpSessionEvent
HttpSessionListener
The session was destroyed
Attributes An attribute was added HttpSessionBindingEvent
HttpSessionAttributeListener
An attribute was removed
An attribute was replaced
Migration The session is about to be passivated (when the Container is about to migrate the session into a different VM) HttpSessionEvent
HttpSessionActivationListener
The session has been activated (when the Container has just migrated the session into a different VM)

The code for using a one for the above would be something like, notice the listener and event, if you have gone a bit of Java programming its like creating Swing buttons, you attach a listener on the button and when clicked the event code is executed but in this case the action is taken care of.

HttpSessionBindingListener example

package com.example;
import javax.servlet.http.*;

public class Dog implements HttpSessionBindingListener {

   private String breed;

   public Dog(String breed) {
     this.breed = breed;
   }

   public String getBreed() {
     return breed;
   }

   // the work bound means someone added this attribute to a session
   public void valueBound(HttpSessionBindingEvent event) {
      // code to run now that i know i'm in a session
   }

   public void valueUnBound(HttpSessionBindingEvent event) {
      // code to run now that i know i am no longer part of a session
   }
}

Note: the Container will call the event-handling callbacks (valueBound() and valueUnbound()) when an instance of this class is added to or removed from a session.

Remember for HttpSessionListener and HttpSessionAtributeListener must be registered in the DD, since they are related to the session itself, rather than an individual attribute placed in the session.

Here are some more listener examples in action

Keep track of the number of active session in a web app

package com.example;
import javax.servlet.http.*;

public class CoffeeSessionCounter implements HttpSessionListener {

  static private int activeSessions;

  // deploy this in the WEB-INF/classes so all other servlets can have access to this method
  public static int getActiveSessions() {
    return activeSessions;
  }

  public void sessionCreated(HttpSessionEvent event) {
     activeSessions++;
  }

  public void sessionDestroyed(HttpSessionEvent event) {
     activeSessions-;
  }
}

## Configuring the listener in the DD
<web-app ...>
  ...
    <listener>
      <listener-class>com.example.CoffeeSessionCounter</listener-class>
    </listener>
</web-app>

Track if any attribute is added to, removed from or replaced in a session package com.example;
import javax.servlet.http.*;

public class CoffeeAttributeListener implements HttpSessionAttributeListener {

  public void attributeAdded(HttpSessionBindingEvent event) {

    String name = event.getName();
    Object value = event.getValue();

    System.out.println("Attribute added: " + name + ": + value);
  }

   public void attributeRemoved(HttpSessionBindingEvent event) {

    String name = event.getName();
    Object value = event.getValue();

    System.out.println("Attribute removed: " + name + ": + value);
  }

   public void attributeReplaced(HttpSessionBindingEvent event) {

    String name = event.getName();
    Object value = event.getValue();

    System.out.println("Attribute replaced: " + name + ": + value);
  }
}

## Configuring the listener in the DD
<web-app ...>
  ...
    <listener>
      <listener-class>com.example.CoffeeAttributeListener</listener-class>
    </listener>
</web-app>
Keep track of events that might be important to the attribute itself (session migration) package com.example;
import javax.servlet.http.*;
import java.io.*;

public class Dog implements HttpSessionBindingListener,
                            HttpSessionActivationistener,
                            Serializable
{

   private String breed;

   // Put you constructor and setter/getter methods here

   // the work bound means someone added this attribute to a session
   public void valueBound(HttpSessionBindingEvent event) {
      // code to run now that i know i'm in a session
   }

   public void valueUnBound(HttpSessionBindingEvent event) {
      // code to run now that i know i am no longer part of a session
   }

   // Notice the below method take an HttpSessionEvent
   public void sessionWillassivate(HttpSessionEvent event) {
      // code to get my non-serizable fields in a state that can survive the move to a new VM
   }

   public void sessionDidassivate(HttpSessionEvent event) {
      // code to restore my fields to redo whatever i undid in sessionWillPassivivate()
   }
}