一、汇总对比
HTTP0.9
HTTP/0.9 是 1991 年发布的第一个版本,只支持一种请求方式 — GET,没有 HTTP 请求头,没有状态码,也没有版本号。 后来它的版本号才被定义成 0.9,用来和其他版本的 HTTP 区分。
HTTP/0.9 的请求只有一行:
GET /hello.html
响应也是非常简单的,只包含 html 文档本身:
<HTML>
Hello world
</HTML>
当 TCP 建立连接之后,服务器向客户端发送 HTML 格式的字符串,发送完毕后,就关闭 TCP 连接。由于没有状态码和错误代码,如果服务器处理的时候发生错误,只会传回一个包含问题描述信息的特殊的 HTML 文件。
此时的 HTTP 是无状态的,无状态是指对事务处理没有记忆能力,如果后续处理需要前面的信息,就必须重传。比如说访问一个网站需要反复进行登录操作。
HTTP1.0
- 无连接
- 引入了 POST 命令和 HEAD 命令
- 请求与响应支持 HTTP 头,增加了状态码
- 传输 HTML 文件以外其他类型的内容
HTTP1.1
- 持久连接
- 请求管道化(是将多个 HTTP 请求整批提交的技术)
- 增加缓存处理(新的字段如cache-control)
- 增加Host字段、支持断点传输等(把文件分成几部分)
- 新增了 OPTIONS、PUT、 DELETE、TRACE、CONNECT 命令
HTTP2.0
- 二进制分帧
- 多路复用(或连接共享)
- 头部压缩
- 服务器推送
二、HTTP1.0:
浏览器的每次请求都需要与服务器建立一个TCP连接,服务器处理完成后立即断开TCP连接(无连接),服务器不跟踪每个客户端也不记录过去的请求(无状态)。
相比 HTTP/0.9,HTTP/1.0 主要有如下特性:
- 丰富了传输内容,支持传输 HTML 文件以外其他类型的内容(文字、图片、视频);
- 除了 GET 命令外,还引入了 POST 命令和 HEAD 命令;
- 请求与响应支持 HTTP 头,增加了状态码,响应数据的一开始是一个响应状态行;
三、HTTP1.1:
HTTP/1.0中默认使用Connection: close。在HTTP/1.1中已经默认使用Connection: keep-alive,避免了连接建立和释放的开销,但服务器必须按照客户端请求的先后顺序依次回送相应的结果,以保证客户端能够区分出每次请求的响应内容。通过Content-Length字段来判断当前请求的数据是否已经全部接收。不允许同时存在两个并行的响应。
- 可以在一个 http 链接上同时发送多个请求 (可以克服同域并行连接限制带来的阻塞)
- 服务器可以同时处理多个请求,但是必须按照请求的顺序返回结果(即使后面的请求先处理完成也还是需要缓存起来,等前面的请求处理完返回之后再返回)
可以复用连接;
- 增加 pipeline:HTTP 管线化是将多个 HTTP 请求整批提交的技术,而在传送过程中不需先等待服务端的回应。管线化机制须通过永久连接(persistent connection)完成。 浏览器将 HTTP 请求大批提交可大幅缩短页面的加载时间,特别是在传输延迟(lag/latency)较高的情况下。有一点需要注意的是,只有幂等的请求可以使用 pipeline,如 GET,HEAD 方法。
- chunked 编码传输:该编码将实体分块传送并逐块标明长度,直到长度为 0 块表示传输结束, 这在实体长度未知时特别有用(比如由数据库动态产生的数据);
- 引入更多缓存控制机制:如 etag,cache-control - 引入内容协商机制,包括语言,编码,类型等,并允许客户端和服务器之间约定以最合适的内容进行交换;
- 请求消息和响应消息都支持 Host 头域:在 HTTP 1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,请求消息中的 URL 并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个 IP 地址。因此,Host 头的引入就很有必要了;
- 新增了 OPTIONS、PUT、 DELETE、TRACE、CONNECT 命令;
四、HTTP2.0:
HTTP/2引入二进制数据帧和流
的概念,其中帧对数据进行顺序标识
,如下图所示,这样浏览器收到数据之后,就可以按照序列对数据进行合并,而不会出现合并后数据错乱的情况。同样是因为有了序列,服务器就可以并行的传输数据,这就是流所做的事情
。
概念 | 解释 |
---|---|
流(stream) | 已建立连接上的双向字节流 |
消息 | 与逻辑消息对应的完整的一系列数据帧 |
帧 | HTTP2.0通信的最小单位,每个帧包含帧头部,至少也会标识出当前帧所属的流(stream id)。 |
流: 存在于连接中的一个虚拟通道。流可以承载双向消息,每个流都有一个唯一的整数 ID。
HTTP/2 长连接中的数据包是不按请求-响应顺序发送的,一个完整的请求或响应(称一个数据流 stream,每个数据流都有一个独一无二的编号)可能会分成非连续多次发送。它具有如下几个特点:
双向性:同一个流内,可同时发送和接受数据。
有序性:流中被传输的数据就是二进制帧 。帧在流上的被发送与被接收都是按照顺序进行的。
并行性:流中的 二进制帧 都是被并行传输的,无需按顺序等待。
流的创建:流可以被客户端或服务器单方面建立, 使用或共享。
流的关闭:流也可以被任意一方关闭。
HEADERS 帧在 DATA 帧前面。
流的 ID 都是奇数,说明是由客户端发起的,这是标准规定的,那么服务端发起的就是偶数了。
多路复用:
1、所有的HTTP2.0通信都在一个TCP连接上完成,这个连接可以承载任意数量的双向数据流。
2、每个数据流以消息的形式发送,而消息由一或多个帧组成。这些帧可以乱序发送,然后再根据每个帧头部的流标识符(stream id)重新组装。
举个例子,每个请求是一个数据流,数据流以消息的方式发送,而消息又分为多个帧,帧头部记录着stream id用来标识所属的数据流,不同属的帧可以在连接中随机混杂在一起。接收方可以根据stream id将帧再归属到各自不同的请求当中去。
3、另外,多路复用(连接共享)可能会导致关键请求被阻塞。HTTP2.0里每个数据流都可以设置优先级和依赖,优先级高的数据流会被服务器优先处理和返回给客户端,数据流还可以依赖其他的子数据流。
4、可见,HTTP2.0实现了真正的并行传输,它能够在一个TCP上进行任意数量HTTP请求。而这个强大的功能则是基于“二进制分帧”的特性。
头部压缩
在HTTP1.x中,头部元数据都是以纯文本的形式发送的,通常会给每个请求增加500~800字节的负荷。
HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。高效的压缩算法可以很大的压缩header,减少发送包的数量从而降低延迟。
现在打开一个网页上百个请求已是常态,而每个请求带的一些首部字段都是相同的,例如cookie、user-agent等。HTTP2为此采用HPACK压缩格式来压缩首部。
头部压缩需要在浏览器和服务器端之间:
维护一份相同的静态字典,包含常见的头部名称,以及常见的头部名称和值的组合
维护一份相同的动态字典,可以动态的添加内容
通过静态Huffman编码对传输的首部字段进行编码
HTTP2的静态字典是长这个样子的(只截取了部分,完整表格在这里):
所以我们在传输首部字段的时候,例如要传输method:GET,那我们只需要传输静态字典里面method:GET对应的索引值就可以了,一个字节搞定。像user-agent、cookie这种静态字典里面只有首部名称而没有值的首部,第一次传输需要user-agent在静态字典中的索引以及他的值,值会采用静态Huffman编码来减小体积。
第一次传输过user-agent 之后呢,浏览器和服务器端就会把它添加到自己的动态字典中。后续传输就可以传输索引了,一个字节搞定。
我们用WireShark来抓包验证一下:
HTTP2目前都是HTTPS的请求,WireShark对HTTPS网站抓包解密请参考这里。
- 首次传输user-agent和第二次传输user-agent
由于第一次传输的时候,字典里面并没有user-agent的值,这时候user-agent是63字节,第二次传输时,他已经在动态字典里面了,只传索引,一个字节搞定。
- HPACK的首部压缩力度
Header解码后的长度有471个字节,而HEADERS流只有246个字节。这只是第一个请求,后续的请求压缩力度会更大,因为前面请求用到的首部(静态字典中没有的)会添加到动态字典中,使得后续请求只需要传输字典里面的索引。
服务器推送:
服务器除了对最初请求的响应外,服务器还可以额外的向客户端推送资源,而无需客户端明确的请求。
服务器端推送使得服务器可以预测客户端需要的资源,主动推送到客户端。
例如:客户端请求index.html,服务器端能够额外推送script.js和style.css。
实现原理就是客户端发出页面请求时,服务器端能够分析这个页面所依赖的其他资源,主动推送到客户端的缓存,当客户端收到原始网页的请求时,它需要的资源已经位于缓存。
针对每一个希望发送的资源,服务器会发送一个PUSH_PROMISE帧,客户端可以通过发送RST_STREAM帧来拒绝推送(当资源已经位于缓存)。这一步的操作先于父响应(index.html),客户端了解到服务器端打算推送哪些资源,就不会再为这些资源创建重复请求。当客户端收到index.html的响应时,script.js和style.css已经位于缓存。