网络协议(二):HTTP 协议

553 阅读29分钟

什么是 HTTP 协议

HTTP(超文本传输协议),是一种实现网络通信的规范。它定义了客户端和服务器之间交换报文的格式和方式,默认使用的是 80 端口,其底层使用 TCP 作为传输层协议,保证了数据传输的可靠性。

特点:

  • 无状态:协议对客户端没有状态存储,对事物处理没有“记忆”能力,比如访问一个网站需要反复进行登录操作

  • 基于请求和响应:基本的特性,由客户端发起请求,服务端响应

  • 使用明文通信

  • 无连接:很多资料都将“无连接”作为 HTTP 的一个特点,但是并不准确。

    • 在 HTTP/1.0 中每次连接要经历 TCP 的 3 次握手和 4 次挥手,这可以看作是短连接,尽管可以通过 Keep-Alive 来避免,但并不是所有的浏览器都兼容,因为在 HTTP/1.0 中 Keep-Alive 并没有得到官方的广泛支持;

    • 在 HTTP/1.1 Keep-Alive 被正式纳入规范,并且默认情况下是开启的,也就是说默认是长连接,除非在请求或响应的 HTTP 头部中包含 Connection: close。这样,在请求/响应完成后,TCP 连接就会被关闭。。

    • 在 HTTP/2 中引入了多路复用和服务器推送等技术,单个 TCP 连接被用于所有 HTTP 消息的交换

    • HTTP/3 用 QUIC 作为传输层协议,不是基于 TCP 了

HTTP 各版本区别

HTTP/1.0

在 HTTP/1.0 中,每个 TCP 连接通常只能处理一个 HTTP 请求和一个响应,即非持久连接(也称为短连接)。这意味着对于每个 HTTP 请求,都需要进行 TCP 的三次握手来建立连接,并在请求和响应完成后进行 TCP 的四次挥手来关闭连接,极大的增加了通信开销。。

HTTP/1.1

  • HTTP/1.1 引入了更多的缓存控制策略,如 Cache-Control、ETag 等

  • HTTP/1.1 可以在请求头中加入 Range 头部,表示允许范围请求,用于指定希望接收的资源范围

  • HTTP/1.1 的请求消息和响应消息都必须包含 Host 头部,用于指定请求的目标主机名和端口号(如果端口号不是80)

  • 管线化,客户端可以同时发出多个HTTP请求,而不用一个个等待响应

  • HTTP/1.1 默认开启持久连接(长连接),一定时间内,同一域名下的 HTTP 请求,只要两端都没有提出断开连接,则持久保持 TCP 连接状态,其他请求可以复用这个连接通道。但是持久连接采用阻塞模式,下次请求必须等到上次响应返回后才能发起,如果上次的请求还没返回响应内容,下次请求就只能等着(就是常说的线头阻塞)。

HTTP/2.0

在 HTTP/2 中,有两个非常重要的概念,分别是帧(frame)和流(stream),理解这两个概念是理解下面多路复用的前提。

帧代表数据传输的最小的单位,每个帧都有序列标识表明该帧属于哪个流。

流也就是多个帧组成的数据流,每个流表示一个请求。

  • 二进制分帧: HTTP/2.0 通过采用二进制格式进行协议解析,克服了HTTP/1.x 基于文本解析的缺陷(例如空格、换行符、引号等字符在文本中的使用可能导致解析上的歧义或错误),提高了传输效率,增强了功能支持,并提升了协议的稳定性和可靠性。

  • 多路复用: HTTP/2.0 支持多路复用,这是 HTTP/1.1 持久连接的升级版。

    • 多路复用,就是在一个 TCP 连接中可以存在多条流,也就是可以发送多个请求,服务端则可以通过帧中的标识知道该帧属于哪个流(即请求),通过重新排序还原请求。

    • 多路复用允许并发的发起多个请求,每个请求及该请求的响应不需要等待其他的请求或响应,避免了线头阻塞问题。这样某个请求任务耗时严重,不会影响到其它连接的正常执行,极大的提高传输性能。

    • 简单来说就是 HTTP/1.1 中多个请求通常是串行的,而 HTTP/2.0 则支持多个请求并行处理

  • 头部压缩: HTTP/1.x 的请求和响应头部带有大量信息,而且每次请求都要重复发送。HTTP/2.0 引入的头部压缩机制,通过 HPACK 算法和静态/动态字典,高效压缩 HTTP 头部信息,显著减少传输数据量,加快页面加载速度,优化缓存效率。

  • 服务端推送: HTTP/2.0 的服务端推送功能允许服务器在响应客户端的初始请求时,预测并主动推送客户端可能需要的额外资源。这些资源可以是 CSS 样式表、JavaScript 脚本、图片等。通过服务端推送,客户端可以在一次 HTTP 连接中同时获取到多个资源,减少了后续的请求次数和延迟。

特性HTTP/1.1 长连接HTTP/2.0 多路复用
连接类型持久连接,减少建立/关闭开销持久连接,支持并行处理
请求处理顺序处理并行处理,解决队头阻塞
数据传输文本协议,可能存在解析复杂性二进制分帧,提高传输效率
头部信息每次请求/响应需携带完整头部,存在冗余引入头部压缩,减少传输量
额外功能无服务器推送支持服务器推送,减少请求延迟

HTTP/3.0

HTTP/2.0 使用了多路复用,一般来说同一域名下只需要使用一个 TCP 连接。但当这个连接中出现了丢包的情况,那就会导致整个 TCP 都要开始等待重传,也就导致了后面的所有数据都被阻塞了。反而对于 HTTP/1.0 来说,可以开启多个 TCP 连接,出现丢包反倒只会影响其中一个连接,剩余的 TCP 连接还可以正常传输数据。 出现包阻塞的原因是因为底层 TCP 协议导致的问题,但是修改 TCP 协议是不现实的问题。既然不能修改你,那就另起一个协议取代你。Google 基于 UDP 协议推出了一个的 QUIC 协议,并且使用在了 HTTP/3 上。

QUIC 基于 UDP,但是 UDP 本身存在不稳定性等诸多问题,所以 QUIC 在 UDP 的基础上新增了很多功能,比如多路复用、0-RTT、使用 TLS1.3 加密、流量控制、有序交付、重传等等功能。

HTTP/3.0 的几个主要功能点:

  • 避免包阻塞: 多个流的数据包在 TCP 连接上传输时,若一个流中的数据包传输出现问题,TCP 需要等待该包重传后,才能继续传输其它流的数据包。但在基于 UDP 的 QUIC 协议中,不同的流之间的数据传输真正实现了相互独立互不干扰,某个流的数据包在出问题需要重传时,并不会对其他流的数据包传输产生影响。

  • 快速重启会话: 普通基于 TCP 的连接,是基于两端的 I、端口、协议来建立的。在网络切换场景,例如手机端切换了无线网,使用 4G 网络,会改变本身的 IP,这就导致 TCP 连接必须重新创建。而 QUIC 协议使用特有的 UUID 来标记每一次连接,在网络环境发生变化的时候,只要 UUID 不变,就能不需要握手,继续传输数据。

  • 0-RTT: 是一种在协议握手过程中减少延迟的机制,它允许客户端在建立连接的同时发送加密的应用数据,而无需等待服务器完成完整的握手过程。也就是说客户端可以在发送 Client Hello 消息时,立即附带加密的应用数据。

优化方面HTTP/2.0HTTP/3.0
传输层协议TCPQUIC(基于UDP)
连接建立和握手时延TCP三次握手 + TLS四次握手,时延较长QUIC同时处理连接和TLS握手,支持0-RTT,时延显著降低
性能提升多路复用,但存在TCP队头阻塞问题QUIC原生支持多路复用,无队头阻塞,提高资源利用效率
连接迁移和持久性网络变化时需重新建立TCP连接和TLS握手支持连接迁移,网络变化时无缝复用原连接,减少重连成本
安全性支持TLS加密,但握手过程与TCP连接分离TLS加密集成到QUIC协议栈中,更高效的安全机制
头部压缩使用HPACK算法进行头部压缩继承并可能进一步优化HTTP/2.0的头部压缩功能(具体取决于实现)
流量控制和拥塞控制依赖于TCP的流量控制和拥塞控制算法QUIC实现自己的流量控制和拥塞控制算法,可能更适应Web流量特性

HTTP 请求为什么要分 header 和 body

  • 结构清晰: HTTP 头部包含了关于请求的元数据(metadata),比如请求的方法(GET, POST, PUT 等)、请求的 URL、请求的协议版本、请求和响应的缓存信息、内容类型等。 而 HTTP 主体则包含了实际要发送或接收的数据。将这两部分分开,有助于服务器和客户端更好地理解请求的内容和目的。

  • 灵活性: 由于 HTTP 头部和主体是分离的,因此它们可以独立地进行扩展。例如,可以添加新的头部字段来支持新的功能或协议,而不需要改变主体的格式。 同样,主体部分也可以根据需要采用不同的格式(如 JSON、XML、文本、二进制等),从而支持不同的数据传输需求。

  • 安全性:通过 HTTP 头部中的某些字段(如 Authorization、Cookie 等),可以实现身份验证和授权等安全功能。 将这些安全相关的信息与实际的请求数据分开,有助于更好地保护用户的隐私和安全。

HTTP 报文组成

请求报文

  • 请求行:包括请求方法、URL、协议/版本

  • 请求头(Request Header):一系列的键值对

  • 空行:用于区分请求头部和请求正文

  • 请求正文(Request Body):不是所有的请求报文都包含请求正文,如 GET 请求通常不包含请求正文。

408dcc817435cdd6d6023df131a132d.jpg

响应报文

  • 状态行:包含 HTTP 协议版本、状态码和状态消息,如 HTTP/1.1 200 OK

  • 响应头:一系列的键值对

  • 空行:用于区分响应头部和响应正文

  • 响应正文(Response Body)

4c96823625dccc44980128a0ed96000.jpg

HTTP 缓存

是在 HTTP 协议中定义的一种数据缓存机制,通过在客户端(如浏览器)或代理服务器(如 nginx)中存储响应数据,以便在后续请求中复用这些数据

HTTP 缓存主要解决哪些问题?

  • 减少不必要的网络传输

  • 减低延迟、提高响应速度

  • 减少服务器负载

  • 可以离线预览

缺点就是会占用内存。

HTTP 缓存又分为两种两种缓存,强制缓存协商缓存

强制缓存

如果浏览器判断请求的目标资源有效命中强缓存,则可以直接从内存中读取目标资源,无需与服务器做任何通讯。

Expires

在以前,我们通常会使用响应头的 Expires 字段去实现强缓存:

public class CacheControlServlet extends HttpServlet {  
  
    @Override  
    protected void doGet(HttpServletRequest request, HttpServletResponse response)  
            throws ServletException, IOException {  
  
        // 设置内容类型  
        response.setContentType("text/html;charset=UTF-8");  
  
        // 创建一个表示资源过期时间的Date对象  
        // 例如,设置资源在1小时后过期  
        Date expires = new Date(System.currentTimeMillis() + 1000 * 60 * 60); // 1小时后的时间  
  
        // 设置Expires头部  
        response.setDateHeader("Expires", expires.getTime());  
  
        // 你可以在这里添加更多的逻辑来生成响应内容  
  
        // 响应完成  
    }  
  
    // 注意:doPost等其他方法也可以根据需要被重写  
}

Expires 判断强缓存是否过期的机制是:获取本地时间戳,与资源文件中的 Expires 字段的时间做比较,在时间范围内,则从内存(或磁盘)中读取缓存返回。

这里有一个巨大的漏洞:如果我本地时间不准咋办?

所以,Expires 字段几乎不被使用了。现在的项目中,我们使用 Cache-control 字段来代替 Expires 字段的强缓存功能。

Cache-control

Cache-control 是在资源的响应头上设置缓存时间,单位是秒。

response.setHeader("Cache-Control", "max-age=3600"); // 设置资源在1小时内有效

从第一次请求资源的时候开始,往后 N 秒内,资源若再次请求,则直接从内存(或磁盘)中读取,不与服务器做任何交互。

Cache-Control 有 6 个属性:

  • max-age:决定客户端资源被缓存多久。

  • s-maxag:决定代理服务器(如 nginx)缓存的时长。

  • no-cache:表示强制进行协商缓存,即跳过强缓存校验,直接去服务器进行协商缓存。

  • no-store:是表示禁止任何缓存策略。

  • public:表示资源即可以被浏览器缓存也可以被代理服务器缓存。

  • private:表示资源只能被浏览器缓存。

no-cache 和 no-store 互斥(即不能同时存在),public 和 private 互斥

Cache-Control 设置多个属性:

response.setHeader("Cache-Control", "max-age=10000,s-maxage=200000,public");

协商缓存

协商缓存主要有四个头字段,它们两两组合配合使用,Last-Modified 和 If-Modified-Since 一组,Etag 和 If-None-Match 一组,当同时存在的时候会以Etag 和 If-None-Match 为主。

当命中协商缓存的时候,服务器 HTTP 状态码会返回 304,让客户端直接从本地缓存里面读取文件。

Last-Modified 和 If-Modified-Since

流程:

1、首次请求资源

1.1、浏览器发送请求:

浏览器首次向服务器请求某个资源(如HTML、CSS、JS文件等)

1.2、服务器响应请求:

  • 服务器在返回资源的同时,在 HTTP 响应头中添加 Last-Modified 字段,该字段的值表示资源在服务器上的最后修改时间

  • 浏览器接收资源并缓存起来,同时缓存响应头中的 Last-Modified 值

2、再次请求资源

2.1、浏览器检查缓存:

当浏览器再次请求相同的资源时,它会先检查本地缓存中是否存在该资源

2.2、构造请求头:

如果缓存资源存在且未过期且协商缓存被启用,浏览器会在 HTTP 请求头中添加 If-Modified-Since 字段,其值为上一次请求时服务器返回的 Last-Modified 值

2.3、发送请求:

浏览器将包含 If-Modified-Since 请求头的请求发送给服务器

3、服务器处理请求

3.1、检查资源修改时间:

  • 服务器收到请求后,会检查请求头中的 If-Modified-Since 字段。

  • 然后,服务器会将该字段值与资源在服务器上的当前最后修改时间做比较。

3.2、返回响应:

  • 如果资源未修改(即服务器的最后修改时间未超过 If-Modified-Since 指定的时间):

    • 服务器将返回 HTTP 状态码 304(Not Modified),并且不会返回资源内容。

    • 浏览器接收到 304 状态码后,会从本地缓存中加载资源。

  • 如果资源已修改:

    • 服务器将返回 HTTP 状态码 200(OK),并发送资源的新内容。

    • 同时,服务器还会在响应头中更新 Last-Modified 值,以便下次请求时使用。

4、浏览器更新缓存

  • 如果服务器返回了 304 状态码,浏览器将保持本地缓存不变,并继续从缓存中加载资源。

  • 如果服务器返回了 200 状态码和新的资源内容,浏览器将更新本地缓存中的资源文件和 Last-Modified 值。

这种方式的重点是判断资源文件的修改时间,以此来判断资源文件有没有被修改,而不是判断时间有效期。

这种方式的缺点是:

  1. 只要编辑了,不管内容是否真的有改变,都会更新最后修改时间。

  2. Last-Modified 过期时间只能精确到秒。如果在同一秒既修改了文件又获取文件,客户端是获取不到最新文件的。

为了解决上述问题,从 HTTP.1 开始新增了一个头信息,ETag。

ETag 和 If-None-Match

ETag 和 If-None-Match 是 HTTP/1.1 引入,If-Modified-Since 和 Last-Modified HTTP/1.0 中就已经存在。

ETag 就是将 Last-Modified 那套比较时间戳的形式修改成了比较文件指纹。

文件指纹就是根据文件内容计算出的唯一哈希值。文件内容一旦改变则指纹改变。

ETag 和 If-None-Match 流程与 Last-Modified 和 If-Modified-Since 流程几乎一样,只是将 Last-Modified 替换成 ETag,If-Modified-Since 替换成 If-None-Match,比较文件修改时间替换成比较文件指纹

ETag 有强验证和弱验证:

  • 强验证:哈希码深入到每个字节,哪怕文件中只有一个字节改变了,也会生成不同的哈希值,它可以保证文件内容绝对的不变。

  • 弱验证:提取文件的部分属性来生成哈希值,整体速度会比强验证快,但是准确率不高

ETag 缺点:

  • 计算文件指纹意味着服务端需要更多的计算开销,文件尺寸大、数量多会影响服务器的性能,尤其是强验证会非常消耗计算量

很多网站在获取静态资源时会同时使用 Last-Modified 和 ETag,可能是考虑浏览器的兼容性问题吧。

强制缓存和协商缓存区别

强制缓存协商缓存
工作原理浏览器直接从本地缓存中读取资源,不向服务器发送请求浏览器向服务器发送请求,询问资源是否更新,根据服务器响应决定是否使用缓存
HTTP头字段主要依赖 Cache-Control 或 Expires主要依赖 ETag 或 Last-Modified
响应状态码缓存命中时,无请求发出,因此无状态码缓存命中时返回 304 Not Modified
适用场景适用于不经常变动的静态资源,如图片、CSS、JavaScript文件等适用于可能被频繁更新的资源,如动态数据等
性能影响减少网络请求,提高页面加载速度,降低服务器压力仍然需要网络请求,但可以减少数据传输量,对于频繁更新的资源能确保用户获取最新内容

如果同时设置了强制缓存和协商缓存,浏览器会先判断强制缓存是否命中,如果强制缓存未命中,则再判断协商缓存是否命中。

使用 nginx 配置缓存

server {  
    listen 80;  
  
    server_name your-domain.com; # 修改为你的域名  
  
    location /vue-app/ {  
        alias /path/to/your/dist/; # 修改为你的Vue项目dist目录的实际路径  
        try_files $uri $uri/ /vue-app/index.html; # 对于单页面应用,确保所有路由都返回index.html  
  
        # 开启强制缓存   
        add_header Cache-Control "public, max-age=3000";
        # 跳过强制缓存,强制进行协商缓存
        # add_header Cache-Control "no-cache";
        # 禁用缓存
        # add_header Cache-Control "no-store";
        
        # nginx 会自动给静态文件添加 Last-Modified 和 ETag 头部,无需额外配置
        ......
    }  
  
    # 其他location块或server配置...  
}

浏览器的缓存策略

在浏览器地址栏中输入然后通过回车/书签访问:这种情况下,默认使用浏览器的缓存机制,如果缓存有对应的资源,则直接从缓存中读取,否则会向服务器请求资源。

F5/点击工具栏中的刷新按钮/右键菜单重新加载:这种情况下,浏览器发送的请求头中加入了 Cache-Control: no-cache,这表示客户端不想使用缓存数据。服务器收到请求后,会忽略客户端的缓存,重新生成响应返回给客户端。

Ctl+F5:这种情况下,浏览器发送的请求头中加入了 Cache-Control: no-store,这表示客户端不希望在任何情况下使用缓存,所有数据都必须从服务器获取。服务器收到请求后,不仅忽略客户端的缓存,而且还不会在服务器端缓存响应数据。

HTTP 状态码

HTTP状态码表示客户端HTTP请求的返回结果、标识服务器处理是否正常、表明请求出现的错误等。

2XX成功(这系列表明请求被正常处理了)
200OK,表示从客户端发来的请求在服务器端被正确处理
204No content,表示请求成功,但响应报文不含实体的主体部分
206Partial Content,进行范围请求成功
3XX重定向(表明浏览器要执行特殊处理)
301moved permanently,永久性重定向,表示资源已被分配了新的 URL
302found,临时性重定向,表示资源临时被分配了新的 URL
303see other,表示资源存在着另一个 URL,应使用 GET 方法获取资源(对于301/302/303响应,几乎所有浏览器都会删除报文主体并自动用GET重新请求)
304not modified,协商缓存,与重定向无关
307temporary redirect,临时重定向,和302含义类似,但是期望客户端保持请求方法不变向新的地址发出请求
4XX客户端错误
400bad request,请求报文存在语法错误
401unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息
403forbidden,表示对请求资源的访问被服务器拒绝,可在实体主体部分返回原因描述
404not found,表示在服务器上没有找到请求的资源
5XX服务器错误
500internal sever error,表示服务器端在执行请求时发生了错误
501Not Implemented,表示服务器不支持当前请求所需要的某个功能
503service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求

HTTP 头部字段

通用首部作用(请求报文和响应报文都可能使用)
Cache-Control控制缓存的行为:no-cache(强制向服务器再次验证)、no-store(不做任何缓存)、max-age=111111(资源可缓存最大时间 秒)、public(客户端、代理服务器都可利用缓存)、private(代理服务器不可用缓存)
Connection浏览器想要优先使用的连接类型: keep-alive close(开启和关闭持久连接)
Date创建报文时间
Pragma只用于请求报文,客户端要求中间服务器不返回缓存的资源
Via代理服务器相关信息,每经过一个代理服务器就会添加相关信息,用逗号分割
Transfer-Encoding传输编码方式:chunked分块传输
Upgrade要求客户端使用的升级协议,需配合Connection: Upgrade一起使用:websocket
Warning缓存相关问题的警告

请求首部作用(请求报文专用)
Accept能正确接收的媒体类型:application/json text/plain
Accept-Charset能正确接收的字符集: unicode-1-1
Accept-Encoding能正确接收的编码格式列表:gzip deflate
Accept-Language能正确接收的语言列表:zh-cn,zh;1=0.9,en,1=0.8
Authorization客户端认证信息:Bearer dSdSdFFlsfdjasd123,一般存token用
Cookie发送给服务器的Cookie信息
Expect期待服务端的指定行为
From请求方邮箱地址
Host服务器的域名,用于区分单台服务器多个域名的虚拟主机,是HTTP/1.1唯一必须包含的字段。
If-Match两端资源标记比较,只有判断条件为真服务端才会接受请求:If-Mach: "123456,和服务端文件标记比较
If-Modified-Since本地资源未修改返回 304(比较时间)
If-None-Match本地资源未修改返回 304(比较标记)
User-Agent客户端信息
Max-Forwards限制可被代理及网关转发的次数
Proxy-Authorization向代理服务器发送验证信息
Range请求某个内容的一部分,配合If-Range使用
Referer请求发起页面的原始URI
TE传输编码方式

响应首部作用(响应报文专用)
Accept-Ranges告知客户端服务器是否可接受范围请求,是bytes,否none
Age资源在代理缓存中存在的时间
ETag资源标识,资源发生变化时标识也会发生改变
Location客户端重定向到某个 URL
Proxy-Authenticate向代理服务器发送验证信息
Server服务器名字:Apache Nginx
WWW-Authenticate获取资源需要的认证方案
Set-Cookie需要存在客户端的信息,一般用于识别用户身份

实体首部作用(补充请求报文或响应报文相关信息)
Allow资源的正确请求方式:GET HEAD POST
Content-Encoding内容的编码格式:gzip deflate
Content-Language内容使用的语言:zh-CN
Content-Lengthrequest body 长度(即实体主体的大小)
Content-Location返回数据的备用地址
Content-MD5Base64加密格式的内容 MD5检验值
Content-Range响应主体的内容范围
Content-Type内容的媒体类型(如'application/json;charset=UTF-8'则会发送预检请求)
Expires内容的过期时间
Last_modified内容的最后修改时间

在浏览器访问一个 URL 会发生什么?

  1. URL 解析:首先会判断输入的是一个合法 url 还是关键词,并根据输入的内容进行相应的操作。

  2. 查找缓存:浏览器会判断所请求的资源是否在浏览器缓存中,以及是否失效。如果没有失效就直接使用;如果没有缓存或失效了,就继续下一步。

  3. DNS 解析:此时需要获取 URL 中域名对应的 IP 地址。

    • 浏览器会依次查看浏览器缓存、操作系统缓存中是否有 IP 地址

    • 如果缓存中没有就会向本地域名服务器发起请求,获取 IP 地址。本地域名服务器也会先检查缓存,有则直接返回;

    • 如果也没有,则采用迭代查询方式,向上级域名服务器查询。

      • 先向根域名服务器发起请求,获取顶级域名服务器的地址;

      • 再向顶级域名服务器发起请求以获取权限域名服务器地址;

      • 然后向权限域名服务器发起请求并得到 URL 中域名对应的 IP 地址。

    • 本地域名服务器、操作系统、浏览器依次将 IP 缓存起来

  4. 建立 TCP 连接:根据ip地址,三次握手与服务器建立TCP连接。

  5. 发起请求:浏览器向服务器发起HTTP请求。

  6. 响应请求:服务器响应HTTP请求,将相应的HTML文件返回给浏览器。

  7. 关闭 TCP 连接四次挥手关闭TCP连接。

  8. 渲染页面:浏览器解析HTML内容,并开始渲染:

    • 构建 DOM 树

    • 构建 CSSOM 树

    • 生成渲染树(Render Tree)

    • 布局(Layout)

    • 绘制(Paint)

    • 重排和重绘(Reflow & Repaint)

    详细过程请参阅:前端(五):浏览器渲染流程

GET 和 POST 请求的本质区别是什么

标准答案:

  • GET 在浏览器回退时是无害的,而 POST 会再次提交请求。

  • GET 产生的 URL 地址可以被保存为书签,而 POST 不可以。

  • GET 请求会被浏览器主动 cache,而 POST 不会,除非手动设置。

  • GET 请求只能进行 url 编码,而 POST 支持多种编码方式。

  • GET 请求参数会被完整保留在浏览器历史记录里,而 POST 中的参数不会被保留。

  • GET 请求在 URL 中传送的参数是有长度限制的,而 POST 没有。

  • 对参数的数据类型,GET 只接受 ASCII 字符,而 POST 没有限制。

  • GET 比 POST 更不安全,因为参数直接暴露在 URL 上,所以不能用来传递敏感信息。

  • GET 参数通过 URL 传递,POST 放在 Request body 中。

(本标准答案参考自w3schools)

“很遗憾,这不是我们要的回答!”

请告诉我真相。。。

如果我告诉你 GET 和 POST 本质上没有区别你信吗?

让我们扒下 GET 和 POST 的外衣,坦诚相见吧!

GET 和 POST 是什么?HTTP 协议中的两种发送请求的方法。

HTTP 是什么?HTTP 是基于 TCP/IP 的关于数据如何在万维网中如何通信的协议。

HTTP 的底层是 TCP/IP。所以 GET 和 POST 的底层也是 TCP/IP,也就是说,GET/POST 都是 TCP 链接。GET 和 POST 能做的事情是一样一样的。你要给 GET 加上 request body,给 POST 带上 url 参数,技术上是完全行的通的。

在我大万维网世界中,TCP 就像汽车,我们用 TCP 来运输数据,它很可靠,从来不会发生丢件少件的现象。但是如果路上跑的全是看起来一模一样的汽车,那这个世界看起来是一团混乱,送急件的汽车可能被前面满载货物的汽车拦堵在路上,整个交通系统一定会瘫痪。

为了避免这种情况发生,交通规则 HTTP 诞生了。HTTP 给汽车运输设定了好几个服务类别,有 GET、POST、PUT、DELETE 等等,HTTP 规定,当执行 GET 请求的时候,要给汽车贴上 GET 的标签(设置 method 为 GET),而且要求把传送的数据放在车顶上(url 中)以方便记录。如果是 POST 请求,就要在车上贴上 POST 的标签,并把货物放在车厢里。

当然,你也可以在 GET 的时候往车厢内偷偷藏点货物,但是这是很不光彩;也可以在 POST 的时候在车顶上也放一些数据,让人觉得傻乎乎的。HTTP 只是个行为准则,而 TCP 才是 GET 和 POST 怎么实现的基本。

但是,我们只看到 HTTP 对 GET 和 POST 参数的传送渠道(url 还是 requrest body)提出了要求。“标准答案”里关于参数大小的限制又是从哪来的呢?

在我大万维网世界中,还有另一个重要的角色:运输公司。不同的浏览器(发起 http 请求)和服务器(接受 http 请求)就是不同的运输公司。虽然理论上,你可以在车顶上无限的堆货物(url 中无限加参数)。

但是运输公司可不傻,装货和卸货也是有很大成本的,他们会限制单次运输量来控制风险,数据量太大对浏览器和服务器都是很大负担。业界不成文的规定是,(大多数)浏览器通常都会限制 url 长度在 2K 个字节,而(大多数)服务器最多处理 64K 大小的 url。

超过的部分,恕不处理。如果你用 GET 服务,在 request body 偷偷藏了数据,不同服务器的处理方式也是不同的,有些服务器会帮你卸货,读出数据,有些服务器直接忽略,所以,虽然 GET 可以带 request body,也不能保证一定能被接收到哦。

好了,现在你知道,GET 和 POST 本质上就是 TCP 链接,并无差别。但是由于 HTTP 的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。

我们的大BOSS还等着出场呢。。。

这位BOSS有多神秘?当你试图在网上找"GET 和 POST 的区别"的时候,那些你会看到的搜索结果里,从没有提到他。他究竟是什么呢。。。

GET 和 POST 还有一个重大区别

简单的说:

  • GET 产生一个 TCP 数据包;POST 产生两个 TCP 数据包。

长的说:

  • 对于 GET 方式的请求,浏览器会把 http header 和 data 一并发送出去,服务器响应 200(返回数据);

  • 而对于 POST,浏览器先发送 header,服务器响应 100 continue,浏览器再发送 data,服务器响应 200 ok(返回数据)。

也就是说,GET 只需要汽车跑一趟就把货送到了。而 POST 得跑两趟,第一趟,先去和服务器打个招呼“嗨,我等下要送一批货来,你们打开门迎接我”,然后再回头把货送过去。

因为 POST 需要两步,时间上消耗的要多一点,看起来 GET 比 POST 更有效。因此 Yahoo 团队有推荐用 GET 替换 POST 来优化网站性能。但这是一个坑!跳入需谨慎。为什么?

  1. GET 与 POST 都有自己的语义,不能随便混用。

  2. 据研究,在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的 TCP 在验证数据包完整性上,有非常大的优点。

  3. 并不是所有浏览器都会在 POST 中发送两次包,Firefox 就只发送一次。

现在,当面试官再问你"GET 与 POST 的区别"的时候,你的内心是不是这样的?

在这里插入图片描述

参考资料

前端基础篇之HTTP协议

2023前端面试系列--网络篇