HTTP 协议自身是属于 “无状态” 协议,默认情况下 HTTP 协议的客户端和服务器之间的这次通信和下次通信之间没有直接的联系。
但是实际开发中,我们很多时候是需要知道请求之间的关联关系的。例如登陆网站成功后,第二次访问的时候服务器就能知道该请求是否是已经登陆过了。
Cookie和Session的主要目的就是为了弥补HTTP的无状态特性
Cookie是在浏览器这边存储的机制
举个栗子:
我们去医院看病时应该先挂号,挂号时候需要提供身份证,同时得到了一张 “就诊卡”,这个就诊卡就相当于患者的 “令牌”。后续去各个科室进行检查、诊断、开药等操作,都不必再出示身份证了,只要凭就诊卡即可识别出当前患者的身份。
当我们看完病了之后,不想要就诊卡了,就可以注销这个卡。此时患者的身份和就诊卡的关联就销毁了(类似于网站的注销操作)。当我们再来看病时,可以办一张新的就诊卡,此时就得到了一个新的 “令牌”
此时在服务器这边就需要记录令牌信息,以及令牌对应的用户信息,这个就是 Session 机制所做的工作
服务器同一时刻收到的请求是很多的,而且还需要清楚地区分出不同的请求是从属于哪个用户,就需要在服务器这边记录每个用户令牌以及用户的信息的对应关系;在上面的例子中,就诊卡就是一张 “令牌”。要想让这个令牌能够生效,就需要医院这边通过系统记录每个就诊卡和患者信息之间的关联关系。
举个栗子,当用户第一次登录某网站添加了相关商品到自己购物车并退出后,第二次想要再次查看自己的购物车信息时,其流程如下:
Session是在服务器这边存储的机制
我们用前面讲的知识可以自己实现一个用户登录的案例,其具体思路是:
html
,包含用户名密码的输入框,以及登录按钮然后要有一个LogServlet
,来处理登录请求。LogServlet
,来处理登录请求。IndexServlet
,模拟登录完成后,跳转到的主页。在这个主页里面就能够获取到用户的身份信息(这里就可以存程序员自定义的用户数据。比如可以存一个当前用户访问的次数)我们可以简单写一个登录页面(没必要太复杂,主要是学会核心登录逻辑,大家可以自行美化)
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录页面title>
head>
<body>
<form action="login" method="post">
用户名:<input type="text" name="username">
<br>
密码:<input type="password" name="password">
<br>
<input type="submit" value="登录">
form>
body>
html>
step1:从请求的 body 中读取用户名和密码
String username = req.getParameter("username");
String password = req.getParameter("password");
step2:判定一下用户名密码是否正确(此处就不读数据库了, 直接固定用户名密码,我们直接将用户名设置成zhangsan,将密码设置成123
if (!"zhangsan".equals(username) || !"123".equals(password)) {
// 登录失败!!
resp.getWriter().write("登录失败!");
return;
}
step3:登陆成功
System.out.println("登录成功");
HttpSession httpSession = req.getSession(true);
// 还可以存入程序员自定义的数据, 可以存入身份信息(用户名和登录次数)
httpSession.setAttribute("username", "zhangsan");
httpSession.setAttribute("loginCount", 0);//自动装箱机制帮助我们把整数0转换成包装类
step4:让页面跳转到主页, 使用重定向的方式实现即可
resp.sendRedirect("index");
完整的LoginServlet类代码:
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
String username = req.getParameter("username");
String password = req.getParameter("password");
if (!"zhangsan".equals(username) || !"123".equals(password)) {
resp.getWriter().write("登录失败!");
return;
}
System.out.println("登录成功");
HttpSession httpSession = req.getSession(true);
httpSession.setAttribute("username", "zhangsan");
httpSession.setAttribute("loginCount", 0);
resp.sendRedirect("index");
}
}
核心步骤:
完整的IndexServlet类代码:
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 根据当前用户请求中的 sessionId, 获取到用户信息, 并显示到页面上.
resp.setContentType("text/html; charset=utf-8");
// 1. 判定当前用户是否已经登录了. (请求中有没有 sessionId, 以及 sessionId 是否合法)
// 如果会话不存在, 就不能创建了~ 只是查询, 不是登录
HttpSession httpSession = req.getSession(false);
if (httpSession == null) {
// 当前没有找到合法会话, 当前用户尚未登录, 重定向到 login.html, 让用户进行登录
resp.sendRedirect("login.html");
return;
}
// 2. 如果用户已经登录, 就可以从 HttpSession 中拿到用户信息了.
String username = (String) httpSession.getAttribute("username");
Integer loginCount = (Integer) httpSession.getAttribute("loginCount");
loginCount = loginCount + 1;
httpSession.setAttribute("loginCount", loginCount);
// 3. 返回一个 HTML 页面
StringBuilder html = new StringBuilder();
html.append("用户: " + username + "");
html.append("访问次数: " + loginCount + "");
resp.getWriter().write(html.toString());
}
}
在完成前面所有的步骤后,我们就可以验证程序了(我们通过Smart Tomcat进行打包部署程序)
登录成功后,我们也可以通过Fiddler捕捉到浏览器和服务器之间的两次交互了:
客户端和服务器的第一次交互为一个POST请求,body中就包含了用户名和密码的信息:
也可以看到响应中的sessionId字段:
这个Location就表示我们要重定向到的位置,正是因为这个重定向机制,就触发了浏览器和服务器之间的第二次交互:
第二次交互中的请求是一个GET请求
其中也包括了和之前服务器返回的sessionId相同的字段
最后,可以看到响应中也是有相应的html内容的。