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
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()) { |
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); Note: this code is the same as the "Existing session?" one, but more inefficient |
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>"); |
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.
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 ...> 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. |
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.*; |
JSP to render the view from the above servlet | # Create the below file displayCookie.jsp |
Getting the cookie | # Create file called getCookie.java and compile it import javax.servlet.*; |
Servlet mapping | # copy below in the WEB-INF/web.xml file <display-name>Cookie Application</display-name> <servlet-mapping> <servlet-mapping>
|
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; 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; ## Configuring the listener in the DD |
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() } } |