本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
HTTP协议
Http - HyperText Transfer Protocol,超文本传输协议,用于客户端和服务器端之间的通信。
Http 1.0
Http/1.0 是无状态[1]、无连接[2]的协议。
缺陷
-
无状态
每个TCP连接只能发送一个请求,发送完数据后连接即关闭,如果还要请求必须新建请求连接。没有持久化处理。
-
不安全
HTTP 比较严重的缺点就是不安全:
- 通信使用明文(不加密),内容可能会被窃听。比如,账号信息容易泄漏,号没了。
- 不验证通信方的身份,因此有可能遭遇伪装。比如,访问假的淘宝、拼多多,钱没了。
- 无法证明报文的完整性,所以有可能已遭篡改。比如,网页上植入垃圾广告,视觉污染,眼没了。
HTTP 的安全问题,可以用 HTTPS 的方式解决,也就是通过引入 SSL/TLS 层,使得在安全上达到了极致。
Http 1.1
Http/1.1 是无状态1的协议,为了实现期望的保持状态功能,引入了 Cookie 技术来管理状态。
TCP连接的新建成本很高,因为需要客户端和服务器端三次握手。 HTTP、1.1版本是最流行的版本,可以持久连接,TCP连接默认不关闭(长连接4),可以被多个请求复用,只有在一段时间内,没有请求,就可以自动关闭。HTTP是基于TCP/IP协议的应用层协议,不涉及数据包传输,规定了客户端和服务器端之间的通信方式,默认使用80端口,就如同他俩交流的语言。
HTTP 协议是基于 TCP/IP,并且使用了「请求 - 应答」的通信模式,所以性能的关键就在这两点里。
-
长连接
这种方式的好处在于减少了 TCP 连接的重复建立和断开所造成的额外开销,减轻了服务器端的负载。
-
管道网络传输
即可在同一个 TCP 连接里面,客户端可以发起多个请求,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。 但是服务器还是按照顺序,先回应 A 请求,完成后再回应 B 请求。要是 前面的回应特别慢,后面就会有许多请求排队等着。这称为「队头堵塞」。
队头阻塞
「请求 - 应答」的模式加剧了 HTTP 的性能问题。
因为当顺序发送的请求序列中的一个请求因为某种原因被阻塞时,在后面排队的所有请求也一同被阻塞了,会招致客户端一直请求不到数据,这也就是「队头阻塞」。好比上班的路上塞车。
结构
消息头
消息头是在客户端发送请求时,向服务器传输的信息。
- Accept为告诉服务器接收哪些格式的文件
- Accept-Encoding为接收的编码方式
- Accept-Language为接收的语言
- Cache-Control为是否使用缓存
- Connection为连接方式
- Data表示格林威治标准时间
- Cookie为向服务器发送的Cookie信息
- Host为服务器域名。
- Content-Length为服务器返回给客户端的内容的长度
- Content-Type为服务器返回给客户端的内容的类型
- Keep-Alive为缓存的时间
- Server为服务器的相关信息。
状态码
HTTPS (2.0)
Https - Hypertext Transfer Protocol Secure,多了安全性的概念。Https 由 Http + TLS / SSL 协议组合而成(目前我们已经使用 TLS 取代了废弃的 SSL 协议,不过仍然使用 SSL 证书一词)。
与Http区别
- HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。
- HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。
- HTTP 的端口号是 80,HTTPS 的端口号是 443。
- HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的。
可以很好的解决了上述的风险:
- 信息加密[混合加密]:交互信息无法被窃取,但你的号会因为「自身忘记」账号而没。
- 校验机制[摘要算法]:无法篡改通信内容,篡改了就不能正常显示,但百度「竞价排名」依然可以搜索垃圾广告。
- 身份证书[数字证书]:证明淘宝是真的淘宝网,但你的钱还是会因为「剁手」而没。
可见,只要自身不做「恶」,SSL/TLS 协议是能保证通信是安全的。
-
混合加密
HTTPS 采用的是对称加密和非对称加密结合的「混合加密」方式:
采用「混合加密」的方式的原因:
- 在通信建立前采用非对称加密的方式交换「会话秘钥」,后续就不再使用非对称加密。
- 在通信过程中全部使用对称加密的「会话秘钥」的方式加密明文数据。
- 对称加密只使用一个密钥,运算速度快,密钥必须保密,无法做到安全的密钥交换。
- 非对称加密使用两个密钥:公钥和私钥,公钥可以任意分发而私钥保密,解决了密钥交换问题但速度慢。
-
摘要算法
客户端在发送明文之前会通过摘要算法算出明文的「指纹」,发送的时候把「指纹 + 明文」一同 加密成密文后,发送给服务器,服务器解密后,用相同的摘要算法算出发送过来的明文,通过比较客户端携带的「指纹」和当前算出的「指纹」做比较,若「指纹」相同,说明数据是完整的。
-
数字证书
客户端先向服务器端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。
这就存在些问题,如何保证公钥不被篡改和信任度?
所以这里就需要借助第三方权威机构
CA(数字证书认证机构),将服务器公钥放在数字证书(由数字证书认证机构颁发)中,只要证书是可信的,公钥就是可信的。
连接过程
SSL/TLS 协议基本流程 (前两步也就是 SSL/TLS 的建立过程,也就是握手阶段):
- 客户端向服务器索要并验证服务器的公钥。
- 双方协商生产「会话秘钥」。
- 双方采用「会话秘钥」进行加密通信。
SSL/TLS 的「握手阶段」涉及四次通信:
-
ClientHello
首先,由客户端向服务器发起加密通信请求,也就是
ClientHello请求。在这一步,客户端主要向服务器发送以下信息:
- 客户端支持的 SSL/TLS 协议版本,如 TLS 1.2 版本。
- 客户端生产的随机数(
Client Random),后面用于生产「会话秘钥」。 - 客户端支持的密码套件列表,如 RSA 加密算法。
-
SeverHello
服务器收到客户端请求后,向客户端发出响应,也就是
SeverHello。服务器回应的内容有如下内容:- 确认 SSL/ TLS 协议版本,如果浏览器不支持,则关闭加密通信。
- 服务器生产的随机数(
Server Random),后面用于生产「会话秘钥」。 - 确认的密码套件列表,如 RSA 加密算法。
- 服务器的数字证书。
-
客户端回应
客户端收到服务器的回应之后,首先通过浏览器或者操作系统中的 CA 公钥,确认服务器的数字证书的真实性。
如果证书没有问题,客户端会从数字证书中取出服务器的公钥,然后使用它加密报文,向服务器发送如下信息:
- 一个随机数(
pre-master key)。该随机数会被服务器公钥加密。 - 加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。
- 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供服务端校验。
上面第一项的随机数是整个握手阶段的第三个随机数,这样服务器和客户端就同时有三个随机数,接着就用双方协商的加密算法,各自生成本次通信的「会话秘钥」。
- 一个随机数(
-
服务器的最后回应
服务器收到客户端的第三个随机数(
pre-master key)之后,通过协商的加密算法,计算出本次通信的「会话秘钥」。然后,向客户端发生最后的信息:- 加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。
- 服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供客户端校验。
至此,整个 SSL/TLS 的握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的 HTTP 协议,只不过用「会话秘钥」加密内容。
七次握手、九倍时延
HTTPS 是对 HTTP 协议的扩展,我们可以使用它在互联网上安全地传输数据,然而 HTTPS 请求的发起方第一次从接收方获取响应需要经过 4.5 倍的RTT5。
-
TCP 协议 — 通信双方通过三次握手建立 TCP 连接 (1.5-RTT)
-
TLS 协议 — 通信双方通过四次握手建立 TLS 连接 (2-RTT)
在 TLS 1.2 中,我们需要 2-RTT 才能建立 TLS 连接,但是 TLS 1.3 通过优化协议,将两次往返延迟降低至一次,大幅度减少建立 TLS 连接所需要的时间,让客户端可以在 1-RTT 之后就能向服务端传输应用层数据。
除了减少常规握手下的网络开销,TLS 1.3 还引入了 0-RTT 的连接建立过程;60% 的网络连接都是用户在第一次访问网站或者间隔一段时间后访问时建立的,剩下的 40% 可以通过 TLS 1.3 的 0-RTT 策略解决,然而该策略与 TFO 的实现原理比较相似,都是通过重用会话和缓存来实现的,所以存在一定的安全风险,使用时也应该结合业务的具体场景。
-
HTTP 协议 — 客户端向服务端发送请求,服务端发回响应 (1RTT)
需要注意的是,本文对往返延时的计算都基于特定的场景以及特定的协议版本,网络协议的版本在不断更新和演进,过去忽略的问题最开始都会通过补丁的方式更新,但是最后仍然会需要从底层完成重写。
HTTP/3 就是一个这样的例子,它会使用基于 UDP 的 QUIC 协议进行握手,将 TCP 和 TLS 的握手过程结合起来,把 7 次握手减少到了 3 次握手,直接建立了可靠并且安全的传输通道,将原本 ~900ms 的耗时降低至 ~500ms
演变
说说 HTTP/1.1 相比 HTTP/1.0 提高了什么性能?
HTTP/1.1 相比 HTTP/1.0 性能上的改进:
- 使用 TCP 长连接的方式改善了 HTTP/1.0 短连接造成的性能开销。
- 支持 管道(pipeline)网络传输,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。
但 HTTP/1.1 还是有性能瓶颈:
- 请求 / 响应头部(Header)未经压缩就发送,首部信息越多延迟越大。只能压缩
Body的部分; - 发送冗长的首部。每次互相发送相同的首部造成的浪费较多;
- 服务器是按请求的顺序响应的,如果服务器响应慢,会招致客户端一直请求不到数据,也就是队头阻塞;
- 没有请求优先级控制;
- 请求只能从客户端开始,服务器只能被动响应。
那上面的 HTTP/1.1 的性能瓶颈,HTTP/2 做了什么优化?
HTTP/2 协议是基于 HTTPS 的,所以 HTTP/2 的安全性也是有保障的。
-
头部压缩
HTTP/2 会压缩头(Header)如果你同时发出多个请求,他们的头是一样的或是相似的,那么,协议会帮你消除重复的分。
这就是所谓的
HPACK算法:在客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。 -
二进制格式
HTTP/2 不再像 HTTP/1.1 里的纯文本形式的报文,而是全面采用了二进制格式。
头信息和数据体都是二进制,并且统称为帧(frame):头信息帧和数据帧。
这样虽然对人不友好,但是对计算机非常友好,因为计算机只懂二进制,那么收到报文后,无需再将明文的报文转成二进制,而是直接解析二进制报文,这增加了数据传输的效率。
3. 数据流
HTTP/2 的数据包不是按顺序发送的,同一个连接里面连续的数据包,可能属于不同的回应。因此,必须要对数据包做标记,指出它属于哪个回应。
每个请求或回应的所有数据包,称为一个数据流(Stream)。
每个数据流都标记着一个独一无二的编号,其中规定客户端发出的数据流编号为奇数, 服务器发出的数据流编号为偶数
客户端还可以指定数据流的优先级。优先级高的请求,服务器就先响应该请求。
4. 多路复用
HTTP/2 是可以在一个连接中并发多个请求或回应,而不用按照顺序一一对应。
移除了 HTTP/1.1 中的串行请求,不需要排队等待,也就不会再出现「队头阻塞」问题,降低了延迟,大幅度提高了连接的利用率。
举例来说,在一个 TCP 连接里,服务器收到了客户端 A 和 B 的两个请求,如果发现 A 处理过程非常耗时,于是就回应 A 请求已经处理好的部分,接着回应 B 请求,完成后,再回应 A 请求剩下的部分。
5. 服务器推送
HTTP/2 还在一定程度上改善了传统的「请求 - 应答」工作模式,服务不再是被动地响应,也可以主动向客户端发送消息。
举例来说,在浏览器刚请求 HTML 的时候,就提前把可能会用到的 JS、CSS 文件等静态资源主动发给客户端,减少延时的等待,也就是服务器推送(Server Push,也叫 Cache Push)。
HTTP/2 有哪些缺陷?HTTP/3 做了哪些优化?
HTTP/2 主要的问题在于:多个 HTTP 请求在复用一个 TCP 连接,下层的 TCP 协议是不知道有多少个 HTTP 请求的。所以一旦发生了丢包现象,就会触发 TCP 的重传机制,这样在一个 TCP 连接中的所有的 HTTP 请求都必须等待这个丢了的包被重传回来。
- HTTP/1.1 中的管道( pipeline)传输中如果有一个请求阻塞了,那么队列后请求也统统被阻塞住了
- HTTP/2 多请求复用一个TCP连接,一旦发生丢包,就会阻塞住所有的 HTTP 请求。
这都是基于 TCP 传输层的问题,所以 HTTP/3 把 HTTP 下层的 TCP 协议改成了 UDP!
URI、URL、URN
URI - Uniform Resource Identifier,统一资源标识符 URL - Uniform Resource Locator,统一资源定位符 URN - Uniform Resource Name,统一资源名称
URL
URL是一种具体的URI,其中URL包含位置和协议统一资源定位符(URL)。完整定义如下:
协议类型:[//[访问资源需要的凭证信息@]服务器地址[:端口号]][/资源层级UNIX文件路径]文件名[?查询][#片段ID]
大多数网页浏览器不要求用户输入网页中“http:// ”的部分,因为绝大多数网页内容是超文本传输协议文件。同样,“80”是超文本传输协议文件的常用端口号,因此一般也不必写明
URI
事实上,URI 是一个给程序提供资源标识的 框架,任何程序都可以使用它来标识自己的资源。
URI = scheme : [[userinfo@]host[:port]/]path[?query][#fragment]
例如,xml 格式文件应该都熟悉,其中的顶层标签往往会有很多约束条件的配置,看起来像是网址一样,其实,那是 xml 使用了 uri 框架来标识这些约束条件,而xml的解析器有自己实现的 uri 的解析器来识别其内容。其他的例子还有很多,例如安卓系统就也使用URI框架来标识自己的资源
当然,根据上述描述不难看出,完全相同的 URI 给不同的程序可能识别出完全不同的含义,因为其含义本身就由程序本身定义(这也是通用二字的含义)
URN
URN 是统一资源名称,名称!
[1] 无状态指每次Http请求都是独立的,对于事务处理没有记忆能力,任意两请求间没有必然联系。
[2] 无连接指每次服务器处理完请求并受到客户端应答后就断开通信,这种方式可以节省传输时间。
[3] 在 HTTP 协议里,所谓的「安全」是指请求方法不会「破坏」服务器上的资源。
[4] 4 也叫持久连接, 持久连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。
[5] 即往返延迟,RTT,Round-Trip Time。