JavaWeb之cookie&session
一、会话概述
日常生活中:从拨通电话到挂断电话之间的一连串你问我答的过程就是一个会话。
B/S架构中 : 从浏览器第一次给服务器发送请求时,建立会话;直到有一方断开,会话结束。
一次会话:包含多次请求响应。
会话中出现的问题:由于Http是一个无状态协议,不会记录每次请求的状态,这就造成了同一个会话的两请求之间相互独立,彼此没有联系。
此时就需要一种技术在客户端或者服务端,记录会话过程中产生的一些数据,这就是会话管理技术。
两种会话管理技术:
cookie
:在一次会话的多次请求响应之间共享数据,将数据保存到客户端(浏览器)session
:在一次会话的多次请求之间共享数据,它将数据保存到服务器
二、cookie
cookie
:利用客户端保存共享信息实现多次请求的数据共享。
使用cookie需要导入javax.servlet.http.Cookie
。
2.1 cookie的属性
name
cookie的名称value
cookie的值domain
cookie的域名,默认值是访问服务的主机名称path
cookie的路径,默认值是应用的发布URI(发布名称/发布路径)maxAge
cookie的存活时间,0表示立即销毁comment
描述信息 (无关紧要的属性)version
版本号 (无关紧要的属性)
2.2 cookie相关API
方法名 | 作用 |
---|---|
Cookie cookie = new Cookie(name,value) | 创建Cookie对象 |
response.addCookie(Cookie c) | 将cookie写回浏览器 |
cookie.getName() | 获取cookie名 |
cookie.getValue() | 获取cookie的值 |
Cookie [] cookis = request.getCookies() | 获取cookie数组 |
cookie.setMaxAge(秒) | 设置cookie的有效时间 |
cookie.setPath("/path") | 设置cookie再次访问服务器时,自动携带cookie的依据 |
客户端带cookie到服务器机制:
请求资源URI去掉资源的部分之后和Cookie的Path进行比较,请求资源URI去掉资源的部分.startWith(cookiePath)
,结果为true,客户端就会带着cookie访问浏览器;如果为false,客户端访问浏览器就不会带cookie。
coolkiePath /cookieandsession/servlet
PathQuestionDemo2 访问URI: /cookieandsession/PathQuestionDemo2
URI去掉资源的部分: /cookieandsession
PathQuestionDemo3 访问URI: /cookieandsession/servlet/PathQuestionDemo3
URI去掉资源的部分: /cookieandsession/servlet
在上面的案例中,只有PathQuestionDemo3
访问服务器会带着cookie
。
2.3 cookie的一些细节
- 一个服务器在浏览器端最多支持20个Cookie
- 所有服务器在浏览器端最多可以写300个Cookie
- 每个Cookie的大小不能超过4KB
- 定位一个cookie,domain+path+name
2.4 使用cookie完成购物车案例
xml配置:
<servlet>
<servlet-name>CartServlet</servlet-name>
<servlet-class>com.itcast.cart.CartServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CartServlet</servlet-name>
<url-pattern>/cart</url-pattern>
</servlet-mapping>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>购物车</title>
</head>
<body>
<a href="/cookieandsession/cart?action=showProductList">查看商品列表</a>
<hr/>
<a href="/cookieandsession/cart?action=showCartInfo">查看购物车</a>
</body>
</html>
public class CartServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置请求正文字符集
req.setCharacterEncoding("UTF-8");
//设置响应正文的MIME类型和字符集
resp.setContentType("text/html;charset=UTF-8");
//获取请求参数
String action = req.getParameter("action");
if ("showProductList".equals(action)){
showProductList(req,resp);
}else if ("showCartInfo".equals(action)){
showCartInfo(req,resp);
}else {
System.out.println("无信息");
}
//获取请求参数
String productName = req.getParameter("productName");
if (productName != null){
addCart(req,resp,productName);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
private void showProductList(HttpServletRequest req, HttpServletResponse resp){
PrintWriter out = null;
try {
//获取输出流
out = resp.getWriter();
} catch (IOException e) {
e.printStackTrace();
}
//输出商品列表
out.write("01.Xiaomi <a href='"+req.getContextPath()+"/cart?action=showProductList&productName=Xiaomi'>添加购物车</a><br/>");
out.write("02.Huawei <a href='"+req.getContextPath()+"/cart?action=showProductList&productName=Huawei'>添加购物车</a><br/>");
out.write("03.Lianxiang <a href='"+req.getContextPath()+"/cart?action=showProductList&productName=Lianxiang'>添加购物车</a><br/>");
}
private void showCartInfo(HttpServletRequest req, HttpServletResponse resp){
PrintWriter out = null;
try {
//获取输出流
out = resp.getWriter();
} catch (IOException e) {
e.printStackTrace();
}
out.write("<h1>购物车的详情:</h1>");
//获取Cookie
Cookie[] cookies = req.getCookies();
//4.判断
if(cookies == null || cookies.length == 0){
out.write("您还没有添加商品到购物车");
}else {
for (Cookie cookie : cookies){
//判断
if("CartInfo".equals(cookie.getName())){
//找到了我们想要的cookie
String value = cookie.getValue();//Xiaomi-Xiaomi-Lianxiang
//分隔
String[] productNames = value.split("-");
//遍历名称
for(String productName : productNames){
out.write(productName+"<br/>");
}
}
}
}
}
private void addCart(HttpServletRequest req, HttpServletResponse resp,String productName){
PrintWriter out = null;
try {
//取出输出流
out = resp.getWriter();
} catch (IOException e) {
e.printStackTrace();
}
//取出Cookie
Cookie[] cookies = req.getCookies();
//定义要写到客户端的Cookie
Cookie cookie = null;
//判断,如果没有Cookies就是第一次
if(cookies == null || cookies.length == 0){
cookie = new Cookie("CartInfo",productName);
}else {
//遍历
for(Cookie c : cookies){
//判断名称
if("CartInfo".equals(c.getName())){
cookie = c;
break;
}
}
//给cookie拼值,就是在原来的value上,再加上新的value(productName)
String value = cookie.getValue()+"-"+productName;
cookie.setValue(value);
}
//设置最大存活时间
cookie.setMaxAge(Integer.MAX_VALUE);
//把cookie写到浏览器
resp.addCookie(cookie);
//提示
out.write("<hr size='7px' color='orange'>");
out.write("添加成功。<a href='"+req.getContextPath()+"/cart?action=showCartInfo'>查看购物车</a>");
}
}
三、session
3.1 session概述
session:在一次会话的多次请求之间共享数据,将数据保存到服务器端
在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),注意:一个浏览器独占一个session对象(默认情况下)。因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的session中,当用户使用浏览器访问其它程序时,其它程序可以从用户的session中取出该用户的数据,为用户服务。
Session对象由服务器创建,开发人员可以调用request对象的getSession方法得到session对象。
使用session需要导入javax.servlet.http.HttpSession
。
3.2 session的基本使用
3.2.1 获取session的方法
HttpSession session = request.getSession();
无论之前有还是没有session,都会有返回一个session,若之前的找到了就使用之前的session;若之前的session没有找到,就创建一个sessionHttpSession session = request.getSession(boolean create);
当create为true
时,与request.getSession()
使用情况一致;当create为false
时,若之前的找到了就使用之前的session;若之前的session没有找到,就返回null
3.2.2 会话域
HttpSession也是一个域对象,它表示的是会话域,作用范围是一次会话之中(包含多次请求)。
域对象常用api:
getAttribute(String name)
通过属性名获取域对象的属性setAttibute(String name , Object obj)
设置域对象的属性removeAttribtue(String name)
通过属性名移除域对象的属性
session生命周期:
- 创建:第一次调用request.getSession() 创建session对象
- 销毁:
1.关闭浏览器,会话结束;
2.超过一定时间没有对网站有任何操作时,会话超时结束,默认时间是30分钟;
3.手动调用 session.invalidate() 销毁session对象;
4.服务器非正常关闭销毁对象(关机 死机 蓝屏等)
正常关闭服务器将session保存到本地:
正常关闭session没有销毁 而是保存到本地 , 再次启动服务器时会加载本地文件到内存中。
3.2.3 HttpSession原理
HttpSession是利用了Cookie实现的,其本质是一个特殊的Cookie。
Cookie名称是固定的:JSESSIONID
,Cookie的值就是HttpSession的唯一标识。
3.2.4 cookie和session的区别
- cookie : 存入浏览器 cookie的值存储是有限制 不安全
- session: 存入服务器 session存储没有限制 相对安全
如果cookie被清除了,session还在,只不过无法直接找到session, 可以通过url重写地址栏可以找回来的。
如果浏览器禁用cookie,session还可以使用,session的底层基于cookie 但不完全依赖cookie 有其他的方式可以替代。
session是一个域对象 , 为什么cookie不是域对象?因为域对象指的是服务器端的技术,而cookie是浏览器端的技术。
3.2.5 持久化级别session
通过创建cookie,可以持久化保存session
//创建session对象
HttpSession session = request.getSession();
//自己创建cookie
Cookie cookie = new Cookie("JSESSIONID" , session.getId());
cookie.setMaxAge(60*60*7);
response.addCookie(cookie);
System.out.println("session地址值:" + session);
System.out.println("session的id为:" + session.getId());
System.out.println("session是否为新创建的:" + session.isNew());
3.2.6 session的使用细节
session的活化与钝化:
- 钝化:当应用停止服务时,服务器会把Session域中内容序列化到磁盘上
- 当应用再次启动时,服务器会把磁盘上的.ser文件内容加载回内存中
前提:
- 正常关闭
- 存入的是对象必须实现序列化接口
客户端禁用Cookie的会话保持:
3. 文字提示,请不要禁用您的Cookie
4. URL重写,在访问地址后面拼接一个JSESSIONID(不推荐使用)
encodeUrl(url)
:其他的连接使用此方法endcodeRedirectUrl(url)
:重定向的连接就使用此方法
//获取session
HttpSession session = request.getSession();
session.setAttribute("username","url重写");
String url=request.getContextPath();
System.out.println(response.encodeRedirectURL(url));
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("<a href='"+url+"'>返回首页</a><br>");
String url2 = request.getContextPath()+"/demo2";
url2=response.encodeURL(url2);
System.out.println(url2);
response.getWriter().print("<a href='"+url2+"'>获取数据</a>");
3.3 表单重复提交问题解决
当网络延迟高或者刷新网页可能会导致表单重复提交问题,这时候就要使用令牌思想,即在表单页面,生成一个令牌token
,同时放入会话域和表单参数,当第一次表单提交时,判断会话域中的token
和表单中的checkToken
是否相等,相等就执行表单之后的操作,然后移除token
。
FormUI:
public class FormUI extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.设置响应正文的MIME类型和字符集
resp.setContentType("text/html;charset=UTF-8");
//2.获取输出流
PrintWriter out = resp.getWriter();
//生成一个令牌:
String token = UUID.randomUUID().toString();//它是一个唯一标识符。是由32个16进制的数组成的一个128位的数 xxxxxxxx-xxxxxxxxxxxx-xxxx-xxxx-xxxx
System.out.println(token);
token = token.replace("-","").toUpperCase();
//把令牌存入会话域中
HttpSession session = req.getSession();
session.setAttribute("token",token);
//3.输出一个表单
out.write("<html>");
out.write("<head>");
out.write("<title>转账操作</title><meta charset='UTF-8'></meta>");
out.write("</head>");
out.write("<body>");
out.write("<form action='"+req.getContextPath()+"/TransferServlet' method='post'>");
out.write("转账金额:<input type='text' name='money' value=''><br/>");
out.write("<input type='hidden' name='checkToken' value='"+token+"'><br/>");
out.write("<input type='submit' value='转账'>");
out.write("</form>");
out.write("</body>");
out.write("</html>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
TransferServlet
@WebSevlet("/TransferServlet")
public class TransferServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.设置响应正文的MIME类型和字符集
resp.setContentType("text/html;charset=UTF-8");
//2.获取输出流
PrintWriter out = resp.getWriter();
//模拟网络慢
try{
Thread.sleep(5000);
}catch (Exception e){
e.printStackTrace();
}
//校验
//取出表单中的checkToken
String checkToken = req.getParameter("checkToken");
//取出会话域中token
HttpSession session = req.getSession();
String token = (String)session.getAttribute("token");
if(checkToken.equals(token)){
//3.获取请求参数
String money = req.getParameter("money");
System.out.println(money);
//4.输出
out.write("转账成功!");
//非常重要的事,移除会话域中的令牌
session.removeAttribute("token");
}else {
out.write("请不要重复提交!");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}