3、抓包及过程解析
在看了上面的代码后,可能你会觉得很简单,因为现在有了像【Spring Boot】这样的成熟框架后几乎没什么人学Servlet了,但是我要介绍得是这种思想,读者要注重理解我所讲解的这种思想
- 首先我们启动Tomcat服务器,然后进入登录页面输入用户名和密码
- 若此时用户名和密码输入有误的话就会回到登录页面让用户去进行重新输入
- 只有当输入正确的用户名和密码后,浏览器才会构造出欢迎界面
- 此时我们顺手通过Fiddler去抓包观察,就可以观察到前后端其实进行了两次交互,一次访问的是
login
这个接口,另外一次访问的则是index
这个接口,也说明前端向后端发起了两次请求
- 然后来看第一次所发起的POST请求,通过【手撕】了一下这个报文,知晓了它都是由哪些内容构成的,这个相信读者应该很熟悉了,不过要注意的第一点是:此次的请求中是不带有Cookie的,因为这是我们第一次去访问这个页面,此时服务器哪里还没有为我们新建一个会话,所以然后没有分配一个SessionId
- 然后来看到服务器对于上面的这次请求所发回的响应看到有一个Set-Cookie字段,看到最前面有个
JSESSIONID
,这其实就是服务器给当前用户创建了一个临时会话并分配一个SessionId - 不仅如此,还去进行了一个重定向,跳转到了我们第二个
@WebServlet()
的注解处
resp.sendRedirect("index");
- 当第一次请求完成后,当前用户就有一份SessionId,相当于是一个令牌,之后发起的第二次请求,我们就要去构造出一个HTML页面,返回给用户,所以用户发送的是GET请求,经过上面的【重定向】,我们跳转到了
@WebServlet("/index")
这里 - 并且在此次的请求中,我们看到了Cookie,也就是上面服务端为其构造出来的一个身份标识
- 此次的请求,服务端会为其做出处理,因为我们在
getSession()
中传入的是【false】,所以当用户不存在的时候就会返回null,我们就靠其来进行一个判断,是否要让用户进行登录的操作 - 最后的话就是服务器对于上面一次的请求发回的响应因为有了下面这句话的存在,所以我通过
resp
对象返回给浏览器的所有响应都会被视为HTML中的元素,所以有些同学可能疑惑这里为什么会有<div>
resp.setContentType("text/html;charset=utf-8");
- 然后我们再去访问的话就可以看到HTTP请求报文中就带有了Cookie字段,里面存放的便是服务端为当前用户所专门构造的身份识别令牌
- 而且后续再去进行多次登录的时候,我们前面所设置
loginCount
就起到作用了,它会记录下每个用户的登录次数,也是一种键值对的形式进行组织
4、整体交互过程梳理及代码
上面是边抓包和代码一起进行分析,最后的话我们再来梳理一下整体的交互逻辑
然后再看看整体代码
login.html
<form action="login" method="post"> <label>用户名:</label> <input type="text" name="userName"><p></p> <label>密码:</label> <input type="text" name="passWord"><p></p> <input type="submit" value="登录"> </form>
loginServlet.java
@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"); /** * 判断用户输入的用户名和密码是否正确 */ // if(useName.equals("zhangsan") || passWord.equals("lisi")){ // if(passWord.equals("123456")){ // // 登录成功 // }else{ // // 登录失败 // } // }else{ // // 登录失败 // } if(!userName.equals("zhangsan") && !userName.equals("lisi")){ // 登录失败 // 1.打印日志 System.out.println("用户名错误"); // 2.重定向 resp.sendRedirect("login.html"); return; } if(!passWord.equals("123456")){ // 登录失败 // 1.打印日志 System.out.println("密码错误"); // 2.重定向 resp.sendRedirect("login.html"); return; }// 登录成功 // 1.创建一个会话【所谓的会话就是一个键值对, key是sessionId, value是HttpSession对象】 HttpSession httpSession = req.getSession(true); // 2.将当前登录的用户名保存到会话中,HttpSession所构建对象的value值也是一个 map 结构 httpSession.setAttribute("userName", userName); httpSession.setAttribute("passWord", passWord); httpSession.setAttribute("loginCount", "0"); // 3.重定向到主页 resp.sendRedirect("index"); } }
indexServlet.java
@WebServlet("/index") public class IndexServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8"); HttpSession httpSession = req.getSession(false); // 1.首先根据用户名判断当前用户是否登录过 if(httpSession == null){ // 说明该用户还未登录,提示登录 System.out.println("您还未登录,请重新登录"); resp.sendRedirect("login.html"); return; } // 2.表明用户登录过,根据根据sessionId,显示欢迎界面 String userName = (String)httpSession.getAttribute("userName"); String CountString = (String)httpSession.getAttribute("loginCount"); int loginCount = Integer.parseInt(CountString); loginCount += 1; httpSession.setAttribute("loginCount", loginCount + ""); // 构造页面 StringBuilder html = new StringBuilder(); html.append(String.format("<div>用户名:%s</div>", userName)); html.append(String.format("<div>登录次数:%d</div>", loginCount)); resp.getWriter().write(html.toString()); } }
5、登录过程感性理解 —— 医院挂号🏥
然后我们通过一个生活中的小案例去再度理解一下这个交互的过程,帮助读者进一步有更好的理解
其实对于上面的这么一个登录过程,可以和我们在医院看病的这么一个流程很相似。
- 首先我们到达医院,要拿着身份证🆔先去挂号,办理就诊卡,此时就是我们第一次进行登录的时候,需要为我们当前和这个用户创建会话并分配SessionId,那对应到医院这里就是在挂号系统中新增一个用户然后会自动为你生成一个编号,给你一张【就诊卡】,然后你的信息就进入医院的系统了,这张【就诊卡】就是你在医院的身份象征,到哪里你都要带着它
- 然后的话我最近身体有些地方不太舒服,所以打算挂个内科看看,那此时我就先到了内科的诊室。但是医生无法平白无故地给我看需要提供一些依据他才可以诊断我到底哪里出问题了,所以他会先让我去抽个血、拍个片之类的,此时呢就给我的【就诊卡】中写入了一些信息,让我去某某科室先进行一些其他的检查,这里其实就是给HttpSession设置了一些
attribute
- 那此时我去就【化验科】验血去了,那么那边的医生也需要我先出示就诊卡她才能给我抽血,否则不能平白无故地给我验,当我抽完血后,登上一段时间(可能要很久),就可以去拿单子了,回忆一下你在拿单子的时候是不是也要出示就诊卡,此时你的卡中又会多出一些信息,也就是也设置了一些
attribute
- 验完血后接着去【放射科】,此时你也要先把就诊卡给到那边的医生/护士,它们才知道要给你拍X光还是CT,又或者是磁共振,当你拍完去取片子的时候也是一样,你的卡里也会多出来细一些信息
- 最后医生就会通过读取我卡中的拍摄记录然后去诊断我到底是哪里出了问题,最后的话再告诉就诊结果以及要平常应该注意些什么、吃I些什么药物可以治疗,此时也会往我的就诊卡中写入一些信息
- 对于上面每一次的刷卡,都是通过我的SessionId去系统中进行查询,进一步得到里面的
attribute
,知道要做哪些检查和结果是什么。所以我们的这张就诊卡其实就相当于是Cookie,里面存放了你自己的身份表示信息SessionId,如果有这张卡的话再进出其他科室的时候就无需再告诉医生你叫什么,是从哪里过来的了,直接一刷卡就行 - 当然上面也说过Cookie也会丢失、也会过期,即我们的就诊卡可能也会损坏、丢失,那下次去医院的时候就需要重新办理一些相关的手续获取一个新的SessionId即你专属的【就诊卡】
八、疑难解惑
看完了用户登录这个案例,你是否对Cookie和Session的理解更上一层楼了呢?接下去我再把同学们遇到的一些疑惑解答一下
💬 刚才getSession()中所传递的【true】和【false】还有些不太明白,可以再解释一下吗?
- 对于
getSession()
来说,它是HttpServletRequest类的一个方法,传入【true】的话,在判定其返回值时,如果存在就返回现成的,如果不存在的话就创建一个新的,一般我们放在用户初次登录的时候;传入【false】的话,存在也会返回现成的,但若是不存在的话就返回null,一般我们放在用户已经存在的时候
💬 如果在一个浏览器中开同一个标签页进行登录,假设两个标签页所登录的账号是不同的,那在第二个标签页进行登录的时候是否会把第一个标签页中SessionId所对应的HttpSession对象给改了呢?
- 这个是不会的,因为同一个网页是做不到同时登录两个用户的,因为你开的这两个页面用的是同一个Cookie,共用同一个登录状态,所以是无法进行兼容的,一次只能有一位用户进行登录
💬 追问:如果开无痕模式呢?
- 这个就不一样了,对于无痕模式而言,它是有一套自己的Cookie的, 和其他页面的Cookie不混淆,因为浏览器进行了特殊的处理,所以我们在上网查询一些 “学习资料” 的时候就很方便了
💬 一个域名可以同时有两个Cookie?
- 这个当然是可以的,还记得上面我们看的Cookie吗,足足有90多个,一个网页是可以对应多个Cookie的,但是SessionId只能有一个,是作为访问服务器的唯一身份标识
💬 有没有办法让两个标签页不用同一个SessionId呢?
- 不用Cookie就可以了~~当我们在实现保存用户信息的时候,是可以使用其他手段的,不一样非要使用到Cookie的,基于Cookie去进行存放是一个当前比较流行的实现方式,让浏览器进行一些本地的存储是有其他方式的,例如:LocalStroage(存键值对)、IndexDB也可以存放用户信息,让不同的页面存储不同的SessionId即可
九、总结与提炼
最后来总结一下本文所介绍的内容:book:
- 首先我们了解了什么是Cookie以及它的一系列相关概念,见到了Cookie长什么样,知晓了它有什么作用,然后便了解了一下会话机制Session,知道了在服务器同时接受多个浏览器的访问时需要通过SessionId来进行相应的识别,此时会为每一个接入用户创建一个新的会话来进行存放,构造好用户的对应信息后就可以在后面用户再度接入时==通过搜索相关的SessionId去进行一个精准地查询==
- 当然,Cookie和Session也是面试中常考的一个知识点,会让你去区分一下它们之间的区别在哪里,然后这要建立在你已经理解它们之间交互逻辑的基础上,这点是很重要的
- 所以接下去,我就通过一个【用户登录】的案例带你理解了前后端之间交互地这么一个逻辑,双方是如何进行一发一收的,重点是在于理解会话Session的建立以及SessionId的分配、判定用户是否登录的逻辑,如果不清楚的可以看我话的交互图,再捋一遍思路,就能明白Cookie和Session的作用到底在哪里
- 最后我也通过了一个生活中的小案例 —— 医院挂号来帮助理解,希望看完了这个案例后你能对Cookie和Session的理解更上一层楼
以上就是本文要介绍的所有内容,感谢您的阅读:rose: