HTTP与HTTP2
HTTP
此文章只讨论HTTP和HTTP2,不讨论TLS和HTTP3
什么是HTTP
HTTP(超文本传输协议,HyperText Transfer Protocol)是
- OSI七层(应用层,表示层,会话层,传输层,网络层,数据链路层,物理层)
- TCP/IP四层(应用层,传输层,网络层,数据链路层)
中的应用层协议。
为什么需要HTTP?
HTTP(超文本传输协议)的主要作用是在客户端和服务器之间传输超文本(如HTML)及其他资源。
为什么不直接用tcp传输
- tcp是一个面向字节流的协议,会有分包,粘包等
- tcp传输数据没有规定格式,无法解析数据(就是这些规定形成了应用层协议)
- tcp没标准化,没高层语义
HTTP/1.1
HTTP/1.1就是要解决上面直接用tcp的问题
HTTP/1.1 request由四个部分组成

- http url http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]
- http 请求行 GET / HTTP/1.1
- http headers Content-Length: 11
- http body { "json":"example" }
http/1.1的三种发送方式
1. http短连接

2. http长连接

3. tcp管道

其中tcp管道已被证明难以实现因为返回时需要按发送的顺序返回。
http/1.1的缺点
- 请求 / 响应头部(Header)未经压缩就发送,首部信息越多延迟越大。只能压缩 Body 的部分;
- 发送冗长的首部。每次互相发送相同的首部造成的浪费较多;
- 服务器是按请求的顺序响应的,如果服务器响应慢,会招致客户端一直请求不到数据,队头阻塞;
- 没有请求优先级控制;
- 请求只能从客户端开始,服务器只能被动响应。
HTTP/2
HTTP/2就是来解决HTTP/1.1的缺点的
- 使用HPACK头部压缩
- 使用Stream实现多路复用
- 有优先级控制,stream dependency等设置
- 支持服务器推送
HTTP/2的优先级,依赖,流量控制后面再实现,这里先实现http2的编解码
HTTP/2 Connection
HTTP/2 建议在客户端与服务端之间只建立一条tcp连接,因为HTTP2可以多路复用
HTTP/2 Stream
- 流 是 HTTP/2 连接中的一个逻辑通道,用于传输一个 HTTP 请求和响应。
- 每个流有一个唯一的 流标识符(Stream ID) ,用于区分不同的流。
- 流是双向的,客户端和服务器可以在同一个流上发送和接收帧。
- 一个流只能用于传输一个 HTTP 请求和响应,不能复用。
流的特点
- 唯一性:每个流有一个唯一的流标识符(Stream ID),由发起流的端点分配。
- 独立性:多个流可以在同一个连接上并行传输,互不干扰。
- 双向性:客户端和服务器可以在同一个流上发送和接收帧。
- 有序性:流中的帧必须按顺序发送和接收,以确保消息的完整性。
stream state

idle(空闲)
- 初始状态:流在创建时处于
idle
状态。 - 转换条件:
- 发送或接收
HEADERS
帧(带END_STREAM
标志)或PUSH_PROMISE
帧时,流会从idle
状态转换到其他状态。 - 发送
PUSH_PROMISE
帧时,流会进入reserved (local)
状态。 - 接收
PUSH_PROMISE
帧时,流会进入reserved (remote)
状态。
- 发送或接收
reserved (local)(本地保留)
- 状态描述:当本地端点(客户端或服务器)发送了
PUSH_PROMISE
帧,表示它承诺将来会推送资源,流进入reserved (local)
状态。 - 转换条件:
- 发送
HEADERS
帧后,流会进入half closed (remote)
状态。 - 如果发送或接收
RST_STREAM
帧,流会进入closed
状态。
- 发送
reserved (remote)(远程保留)
- 状态描述:当远程端点发送了
PUSH_PROMISE
帧,表示它承诺将来会推送资源,流进入reserved (remote)
状态。 - 转换条件:
- 接收
HEADERS
帧后,流会进入half closed (local)
状态。 - 如果发送或接收
RST_STREAM
帧,流会进入closed
状态。
- 接收
open(打开)
- 状态描述:流处于打开状态,双方都可以发送和接收数据帧。
- 转换条件:
- 如果发送或接收
HEADERS
帧(带END_STREAM
标志),流会进入half closed
状态。 - 如果发送或接收
RST_STREAM
帧,流会进入closed
状态。
- 如果发送或接收
half closed (local)(本地半关闭)
- 状态描述:本地端点已经发送了
END_STREAM
标志,表示本地不会再发送数据,但仍然可以接收来自远程端点的数据。 - 转换条件:
- 如果接收到
END_STREAM
标志,流会进入closed
状态。 - 如果发送或接收
RST_STREAM
帧,流会进入closed
状态。
- 如果接收到
half closed (remote)(远程半关闭)
- 状态描述:远程端点已经发送了
END_STREAM
标志,表示远程不会再发送数据,但本地仍然可以发送数据。 - 转换条件:
- 如果发送
END_STREAM
标志,流会进入closed
状态。 - 如果发送或接收
RST_STREAM
帧,流会进入closed
状态。
- 如果发送
closed(关闭)
- 状态描述:流已经完全关闭,不能再发送或接收任何数据。
- 转换条件:
- 一旦流进入
closed
状态,就不能再转换到其他状态。 - 如果发送或接收
RST_STREAM
帧,流会进入closed
状态。
- 一旦流进入
HTTP/2 message
在 HTTP/2 中,消息(Message) 是一个逻辑概念,表示一个完整的 HTTP 请求或响应。一个 HTTP/2 消息由一个或多个 帧(Frames) 组成,这些帧通过 流(Stream) 传输。
一个 HTTP/2 消息由以下部分组成:
- HEADERS 帧:包含消息的头部字段(如请求方法、状态码、头部字段等)。
- DATA 帧(可选):包含消息的负载数据(如请求体或响应体)。
- 其他帧(可选):如
PRIORITY
帧、CONTINUATION
帧等。
HTTP/2 frame
frame Name | frame Feature |
---|---|
DATA | HTTP DATA |
HEADERS | HTTP HEADER |
PRIORITY | 设置优先级 |
RST_STREAM | 错误处理 |
SETTINGS | 参数设置 |
PUSH_PROMISE | 服务端推送 |
PING | CHECK ALIVE |
GOAWAY | 停止接受新流 |
WINDOW_UPDATE | 流量控制 |
CONTINUATION | 延续HEADER |
在HTTP/2中没有请求行,使用伪头部字段代替,
伪头部字段特点
- 名称以冒号开头:伪头部字段的名称以冒号(
:
)开头,例如:method
、:path
。 - 必须出现在普通头部字段之前:在 HTTP/2 的
HEADERS
帧中,伪头部字段必须出现在所有普通头部字段之前。 - 不可重复:每个伪头部字段在同一个请求或响应中只能出现一次。
- 大小写敏感:伪头部字段的名称必须是小写的。
伪头部字段的使用规则
- 请求中必须包含的伪头部字段:
- 每个请求必须包含
:method
、:scheme
、:path
伪头部字段。 - 如果请求的目标是权威服务器(如 HTTP/1.1 的
Host
头部字段),则必须包含:authority
伪头部字段。
- 每个请求必须包含
- 响应中必须包含的伪头部字段:
- 每个响应必须包含
:status
伪头部字段。
- 每个响应必须包含
- 伪头部字段的顺序:
- 伪头部字段必须出现在普通头部字段之前。
- 伪头部字段的顺序没有严格要求,但通常按照
:method
、:scheme
、:authority
、:path
的顺序排列。
HPACK
HPACK是Header帧中data的压缩算法,只用于Header帧的data中
HPACK使用两个表将标题字段与索引相关联。
- 静态表是预定义的,包含公共标题字段(其中大多数字段为空值)。
- 动态表动态的,编码器可以使用它来索引编码标题列表中重复的标题字段。
静态表
静态表由预定义的标题字段静态列表组成。
动态表
动态表由按先进先出顺序维护的标题字段列表组成。动态表中的第一个也是最新的条目位于最低索引,而动态表中最早的条目位于最高索引。
动态表最初为空。在解压每个头块时添加条目。
动态表可以包含重复的条目(即具有相同名称和相同值的条目)。因此,解码器不得将重复条目视为错误。
编码器决定如何更新动态表,因此可以控制动态表使用的内存量。为了限制解码器的内存需求,动态表大小受到严格限制。
动态表可以看作一个队列,当容量超过时,先进先出

HPACK编码
HPACK编码使用两种基本类型:无符号变长整数和八位字节字符串。
无符号边长整数
如果整数值足够小,即严格小于2^N-1,则在N位前缀内对其进行编码。
否则,前缀的所有位都设置为1,值减少2^N-1,使用一个或多个八位字节的列表进行编码。
每个八位字节的最高有效位用作连续标志:除列表中的最后一个八位字节外,其值设置为1。八位字节的剩余位用于对减少的值进行编码。

字符串
标题字段名称和标题字段值可以表示为字符串文字。字符串文字被编码为八位字节序列,可以直接编码字符串文字的八位字节,也可以使用哈夫曼代码

字符串文字表示法包含以下字段:
- H:一位标志,H,指示字符串的八位字节是否为哈夫曼编码。
- 字符串长度:用于编码字符串文字的八位字节数,编码为带有7位前缀的整数
- 字符串数据:字符串文字的编码数据。如果H为“0”,则编码数据为字符串文字的原始八位字节。如果H是“1”,则编码数据是字符串文本的哈夫曼编码。