JavaWeb(六):会话与状态管理
HTTP协议是一种无状态的协议,WEB服务器本身不能识别出哪些请求是同一个浏览器发出的 ,浏览器的每一次请求都是完全孤立的。即使 HTTP1.1 支持持续连接,但当用户有一段时间没有提交请求,连接也会关闭。怎么才能实现网上商店中的购物车呢:某个用户从网站的登录页面登入后,再进入购物页面购物时,负责处理购物请求的服务器程序必须知道处理上一次请求的程序所得到的用户信息。
作为 web 服务器,必须能够采用一种机制来唯一地标识一个用户,同时记录该用户的状态。
WEB应用中的会话是指一个客户端浏览器与WEB服务器之间连续发生的一系列请求和响应过程。WEB应用的会话状态是指WEB服务器与浏览器在会话过程中产生的状态信息,借助会话状态,WEB服务器能够把属于同一会话中的一系列的请求和响应过程关联起来。
WEB服务器端程序要能从大量的请求消息中区分出哪些请求消息属于同一个会话,即能识别出来自同一个浏览器的访问请求,这需要浏览器对其发出的每个请求消息都进行标识:属于同一个会话中的请求消息都附带同样的标识号,而属于不同会话的请求消息总是附带不同的标识号,这个标识号就称之为会话ID(SessionID)。
在 Servlet 规范中,常用以下两种机制完成会话跟踪
- Cookie
- Session
一、Cookie
cookie机制采用的是在客户端保持 HTTP 状态信息的方案,Cookie是在浏览器访问WEB服务器的某个资源时,由WEB服务器在HTTP响应消息头中附带传送给浏览器的一个小文本文件。一旦WEB浏览器保存了某个Cookie,那么它在以后每次访问该WEB服务器时,都会在HTTP请求头中将这个Cookie回传给WEB服务器。
底层的实现原理: WEB服务器通过在HTTP响应消息中增加Set-Cookie响应头字段将Cookie信息发送给浏览器,浏览器则通过在HTTP请求消息中增加Cookie请求头字段将Cookie回传给WEB服务器。
一个Cookie只能标识一种信息,它至少含有一个标识该信息的名称(NAME)和设置值(VALUE)。一个WEB站点可以给一个WEB浏览器发送多个Cookie,一个WEB浏览器也可以存储多个WEB站点提供的Cookie。浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。
1.1 Cookie常用API
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" session="false"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <% //在 JavaWEB 规范中使用 Cookie 类代表 cookie //1. 获取 Cookie Cookie [] cookies = request.getCookies(); if(cookies != null && cookies.length > 0){ for(Cookie cookie: cookies){ //2. 获取 Cookie 的 name 和 value out.print(cookie.getName() + ": " + cookie.getValue()); out.print("<br>"); } }else{ out.print("没有一个 Cookie, 正在创建并返回"); //1. 创建一个 Cookie 对象 Cookie cookie = new Cookie("name", "aidata"); //setMaxAge: 设置 Cookie 的最大时效, 以秒为单位, 若为 0 , 表示立即删除该 Cookie //若为负数, 表示不存储该 Cookie, 若为正数, 表示该 Cookie 的存储时间. cookie.setMaxAge(30); //2. 调用 response 的一个方法把 Cookie 传给客户端. response.addCookie(cookie); } %> </body> </html>
Servlet API中提供了一个javax.servlet.http.Cookie类来封装Cookie信息,它包含有生成Cookie信息和提取Cookie信息的各个属性的方法。
Cookie类的方法:
- 构造方法: public Cookie(String name,String value)
- getName方法
- setValue与getValue方法
- setMaxAge与getMaxAge方法
- setPath与getPath方法
HttpServletResponse接口中定义了一个addCookie方法,它用于在发送给浏览器的HTTP响应消息中增加一个Set-Cookie响应头字段。
HttpServletRequest接口中定义了一个getCookies方法,它用于从HTTP请求消息的Cookie请求头字段中读取所有的Cookie项。
Cookie的发送
1.创建Cookie对象
2.设置最大时效
3.将Cookie放入到HTTP响应报头
如果创建了一个cookie,并将他发送到浏览器,默认情况下它是一个会话级别的cookie; 存储在浏览器的内存中,用户退出浏览器之后被删除。若希望浏览器将该cookie存储在磁盘上,则需要使用maxAge,并给出一个以秒为单位的时间。将最大时效设为0则是命令浏览器删除该cookie。
发送cookie需要使用HttpServletResponse的addCookie方法,将cookie插入到一个 Set-Cookie HTTP响应报头中。由于这个方法并不修改任何之前指定的Set-Cookie报头,而是创建新的报头,因此将这个方法称为是addCookie,而非setCookie。
会话Cookie和持久Cookie
如果不设置过期时间,则表示这个cookie生命周期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不保存在硬盘上而是保存在内存里。
如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie依然有效直到超过设定的过期时间。
存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存的cookie,不同的浏览器有不同的处理方式。
Cookie的读取
1.调用request.getCookies
要获取浏览器发送来的cookie,需要调用HttpServletRequest的getCookies方法,这个调用返回Cookie对象的数组,对应由HTTP请求中Cookie报头输入的值。
2.对数组进行循环,调用每个cookie的getName方法,直到找到感兴趣的cookie为止
1.2 利用Cookie自动登陆
不需要填写用户名和密码等信息,可以自动登录到系统。
登陆页面
login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <form action="index.jsp" method="post"> name: <input type="text" name="name"/> <input type="submit" value="Submit"/> </form> </body> </html>
登陆会转向index.jsp
- 若获取的请求参数 loginName不为空, 则打印出欢迎信息。把登录信息存储到 Cookie 中,并设置 Cookie 的最大时效为 30S
- 从 Cookie 中读取用户信息,若存在则打印欢迎信息
- 若既没有请求参数,也没有 Cookie,则重定向到 login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <% //若可以获取到请求参数 name, 则打印出欢迎信息。把登录信息存储到 Cookie 中,并设置 Cookie 的最大时效为 30S String name = request.getParameter("name"); if(name != null && !name.trim().equals("")){ Cookie cookie = new Cookie("name", name); cookie.setMaxAge(30); response.addCookie(cookie); }else{ //从 Cookie 中读取用户信息,若存在则打印欢迎信息 Cookie [] cookies = request.getCookies(); if(cookies != null && cookies.length > 0){ for(Cookie cookie : cookies){ String cookieName = cookie.getName(); if("name".equals(cookieName)){ String val = cookie.getValue(); name = val; } } } } if(name != null && !name.trim().equals("")){ out.print("Hello: " + name); }else{ //若既没有请求参数,也没有 Cookie,则重定向到 login.jsp response.sendRedirect("login.jsp"); } %> </body> </html>
1.3 利用Cookie显示最近浏览的商品
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.*"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h4>Books Page</h4> <a href="book.jsp?book=JavaWeb">Java Web</a><br><br> <a href="book.jsp?book=Java">Java</a><br><br> <a href="book.jsp?book=Oracle">Oracle</a><br><br> <a href="book.jsp?book=Ajax">Ajax</a><br><br> <a href="book.jsp?book=JavaScript">JavaScript</a><br><br> <a href="book.jsp?book=Android">Android</a><br><br> <a href="book.jsp?book=Jbpm">Jbpm</a><br><br> <a href="book.jsp?book=Struts">Struts</a><br><br> <a href="book.jsp?book=Hibernate">Hibernate</a><br><br> <a href="book.jsp?book=Spring">Spring</a><br><br> <br><br> <% //显示最近浏览的 5 本书 //获取所有的 Cookie Cookie [] cookies = request.getCookies(); //从中筛选出 Book 的 Cookie:如果 cookieName 为 ATGUIGU_BOOK_ 开头的即符合条件 //显示 cookieValue if(cookies != null && cookies.length > 0){ for(Cookie c: cookies){ String cookieName = c.getName(); if(cookieName.startsWith("AIDATA_BOOK_")){ out.println(c.getValue()); out.print("<br>"); } } } %> </body> </html>
显示最近浏览的 5 本书
获取所有的 Cookie
从中筛选出 Book 的 Cookie:如果 cookieName 为 AIDATA_BOOK_ 开头的即符合条件
显示 cookieValue
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.*"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h4>Book Detail Page</h4> Book: <%= request.getParameter("book") %> <br><br> <a href="books.jsp">Return</a> <% String book = request.getParameter("book"); //把书的信息以 Cookie 方式传回给浏览器,删除一个 Cookie //1. 确定要被删除的 Cookie: //前提: AIDATA_BOOK_ 开头的 Cookie 数量大于或等于 5, Cookie [] cookies = request.getCookies(); //保存所有的 AIDATA_BOOK_ 开头的 Cookie List<Cookie> bookCookies = new ArrayList<Cookie>(); //用来保存和 books.jsp 传入的 book 匹配的那个 Cookie Cookie tempCookie = null; if(cookies != null && cookies.length > 0){ for(Cookie c: cookies){ String cookieName = c.getName(); if(cookieName.startsWith("AIDATA_BOOK_")){ bookCookies.add(c); if(c.getValue().equals(book)){ tempCookie = c; } } } } //①. 且若从 books.jsp 页面传入的 book 不在 AIDATA_BOOK_ 的 Cookie 中则删除较早的那个 Cookie //( ATGUIGU_BOOK_ 数组的第一个 Cbookie), if(bookCookies.size() >= 5 && tempCookie == null){ tempCookie = bookCookies.get(0); } //②. 若在其中,则删除该 Cookie if(tempCookie != null){ tempCookie.setMaxAge(0); response.addCookie(tempCookie); } //2. 把从 books.jsp 传入的 book 作为一个 Cookie 返回 Cookie cookie = new Cookie("AIDATA_BOOK_" + book, book); response.addCookie(cookie); %> </body> </html>
把书的信息以 Cookie 方式传回给浏览器,删除一个 Cookie
确定要被删除的 Cookie: AIDATA_BOOK_ 开头的 Cookie 数量大于或等于 5,且若从 books.jsp 页面传入的 book 不在 ATGUIGU_BOOK_ 的 Cookie 中则删除较早的那个 Cookie( AIDATA_BOOK_ 数组的第一个 Cbookie),若在其中,则删除该 Cookie
把从 books.jsp 传入的 book 作为一个 Cookie 返回
1.4 设置Cookie作用路径
功能:
帮助网站实现提示客户端计算机上次访问网站的时间
实现原理:
将每一个会话作为一次访问过程,将每次会话的开始时间作为每次访问网站的时间,然后将这个时间以Cookie的形式存储到客户端的计算机中,客户端进行下次访问时通过该Cookie回传上次访问站点的时间值。
为了让Cookie信息在客户端浏览器或计算机关闭后仍然保持存在,Cookie的保存时间被设置为了一年。