目录
一、 HTTP协议
1.1 为什么HTTP协议是无状态的?
1.2 在HTTP协议中流式传输和分块传输编码的区别
二、Cookie和Session
2.1 Cookie
2.2 Session
2.3 Cookie和Session的区别
三、servlet中与Cookie和Session相关的API
3.1 HttpServletRequest 类中的相关方法:
3.2 HttpServletResponse 类中的相关方法
3.3 HttpSession 类中的相关方法
3.4 Cookie 类中的相关方法
四、实现模拟登录流程
我们知道,HTTP协议是一种无状态协议,这意味着每个HTTP请求都是独立的,服务器不会记住之前的任何请求,也不会记录当前请求与之前请求的关系。每个请求都是独立的、不相关的,服务器不会保留任何有关请求或响应的信息,也不会保存客户端的状态。
1.1 为什么HTTP协议是无状态的?
这是由于HTTP协议的工作机制决定的:客户端向服务器发起请求,服务器响应请求并返回相应的数据。在请求完成后,服务器会立即关闭连接,不会再保持连接状态,下一次请求需要重新建立连接。因此,服务器无法保存客户端的状态信息。
为了解决这个问题,Web应用程序通常使用一些技术来维护客户端状态,例如Cookie和Session。这些技术在客户端和服务器之间传递状态信息,使得应用程序能够跟踪每个客户端的状态,实现有状态的应用程序。
需要注意的是。虽然HTTP协议为无状态协议,但是与HTTP支持长连接不冲突。
长连接(也称为持久连接)是一种HTTP/1.1协议引入的技术,它允许客户端和服务器在一个TCP连接上发送多个HTTP请求和响应,而不需要为每个请求/响应都建立一个新的TCP连接。这种技术可以显著减少网络连接的建立和关闭开销,提高网络传输效率,同时也能够更好地支持HTTP协议的特性,如流式传输和分块传输编码等。
在长连接中,客户端和服务器之间的TCP连接在一个HTTP请求/响应完成后不会立即关闭,而是继续保持连接状态,等待下一个HTTP请求/响应。这样,当客户端发送下一个HTTP请求时,可以直接利用之前的TCP连接,不需要再进行TCP握手和挥手等操作,从而节省了网络资源和时间。
因此,虽然HTTP协议是无状态的,但是通过使用长连接技术,可以在保持无状态的前提下提高HTTP协议的效率和性能。需要注意的是,长连接需要服务器和客户端都支持,否则无法建立长连接。同时,长连接也可能会导致资源占用问题,因此需要合理使用和配置。
以下为浏览器请求BIng主页的抓包信息(Connection:keep-alive就为长连接的意思):
在HTTP/1.1中,如果客户端请求头中没有明确指定Connection头字段,那么服务器会默认使用长连接,即保持TCP连接处于打开状态,直到客户端或服务器明确要求关闭连接为止。这种方式可以减少每次请求和响应时建立和关闭TCP连接的开销,提高网络传输效率和性能。但需要注意的是,长连接并不是在所有情况下都适用,有时候也需要根据实际情况关闭连接。
1.2 在HTTP协议中流式传输和分块传输编码的区别
流式传输和分块传输编码都是HTTP协议中用于数据传输的技术,它们的区别如下:
数据分割方式不同:流式传输是将响应数据(如HTML,JSON等)按照一定的块大小进行分割,逐步发送到客户端;而分块传输编码是将数据分成若干个块(Chunk),每个块有独立的长度信息和数据内容,使用分块传输编码方式将数据分成多个块进行传输,每个块之间用一个CRLF(回车换行)分隔符分开。
响应方式不同:流式传输是一次性发送响应数据,客户端在接收到第一个数据块时就可以开始处理;而分块传输编码则是将响应数据分成多个块逐个发送,客户端需要在接收完所有块后再进行处理。
大小控制不同:流式传输中每个数据块的大小是固定的,由服务器决定;而分块传输编码中每个块的大小是不固定的,客户端需要通过读取长度信息来确定每个块的大小。
读取速度,应用场景不同:流式传输是在客户端接收到第一个数据块时就可以开始处理,而不必等待整个响应数据全部接收完毕。这种方式可以减少客户端等待时间,提高响应速度。流式传输常用于需要实时更新的数据场景,如股票行情、新闻快讯等。而分块传输编码客户端接收到每个块后,会先读取块的长度信息,再读取对应长度的数据内容。这种方式可以避免一次性传输大量数据造成的网络拥塞和连接中断,并且可以让客户端在接收到部分数据时就开始处理,从而提高传输效率和响应速度。
总之,流式传输和分块传输编码都可以提高HTTP数据传输的效率和响应速度,但应根据具体的场景和需求进行选择和使用。流式传输适用于需要实时更新的数据场景,如股票行情、新闻快讯等;而分块传输编码适用于需要传输大文件的场景,能够避免一次性传输造成的网络拥塞和连接中断。
上面我们说到,由于HTTP是无状态的,所以需要引入Cookie来解决这一问题。
Cookie允许服务器在客户端上存储少量的数据,例如:用户的身份,偏好,购物车信息等。以便在之后的HTTP请求中将该数据发送回服务器,通过Cookie,服务器可以识别和跟踪用户的身份和状态,从而提供更加个性化和定制化的服务。
回顾以下,Cookie是什么,从哪里来,发送到哪去,存储在哪?
Session(会话)是Web应用程序中的一个概念,它指的是一系列相关的HTTP请求和响应,通常用于在客户端和服务器之间维护一些状态信息。在一个会话中,服务器会创建一个唯一的Session ID(会话标识符),并将该Session ID发送给客户端浏览器。客户端在之后的每个HTTP请求中都会携带该Session ID,以便服务器能够识别并跟踪该客户端的身份和状态。
为了加深理解,我们来看一组登录流程:
Session通常是基于Cookie实现的。具体来说,当服务器创建一个新的Session时,它会在响应头中添加一个Set-Cookie字段,其中包含了一个名为SESSIONID的Cookie值,该值就是新创建的Session ID。客户端在收到该响应时会将该Cookie值保存在浏览器中,并在之后的每个HTTP请求中发送回服务器。服务器在接收到每个HTTP请求时都会检查该请求中是否包含了有效的Session ID,如果存在则说明该请求属于某个已经创建的Session,服务器就可以根据该Session ID来识别和跟踪客户端的身份和状态。
需要注意的是:Cookie是有存储期限的,越敏感的网站存储期限越短。
Cookie和Session是Web开发中常用的两种技术,用于在服务器和客户端之间保持状态信息。它们的区别如下:
存储位置不同:Cookie是存储在客户端的浏览器中的文本文件,而Session是存储在服务器端的内存中或者数据库中的键值对。
安全性不同:由于Cookie是存储在客户端中的,所以存在被窃取或者篡改的风险。而Session存储在服务器端,相对来说更加安全。
存储容量不同:Cookie的存储容量较小,通常为4KB左右,而Session的存储容量可以比较大,但会占用服务器的内存或者存储资源。
生命周期不同:Cookie可以设置过期时间,可以长期保存在客户端的浏览器中;而Session在用户关闭浏览器或者超过一定时间后,会自动销毁。
使用方式不同:Cookie是通过在服务器端发送HTTP响应头来设置的,客户端的浏览器会自动保存Cookie,下一次请求时会自动发送Cookie到服务器端。而Session需要在服务器端通过某种方式生成Session ID,并将Session ID存储在Cookie中或者URL参数中,客户端浏览器会将Session ID发送到服务器端,服务器根据Session ID来获取Session数据。
综上所述,Cookie和Session都可以用于在服务器和客户端之间保持状态信息,但它们的应用场景和特点有所不同,具体使用哪种技术要根据实际需求和安全要求来决定。
方法 | 描述 |
HttpSession getSession() | 在服务器中获取会话. 参数如果为 true, 则当不存在会话时新建会话; 参数如果 为 false, 则当不存在会话时返回 null |
Cookie[] getCookies() | 返回一个数组, 包含客户端发送该请求的所有的 Cookie 对象. 会自动把 Cookie 中的格式解析成键值对. |
方法 | 描述 |
void addCookie(Cookie cookie) | 把指定的 cookie 添加到响应中. |
方法 | 描述 |
Object getAttribute(String name) | 该方法返回在该 session 会话中具有指定名称的对象,如果没 有指定名称的对象,则返回 null. |
void setAttribute(String name, Object value) | 该方法使用指定的名称绑定一个对象到该 session 会话 |
boolean isNew() | 判定当前是否是新创建出的会话 |
每个 Cookie 对象就是一个键值对
方法 | 描述 |
String getName() | 该方法返回 cookie 的名称。名称在创建后不能改变。(这个值是 Set Cooke 字段设置给浏览器的) |
String getValue() | 该方法获取与 cookie 关联的值 |
void setValue(String newValue) | 该方法设置与 cookie 关联的值。 |
第一步,约定前后端接口。
我们需要实现两套交互逻辑,一是登录跳转,二是获取主页。
登录跳转约定:
约定使用POST请求,响应采用302重定向。
package login;
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;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 86136
* Date: 2023-04-01
* Time: 18:53
*/
@WebServlet("/loginServlet")
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");
//因为这里我们主要是演示Cookie和Session,所以为了简化流程,并不将用户名和密码
//存入到数据库中,而是直接定死。约定用户名合法为 Jay 和 Lin
//密码合法为123。
if(!username.equals("Jay") && !username.equals("Lin")) {
//登录失败
System.out.println("用户名错误");
resp.sendRedirect("login.html");
return;
}
if (!password.equals("123")) {
//登录失败
System.out.println("密码错误");
resp.sendRedirect("login.html");
return;
}
//登录成功
//1.创建一个会话
HttpSession session = req.getSession(true);
//2.把当前的用户名保存在会话中
//void setAttribute(String var1, Object var2);
session.setAttribute("username",username);
//初始情况下设置登录次数
session.setAttribute("count",0);
//3.重定向到主页
resp.sendRedirect("indexServlet");
}
}
分析:
其中的getSession(true):判定当前请求是否已经有对应的会话(拿着来自客户端的请求中Cookie里的sessionId查一下),如果sessionId不存在,或者没有被查到,那么就创建一个新的会话,并插入到哈希表中。如果查到了,就直接返回查到的结果。
创建新的会话的流程如下:构造一个HttpSession对象,构造唯一的sessionId,把这个键值对插入到哈希表中,最后把sessionId设置到响应报文的Set-Cookie字段中,有服务器发送给浏览器。
获取主页约定:
采用GET请求,响应返回一个页面:
package login;
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("/indexServlet")
public class IndexServlet extends HttpServlet {
//因为通过浏览器是通过重定向来发送请求的,所以以下为doGet
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//需要先判定用户的登录状态
//如果用户没有登录,要求先登录,如果登录了,则根据 会话 中的用户名,来显示到页面上
//这个操作不会触发会话的创建
HttpSession session = req.getSession(false);
if (session == null) {
//未登录状态
System.out.println("用户未登录");
}
//已经登录,取出会话信息
String username = (String) session.getAttribute("username");
Integer cnt = (Integer) session.getAttribute("count");
//访问次数加1
cnt++;
//写回到会话中
session.setAttribute("count",cnt);
//构造页面
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("欢迎" + username + "
这个页面已经被访问了"+cnt+"次
");
}
}
第二步,编写前端交互页面
我们的重点是来学习登录的逻辑,因此登录的界面不需要很好看很复杂,只要能够有两个输入框和一个提交按钮让我们输入账号密码就行。目标页面如下:
前面我们约定了登录的跳转采用post请求,由于场景很简单,我们直接使用form表单构造post请求就可以了:
Document
其中input标签的name属性就对应键值对的key,输入的内容就对应键值对的value。
效果演示:
下面利用抓包结果来进一步加深理解:
第一次交互
请求部分:注意,第一次请求是没有Cookie的。
服务器返回给登录界面的响应部分:
第二次交互
在登录状态下,主页向服务器发起访问请求:
这个带有Cookie的get请求到达服务器的时候,Servlet会在getSession方法中根据sessionId来查询HttpSession对象:
由于sessionId查的到,于是就返回信息。
服务器返回给浏览器的响应部分: