内容简介:最近阅读了一下RFC7540和一部分HTTP/2的Go语言支持实现,故作此记录。回想一下作为一个浏览器,请求HTTP/1网站的过程。浏览器经过一系列操作之后,和服务器建立了通信,并且发送请求,例如:此时服务器收到请求,并且返回
最近阅读了一下RFC7540和一部分HTTP/2的 Go 语言支持实现,故作此记录。
HTTP/1 的问题在哪
回想一下作为一个浏览器,请求HTTP/1网站的过程。浏览器经过一系列操作之后,和服务器建立了通信,并且发送请求,例如:
GET / HTTP/1.1 Host: jiajunhuang.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate
此时服务器收到请求,并且返回 /
对应的内容,此处,是一个网页。网页中包含了一堆的图片,css和js的链接,浏览器解析出来之后,
为了获取这些内容,浏览器必须新建一些和服务器的连接,请求图片,css和js等内容。例如请求 common.min.css
,会发起一个这样的
报文:
GET /static/css/common.min.css HTTP/1.1 Host: jiajunhuang.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate
可以发现,如果有n个这样的请求,请求中header里大部分的内容都是相同的例如 Host
, User-Agent
等。多次请求中包含同样的内容,
是一种资源浪费,而且,如果有多个资源需要请求,则需要建立多个TCP连接,请求完之后这个连接就废掉了,没法重复利用,对于TCP连接
的利用率实在是很低。
HTTP/1.1 中有 request pipelining,但是有缺陷,例如只能对GET请求进行pipelinning,详见: https://en.wikipedia.org/wiki/HTTP_pipelining
而HTTP/2就是为了解决上述问题而存在的。
HTTP/2
首先得说明,HTTP/2不再像HTTP/1那样是明文协议,HTTP/2是二进制协议,也就意味着,我们没法再简单的通过 telnet jiajunhuang.com 80
这样去请求一些内容。为什么HTTP/2要做成二进制协议呢?主要原因在 https://http2.github.io/faq/#why-is-http2-binary:
- 使用TLS之后,也没法直接telnet这样进行调试
- 相比明文协议(可以手工输入),二进制协议必须使用库或者工具(当然也可以自己写),相对来说更不容易出错
- 二进制协议解析起来更加高效(再也不用逐个字符去判断哪里是头部结束了,直接偏移多少就可以知道对应的字节表示什么意思)
frame(帧)
frame 是HTTP/2中最小的传输单位。HTTP/2 相对HTTP/1来说,把原来的头部和body分开来了,统一都丢到frame里,头部对应的frame的类型 是HEADERS,而body对应的frame的类型是DATA。除此之外,frame的类型还有例如:GOAWAY, WINDOW_UPDATE等等。可以这么理解,HTTP/2 连接开始之后,所有的数据都是一个frame的内容,直到开始下一个frame。因此,Go语言gRPC实现中有这么一段代码:
func readFrameHeader(buf []byte, r io.Reader) (FrameHeader, error) { _, err := io.ReadFull(r, buf[:frameHeaderLen]) if err != nil { return FrameHeader{}, err } return FrameHeader{ Length: (uint32(buf[0])<<16 | uint32(buf[1])<<8 | uint32(buf[2])), Type: FrameType(buf[3]), Flags: Flags(buf[4]), StreamID: binary.BigEndian.Uint32(buf[5:]) & (1<<31 - 1), valid: true, }, nil }
第一行,frameHeaderLen 的定义是 const frameHeaderLen = 9
,为什么呢?我们来看看RFC中对frame的格式的定义:
+-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | Type (8) | Flags (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (31) | +=+=============================================================+ | Frame Payload (0...) ... +---------------------------------------------------------------+
数一下 frame payload 之前一共是多少个bit, 24 + 8 + 8 + 1 + 31 = 72
,72 bit,也就是9byte了。既然提到了frame的格式,
那我们还是要看看frame中各个字段的用处:
-
Length: The length of the frame payload expressed as an unsigned 24-bit integer. Values greater than 2^14 (16,384) MUST NOT be sent unless the receiver has set a larger value for SETTINGS_MAX_FRAME_SIZE. 这24bit是一个24bit的无符号整数, 因此最大可以表示2^14=16384,所以通常来说,frame的最大长度是16384 byte, 也就是16k。当接收者设置了 SETTINGS_MAX_FRAME_SIZE 之后,才可以传输超过这个大小的frame。
-
Type: The 8-bit type of the frame. The frame type determines the format and semantics of the frame. 这8bit用来明确 所在frame的类型。
-
Flags: An 8-bit field reserved for boolean flags specific to the frame type. 这8bit用来标志当前类型的frame的一些其他 特征,例如DATA这种类型的frame有定义 END_STREAM(0x1) 表示当前frame是这个stream中最后的一个frame。
-
R: A reserved 1-bit field. The semantics of this bit are undefined, and the bit MUST remain unset (0x0) when sending and MUST be ignored when receiving. 保留位,没用。
-
Stream Identifier: A stream identifier (see Section 5.1.1) expressed as an unsigned 31-bit integer. The value 0x0 is reserved for frames that are associated with the connection as a whole as opposed to an individual stream. 当前frame 所在的stream的id。stream的概念在下一小节会介绍。
stream(流)
上一节我们看到了每个frame的定义中都有31bit用来标识当前frame所在stream的id。那么stream是个什么鬼?stream是一个抽象概念, 因为HTTP/2把原本HTTP/1中一个请求中的头部和body打散了,分成了HEADERS和DATA两个frame。那当服务器收到一堆的frame之后,他如何 知道哪个frame和哪个frame是一起的,组合起来是一个完整的请求呢?所以我们需要stream这个抽象概念,而且由于一个HTTP/2连接可以 同时传输多个stream,所以我们可以通过下面的图片来理解stream:
- 这是stream 1的内容:
- 这是stream 2的内容:
- 这是实际传输流程中的样子:
也就是这样:
从HTTP/1升级
接下来我们看看,由于现实世界中大部分网站还是HTTP/1的,HTTP/2在实现细节上与HTTP/1相差太大,是如何做到兼容的。
首先可以肯定的是, http://
和 https://
这两个scheme不能改,要不然估计HTTP/2别想推广开来,不信可以看看ipv6今天的覆盖
程度,ipv6可是推出二十好几年了呢。
那我们怎么判断服务器是不是支持HTTP/2呢?HTTP/1中有一个状态码,叫做 101 Switching Protocols 。从HTTP/1升级到HTTP/2连接,就靠它了。
https://tools.ietf.org/html/rfc7540#section-3.2
首先发起HTTP/1请求,并且给出如下报文:
GET / HTTP/1.1 Host: jiajunhuang.com Connection: Upgrade, HTTP2-Settings Upgrade: h2c HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
如果服务器不支持HTTP/2,就该怎么返回怎么返回,例如返回:
HTTP/1.1 200 OK Content-Length: 243 Content-Type: text/html ...
但是如果服务器支持HTTP/2,那就返回101状态码,然后随即开始的内容就是HTTP/2的二进制内容了:
HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: h2c [ HTTP/2 connection ...
没有讲到的东西
- HPACK HTTP/2 头部压缩
- Stream prioritization
- Server Push
- Flow Control
说穿了,HTTP/2就是把HTTP/1建立在多个TCP之上的这一整套流程,建立在一个TCP之上,所以有这么一坨的概念,例如multiplexing, 优先级等等。
头部压缩我准备在读完 RFC 7541 之后再单独介绍。
参考
以上所述就是小编给大家介绍的《HTTP/2 简介》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
P2P网贷投资手册
徐红伟 / 同济大学出版社 / 2015-4 / CNY 28.00
《P2P网贷投资手册》由“P2P网络借贷知多少”、“新手如何开始P2P网贷投资”和“如何确定适合自己的网贷投资策略”三部分组成。将网贷之家平台上众多投资人和从业者的智慧集结成册,分享给网贷投资上的同路人。一起来看看 《P2P网贷投资手册》 这本书的介绍吧!