前面我们说Cookie可以用来跟踪客户端的行为,但是Cookie是Http协议的内容,JavaWeb也提供了一种可以进行会话跟踪的技术,即HttpSession,什么是会话?会话就是从用户打开浏览器,发出第一个请求之后,一个会话就开始,之后用户的一系列的请求都属于这个会话范围之内,直到用户关闭浏览器,会话也就结束了,注意HttpSession是JavaWeb提供的,与Http无关,Http协议本身是无状态的。
JavaWeb使用HttpSession来表示会话,服务器会为每一个会话创建一个唯一的HttpSession来表示这个会话,同时HttpSession也是一个域对象,在一个会话范围内的请求,HttpSession可被当前会话中所有servlet共享,如何得到HttpSesson:
public class AServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
HttpSession session = request.getSession();
session.setAttribute("name", "张三");
}
}
@WebServlet(name = "BServlet", urlPatterns = {"/BServlet"})
public class BServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
HttpSession session = request.getSession();
String name = (String) session.getAttribute("name");
System.out.println(name);
}
}
在AServlet中,我们向session中保存了一个键值对,在BServlet中,我们将session中的数据取出来,现在我们使用浏览器先访问AServlet,服务将调用AServlet的doGet方法来完成响应,即向HttpSession中添加数据,然后我们再请求BServlet,发现控制台能够打印出内容,因为这些请求是在一个会话的范围之内的,HttpRequest就做不到的,HttpRequest只能在请求转发或者包含的时候才能使用,客户端多个请求是不能使用同一个HttpRequest来存取数据的,而session可以跨多个请求,只要这些请求在一个会话中,当用户关闭浏览器器,会话就结束了。
我们重启浏览器再访问BServlet,控制台打印null,证明这个会话是一个新的会话,想要控制台打印内容,必须再次访问AServlet,然后再访问BServlet。
那么HttpSession是如何实现这一点的呢?,首先不考虑细节,先考虑逻辑上如何实现会话的概念,既然要实现会话,那么服务器端必须要有一种机制能够识别用户的身份,这不难,为 每一个用户分配一个唯一的标识符即可,但是服务器是如何知道会话已经结束了?实际上服务器永远都不可能知道客户端浏览器关闭了,所以我们也没法做到识别会话已经结束,一种折中的方法是超过一定时间,客户端没有访问服务器,服务器将自动结束会话,即删除长时间不活动的HttpSession,但是服务器还是需要是识别一个请求是否属于某一个会话,我们想到一种机制,当客户端第一次访问服务器时,我们可以给客户端设置一个唯一的Cookie,value的值是唯一的,Cookie的path是“/项目名”,即访问所有的服务器的资源都会带上这个Cookie,这样我们就可以通过Cookie的value识别出用户,还可以将这个Cookie的Max-Age设置为负值,即浏览器一旦关闭这个Cookie将不会被保存下来,那么在服务器端这个会话就会长时间不活动被删除,现在我们来验证猜想,首先重启浏览器,打开浏览器自带的调试工具,访问AServlet,查看响应头中是否包含上面所说的特性的Cookie,我们发现服务器给我们响应了响应头:
Set-Cookie:JSESSIONID=486878304FCEA714A84…; Path=/FirstWebApp; HttpOnly
这就是服务器用来完成会话功能所依赖的Cookie,其中Path=/FirstWebApp,这表示这个Cookie在访问任何服务器的资源时都会被带上,同时这个Cookie的过期时间是浏览会话结束时,会话一结束,这个Cookie也就被删除了,再次请求服务器时,就没有JSESSIONID了,所以所以我们可以根据请求头中是否包含JSESSIONID来判断请求是否在某一个会话范围内,当然服务器端也必须保存这个唯一的JSESSIONID,根据JSESSIONID找到特定的HttpSession对象。但是如果客户端请求服务器一次,然后就关闭了浏览器会话就结束了,但是服务器不知道会话结束了,所以服务器一直保存HttpSession对象是不明智的,当客户端长时间不发出请求时,超过一定的时间,服务器应该销毁为那个会话创建的HttpSessin以节省资源,在Tomcat中HttpSession的默认最大不活动时间为30分钟,我们也可以到web.xml文件中配置,来改变最大不活动时间:
<session-config>
<session-timeout>30</session-timeout>
</session-config>
我们还可以使用编程的方式来设置最大不活动时间:
调用HttpSession的setMaxInactiveInterval(int interval)方法即可设置,
下面是一些常用的方法:
- int getMaxInactiveInterval()可以获取session的最大不活动时间,
- String getId():返回JSESSIONID的值,32位字符串
- long getCreationTime():返回session创建的时间
- long getLastAccessedTime():session的最后活动的时间
- void invalidate():让session失效,这个方法非常有用,想象一个场景,用户登陆之后想要注销,此时我们就让这个会的session失效即可,即使浏览器再次请求服务器,服务器没有相应的session与之匹配,那么服务器将会创建一个新的session,
- boolean isNew():判断会话是否在当前请求期间创建,若方法返回值为真,意味这客户端尚未收到JSESSIONID
还有一个问题需要解决,服务器什么时候为会话创建session?当我们第一次访问服务器时,服务器为我们创建了session,第二次再访问时,服务器就不用为同一个会话再创建一个session了,我们来重复请求AServlet,看服务器是否会为一个同一个会话创建多个session,结果发现只有第一次时,响应头中才有包含JSESSION的Cookie,其余的响应都没有,AServlet中与sessin有关的代码就一句:
HttpSession session = request.getSession();
我们来分析一下这个getSession();方法做了什么
当我们第一次请求AServle时,服务器我们给响应了会话ID,可见getSession();方法,创建了一个会话ID,并将这个ID,当作Cookie的value响应给客户端了,
我们再次请求AServlet时,带上了这个ID,服务器端调用getSession()方法,但是这一次没有创建会话ID,那么这个方法肯定会首先查看请求头中是否包含JSESSIONID,如果不包含,创建新的session,设置Cookie,响应给客户端,如果包含ID,那么getSession()会检查所有的session中保存的ID是否与这个ID一致,如果一致,就返回这个session对象,如果没有任何一个sessin对象中保存的ID与客户端的ID一致,那么创建一个新的sessin,设置Cookie,响应给客户端。
以上就是getSession方法的功能,不过这些都只是根据getSession方法的特性所做出的推断而已,
getSession方法其实有还有一个重载版本
- getSession()
- getSession(boolean b)
getSession方法实际上是在调用getSession(true),而getSession(false)则有些不同了,它实现的是如下的逻辑:
- 若客户端的请求头中没有会话ID,那么方法返回null,即不创建新的会话。
- 若客户端的请求头中存在会话ID,那么方法将会检查现存的session中,是否有相匹配的,若存在相匹配的,则返回那个session对象,若没有相匹配的,则返回null
这个方法可以检查是否已经创建了会话,若创建了会话,则返回相关的session对象,若会话没有创建,则返回null,可以通过方法的返回值来判断是否已经创建了会话。我们来测试一下,修改AServlt和BServlet
public class AServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 这里使用了getSession(false)
HttpSession session = request.getSession(false);
System.out.println(session);
}
}
public class BServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
HttpSession session = request.getSession();
}
}
重启浏览器,再次请求AServlet,发现控制台打印null,先请求BServlet,然后再请求AServlet,发现控制台打印
===============================================================
考虑一个问题,当客户端禁用了Cookie了怎么办?我们如何如何进行会话跟踪?会话跟踪要求服务器必须能够识别出这个请求是否包含在某个会话的范围之内,换句话说,来自客户端的请求只要带上一个能够唯一标识会话的标识符就可以了,不一定非要使用Cookie,我们可以将会话ID添加到服务器每一个URL之后,无论是表单的提交,点击超链接,都带上会话ID就可以完成于Cookie同样的功能。
这种方法叫做URL重写,我们在URL之后保存sessionId,这需要在每个URL后面都加上sessionId!这样用户的请求中就包含了sessionId,服务器就可以通过sessionId找到对应的session对象了。
使用response.encodeURL()方法对URL进行编码,这样URL中会智能的添加sessionId。
当浏览器支持cookie时,response.encodeURL()方法不会在URL后追加sessionId
当浏览器不支持cookie时,response.encodeURL()方法会在URL后追加sessionId