Session实现原理深入讨论

1.Session实现机制
服务器是如何实现一个session为一个用户浏览器服务的?

Session原理示意图

解释:
假如浏览器A先访问Servlet1,这时候它创建了一个Session,ID号为110,然后Servlet1将这个ID号以Cookie的方式返回给浏览器A,接着,如果浏览器A继续访问Servlet2,那么这个请求会带上Cookie值: JSESSIONID=110,然后服务器根据浏览器A传递过来的ID号找到内存中的这个Session。
这时候假如浏览器B来访问Servlet1了,它的请求并没有带上 JSESSIONID这个Cookie值,由于它也要使用Session,所以服务器会新创建一个Session,ID号为119,并将这个ID号以Cookie的方式返回给浏览器B。之后的过程就同A了。

我们通过【Session(1)之基础知识】中的Servlet1和Servlet2来说明这个原理,其中Servlet1用于创建Session,并添加属性;Servlet2用于读取Session中的属性。
现在我们访问Servlet1,并抓包:

Set-Cookie

可以看到,Request Headers中并没有Cookie的信息,而Response Headers中有这么一句话:

Set-Cookie: JSESSIONID=2150AE444BF83FDACEA04BD0289F5AE2; Path=/Session1/; HttpOnly

说明这个时候服务器向客户端通过Cookie传递回了 JSESSIONID这个属性。

然后我们再访问Servlet2,抓包如下:

Cookie

可以看到Response Headers中没有出现Set-Cookie这个头,而Request Headers中带上了Cookie这个头:

Cookie: JSESSIONID=2150AE444BF83FDACEA04BD0289F5AE2

而这个头中正包含 JSESSIONID,并且它的值也就是我们之前Set-Cookie中 JSESSIONID的值。
这就证明了我们之前图解的Session的原理,也就是服务器能够为不同的浏览器区分不同的Session的机制。

但是这里又有一个问题:我们之前说过Cookie是多浏览器共享的,既然 JSESSIONID是以Cookie的方式保存于客户端的,那么为什么却不能为多个浏览器共享呢?

其实答案很简单,这个保存 JSESSIONID的Cookie是Session Cookie,而不是Persistent cookie。在学习Cookie的时候我们学到如何设置Cookie的生命周期,如果设置了Cookie的生命周期,那么这个Cookie就会被写入磁盘文件,并一直存放至它的生命周期结束之后,然后自动清除;而如果没有设置Cookie的生命周期,那么这个Cookie就是Session Cookie,也就是会话Cookie,它只存在于浏览器打开的这段时间,如果浏览器关闭,这个Cookie也就一并消失。
也就是说,Session Cookie是存在于浏览器的内存中的,浏览器关闭的时候它也就消失了。因此呢, JSESSIONID也是存在于Session Cookie中的,所以很自然的,它也就不能为多个浏览器共享!

那么有没有可能当关闭一个浏览器之后,再打开浏览器访问刚才的页面,而这个页面还可以保存着你的信息?比如,关掉IE后,再打开IE,上次购买的商品还在。

分析,假如我们没有在程序中设置Session的生命周期,所以它的生命周期是默认时间30min,那么如果浏览器关闭的话,根据前面介绍的Session实现机制,这时候服务器是不知道浏览器是否已经关闭的,也就是说,服务器端的Session仍然存在,而且会等到30min之后,才会被销毁。
那么如果这时候我们带上Cookie:JSESSIONID=2150AE444BF83FDACEA04BD0289F5AE2这个头,去访问刚刚浏览器关闭之前的页面,服务器依然是能够找到之前的Session,并给我们提供相关数据的。

通过上述分析,这个功能可以使用Session与Cookie的结合来能实现。

现在呢,让我们修改Servlet1和Servlet2。
在Servlet1中设置Session,并添加属性,然后将该Session的ID通过Cookie的方式回传给客户端,并且这个Cookie的name为“JSESSIONID”,value就是该Session的ID,这个ID可以直接通过session.getId()得到。,它的doGet方法如下:

public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    response.setContentType("text/html;charset=utf-8");
    PrintWriter out = response.getWriter();

    //创建一个session,默认生命周期为30min,并放入一个属性
    HttpSession session = request.getSession();
    session.setAttribute("name", "小明");
    out.println("创建Session成功,并放入了一个值");

    // 把该sessionid保存在cookie
    // 这个cookie的键为JSESSIONID
    Cookie cookie = new Cookie("JSESSIONID", session.getId());
    cookie.setMaxAge(3600);
    response.addCookie(cookie);
}

Servlet2保持不变,它依然只是去session中取出这个属性。实验可得,当关闭了浏览器之后,再次打开浏览器访问Servlet2,依然能够取出Session中的值。

测试

为什么呢,因为我们的Servlet1已经将JSESSIONID通过cookie的方式保存在了磁盘文件。理解了Session的实现机制,这个也就不难理解了。

2.用户禁用Cookie后的Session处理
这里存在一种情况,假如用户浏览器禁用了Cookie怎么办?比如我把Chrome的Cookie禁用,如下:

禁用Cookie

这时候,我们去访问一些大型的网站,比如购物网站,可以明显发现网站的很多功能已经无法正常实现。比如我去JD登录,服务器是无法获取我输入的验证码的,如下图所示:

JD提示

道理很简单,因为我禁用了Cookie,那么同样的,服务器端的Session也无法使用,因为Cookie不能保存JSESSIONID了。

说回【Servlet(2)之简单应用】中最后一个简易购物车的例子,如果我们禁用了Cookie,会发现购物车中每次都只有当前刚刚购买的一本书,而找不到之前购买的了,道理相同,那么这种情况该怎么解决呢?

解决方法:URL重写

Servlet中的response提供了对URL重写的方法:

response.encodeRedirectURL(String url) – 用于对sendRedirect方法后的url地址进行重写
response.encodeURL(String url) – 用于对表单action和超链接的url地址进行重写

那么url重写是什么意思呢?其实就是人为地把JSESSIONID附在了url的后面,比如我们修改之前写的简易购物车,ShowBook中所有的点击购买超链接都要重写。
之前我们是这么写的:

out.println("<tr><td>"+book.getName()+"</td><td><a href='/MyCart/BuyBookCl?id="+book.getId()+"'>点击购买</a></td></tr>");

现在进行URL重写:

request.getSession();
String url = "/MyCart/BuyBookCl?id="+book.getId();
url = response.encodeURL(url);
out.println("<tr><td>"+book.getName()+"</td><td><a href='"+url+"'>点击购买</a></td></tr>");

需要注意的是,重写之前一定要调用或者确保使用过request.getSession()这个方法。

我们可以通过源代码文件查看这些链接的区别。重写之前,访问ShowBook的源代码是这样的:

URL重写之前的ShowBook的源代码

重写之后呢:

URL重写之后的ShowBook的源代码

可以看到URL重写之后,jsessionid这个参数自动附在了url后面,由此,得以确保我们的Session在Cookie被禁用的情况下继续正常使用。这时候我们查看购物车的地址栏如下,可以明显看到jsessionid这个参数:

ShowMyCart
转自

发布了17 篇原创文章 · 获赞 24 · 访问量 28万+

猜你喜欢

转载自blog.csdn.net/qq_22956867/article/details/79415953