HTTP

HTTP1

常见相关问题:

  • HTTP 基本概念
    • 是什么?
    • 常见状态码
    • 常见字段
  • GET 与 POST
    • 区别? - 是否安全和幂等?
  • HTTP 特性
    • 优点?
    • 缺点?
    • 性能?
  • HTTPS 与 HTTP
    • 区别?
    • 解决的问题?
    • 如何解决的?
    • 如何建立连接的?
  • HTTP/1.1, HTTP/2, HTTP/3 演变
    • HTTP/1.1 比 HTTP/1.0 提高了什么性能?
    • HTTP/2 针对 HTTP/1 做了什么优化?
    • HTTP/2 的缺陷以及 HTTP/3 的优化?

HTTP 基本概念

HTTP 是超文本传输协议(HyperText Transfer Protocol),可以分成 3 个部分:

超文本指不止是文本,还有图片、音视频、文档等,传输是指在两个通信端点间传输。

HTTP 的优点:简单、灵活和易于扩展、应用广泛和跨平台。 简单:由 请求行 + 请求头部 + 请求内容 组成,请求行是文本,头部是 key-value 结构的文本形式,请求内容可多样。 灵活和易于扩展:各类请求方法、 URI/URL 、状态码、头部字段等没有被固定死,允许开发人员自定义和扩充。HTTP 是应用层协议,下层协议可变化。 应用广泛和跨平台:各类设备的浏览器都支持。

HTTP 的缺点:无状态、明文传输不安全。 无状态:服务器不会记录 HTTP 的状态,不需要额外的资源来记录状态信息,能减轻服务器负担,节约 CPU 和内存。但对于关联性操作不友好,所以出现了 Cookie 等用于身份认证的技术。 明文传输不安全:无法防御窃听、假冒、篡改攻击,无保证信息的机密性和完整性。

状态码

Table 1: 常见状态码
状态码 含义 示例
1xx 提示信息,表示协议中间状态,还需后续处理 101
2xx 成功,报文已经收到并成功处理 200, 204, 206
3xx 重定向,资源位置发生变动,需要客户端重新发送请求 301, 302, 304
4xx 客户端错误,请求保温有误,服务器无法处理 400, 403, 404
5xx 服务器错误,服务器在处理请求时内部发生了错误 500, 502, 503, 504

常见字段

HTTP 的 header 是 key-value 结构,key 在规范中不允许使用下划线 _ 连接两个单词,需使用 - ,不允许使用 @

Host: 客户端指定服务器的域名 Content-Length: 服务器表明响应数据的长度 Connection: 客户端要求服务器使用 TCP 持久连接,以便其他请求复用这个 TCP 连接 Content-Type: 服务器表明响应数据的格式,如 text/html, application/json Accept: 客户端表明自己能接受哪些数据格式,常见 */* Content-Encoding: 服务器表明响应数据的压缩方法 Accpet-Encoding: 客户端表明自己能接受的压缩方法

请求方法

GET: 从服务器获取指定 URL 的资源,如文本、页面、图片等 POST: 向服务器的指定 URL 提交数据,数据存在于 body 中

GET 是安全且幂等的,GET 是只读的操作,无论操作多少次,结果都是一样的 POST 是不安全且不幂等的,每次操作会修改服务器上的资源,多次操作会累积

HTTP/1.1

长连接 keepalive :即持久连接,减少了 TCP 连接的重复建立和断开而额外造成的开销,减轻服务器负载。 管道网络传输 pipe :客户端可以发起多个请求,不必等待响应,继而发送第二个请求,减少整体的响应时间。

问题: 对头阻塞:服务器还是按照请求顺序进行响应,若前面的请求因某种原因而被阻塞时,后面排队的请求也被阻塞了,导致客户端一直获取不到响应。 冗余:请求/响应 header 没有压缩,只能压缩 body 部分,每次都要重复发送 header 造成了冗余。 其他:没有请求优先级的控制,请求只能从客户端发起,服务器只能被动响应。

局限性: Web 网页越来越大,平均网页长度不断增大。单位动不动就是 MB 。 资源越来越多,很多网页都有 100 个以上的资源。 内容样式多样,图片流量占比越来越大。 实时性要求高,要求更快的速度。

但是延迟难以下降,触底延迟的下限。并发连接有限,浏览器限制了并连接数,每一个连接都要 TCP 和 TLS 握手,会受到 TCP 慢启动过程给流量带来的影响。还有前文存在的问题。

优化: 将小图合成大图传输,在客户端使用 JavaScript 切割,图片 Base64 编码传输;打包 JS 和 CSS 静态文件;将页面资源分散到不同的域名,提升并发连接数上限。

keepalive

长连接,前提条件是我们先得确定请求头与响应体的长度。

HTTP1.0 默认 connection 为 close ; HTTP1.1 默认 connection 为 keep-alive 。

对于请求来说,如果当前请求需要有 body ,如 POST 请求,那就需要客户端在请求头中指定 content-length 来表明 body 的大小,否则返回 400 错误。也就是说,请求体的长度是确定的。

响应体的长度:

  • 对于 HTTP1.0 协议来说,如果响应头中 有content-length 头,则以 content-length 的长度就可以知道 body 的长度了,客户端在接收 body 时,就可以依照这个长度来接收数据,接收完后,就表示这个请求完成了。而如果没有 content-length 头,则客户端会一直接收数据,直到服务端主动断开连接,才表示 body 接收完了。
  • 对于 HTTP1.1 协议来说,如果响应头中的 Transfer-encoding 为 chunked 传输,则表示 body 是流式输出, body 会被分成多个块,每块的开始会标识出当前块的长度,此时, body 不需要通过长度来指定。如果是非 chunked 传输,而且有 content-length ,则按照 content-length 来接收数据。否则,如果是非 chunked ,并且没有 content-length ,则客户端接收数据,直到服务端主动断开连接。

pipe

pipeline 其实就是流水线作业,它可以看作为 keepalive 的一种升华,因为 pipeline 也基于长连接的,目的就是利用一个连接做多次请求。如果客户端要提交多个请求,对于 keepalive 来说,那么第二个请求,必须要等到第一个请求的响应接收完全后,才能发起,这和 TCP 的停止等待协议是一样的,得到两个响应的时间至少为 2RTT。而对 pipeline 来说,客户端不必等到第一个请求处理完后,就可以马上发起第二个请求。得到两个响应的时间可能能够达到 1RTT。

HTTP/2

    HTTP
 HPack Stream
    TLS 1.2+
    TCP
    IP

头部压缩:同时发送多个请求,header 是一样或相似的,使用 HPACK 算法在服务器和客户端同时维护一张 header 信息表,字段存入此表,生成索引号,遇到重复字段,只发送索引号。 二进制格式:全面采用二进制格式,header 和 body 统称为帧 (frame):头信息帧和数据帧。这样对于计算机来说,直接解析二进制报文就可以得到信息,提高数据传输效率。 数据流:每个请求或响应的所有数据包,称为一个数据流(stream),数据流有一个独一无二的编号,客户端发出的数据流编号为奇数,服务器发出的数据流编号为偶数。客户端可以指定数据流的优先级,让服务器优先响应。 多路复用:在一个连接中并发多个请求或响应,而不用按顺序一一对应,而不是 HTTP/1.1 那样的串行。 服务器推送:服务器可以提前把要用到的资源文件主动发送给客户端(Server Push/Cache Push)。

问题: 多个 HTTP 请求复用一个 TCP 连接,下层的 TCP 协议不知道有多少个 HTTP 请求,导致一旦传输层发生了丢包,就会出发 TCP 的重传机制,这样在一个 TCP 连接中的所有 HTTP 请求都必须等待这个丢了的包被重传回来。

只改变了应用层,兼容 HTTP/1.1 ,没有改变协议语义,改变了协议语法,相比 HTTP/1.1 改动点: 头部压缩,避免重复信息的多次传输,将 ASCII 编码改成二进制编码,提高效率。

HPack 算法

静态字典;动态字典;Huffman 编码进行压缩。 客户端和服务器都会建立和维护字典,用长度较小的索引号代替 header ,再使用 Huffman 编码压缩数据。 静态字典:HTTP/2 规则有 61 组高频出现的字符串和字段。

Table 2: 静态表编码
Index Header Name Header Value
1 :authority  
2 :method GET
3 :method POST
4 :path /
5 :path /index.html
6 :scheme http
7 :scheme https
8 :status 200
54 server  
55 set-cookie  
56 strict-transport-security  
57 transfer-encoding  
58 user-agent  
59 vary  
60 via  
61 www-authenticate  

非固定的 Header Value 是变化的,会经过 Huffman 编码后发送出去。 根据 RFC7541 规范,如果头部字段属于静态表范围,并且 Value 是变化的,那么他的 HTTP/2 头部的前 2 位固定为 01 ,头部格式如图:

  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| 0 | 1 |      Index (6+)       |
+---+---+-----------------------+
| H |     Value Length (7+)     |
+---+---------------------------+
| Value String (Length octets)  |
+-------------------------------+

HTTP/2 头部因为是二进制编码,不需要冒号空格和末尾的 \r\n 作为分隔符,改用字符串长度来分割 Index 和 Value 。H 标记是否启用 Huffman 编码。HTTP/2 还定义了一张静态 Huffman 表。

动态表编码:Index 从 62 开始,在编码解码时更新。例如若客户端发送了一个 custom-header 字段,双方更新自己的动态表,添加一个 Index 号 62 ,下一次根据规范发送 62 就行了。但动态表生效需要是 同一个连接,重复传输完全相同的 HTTP 头部 。如果字段在一个连接上只发送了一次,或者重复传输时,字段变化了,动态表就无法被充分利用。 服务器一般有 http2_max_requests 限制一个连接能传输的请求数量,避免动态表无线增大。

二进制帧

    +-----------------------------------------------+
    |                 Length (24)                   |
    +---------------+---------------+---------------+
    |   Type (8)    |   Flags (8)   |
    +-+-------------+---------------+-------------------------------+
    |R|                 Stream Identifier (31)                      |
    +=+=============================================================+
    |                   Frame Payload (0...)                      ...
    +---------------------------------------------------------------+

Frame Type: 数据帧(前 3 种)和控制帧:

Table 3: Frame Type
Type Encode Function
DATA 0x0 Transfer body
HEADERS 0x1 Transfer header
PRIORITY 0x2 Designate priority
RST_STREAM 0x3 Reset stream
SETTINGS 0x4 Modify config of connection or stream
PUSH_PROMISE 0x5 Describe frame pushed by server
PING 0x6 Heart beat
GOAWAY 0x7 Close gracefully or send error
WINDOW_UPDATE 0x8 Traffic control
CONTINUATION 0x9 Long frame

Flags 可以携带控制信息如 END_HEADERS, END_STREAM, PRIORITY ;R 为保留位;Stream Identifier 帧标识用于标识该 Frame 属于哪个 Stream 。Payload 存放的是经过 HPack 算法压缩过的 HTTP 头部和包体。

并发传输

1 个 TCP 连接包含 1 个或多个 Stream , Stream 是 HTTP/2 并发的关键技术; Stream 里可以包含 1 个或多个 Message , Message 对应 HTTP/1 中的请求或响应,由 header 和 body 组成; Message 里包含 1 个或多个 Frame , Frame 是 HTTP/2 的最小单位,以二进制压缩格式存放 HTTP/1 中的请求或响应。

HTTP 消息可以由多个 Frame 构成,1 个 Frame 可以由多个 TCP 报文构成。

服务器主动推送

Nginx 主动推送设置:

location /test.html {
    http2_push /test.css;
}

服务器主动的推送,用的是偶数号 Stream ,通过 PUSH_PROMISE 帧传输 HTTP 头部,通过帧的 Promised Stream ID 字段告知客户端,接下来会在哪个偶数号 Stream 中发送包体。

但因为下层 TCP 协议的特性 若 TCP 传输发生丢包,则会造成 队头阻塞

HTTP/3

    HTTP
 QPack Stream
 QUIC  TLS 1.3
    UDP
    IP

传输层使用 UDP 协议,当某个流发生丢包时,只会阻塞这一个流,不会影响其他流。使用 TLS 1.3 减少握手的 RTT 消耗,头部压缩算法使用 QPack 。

所解决的 HTTP/2 缺陷:队头阻塞、 TCP 与 TLS 的握手延迟、网络迁移需要重新连接。

UDP 协议的特点:简单、不可靠。无需握手和回收,包之间没有依赖性,比 TCP 快。

HTTP/3 也是使用二进制帧,但不需要 Stream 了, Stream 由 QUIC 实现,结构如下:

    +---------------+
    |   Type (8)    |
    +---------------+
    |   Length (8)  |
    +---------------+
    | Frame Payload |
    +---------------+

帧也是分为数据帧和控制帧。

QUIC(Quick UDP Internet Connection)协议

无队头阻塞:每个数据包都有一个序号唯一标识,当某个 Stream 流中的一个数据包丢失了,即使该流的其他数据包到达了,数据也无法被 HTTP/3 读取,直到 QUIC 重传丢失的报文。但 这个流不会影响到其他流 。多个 Stream 间是独立的,不像 HTTP/2 那样多个流复用一个 TCP 连接。 更快的连接建立:对于 HTTP/1 和 HTTP/2 , TCP 和 TLS 是分层的,分别属于内核实现的传输层和 OpenSSL 实现的表示/会话层,难以合并在一起,需要分批握手。 HTTP/3 在传输数据前需要 QUIC 握手,需要 1 RTT ,目的是确认双方的 连接ID ,连接 ID 可以用于连接迁移。 QUIC 包含了 TLS 1.3 ,仅需 1 RTT 就可以完成建立连接与密钥协商,在第二次连接的时候,应用数据包可以和 QUIC 握手信息(连接信息 + TLS 信息)一起发送,达到 0-RTT 的效果。 连接迁移:不使用 TCP 的四元组来“绑定”链接,通过 连接ID 来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,当网络发生变化导致 IP 地址改变,只要仍保有上下文信息(连接 ID 、 TLS 密钥),就可以“无缝”地复用原连接,消除重连的成本。

QPack

静态表扩大到 91 项。

HPack的动态表在首次请求-响应后,双方会将未包含在静态表中的 header 项更新到各自的动态表中,接着后续传输时仅用 1 个数字 Index 表示,然后对方可以根据 Index 查到对应的数据,提高传输效率。但在丢包的情况下,会导致双方动态表不同步,造成阻塞。 而 QPack 有两个特殊的 单向 流,用于同步双方的动态表:

  • QPack Encoder Stream: 用于将一个字典(key-value)传递给对方,客户端可以通过这个 Stream 发送不属于静态表的 HTTP header
  • QPack Decoder Stream: 用于响应对方,通知对方其刚发的字典已经更新

HTTPS = HTTP + SSL/TLS

HTTPS 需要向 CA 申请证书,确保服务器的身份是可信的。 HTTPS 实现的功能有:信息加密、校验机制、身份证书,同时使用非对称加密生成会话密钥,使用对称加密通信保证机密性,使用摘要算法实现完整性,使用数字证书防止假冒。

如何提高 HTTPS 的性能?1️⃣ TLS 握手阶段;2️⃣ 对称加密报文传输阶段。

  1. 软件升级:升级 Linux 系统内核版本,升级 OpenSSL 。 协议优化:使用 TLS 1.3(合并了 HELLO 和公钥交换消息) 及 ECDHE 密钥交换算法且选择 x25519 曲线。 证书优化:使用 ECDSA 证书,相比 RSA 证书,在同样的安全强度下,ECC 的密钥长度短的多。避免使用 CRT(Certificate Revocation List)验证证书,而使用 OCSP(Online Certificate Status Protocol)向 CA(Certificate Authority)发送证书查询请求以得到证书的有效状态或使用 OCSP Stapling 让服务器缓存一个从 CA 得到的时间戳和签名的响应以证明证书的有效性。 会话复用:(TLS session resumption)Session ID / Session Ticket / Pre-shared Key。
  2. 使用支持 AES-NI 的 CPU ,对于不支持 AES-NI 的设备,可选择使用 CHACHA20 ,可以适当选择 AES128。

Web

请求行包含请求的方法,URI,协议版本,也可以包含有 host 的。 比如一个请求:

GET http://example.com/uri HTTP/1.0

这样一个请求行也是合法的。

Web 访问过程

  1. 浏览器解析 URL

    protocol://domain/path/file?parameter=...#anchor
    
  2. 浏览器构造 HTTP 请求
  3. DNS 查询
    • 浏览器缓存
    • 操作系统缓存 (hosts)
    • 本地 DNS 服务器
    • 根 DNS 服务器
    • 顶级域 DNS 服务器
    • 权威 DNS 服务器
    • 得到域名的 IP 地址,可能为多个
  4. 调用 Socket 库发送 HTTP 请求
  5. 客户端 TCP
  6. 客户端 IP 层
  7. …网络…
  8. 服务器处理

Crawler / Netspider2

爬虫、反爬虫、反反爬虫。

URI/URL

URI 包括 URL 和 URN 两个类别,URL 是 URI 的子集,所以 URL 一定是 URI ,而 URI 不一定是 URL 。

URI = Universal Resource Identifier 统一资源标志符,用来标识抽象或物理资源的一个紧凑字符串。

URL = Universal Resource Locator 统一资源定位符,一种定位资源的主要访问机制的字符串,一个标准的URL必须包括:protocol、host、port、path、parameter、anchor。

URN = Universal Resource Name 统一资源名称,通过特定命名空间中的唯一名称或ID来标识资源。

Content-Type

常见 content-type :

  • application/x-www-form-urlencoded

    key1=value1&key2=value2
    
  • multipart/form-data
  • application/json

Cache-Control

  • max-age 是指定强缓存的时间
  • no-cache 是会用本地的缓存但每次都会协商
  • no-store 是禁用掉缓存

一般是入口 html 文件设置 no-cache ,资源文件如 js, css 都带有 hash 值,更新 html 后请求的资源文件名不一样,也就相当于刷新了缓存。

Footnotes:

1

图解网络-小林coding-v3.0.pdf

湘ICP备19014083号-1