HTTP与HTTP2

HTTP

此文章只讨论HTTP和HTTP2,不讨论TLS和HTTP3

什么是HTTP

HTTP(超文本传输协议,HyperText Transfer Protocol)是

  • OSI七层(应用层,表示层,会话层,传输层,网络层,数据链路层,物理层)
  • TCP/IP四层(应用层,传输层,网络层,数据链路层)

中的应用层协议。

为什么需要HTTP?

HTTP(超文本传输协议)的主要作用是在客户端和服务器之间传输超文本(如HTML)及其他资源。

为什么不直接用tcp传输

  1. tcp是一个面向字节流的协议,会有分包,粘包等
  2. tcp传输数据没有规定格式,无法解析数据(就是这些规定形成了应用层协议)
  3. tcp没标准化,没高层语义

HTTP/1.1

HTTP/1.1就是要解决上面直接用tcp的问题

HTTP/1.1 request由四个部分组成

HTTP与HTTP2-qc的编程笔记
  1. http url http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]
  2. http 请求行 GET / HTTP/1.1
  3. http headers Content-Length: 11
  4. http body { "json":"example" }

http/1.1的三种发送方式

1. http短连接

HTTP与HTTP2-qc的编程笔记

2. http长连接

HTTP与HTTP2-qc的编程笔记

3. tcp管道

HTTP与HTTP2-qc的编程笔记

其中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

HTTP与HTTP2-qc的编程笔记
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 消息由以下部分组成:

  1. HEADERS 帧:包含消息的头部字段(如请求方法、状态码、头部字段等)。
  2. DATA 帧(可选):包含消息的负载数据(如请求体或响应体)。
  3. 其他帧(可选):如 PRIORITY 帧、CONTINUATION 帧等。

HTTP/2 frame

frame Nameframe Feature
DATAHTTP DATA
HEADERSHTTP HEADER
PRIORITY设置优先级
RST_STREAM错误处理
SETTINGS参数设置
PUSH_PROMISE服务端推送
PINGCHECK 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使用两个表将标题字段与索引相关联。

  • 静态表是预定义的,包含公共标题字段(其中大多数字段为空值)。
  • 动态表动态的,编码器可以使用它来索引编码标题列表中重复的标题字段。

静态表

静态表由预定义的标题字段静态列表组成。

动态表

动态表由按先进先出顺序维护的标题字段列表组成。动态表中的第一个也是最新的条目位于最低索引,而动态表中最早的条目位于最高索引。

动态表最初为空。在解压每个头块时添加条目。

动态表可以包含重复的条目(即具有相同名称和相同值的条目)。因此,解码器不得将重复条目视为错误。

编码器决定如何更新动态表,因此可以控制动态表使用的内存量。为了限制解码器的内存需求,动态表大小受到严格限制。

动态表可以看作一个队列,当容量超过时,先进先出

HTTP与HTTP2-qc的编程笔记

HPACK编码

HPACK编码使用两种基本类型:无符号变长整数和八位字节字符串。

无符号边长整数

如果整数值足够小,即严格小于2^N-1,则在N位前缀内对其进行编码。

否则,前缀的所有位都设置为1,值减少2^N-1,使用一个或多个八位字节的列表进行编码。

每个八位字节的最高有效位用作连续标志:除列表中的最后一个八位字节外,其值设置为1。八位字节的剩余位用于对减少的值进行编码。

HTTP与HTTP2-qc的编程笔记
字符串

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

HTTP与HTTP2-qc的编程笔记

字符串文字表示法包含以下字段:

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

实现参考

RFC 7540: Hypertext Transfer Protocol Version 2 (HTTP/2)

RFC 7541: HPACK: Header Compression for HTTP/2