本文将尽可能用通俗易懂的方式来向读者讲述 HTTP 的知识。javascript
另外,建议在学习 HTTP 知识的时候,利用 Chrome 开发者工具来作实践,这能够帮助你理解得更深入。html
(此图在网上找来的,侵删)前端
HTTP 概述
HTTP 超文本传输协议是位于 TCP/IP 体系结构中的应用层协议,它是万维网数据通讯的基础。java
当咱们访问一个网站时,须要经过统一资源定位符(uniform resource locator,URL)来定位服务器并获取资源。node
<协议>://<域名>:<端口>/<路径>
一个 URL 的通常形式一般如上所示(http://test.com/index.html
),如今最经常使用的协议就是 HTTP,HTTP 的默认端口是 80,一般能够省略。webpack
HTTP/1.1
HTTP/1.1 是目前使用最普遍的版本,通常没有特别标明版本都是指 HTTP/1.1。git
HTTP 链接创建过程
咱们来看一下在浏览器输入 URL 后获取 HTML 页面的过程。github
- 先经过域名系统(Domain Name System,DNS)查询将域名转换为 IP 地址。即将
test.com
转换为221.239.100.30
这一过程。 - 经过三次握手(稍后会讲)创建 TCP 链接。
- 发起 HTTP 请求。
- 目标服务器接收到 HTTP 请求并处理。
- 目标服务器往浏览器发回 HTTP 响应。
- 浏览器解析并渲染页面。
下图中的 RTT 为往返时延(Round-Trip Time: 往返时延。表示从发送端发送数据开始,到发送端收到来自接收端的确认,总共经历的时延)。web
HTTP 链接拆除过程
全部 HTTP 客户端(浏览器)、服务器均可在任意时刻关闭 TCP 链接。一般会在一条报文结束时关闭链接,但出错的时候,也可能在首部行的中间或其余任意位置关闭链接。算法
TCP 三次握手和四次挥手
因为 HTTP 是基于 TCP 的,因此打算在这补充一下 TCP 链接创建和拆除的过程。
首先,咱们须要了解一些 TCP 报文段的字段和标志位:
- 32 比特的序号字段和确认号字段,TCP 字节流每个字节都按顺序编号。确认号是接收方指望从对方收到的下一字节的序号。
- ACK 标志位,用于指示确认字段中的值是有效的 ACK=1 有效,ACK=0 无效。
- SYN 标志位,用于链接创建,SYN 为 1 时,代表这是一个请求创建链接报文。
- FIN 标志位,用于链接拆除,FIN 为 1 时,代表发送方数据已发送完毕,并要求释放链接。
TCP 三次握手创建链接
TCP 标准规定,ACK 报文段能够携带数据,但不携带数据就不用消耗序号。
- 客户端发送一个不包含应用层数据的 TCP 报文段,首部的 SYN 置为 1,随机选择一个初始序号(通常为 0)放在 TCP 报文段的序号字段中。(SYN 为 1 的时候,不能携带数据,但要消耗掉一个序号)
- TCP 报文段到达服务器主机后,服务器提取报文段,并为该 TCP 链接分配缓存和变量。而后向客户端发送容许链接的 ACK 报文段(不包含应用层数据)。这个报文段的首部包含 4 个信息:ACK 置 为 1,SYN 置为 1;确认号字段置为客户端的序号 + 1;随机选择本身的初始序号(通常为 0)。
- 收到服务器的 TCP 响应报文段后,客户端也要为该 TCP 链接分配缓存和变量,并向服务器发送一个 ACK 报文段。这个报文段将服务器端的序号 + 1 放置在确认号字段中,用来对服务器容许链接的报文段进行响应,由于链接已经创建,因此 SYN 置为 0。最后一个阶段,报文段能够携带客户到服务器的数据。而且之后的每个报文段,SYN 都置为 0。
下图是一个具体的示例:
(此截图是我使用 Wireshark 抓包工具截取的 TCP 报文段截图)。
TCP 四次挥手拆除链接
FIN 报文段即便不携带数据,也要消耗序号。
- 客户端发送一个 FIN 置为 1 的报文段。
- 服务器回送一个确认报文段。
- 服务器发送 FIN 置为 1 的报文段。
- 客户端回送一个确认报文段。
TCP 为何是四次挥手,而不是三次?
- 当 A 给 B 发送 FIN 报文时,表明 A 再也不发送报文,但仍能够接收报文。
- B 可能还有数据须要发送,所以先发送 ACK 报文,告知 A “我知道你想断开链接的请求了”。这样 A 便不会由于没有收到应答而继续发送断开链接的请求(即 FIN 报文)。
- B 在处理完数据后,就向 A 发送一个 FIN 报文,而后进入 LAST_ACK 阶段(超时等待)。
- A 向 B 发送 ACK 报文,双方都断开链接。
参考资料:
HTTP 报文格式
HTTP 报文由请求行、首部、实体主体组成,它们之间由 CRLF(回车换行符) 分隔开。
注意:实体包括首部(也称为实体首部)和实体主体,sp 便是空格 space。
请求行和首部是由 ASCII 文本组成的,实体主体是可选的,能够为空也能够是任意二进制数据。
请求报文和响应报文的格式基本相同。
请求报文格式:
<method> <request-URL> <version> <headers> <entity-body>
响应报文格式:
<version> <status> <reason-phrase> <headers> <entity-body>
一个请求或响应报文由如下字段组成:
- 请求方法,客户端但愿服务器对资源执行的动做。
- 请求 URL,命名了所请求的资源。
- 协议版本,报文所使用的 HTTP 版本。
- 状态码,这三位数字描述了请求过程当中所发生的状况。
- 缘由短语,数字状态码的可读版本(例如上面的响应示例跟在 200 后面的 OK,通常按规范写最好)。
- 首部,能够有零或多个首部。
- 实体的主体部分,能够为空也能够包含任意二进制数据。
一个 HTTP 请求示例:
GET /2.app.js HTTP/1.1 Host: 118.190.217.8:3389 Connection: keep-alive User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36 Accept: */* Referer: http://118.190.217.8:3389/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9
一个 HTTP 响应示例:
HTTP/1.1 200 OK X-Powered-By: Express Accept-Ranges: bytes Cache-Control: public, max-age=0 Last-Modified: Sat, 07 Mar 2020 03:52:30 GMT ETag: W/"253e-170b31f7de7" Content-Type: application/javascript; charset=UTF-8 Vary: Accept-Encoding Content-Encoding: gzip Date: Fri, 15 May 2020 05:38:05 GMT Connection: keep-alive Transfer-Encoding: chunked
方法
方法 | 描述 |
---|---|
GET | 从服务器获取一份文档 |
HEAD | 只从服务器获取文档的头部 |
POST | 向服务器发送须要处理的数据 |
PUT | 将请求的数据部分存储在服务器上 |
TRACE | 对可能通过代理服务器传送到服务器上去的报文进行追踪 |
OPTIONS | 决定能够在服务器上执行哪些方法 |
DELETE | 从服务器上删除一份文档 |
GET 和 HEAD
其中 GET 和 HEAD 被称为安全方法,由于它们是幂等的(若是一个请求无论执行多少次,其结果都是同样的,这个请求就是幂等的),相似于 POST 就不是幂等的。
HEAD 方法和 GET 方法很相似,但服务器在响应中只返回首部。这就容许客户端在未获取实际资源的状况下,对资源的首部进行检查。使用 HEAD,能够:
- 在不获取资源的状况下了解资源的状况。
- 经过查看响应状态码,看看某个对象是否存在。
- 经过查看首部,了解测试资源是否被修改了。
服务器开发者必须确保返回的首部与 GET 请求所返回的首部彻底相同。遵循 HTTP/1.1 规范,就必须实现 HEAD 方法。
PUT
与 GET 方法从服务器读取文档相反,PUT 方法会向服务器写入文档。PUT 方法的语义就是让服务器用请求的主体部分来建立一个由所请求的 URL 命名的新文档。 若是那个文档已存在,就覆盖它。
POST
POST 方法一般用来向服务器发送表单数据。
TRACE
客户端发起一个请求时,这个请求可能要穿过路由器、防火墙、代理、网关等。每一个中间节点均可能会修改原始的 HTTP 请求,TRACE 方法容许客户端在最终发起请求时,看看它变成了什么样子。
TRACE 请求会在目的服务器端发起一个“环回”诊断。行程最后一站的服务器会弹回一条 TRACE 响应,并在响应主体中携带它收到的原始请求报文。 这样客户端就能够查看在全部中间 HTTP 应用程序组成的请求/响应链上,原始报文是否被毁坏或修改过。
TRACE 方法主要用于诊断,用于验证请求是否如愿穿过了请求/响应链。它也是一种工具,用来查看代理和其余应用程序对用户请求所产生的效果。 TRACE 请求中不能带有实体的主体部分。TRACE 响应的实体主体部分包含了响应服务器收到的请求的精确副本。
OPTIONS
OPTIONS 方法请求 Web 服务器告知其支持的各类功能。
DELETE
DELETE 方法就是让服务器删除请求 URL 所指定的资源。
状态码
总体范围 | 已定义范围 | 分类 |
---|---|---|
100~199 | 100~101 | 信息提示 |
200~299 | 200~206 | 成功 |
300~399 | 300~305 | 重定向 |
400~499 | 400~415 | 客户端错误 |
500~599 | 500~505 | 服务器错误 |
300~399 重定向状态码
重定向状态码要么告诉客户端使用替代位置来访问他们感兴趣的资源,要么提供一个替代的响应而不是资源的内容。 若是资源已被移动,能够发送一个重定向状态码和一个可选的 Location 首部来告知客户端资源已被移走,以及如今在哪里能够找到它。这样,浏览器能够在不打扰使用者的状况下,透明地转入新的位置。
400~499 客户端错误状态码
有时客户端会发送一些服务器没法处理的东西,例如格式错误的请求报文、一个不存在的 URL。
500~599 服务器错误状态码
有时客户端发送了一条有效请求,服务器自身却出错了。
首部
首部和方法共同配合工做,决定了客户端和服务器能作什么事情。
首部分类:
- 通用首部,能够出如今请求或响应报文中。
- 请求首部,提供更多有关请求的信息。
- 响应首部,提供更多有关响应的信息。
- 实体首部,描述主体的长度和内容,或者资源自身。
- 扩展首部,规范中没有定义的新首部。
通用首部
有些首部提供了与报文相关的最基本信息,它们被称为通用首部。如下是一些常见的通用首部:
请求首部
请求首部是只在请求报文中有意义的首部,用于说明请求的详情。如下是一些常见的请求首部:
响应首部
响应首部让服务器为客户端提供了一些额外的信息。
实体首部
实体首部提供了有关实体及其内容的大量信息,从有关对象类型的信息,到可以对资源使用的各类有效的请求方法。
例如内容首部,提供了与实体内容有关的特定信息,说明了其类型、尺寸以及处理它所需的其余有用信息。
另外,通用的缓存首部说明了如何或何时进行缓存。实体的缓存首部提供了与被缓存实体有关的信息。
性能优化
1. 减小 HTTP 请求
每发起一个 HTTP 请求,都得经历三次握手创建 TCP 链接,若是链接只用来交换少许数据,这个过程就会严重下降 HTTP 性能。因此咱们能够将多个小文件合成一个大文件,从而减小 HTTP 请求次数。
其实因为持久链接(重用 TCP 链接,以消除链接及关闭时延;HTTP/1.1 默认开启持久链接)的存在,每一个新请求不必定都须要创建一个新的 TCP 链接。可是,浏览器处理完一个 HTTP 请求才能发起下一个,因此在 TCP 链接数没达到浏览器规定的上限时,仍是会创建新的 TCP 链接。从这点来看,减小 HTTP 请求仍然是有必要的。
2. 静态资源使用 CDN
内容分发网络(CDN)是一组分布在多个不一样地理位置的 Web 服务器。咱们都知道,当服务器离用户越远时,延迟越高。CDN 就是为了解决这一问题,在多个位置部署服务器,让用户离服务器更近,从而缩短请求时间。
3. 善用缓存
为了不用户每次访问网站都得请求文件,咱们能够经过添加 Expires 头来控制这一行为。Expires 设置了一个时间,只要在这个时间以前,浏览器都不会请求文件,而是直接使用缓存。
不过这样会产生一个问题,当文件更新了怎么办?怎么通知浏览器从新请求文件?
能够经过更新页面中引用的资源连接地址,让浏览器主动放弃缓存,加载新资源。
具体作法是把资源地址 URL 的修改与文件内容关联起来,也就是说,只有文件内容变化,才会致使相应 URL 的变动,从而实现文件级别的精确缓存控制。什么东西与文件内容相关呢?咱们会很天然的联想到利用数据摘要要算法对文件求摘要信息,摘要信息与文件内容一一对应,就有了一种能够精确到单个文件粒度的缓存控制依据了。
参考资料:
4. 压缩文件
压缩文件能够减小文件下载时间,让用户体验性更好。
gzip 是目前最流行和最有效的压缩方法。能够经过向 HTTP 请求头中的 Accept-Encoding 头添加 gzip 标识来开启这一功能。固然,服务器也得支持这一功能。
举个例子,我用 Vue 开发的项目构建后生成的 app.js 文件大小为 1.4MB,使用 gzip 压缩后只有 573KB,体积减小了将近 60%。
5. 经过 max-age 和 no-cache 实现文件精确缓存
通用消息头部 Cache-Control
其中有两个选项:
max-age
: 设置缓存存储的最大周期,超过这个时间缓存被认为过时(单位秒)。在这个时间前,浏览器读取文件不会发出新请求,而是直接使用缓存。no-cache
: 指定 no-cache 表示客户端能够缓存资源,每次使用缓存资源前都必须从新验证其有效性。
咱们能够将那些长期不变的静态资源设置一个很是长的缓存时间,例如设置成缓存一年。
而后将 index.html
文件设置成 no-cache
。这样每次访问网站时,浏览器都会询问 index.html
是否有更新,若是没有,就使用旧的 index.html
文件。若是有更新,就读取新的 index.html
文件。当加载新的 index.html
时,也会去加载里面新的 URL 资源。
例如 index.html
原来引用了 a.js
和 b.js
,如今更新了变成 a.js
和 c.js
。那就只会加载 c.js
文件。
具体请看 webpack + express 实现文件精确缓存。
HTTPS
HTTPS 是最流行的 HTTP 安全形式,由网景公司独创,全部主要的浏览器和服务器都支持此协议。 使用 HTTPS 时,全部的 HTTP 请求和响应数据在发送以前,都要进行加密。加密可使用 SSL 或 TLS。
SSL/TLS 协议做用在 HTTP 协议之下,对于上层应用来讲,原来的发送/接收数据流程不变,这就很好地兼容了老的 HTTP 协议。因为 SSL/TLS 差异不大,下面统一使用 SSL。
要想了解 HTTPS 为什么安全,还得继续了解一下这些概念:加密算法、摘要算法、数字签名和数字证书。
加密算法
对称密钥密码体制
对称密钥密码体制,即加密密钥和解密密钥是使用相同的密码体制。对称密钥加密技术的缺点之一就是发送者和接收者在对话以前,必定要有一个共享的密钥,因此不太安全。
公钥密码体制
公钥密码体制使用不一样的加密密钥与解密密钥。公钥密码体制产生的主要缘由有两个:一是对称密钥密码体制的密钥分配问题,二是对数字签名的需求。
在公钥密码体制中,加密密钥是公开的,解密密钥是须要保密的,加密算法和解密算法也是公开的。
公钥密码体制的加密和解密有以下特色:
- 密钥对产生器产生出接收者 B 的一对密钥,即加密密钥 PK 和解密密钥 SK。
- 发送者 A 用 B 的公钥 PK 做为加密密钥来加密信息,B 接收后用解密密钥 SK 解密。
![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uWP5p7So-1589532545350)(../imgs/is3.png)\]](https://img-blog.csdnimg.cn/2...
使用对称密钥时,因为双方使用一样的密钥,所以在通讯信道上能够进行一对一的双向保密通讯,双方均可以用同一个密钥加密解密。
使用公开密钥时,在通讯信道上能够是多对一的单向保密信道。便可以有多人持有 B 的公钥,但只有 B 才能解密。
摘要算法
摘要算法的主要特征是加密过程不须要密钥,而且通过加密的数据没法被解密,目前能够被解密逆向的只有CRC32算法,只有输入相同的明文数据通过相同的消息摘要算法才能获得相同的密文。
数字签名
用加密系统对报文进行签名,以说明是谁编写的报文,同时证实报文未被篡改过,这种技术称为数字签名。
数字签名是附加在报文上的特殊加密校验码。使用数字签名的好处有:
- 签名能够证实是做者编写了这条报文。只有做者才会有最机密的私有密钥,所以,只有做者才能计算出这些校验和。
- 签名能够防止报文被篡改,若是有人在报文传输过程当中对其进行了修改,校验和就再也不匹配了。
数字签名一般是用非对称公开密钥技术产生的。
看上图,任何人都能用 A 的公钥 PK 对密文进行 E 运算后获得 A 发送的明文。可见这种通讯并不是为了保密,而是为了进行签名和核实签名,即确认此信息是 A 发送的(使用 A 的密钥进行加密的报文,只有使用 A 的公钥才能正确解密)。 但上述过程仅对报文进行了签名,对报文 X 自己却未保密,因此要采用下图的方法,同时实现秘密通讯和数字签名。
数字证书
假如你想访问一个网站,怎么确保对方给你的公钥是你想访问的网站的公钥,而不是被中间人篡改过的?
数字证书的出现就是为了解决这个问题,它是由数字证书认证机构颁发的,用来证实公钥拥有者的身份。换句话说,数字证书的做用就至关于人的身份证,身份证证实了张三就是张三,而不是别人。
数字证书通常包含如下内容:
- 对象的名称(人、服务器、组织等);
- 过时时间;
- 证书发布者(由谁为证书担保);
- 来自证书发布者的数字签名;
- 对象的公钥;
- 对象和所用签名算法的描述性信息。
任何人均可以建立一个数字证书,但由谁来担保才是重点。
数字证书的数字签名计算过程:
- 用摘要算法对数字证书的内容计算出摘要;
- 用数字证书的私钥对摘要进行加密获得数字签名。
当浏览器收到证书时,会对签名颁发机构进行验证,若是颁发机构是个颇有权威的公共签名机构,浏览器可能就知道其公开密钥了(浏览器会预装不少签名颁发机构的证书)。若是对签名颁发机构一无所知,浏览器一般会向用户显示一个对话框,看看他是否相信这个签名发布者。
由于数字证书的公钥是公开的,任何人均可以用公钥解密出数字证书的数字签名的摘要,而后再用一样的摘要算法对证书内容进行摘要计算,将得出的摘要和解密后的摘要做对比,若是内容一致则说明这个证书没有被篡改过,能够信任。
这个过程是创建在被你们所承认的证书机构之上获得的公钥,因此这是一种安全的方式。
HTTPS 链接创建过程
HTTPS 链接创建过程和 HTTP 差很少,区别在于 HTTP(默认端口 80) 请求只要在 TCP 链接创建后就能够发起,而 HTTPS(默认端口 443) 在 TCP 链接创建后,还须要经历 SSL 协议握手,成功后才能发起请求。
我知道确定会有人不知足于简化版的 SSL 握手过程,因此我找了一篇文章SSL/TLS 握手过程详解,这篇文章很是详细的讲解了 SSL 握手的每一步骤。建议有兴趣的同窗看一看。
HTTP/2
HTTP/2 是 HTTP/1.x 的扩展,而非替代。因此 HTTP 的语义不变,提供的功能不变,HTTP 方法、状态码、URL 和首部字段等这些核心概念也不变。
之因此要递增一个大版本到 2.0,主要是由于它改变了客户端与服务器之间交换数据的方式。HTTP 2.0 增长了新的二进制分帧数据层,而这一层并不兼容以前的 HTTP 1.x 服务器及客户端——是谓 2.0。
HTTP/2 链接创建过程
如今的主流浏览器 HTTP/2 的实现都是基于 SSL/TLS 的,也就是说使用 HTTP/2 的网站都是 HTTPS 协议的,因此本文只讨论基于 SSL/TLS 的 HTTP/2 链接创建过程。
基于 SSL/TLS 的 HTTP/2 链接创建过程和 HTTPS 差很少。在 SSL/TLS 握手协商过程当中,客户端在 ClientHello 消息中设置 ALPN(应用层协议协商)扩展来代表指望使用 HTTP/2 协议,服务器用一样的方式回复。经过这种方式,HTTP/2 在 SSL/TLS 握手协商过程当中就创建起来了。
HTTP/1.1 的问题
1. 队头阻塞
在 HTTP 请求应答过程当中,若是出现了某种状况,致使响应一直未能完成,那后面全部的请求就会一直阻塞着,这种状况叫队头阻塞。
2. 低效的 TCP 利用
因为 TCP 慢启动机制,致使每一个 TCP 链接在一开始的时候传输速率都不高,在处理多个请求后,才会慢慢达到“合适”的速率。对于请求数据量很小的 HTTP 请求来讲,这种状况就是种灾难。
3. 臃肿的消息首部
HTTP/1.1 的首部没法压缩,再加上 cookie 的存在,常常会出现首部大小比请求数据大小还大的状况。
4. 受限的优先级设置
HTTP/1.1 没法为重要的资源指定优先级,每一个 HTTP 请求都是一视同仁。
在继续讨论 HTTP/2 的新功能以前,先把 HTTP/1.1 的问题列出来是有意义的。由于 HTTP/2 的某些新功能就是为了解决上述某些问题而产生的。
二进制分帧层
HTTP/2 是基于帧的协议。采用分帧是为了将重要信息封装起来,让协议的解析方能够轻松阅读、解析并还原信息。
而 HTTP/1.1 是以文本分隔的。解析 HTTP/1.1 不须要什么高科技,但每每速度慢且容易出错。你须要不断地读入字节,直到遇到分隔符 CRLF 为止,同时还要考虑不守规矩的客户端,它只会发送 LF。
解析 HTTP/1.1 的请求或响应还会遇到如下问题:
- 一次只能处理一个请求或响应,完成以前不能中止解析。
- 没法预判解析须要多少内存。
HTTP/2 有了帧,处理协议的程序就能预先知道会收到什么,而且 HTTP/2 有表示帧长度的字段。
帧结构
+-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | Type (8) | Flags (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (31) | +=+=============================================================+ | Frame Payload (0...) ... +---------------------------------------------------------------+
名称 | 长度 | 描述 |
---|---|---|
Length | 3 字节 | 表示帧负载的长度,取值范围为 (2 的 14 次方)至 (2 的 24 次方 - 1)。(2 的 14 次方) 16384 字节是默认的最大帧大小,若是须要更大的帧,必须在 SETTINGS 帧中设置 |
Type | 1 字节 | 当前帧类型(见下表) |
Flags | 1 字节 | 具体帧类型的标识 |
R | 1 位 | 保留位,不要设置,不然可能会带来严重的后果 |
Stream Identifier | 31 位 | 每一个流的惟一 ID |
Frame Payload | 长度可变 | 真实的帧内容,长度是在 Length 字段中设置的 |
因为 HTTP/2 是分帧的,请求和响应均可以多路复用,有助于解决相似相似队头阻塞的问题。
帧类型
名称 | ID | 描述 |
---|---|---|
DATA | 0x0 | 传输流的核心内容 |
HEADERS | 0x1 | 包含 HTTP 首部,和可选的优先级参数 |
PRIORITY | 0x2 | 指示或更改流的优先级和依赖 |
RST_STREAM | 0x3 | 容许一端中止流(一般因为错误致使的) |
SETTINGS | 0x4 | 协商链接级参数 |
PUSH_PROMISE | 0x5 | 提示客户端,服务器要推送些东西 |
PING | 0x6 | 测试链接可用性和往返时延(RTT) |
GOAWAY | 0x7 | 告诉另外一端,当前的端已结束 |
WINDOW_UPDATE | 0x8 | 协商一端将要接收多少字节(用于流量控制) |
CONTINUATION | 0x9 | 用以扩展 HEADERS 模块 |
多路复用
在 HTTP/1.1 中,若是客户端想发送多个并行的请求,那么必须使用多个 TCP 链接。
而 HTTP/2 的二进制分帧层突破了这一限制,全部的请求和响应都在同一个 TCP 链接上发送:客户端和服务器把 HTTP 消息分解成多个帧,而后乱序发送,最后在另外一端再根据流 ID 从新组合起来。
这个机制为 HTTP 带来了巨大的性能提高,由于:
- 能够并行交错地发送请求,请求之间互不影响;
- 能够并行交错地发送响应,响应之间互不干扰;
- 只使用一个链接便可并行发送多个请求和响应;
- 消除没必要要的延迟,从而减小页面加载的时间;
- 没必要再为绕过 HTTP 1.x 限制而多作不少工做;
流
HTTP/2 规范对流的定义是:HTTP/2 链接上独立的、双向的帧序列交换。若是客户端想要发出请求,它会开启一个新流,而后服务器在这个流上回复。 因为有分帧,因此多个请求和响应能够交错,而不会互相阻塞。流 ID 用来标识帧所属的流。
客户端到服务器的 HTTP/2 链接创建后,经过发送 HEADERS 帧来启动新的流。若是首部须要跨多个帧,可能还会发送 CONTINUATION 帧。该 HEADERS 帧可能来自请求或响应。 后续流启动的时候,会发送一个带有递增流 ID 的新 HEADERS 帧。
消息
HTTP 消息泛指 HTTP 请求或响应,消息由一或多个帧组成,这些帧能够乱序发送,而后再根据每一个帧首部的流 ID 从新组装。
一个消息至少由 HEADERS 帧(它初始化流)组成,而且能够另外包含 CONTINUATION 和 DATA 帧,以及其余的 HEADERS 帧。
HTTP/1.1 的请求和响应部分都分红消息首部和消息体两部分;HTTP/2 的请求和响应分红 HEADERS 帧和 DATA 帧。
优先级
把 HTTP 消息分解为不少独立的帧以后,就能够经过优化这些帧的交错和传输顺序,进一步提高性能。
经过 HEADERS 帧和 PRIORITY 帧,客户端能够明确地和服务器沟通它须要什么,以及它须要这些资源的顺序。具体来说,服务器能够根据流的优先级,控制资源分配(CPU、内存、带宽),而在响应数据准备好以后,优先将最高优先级的帧发送给客户端。
流量控制
在同一个 TCP 链接上传输多个数据流,就意味着要共享带宽。标定数据流的优先级有助于按序交付,但只有优先级还不足以肯定多个数据流或多个链接间的资源分配。
为解决这个问题,HTTP/2 为数据流和链接的流量控制提供了一个简单的机制:
- 流量控制基于每一跳进行,而非端到端的控制;
- 流量控制基于 WINDOW_UPDATE 帧进行,即接收方广播本身准备接收某个数据流的多少字节,以及对整个链接要接收多少字节;
- 流量控制窗口大小经过 WINDOW_UPDATE 帧更新,这个字段指定了流 ID 和窗口大小递增值;
- 流量控制有方向性,即接收方可能根据本身的状况为每一个流乃至整个链接设置任意窗口大小;
- 流量控制能够由接收方禁用,包括针对个别的流和针对整个链接。
HTTP/2 链接创建以后,客户端与服务器交换 SETTINGS 帧,目的是设置双向的流量控制窗口大小。除此以外,任何一端均可以选择禁用个别流或整个链接的流量控制。
服务器推送
HTTP/2 新增的一个强大的新功能,就是服务器能够对一个客户端请求发送多个响应。换句话说,除了对最初请求的响应外,服务器还能够额外向客户端推送资源,而无需客户端明确地请求。
为何须要这样一个机制呢?一般的 Web 应用都由几十个资源组成,客户端须要分析服务器提供的文档才能逐个找到它们。那为何不让服务器提早就把这些资源推送给客户端,从而减小额外的时间延迟呢?服务器已经知道客户端下一步要请求什么资源了,这时候服务器推送便可派上用场。
另外,客户端也能够拒绝服务器的推送。
首部压缩
HTTP/1.1 存在的一个问题就是臃肿的首部,HTTP/2 对这一问题进行了改进,能够对首部进行压缩。
在一个 Web 页面中,通常都会包含大量的请求,而其中有不少请求的首部每每有不少重复的部分。
例若有以下两个请求:
:authority: unpkg.zhimg.com :method: GET :path: /za-js-sdk@2.16.0/dist/zap.js :scheme: https accept: */* accept-encoding: gzip, deflate, br accept-language: zh-CN,zh;q=0.9 cache-control: no-cache pragma: no-cache referer: https://www.zhihu.com/ sec-fetch-dest: script sec-fetch-mode: no-cors sec-fetch-site: cross-site user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36
:authority: zz.bdstatic.com :method: GET :path: /linksubmit/push.js :scheme: https accept: */* accept-encoding: gzip, deflate, br accept-language: zh-CN,zh;q=0.9 cache-control: no-cache pragma: no-cache referer: https://www.zhihu.com/ sec-fetch-dest: script sec-fetch-mode: no-cors sec-fetch-site: cross-site user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36
从上面两个请求能够看出来,有不少数据都是重复的。若是能够把相同的首部存储起来,仅发送它们之间不一样的部分,就能够节省很多的流量,加快请求的时间。
HTTP/2 在客户端和服务器端使用“首部表”来跟踪和存储以前发送的键-值对,对于相同的数据,再也不经过每次请求和响应发送。
下面再来看一个简化的例子,假设客户端按顺序发送以下请求首部:
Header1:foo Header2:bar Header3:bat
当客户端发送请求时,它会根据首部值建立一张表:
索引 | 首部名称 | 值 |
---|---|---|
62 | Header1 | foo |
63 | Header2 | bar |
64 | Header3 | bat |
若是服务器收到了请求,它会照样建立一张表。
当客户端发送下一个请求的时候,若是首部相同,它能够直接发送这样的首部块:
62 63 64
服务器会查找先前创建的表格,并把这些数字还原成索引对应的完整首部。
性能优化
使用 HTTP/2 代替 HTTP/1.1,自己就是一种巨大的性能提高。
这小节要聊的是在 HTTP/1.1 中的某些优化手段,在 HTTP/2 中是没必要要的,能够取消的。
取消合并资源
在 HTTP/1.1 中要把多个小资源合并成一个大资源,从而减小请求。而在 HTTP/2 就不须要了,由于 HTTP/2 全部的请求均可以在一个 TCP 链接发送。
取消域名拆分
取消域名拆分的理由同上,再多的 HTTP 请求均可以在一个 TCP 链接上发送,因此不须要采起多个域名来突破浏览器 TCP 链接数限制这一规则了。
参考资料
更多文章,敬请关注
本文同步分享在 博客“谭光志”(SegmentFault)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。