0011 Session与Cookie实现原理
第一节
会话管理
Cookie 保存在客户端
Session 保存在服务器内存中,客户端与服务器通讯用SessionId
应用场景在哪里? 登录,购物车,移动App接口会话管理
第二节 Cookie底层原理
1)服务器创建cookie对象,把会话数据存储到cookie对象中。
new Cookie("name","value");
2) 服务器发送cookie信息到浏览器
response.addCookie(cookie);
举例: set-cookie: name=eric (隐藏发送了一个set-cookie名称的响应头)
3)浏览器得到服务器发送的cookie,然后保存在浏览器端。
4)浏览器在下次访问服务器时,会带着cookie信息
举例: cookie: name=eric (隐藏带着一个叫cookie名称的请求头)
5)服务器接收到浏览器带来的cookie信息
request.getCookies();
Cookie细节
1)void setPath(java.lang.String uri) :设置cookie的有效访问路径。有效路径指的是cookie的有效路径保存在哪里,那么浏览器在有效路径下访问服务器时就会带着cookie信息,否则不带cookie信息。
2)void setMaxAge(int expiry) : 设置cookie的有效时间。
正整数:表示cookie数据保存浏览器的缓存目录(硬盘中),数值表示保存的时间。
负整数:表示cookie数据保存浏览器的内存中。浏览器关闭cookie就丢失了!!
零:表示删除同名的cookie数据
3)Cookie数据类型只能保存非中文字符串类型的。可以保存多个cookie,但是浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。
第三节 Session技术
Cookie的局限:
1)Cookie只能存字符串类型。不能保存对象
2)只能存非中文。
3)1个Cookie的容量不超过4KB。
如果要保存非字符串,超过4kb内容,只能使用session技术!!!
Session特点:
会话数据保存在服务器端。(内存中)
服务器创建一个Session后,session信息保存在服务器中,响应中把sessionId返回给客户端
客户端在下次请求中会将sessionId通过请求头的方式传给服务端
1)第一次访问创建session对象,给session对象分配一个唯一的ID,叫JSESSIONID
new HttpSession();
2)把JSESSIONID作为Cookie的值发送给浏览器保存
Cookie cookie = new Cookie("JSESSIONID", sessionID);
response.addCookie(cookie);
3)第二次访问的时候,浏览器带着JSESSIONID的cookie访问服务器
4)服务器得到JSESSIONID,在服务器的内存中搜索是否存放对应编号的session对象。
if(找到){
return map.get(sessionID);
}
Map
<"s001", s1>
<"s001,"s2>
5)如果找到对应编号的session对象,直接返回该对象
6)如果找不到对应编号的session对象,创建新的session对象,继续走1的流程
结论:通过JSESSION的cookie值在服务器找session对象!!!!!
3.4 Sesson细节
1)java.lang.String getId() : 得到session编号
2)两个getSession方法:
getSession(true) / getSession() : 创建或得到session对象。没有匹配的session编号,自动创 建新的session对象。
getSession(false): 得到session对象。没有匹配的session编号,返回null
3)void setMaxInactiveInterval(int interval) : 设置session的有效时间
session对象销毁时间:
3.1 默认情况30分服务器自动回收
3.2 修改session回收时间
3.3 全局修改session有效时间
第四节 自定义缓存
用Map集合去装数据
唯一不重复字符串作为key,简称sessionId
自定义缓存(简单的缓存框架)
public class CacheManager { private MapcacheMap = new HashMap<>(); public void put(String key, Object oj) { put(key, oj, null); } public synchronized void put(String key, Object oj, Long timeOut) { if (oj == null) { return; } Cache cache = new Cache(); cache.setKey(key); if (timeOut != null) cache.setTimeOut(timeOut + System.currentTimeMillis()); cache.setValue(oj); cacheMap.put(key, cache); } public synchronized void deleteCache(String key) { cacheMap.remove(key); } public synchronized Object get(String key) { //此处不加synchronize会出现线程不安全问题 Cache cache = cacheMap.get(key); Object oj = null; if (cache != null) { oj = cache.getValue(); } return oj; } public synchronized void checkValidityData() { for (String key : cacheMap.keySet()) { Cache cache = cacheMap.get(key); Long timeOut = cache.getTimeOut(); if (timeOut == null) { return; } long currentTime = System.currentTimeMillis(); long endTime = timeOut; long result = (currentTime - endTime); if (result > 0) { System.out.println("清除:"+key); cacheMap.remove(key); } } } public static void main(String[] args) throws InterruptedException { CacheManager cacheManager = new CacheManager(); // cacheManager.put("lisi", 0); cacheManager.put("zhangsan", "jj", 5000l); ScheduledExecutorService scheduledThreadPool = //定时删除数据 Executors.newScheduledThreadPool(5); scheduledThreadPool.schedule(new Runnable() { public void run() { cacheManager.checkValidityData(); } }, 5000, TimeUnit.MILLISECONDS); Thread.sleep(5000); System.out.println(cacheManager.get("zhangsan")); } }
第六节 表单重复提交解决方案
出现原因:
1)网络延时 2)重复刷新 3)点击后退按钮到表单页
1. 前端解决办法
1)onSumit中设置提交标志 2)点击提交按钮后设置按钮为不可用
2. 后端解决办法
在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。然后将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。
在下列情况下,服务器程序将拒绝处理用户提交的表单请求:
- 存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。
- 当前用户的Session中不存在Token(令牌)。
- 用户提交的表单数据中没有Token(令牌)
LocaFromServlet
@WebServlet("/LocaFromServlet") public class LocaFromServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 生成token String tokenValue=TokenUtils.getToken(); HttpSession session = req.getSession(); session.setAttribute("sessionToken", tokenValue); req.getRequestDispatcher("from.jsp").forward(req, resp); } }
from.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>Form表单
DoFromServlet
@WebServlet("/DoFormServlet") public class DoFormServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8");// 防止浏览器显示乱码 if(!isBumit(req)){ System.out.println("您提交提交了数据..或者token错误!"); resp.getWriter().write("您提交提交了数据..或者token错误!"); return ; } String userName = req.getParameter("userName"); try { Thread.sleep(300); } catch (Exception e) { // TODO: handle exception } System.out.println("数据库插入数据...userName:" + userName); // 插入数据库... resp.getWriter().write("保存成功.."); req.getSession().removeAttribute("sessionToken"); } public Boolean isBumit(HttpServletRequest request) { String parameterToken = request.getParameter("parameterToken"); String sessionToken = (String) request.getSession().getAttribute("sessionToken"); //判断是否提交 if (sessionToken == null) { return false; } // 判断是否是伪造token if(!(parameterToken.equals(sessionToken))){ return false; } return true; } }
1.访问/LocaFromServlet,服务器端生成token,一份存到session,一份传给from.jsp的隐藏域,然后转发到from.jsp
2。在from.jsp中输入内容,点击提交到DoFormServlet
3.在DoFormServlet中比对请求参数中隐藏域的token和session中的token是否一致,如果一致则说明是第一次提交,然后清除session,当再次提交时,session中的token为空,所以不是第一次提交,返回失败,这样就能解决重复提交的问题