🍒一.回顾Cookie与Session
🍎1.1 Cookie
HTTP 协议自身是属于 “无状态” 协议.
“无状态” 的含义指的是:
默认情况下 HTTP 协议的客户端和服务器之间的这次通信, 和下次通信之间没有直接的联系.但是实际开发中, 我们很多时候是需要知道请求之间的关联关系的.
例如登陆网站成功后, 第二次访问的时候服务器就能知道该请求是否是已经登陆过了
回忆之前的例子:
到了医院先挂号. 挂号时候需要提供身份证, 同时得到了一张 "就诊卡"Cookie, 这个就诊卡就相当于患者的 “令牌”.
后续去各个科室进行检查, 诊断, 开药等操作, 都不必再出示身份证了, 只要凭就诊卡即可识别出当前患者的身份.
看完病了之后, 不想要就诊卡了, 就可以注销这个卡. 此时患者的身份和就诊卡的关联就销毁了. (类似于网站的注销操作)
又来看病, 可以办一张新的就诊卡, 此时就得到了一个新的 “令牌”
此时在服务器这边就需要记录令牌信息, 以及令牌对应的用户信息, 这个就是 Session 机制所做的工作
🍎1.2 Session
服务器同一时刻收到的请求是很多的. 服务器需要清除的区分清楚每个请求是从属于哪个用户, 就需要在服务器这边记录每个用户令牌以及用户的信息的对应关系.在上面的例子中, 就诊卡就是一张 “令牌”. 要想让这个令牌能够生效, 就需要医院这边通过系统记录每个就诊卡和患者信息之间的关联关系.
会话的本质就是一个 “哈希表”, 存储了一些键值对结构. key 就是令牌的 ID(token/sessionId), value 就是用户信息(用户信息可以根据需求灵活设计).
sessionId 是由服务器生成的一个 “唯一性字符串”, 从 session 机制的角度来看, 这个唯一性字符串称为 “sessionId”. 但是站在整个登录流程中看待, 也可以把这个唯一性字符串称为 “token”.sessionId 和 token 就可以理解成是同一个东西的不同叫法(不同视角的叫法)
用户登陆:
●当用户登陆的时候, 服务器在 Session 中新增一个新记录, 并把 sessionId / token 返回给客户端.(例如通过 HTTP 响应中的 Set-Cookie 字段返回).
●客户端后续再给服务器发送请求的时候, 需要在请求中带上 sessionId/ token. (例如通过 HTTP 请求中的 Cookie 字段带上).
●服务器收到请求之后, 根据请求中的 sessionId / token 在 Session 信息中获取到对应的用户信息,再进行后续操作.
Servlet 的 Session 默认是保存在内存中的. 如果重启服务器则 Session 数据就会丢失.
🍎1.3Cookie 和 Session 的区别
●Cookie 是客户端的机制. Session 是服务器端的机制.
●Cookie 和 Session 经常会在一起配合使用. 但是不是必须配合.
●完全可以用 Cookie 来保存一些数据在客户端. 这些数据不一定是用户身份信息, 也不一定是token / sessionId
●Session 中的 token / sessionId 也不需要非得通过 Cookie / Set-Cookie 传递.
🍒二.Servlet会话管理操作
🍎2.1核心方法
HttpServletRequest 类中的相关方法
方法 | 描述 |
HttpSession getSession() | 在服务器中获取会话. 参数如果为 true, 则当不存在会话时新建会话; 参数如果为 false, 则当不存在会话时返回 null |
Cookie[] getCookies() | 返回一个数组, 包含客户端发送该请求的所有的 Cookie 对象. 会自动把Cookie 中的格式解析成键值对. |
Part getPart(String name) | 获取请求中给定 name 的文件 |
Collection getParts() | 获取所有的文件 |
HttpServletResponse 类中的相关方法
方法 | 描述 |
void addCookie(Cookie cookie) | 把指定的 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 关联的值 |
HTTP 的 Cooke 字段中存储的实际上是多组键值对. 每个键值对在 Servlet 中都对应了一个 Cookie对象.
●通过 HttpServletRequest.getCookies() 获取到请求中的一系列 Cookie 键值对.
●通过 HttpServletResponse.addCookie() 可以向响应中添加新的 Cookie 键值对.
Part 类方法
方法 | 描述 |
String getSubmittedFileName() | 获取提交的文件名 |
String getContentType() | 获取提交的文件类型 |
long getSize() | 获取文件的大小 |
void write(String path) | 把提交的文件数据写入磁盘文件 |
🍒三.常见案例实现
🍎3.1登录逻辑的实现
我们经常上网查询一些网站什么的,很多网站都会让你先登录,才能使用其中的一些功能,登录完成之后,一般都会跳到一个主页网站,下面我们就来简单地实现一下这一套逻辑。
第一步,约定前后端接口
我们需要实现两套交互逻辑,一是登录跳转,二是获取主页
登录跳转约定:
约定使用POST请求,响应采用302重定向
获取主页约定:
采用GET请求,响应返回一个页面
第二步,编写前端交互页面
我们的重点是来学习登录的逻辑,因此登录的界面不需要很好看很复杂,只要能够有两个输入框和一个提交按钮让我们输入账号密码就行。目标页面如下:
在pom.xml中引入Servlet依赖
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency>
前端代码
<!DOCTYPE html> <html lang="ch"> <head> <meta charset="UTF-8"> <title>login</title> </head> <body> <form action="login" method="post"> <input type="text" name="username"> <input type="password" name="password"> <input type="submit" value="登录"> </form> </body> </html>
后端代码登陆
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 { req.setCharacterEncoding("utf8"); resp.setCharacterEncoding("utf8"); //获取用户账号 String username = req.getParameter("username"); String password = req.getParameter("password"); //验证账户 //验证按照正常流程应该从数据库读数据,但是为了便于演示登录的逻辑,我们直接将账号密码写死 //假设正确的账号与密码是 zhangsan 123 if ("zhangsan".equals(username) && "123".equals(password)) { //登录成功 //创建会话,为后续需登录的页面做准备 HttpSession httpSession = req.getSession(true); httpSession.setAttribute("username", username); //初始情况下设置登录次数 httpSession.setAttribute("count", 0); //跳转到目标页面index resp.sendRedirect("index"); } else { //登录失败 resp.getWriter().write("登录失败!"); } } }
登陆后重定向页面响应
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 { //返回一个主页 //获取会话,参数必须是false HttpSession httpSession = req.getSession(false); //取出会话信息 String username = (String) httpSession.getAttribute("username"); Integer cnt = (Integer) httpSession.getAttribute("count"); //访问次数加1 cnt++; //写回到会话中 httpSession.setAttribute("count", cnt); //构造页面。我们简单构造一下就好 resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("<h3>欢迎您!" + username + "</h3> <h4>这个主页已经被访问了" + cnt + "次</h4>"); } }
🍎3.2上传文件
前端代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>file</title> </head> <body> <form action="upload" method="post" enctype="multipart/form-data"> <input type="file" name="myfile"> <input type="submit" value="提交"> </form> </body> </html>
后端代码
import javax.servlet.ServletException; import javax.servlet.annotation.MultipartConfig; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; import java.io.IOException; @MultipartConfig @WebServlet("/upload") public class UploadServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取Part对象 Part part = req.getPart("myfile"); //输出文件信息 //文件名 System.out.println("文件名:" + part.getSubmittedFileName()); //文件类型 System.out.println("文件类型:" + part.getContentType()); //文件大小 System.out.println("文件大小:" + part.getSize()); //将文件写入磁盘 part.write("D:\\上传文件"); //返回响应 resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("上传成功!"); } }