12、Cookie & Session
Cookie & Session
会话的概念
日常生活来讲,会话就是两个人聊天.。聊天的前提,聊天双方需要有记忆力.。在聊的过程中,都是基于之前聊的状态,继续往下聊。
我们JavaWeb中,浏览器和服务器也可以看作是双方在聊天(请求,响应).。浏览器服务器双方也需要有"记忆力",保存之前的聊天状态。服务器和浏览器才可以完成会话。
会话的范围
两个从打招呼到两人互相道别。是一次会话。
打开网站,完成我们想做的需求,到关闭浏览器,是一次会话。
- Cookie: 让浏览器能够记录信息。
- Session: 让服务器端能够记录信息。
Cookie原理
第一次访问服务器时候,服务器响应时候,要求浏览器记住一个信息。response.addCookie(cookie);
以后每次访问服务器时候,携带第一次记住的信息访问。
下面是个简单的例子,Aservlet访问服务器,让浏览器记住Cookie,再让Bservlet访问服务器,这时候可以从浏览器将Cookie取出来。
Aservlet
package cookie;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Aservlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 浏览器的Response Headers里面可以看到这么一行。Set-Cookie:username=Tom
// 所以可以用如下写法
// response.addHeader("set-Cookie", "username=Tom");
// 当然更简单的方式是以下
Cookie cookie = new Cookie("username", "Tom");
response.addCookie(cookie);
}
}
Bservlet
package cookie;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Bservlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获得浏览器发来的全部cookie
Cookie[] cookies = request.getCookies();
Cookie nameCookie = null;
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("username".equals(cookie.getName())) {
nameCookie = cookie;
}
}
}
if (nameCookie != null) {
System.out.println(nameCookie.getName() + " : " + nameCookie.getValue());
} else {
System.out.println("未找到cookie");
}
}
}
Cookie细节
Cookie在浏览器存留时间
默认情况是关闭浏览器就会删除Cookie。如下图过期时间所示。
在发送cookie之前手动设置:
cookie.setMaxAge(60*60*24*7*2); //告诉浏览器保存2周
cookie.setMaxAge(-1); // -1代表 在会话结束时删除cookie(默认情况)
cookie.setMaxAge(0); // 通常用于删除已经存在的cookie.使用一个寿命为0的cookie,覆盖要删除的cookie
浏览器在什么情况下发送cookie(cookie的路径问题)
如果 cookie路径是"/cookie_session", 主机(域)是:localhost . 那么浏览器在访问cookie路径的所有子路径时会携带cookie。
http://localhost:8080/cookie_session/BServlet 会发送
http://localhost:8080/cookie_session/ABC/BCD/CServlet 会发送
http://localhost:8080/else/AServlet 不会发送(项目名)
http://www.baidu.com/cookie_session/BServlet 不会发送(主机)
Cookie路径的设置
cookie的默认路径就是 发送cookie的动态资源所在的上一级路径。比如这里/cookie_session/AServlet
,是Aservlet发送的,那么默认就是上一级/cookie_session
。
手动设置:cookie.setPath("/cookie_session/abc");
cookie的主机设置
cookie的主机(域)设置 (了解即可)
默认情况: 发送Cookie的资源所在主机。
手动设置:
//自己当前是什么主机,你就只能设置为什么主机,或者主机名的一部分
// 假设自己的主机是www.baidu.com那么可以如下设置:
c.setDomain(".baidu.com");
Cookie的删除
删除cookie原理就是 使用一个寿命为0的cookie 覆盖需要删除的cookie。c.setMaxAge(0);
覆盖cookie要求?
需要路径、键、主机 一致,即可覆盖。
使用cookie记录中文键值对问题
为什么不能直接使用中文?
因为http协议中,除正文部分都不得使用Latin码表以外的其他码表。所以不管是cookie还是之前的Cotent-disposition。都不能直接使用中文。
- 使用 URLEncoder 对中文进行url编码。
URLEncoder.encode("中文", "UTF-8")
- 在获取cookie时,使用URLDecoder进行解码。
URLDecoder.decode("中文", "UTF-8")
。
使用cookie 记录浏览历史
先做一个工具类,按填入的参数获取指定Cookie。
package cookie;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
public class CookieUtils {
public static Cookie getCooieByName(HttpServletRequest req, String key) {
Cookie cookie = null;
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie c : cookies) {
if (key.equals(c.getName())) {
cookie = c;
}
}
}
return cookie;
}
}
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'list.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<a href="/cookie_session/Cservlet?name=dell">dell</a> <br />
<a href="/cookie_session/Cservlet?name=lenovo">lenovo</a> <br />
<a href="/cookie_session/Cservlet?name=hp">hp</a> <br />
<a href="/cookie_session/Cservlet?name=acer">acer</a> <br />
<a href="/cookie_session/Cservlet?name=apple">apple</a> <br />
浏览历史:${cookie.history.value }
</body>
</html>
package cookie;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Cservlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 使用cookie 记录浏览历史.
// 1.获得参数,地址栏的name=lenovo等
String name = request.getParameter("name");
// 2.获得Cookie
Cookie history = CookieUtils.getCooieByName(request, "history");
//存在 => 修改cookie加上现在浏览器的品牌
if (history != null) {
// 重复点击不会继续增加
if (!history.getValue().contains(name)) {
// 需要注意的是,高版本的Tomcatcookie里不支持“,”逗号。换成其他符号
history = new Cookie("history", history.getValue()+"-"+name);
}
} else {
//不存在 => 创建cookie
history = new Cookie("history", name);
}
// 3.将cookie发送会浏览器
response.addCookie(history);
// 4.重定向到列表页面
response.sendRedirect("/cookie_session/history/list.jsp");
// /cookie_session/WebRoot/history/list.jsp
}
}
效果是每点击一次,浏览历史里面就新增一个,重复点击不会继续增加。
记住用户名和密码
流程如下。
登录界面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'login.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<form action="/cookie_session/Dservlet" >
用户名:<input type="text" name="name" value="${cookie.remember.value}" />
<font color="red">${requestScope.error}</font>
<br>
密码:<input type="text" name="password" /><br>
<input type="checkbox" name="remember" value="yes" ${cookie.remember==null?"":"checked=checked"} />记住用户名<br>
<input type="submit" value="登录" />
</form>
</body>
</html>
Dservlet
package cookie;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Dservlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 1.获得用户名密码
String name = request.getParameter("username");
// 2.校验用户名密码
if (name == null || "".equals(name.trim())) {
// //失败=> 转发到表单页面,并提示错误
request.setAttribute("error", "请输入用户名!"); request.getRequestDispatcher("/remember/login.jsp").forward(request, response);
// 转发后不再继续处理逻辑
return;
}
// 3.创建cookie 添加要保存的用户名,
Cookie c = new Cookie("remember", name);
// 4.查看记住用户名是否被选中
String remember = request.getParameter("remember");
//选中 => 设置保存时间为2周
if ("yes".equals(remember)) {
c.setMaxAge(60*60*24*7*2);
} else {
//没选中=>设置保存事件为0
c.setMaxAge(0); // 清除cookie
}
// 5.添加到响应中
response.addCookie(c);
// 6.重定向到成功页面
response.sendRedirect("/cookie_session/index.jsp");
}
}
Session原理
获取session时候,request.getSession();
若浏览器第一次访问,服务器会响应一个cookie给浏览器。这个cookie记录的就是sessionId,tomcat生成的sessionid叫做jsessionid。以后每次访问携带着这个sessionId,在服务器里找这个sessionId。找到后就可以获取这个sessionId里面的数据。
package session;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class Gservlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//参数类型是boolean型
//true=> 无论如何都要获得session
//false => 如果没有sessionID ,不会获得session
//request.getSession(true);
HttpSession session = request.getSession();//相当于上面的方法 填写true
// 上面的方法执行后,第一次访问服务器时候,服务器会响应一个cookie给浏览器。这个cookie记录的就是sessionId,tomcat生成的sessionid叫做jsessionid。
// 比如Set-Cookie:JSESSIONID=44D210CF971936BF86AACD72DED04A82; Path=/cookie_session
// 再次访问这个网址,携带这个cookie。如Cookie:JSESSIONID=44D210CF971936BF86AACD72DED04A82
}
}
session的操作
getAttribute();
setAttribute();
removeAttribute();
getAttributeNames();
session的细节
session能在服务器端保存多久?
<session-config>
<session-timeout>30</session-timeout>
</session-config>
在tomcat/conf/web.xml 有如上配置。该配置决定了session对象的有效存活时间为30分钟。
在我们项目的web.xml中, 也可以加上如上配置.区别就是影响的范围不同。在项目中配只影响当前项目.
3.(了解)。在session对象中,还有如下方法. 该方法也是控制session对象 的有效存活时间的,单位是秒。范围是只影响调用该方法的某个session.
void setMaxInactiveInterval(int interval)
session的范围问题
浏览器第一次访问服务器,服务器创建session对象,会话开始。
三种方式销毁session
- 浏览器关闭,保存sessionID的cookie丢失。(Cookie默认在浏览器关闭时候过期) 会话结束。(session还在服务器中,等死)
- session到了过期时间(默认30分钟无操作)
- 手动销毁session
session中的其他方法
package session;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class Hservlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession();
System.out.println("session.isNew()"+session.isNew());// 判断session是否 是新的.
System.out.println("session.getCreationTime()"+new Date(session.getCreationTime()));//获得session的创建时间
System.out.println("session.getId()"+session.getId());//获得session的id
System.out.println("session.getLastAccessedTime()"+new Date(session.getLastAccessedTime()));//获得最后一次的访问时间
System.out.println("session.getMaxInactiveInterval()"+session.getMaxInactiveInterval());// 获得session的最大有效时间
session.setMaxInactiveInterval(60);//设置session的最大有效时间为60秒
session.invalidate();//需要记住: 立即让session销毁。
// 所以每次访问都会创建新的session,isNew打印true,ID也是新的,创建时间和上次访问时间也是最新的
}
验证码例子
为什么要用session域来存放验证码的正确code?
- 如果用request域,则开始会向专门生成验证码图片的Aservlet发送请求,正确的code在这个request域中,当我们点击登录,又是一个新的请求,这个新的request里面根本就没有正确code,也不存在和用户自己输入的code进行比较一说。
- 如果用的application域:Application 的作用范围在服务器一开始执行服务,到服务器关闭为止Application 的范围最、停留的时间也最久。一个web项目只有一个,考虑到线程不安全,所以多个用户共享同一个域,后面的会把前面的覆盖。
session在会话期间会一直保持着正确的code,所以session域是最合适的。
登录界面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'login.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<script type="text/javascript">
function fun1() {
// 获得img标签对象
var img = document.getElementById("one");
// 改变src属性,由于Iservlet没有参数,所以这里随意填,用Date的目的是保证每次访问的唯一性
img.src = "/cookie_session/Iservlet?date="+new Date();
}
</script>
<form action="/cookie_session/Jservlet" >
用户名:<input type="text" name="name" value="${cookie.remember.value}" /> <br>
密码:<input type="text" name="password" /><br>
验证码:<input type="text" name="code" size=4 /> ![](/cookie_session/Iservlet)
<!-- javascript:void(0)什么也不做,让fun1完成 -->
<a href="javascript:void(0)" onclick="fun1();">看不清,换一张</a>
<font color="red">${requestScope.error}</font>
<br />
<input type="checkbox" name="remember" value="yes" ${cookie.remember==null?"":"checked=checked"} />记住用户名<br>
<input type="submit" value="登录" />
</form>
</body>
</html>
专门用于生成验证码的Iservlet,注意cn.dsna.util.images.ValidateCode
用的是第三方包。
package session;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.dsna.util.images.ValidateCode;
public class Iservlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 要项浏览器正文输入时,必须指定类型
// response.setContentType("image/jpeg");
// 1. 生成验证码
ValidateCode code = new ValidateCode(120, 40, 4, 2);
// 2. 将正确答案放进Session
String correctCode = code.getCode();
request.getSession().setAttribute("code", correctCode);
// 3. 将图片发送给浏览器
System.out.println(correctCode);
code.write(response.getOutputStream());
}
}
点击登录跳转到Jservlet,取出用户输入的code和session域存放的正确code比较。
package session;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Jservlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 1. 获得用户填写的验证码
String userCode = request.getParameter("code");
// 2. 获得session中的正确验证码
String correctCode = (String) request.getSession().getAttribute("code");
// 3. 比较两者
if (userCode != null && userCode.equalsIgnoreCase(correctCode)) {
// 验证成功,重定向到登录成功的页面
response.sendRedirect("/cookie_session/index.jsp");
} else {
request.setAttribute("error", "请输入正确的验证码!");
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
}
如上图,验证码填错了会转发到登录界面。
注意是转发过去是一次请求,转发过去还是刚才的jsp页面。${requestScope.error}才能收到request.setAttribute("error", "请输入正确的验证码!");
的错误信息;如果是重定向又重新请求了,重新加载jsp,不会显示错误信息。
购物车例子
商城商品页面。list.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'index.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<h1>商品列表</h1>
<table border="1" >
<tr>
<td>
肥皂<br>
<a href="/cookie_session/Kservlet?name=0" >点击加入购物车</a>
</td>
<td>
蜡烛<br>
<a href="/cookie_session/Kservlet?name=1" >点击加入购物车</a>
</td>
</tr>
<tr>
<td>
被罩<br>
<a href="/cookie_session/Kservlet?name=2" >点击加入购物车</a>
</td>
<td>
枕头<br>
<a href="/cookie_session/Kservlet?name=3" >点击加入购物车</a>
</td>
</tr>
</table>
<a href="/cookie_session/cart.jsp" >查看购物车</a>
</body>
</html>
购物车详情,cart.jsp
<%@page import="java.util.Map.Entry"%>
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'cart.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<h1>购物车</h1>
<table border="1">
<tr>
<th>商品名称</th>
<th>商品数量</th>
</tr>
<%
Map<String,Integer> cart = (Map<String,Integer>)session.getAttribute("cart");
if(cart!=null && cart.size()>0){
for(Entry<String,Integer> en : cart.entrySet()){
%>
<tr>
<td><%=en.getKey() %></td>
<td><%=en.getValue() %></td>
</tr>
<% }
}
%>
</table>
</body>
</html>
Kservlet
package session;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Kservlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//0 准备数组,与页面商品对应
String[] products = {"肥皂","蜡烛","被罩","枕头"};
// 1. 获得当前请求要添加的商品
String indexStr = request.getParameter("name");// 0 ~ 3
String productName = products[Integer.parseInt(indexStr)];
// 2.从Session中取出存放购物车的map
Map<String,Integer> cart = (Map<String, Integer>) request.getSession().getAttribute("cart");
if(cart==null){
// //取不到=>初始化map,LinkedHashMap保证存入和取出的顺序相同,因为HashMap是无序的
cart = new LinkedHashMap<String, Integer>();
//并放入session
request.getSession().setAttribute("cart", cart);
}
// 3.使用当前要添加的商品从map中取值,默认数量为1
Integer count = cart.put(productName, 1);
// 不为空说明值被覆盖,加一。为空说明上面设置为1是正确的
if(count!=null){
cart.put(productName, count+1);
}
//取不到=> 放入map,设置数量为1
//取得到=> 将数量+1放回去
// 4.重定向回商品列表页面
response.sendRedirect("/cookie_session/list.jsp");
}
}
注意:后退或者刷新,再次选择物品、购物车依然会累计。但是当浏览器关闭后,再次访问购物车就清零了。因为cookie被销毁,sessionId已经没有了,再去服务器找的时候是没有携带的,需要新建。
Cookie和Session的区别
- session 在服务器端,cookie 在客户端(浏览器)
- session 默认被存在在服务器的一个文件里(不是内存)
- session 的运行依赖 sessionId,而 sessionId 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie ,同时 session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 sessionId)
- session 可以放在 文件、数据库、或内存中都可以。
- 用户验证这种场合一般会用 session
因此,维持一个会话的核心就是客户端的唯一标识,即 sessionId。
by @sunhaiyu
2017.3.31