文章目录
回顾Cookie
在前面的 HTTP 协议中,我们也理解过这个 Cookie,知道了HTTP 协议自身是属于 “无状态” 协议的。
“无状态” 的含义指的是: 默认情况下HTTP协议的客户端和服务器之间的这次通信,和下次通信之间没有直接的联系。
但在实际开发中,我们很多时候是需要知道请求之间的关联关系的
例如在我们登录网站成功后,在我们第二次去访问的时候服务器就能够知道这个请求是否已经登录过了
就像我登录过知乎以后,然后退出去后再重新登陆,此时服务器知道这个请求已经登录过了,因此就直接记住了我的账号和密码。⬇️⬇️⬇️
图解如下:
上面图中的 “令牌” 通常就是存储在 Cookie 字段中
此时不妨让我们一起回忆一下之前医院挂号的例子:
- 我们先到医院去挂号,挂号的时候提供身份证,同时得到一张"就诊卡",里面存储的身份信息,就相当于"令牌"
- 后面去看病的时候,不管走到那个科室(眼科,鼻科,抓药…)就不需要在把身份证拿出来了,直接拿出就诊卡就可以识别你的身份了
- 不想要这张卡了,那我们也可以注销这张卡,此时,里面的身份信息,诊断信息就销毁了
- 又来这个医院看病,可以重新办一张,就又有一个新的"就诊卡"了
此时在服务器这边就需要记录令牌信息,以及令牌对应的用户信息,这个就是 Session 机制所做的工作
理解会话机制 (Session)
由于服务器同一时刻收到的请求是很多的,所以服务器如果想清楚的区分每个请求是从属于哪个用户, 那么就需要在服务器这边记录每个用户令牌以及用户的信息的对应关系。
在上面医院挂号这个例子中,就诊卡就是一张 “令牌”, 要想让这个令牌能够生效,就需要医院这边通过系统记录每个就诊卡和患者信息之间的关联关系。
Cookie 和 Session的区别⭐⭐
- Cookie 是客户端的机制, Session 是服务器端的机制
- Cookie 和 Session 经常会在一起配合使用, 但不是必须配合
- 完全可以用 Cookie 来保存一些数据在客户端,这些数据不一定是用户身份信息, 也不一定是
token / sessionId - Session 中的 token / sessionId 也不需要非得通过 Cookie / Set-Cookie 传递
核心方法
HttpServletRequest 类中的相关方法:
方法 | 描述 |
---|---|
HttpSession getSession() | 在服务器中获取会话. 参数如果为 true, 则当不存在会话时新建会话; 参数如果为 false, 则当不存在会话时返回 null |
Cookie[] getCookies() | 返回一个数组, 包含客户端发送该请求的所有的 Cookie 对象. 会自动把Cookie 中的格式解析成键值对. |
getSession 方法:既能用于获取到服务器上的会话,也能用于创建会话。具体行为取决于参数:
- 如果参数为true:若会话不存在,则创建;若会话存在,则获取
- 如果参数为false:若会话不存在,则返回null;若会话存在,则获取
就还拿之前那个医院看病的例子来说:
当你来到一家新医院的时候,你要先挂号,那么人家挂号的医生就会问你,你有没有这里的"就诊卡"?有就不需要再办了,如果没有就需要办一张新的卡。而当你挂了号之后,你不仅会得到一张"就诊卡",同时在医院的服务器里也会给你开一个档案来存储你的就诊信息(这就是你的会话)。
但是如果这个医院比较特殊,人流量特别多的话,而这个医院的承载病人的能力是有上限的,那么这个时候医生就会问你在这里建过档没有,如果建过才会给你挂号,没有建过的话就不给你挂(就像生孩子的时候得提前预约(当你发现自己怀孕了就提前去建个档),因为妇产科人是特别多的!)
调用getSession()的时候具体要做的事情:
- 创建会话
首先先获取到请求中 Cookie 里面的 sessionId字段(相当于会话的身份标识),判定这个 sessionId 是否在当前服务器上存在,如果不存在,就会进入到创建会话的逻辑⬇️⬇️⬇️
创建会话: 就会创建一个HttpSession 对象 ,并且生成一个 sessionId(这个sessionId 是一个很长的数字,通常是用十六进制来表示,能够保证唯一性),接下来就会把这个 sessionId 作为 key,把这个Httpsession 对象作为value,把这个键值对给保存到服务器内存的一个"哈希表"(也不一定是他,只是类似这种结构)这样的结构中,再然后服务器就会返回一个HTTP 响应,把 sessionId 通过 Set-Cookie 字段返回给浏览器,浏览器就可以保存这个 sessionId 到 Cookie 中了。
- 获取会话
先获取到请求中的 Cookie 里面的 sessionId字段(会话的身份标识),判定这个sessionId 是否在当前服务器上存在(也就是在哈希表中是否存在),如果有就直接查询出这个 HttpSession 对象,并且通过返回值返回回去。
那么HttpSession 对象到底是什么呢❓🤔
这个对象本质上也是一个“键值对”的结构,允许程序猿往HttpSession 对象(value)中,存储任意的键值对数据——key必须是String,value是一个Object
图解如下:
getCookies()方法:获取到请求中的Cookie数据,返回值是Cookie类型的数组,每个元素是一个Cookie对象,每个Cookie对象又包含了两个属性,name和value(还是键值对形式😂)
HTTP 请求中的 Cookie 字段就是按照键值对的方式来组织的,这里的这些键值对,大概的格式是使用 ; 来分割键值对,使用 = 来分割键和值,这些键值对都会在请求中通过 Cookie 字段传给服务器,服务器收到请求后,就会进行解析,解析成 Cookie[] 这样的形式。
由于Cookie这里是可以保存任意自定制的键值对,所以如果是一般的键值对,直接通过getCookies来获取;如果是特殊的键值对(表示sessionId的键值对),不需要使用getCookies,直接使用getSession其实就自动帮我们从Cookie中取sessionId了。
HttpServletResponse 类中的相关方法:
方法 | 描述 |
---|---|
void addCookie(Cookie cookie) | 把指定的 cookie 添加到响应中 |
响应中就可以根据 addCookie 这个方法来添加一个 Cookie 信息到响应报文中,这里添加进来的键值对,就会作为 HTTP 响应中的Set-Cookie 字段来表示
HttpSession 类中的相关方法:
一个 HttpSession 对象里面包含多个键值对,我们可以往 HttpSession 中存任何我们需要的信息
方法 | 描述 |
---|---|
Object getAttribute(Stringname) | 该方法返回在该 session 会话中具有指定名称的对象,如果没有指定名称的对象,则返回 null. |
void setAttribute(Stringname, Object value) | 该方法使用指定的名称绑定一个对象到该 session 会话 |
boolean isNew() | 判定当前是否是新创建出的会话 |
Cookie 类中相关的方法:
每个 Cookie 对象就是一个键值对
方法 | 描述 |
---|---|
String getName() | 该方法返回 cookie 的名称。名称在创建后不能改变。(这个值是 SetCooke 字段设置给浏览器的) |
String getValue() | 该方法获取与 cookie 关联的值 |
void setValue(String newValue) | 该方法设置与 cookie 关联的值 |
案例——网页登录
基本逻辑如下:
约定好前后端交互接口:
编写一个简单的登录页面
使用 form表单来构造 post 请求:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<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>
编写一个 Servlet 来处理这个登录请求
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 {
//处理用户请求
String username=req.getParameter("username");
String password=req.getParameter("password");
//判定用户名或密码是否正确
//正常来说这个判定操作是要放到数据库中进行存取的
//此处为了简单,就直接在代码里面写死了,假设有效的用户名和密码是"panpan","123456"
if("panpan".equals(username)&&"123456".equals(password)){//为避免空指针异常(若用户名或密码为空的话)
//登陆成功
//创建会话,并保存必要的身份信息
HttpSession httpSession=req.getSession(true);
//往会话中存储键值对,必要的身份信息
httpSession.setAttribute("username",username);
//初始情况下,把登录次数设为0
httpSession.setAttribute("count",0);
resp.sendRedirect("index");
}else{
//登陆失败
resp.getWriter().write("login failed!");
}
}
}
编写服务器端返回主页
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 {
//返回一个主页
//此处需要得到用户名是啥,从HttpSession中就能拿到
//此处getSession的参数必须是false,前面在登录过程中,已经创建过会话了,此处是要直接获取到之前的会话
HttpSession session= req.getSession(false);
String username= (String) session.getAttribute("username");
//还要从会话中取出count
Integer count= (Integer) session.getAttribute("count");
count+=1;
//再把自增之后的值写回到会话中
session.setAttribute("count",count);
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("<h3>欢迎你!"+username+"这是你第"+count+"次访问主页</h3>");
}
}
启动服务器进行验证
第一次交互: 浏览器从服务器上拿到登陆页面
第二次交互: 点击登录之后,就要给服务器发送一个登录请求,服务器会返回响应
第三次交互: 浏览器收到302响应之后,再次向服务器发起请求来访问主页
最终效果: