HTTP/2之旅 (翻译)

Journey to HTTP/2
HTTP/2html

距离我上一次经过博客写做以来, 通过了很长的一段安静的时间. 由于一直没有足够的时间投入其中. 直到如今有了一些空闲的时间, 我想利用他们写一些HTTP相关的文章.git

HTTP是一种协议, 每个web开发者都应该知道他是如何推动整个网络的 并应该清楚的知道他是如何帮助你开发更好的应用.github

什么是HTTP

首先, 什么是HTTP? HTTP是一种基于TCP/IP的应用层的传输协议, 规定了客户端和服务器端如何进行通讯的. 定义了在物联网中是请求和传送的内容. 对于应用层的协议, 我理解的只是一层抽象的协议, 让主机(客户端和服务器)之间的交流标准化, 而且依赖于TCP/IP来完成客户端之间的请求和响应.TCP默认使用80端口, 也可使用其余的端口. HTTPS使用过的是443端口.web

HTTP/0.9 一个班机(开始的协议)(1991)

第一个HTTP的版本是HTTP/0.9在1991年以前推出. 那是一种很是简单的协议, 含有一个简单的被称为GET的方法. 若是一个客户端经过访问服务器上的一些网页, 他会发出一个下面这种的简单请求.浏览器

GET /index.html

服务器返回的内容以下面展现的缓存

(response body)
(connection closed)

这就是服务器得到的请求, 在响应中返回一个HTML, 只要内容开始传输, 那响应就会关闭. 他是安全

  • 无头响应
  • GET只是一个请求方法
  • 响应一个HTML服务器

    正如你看到的, 协议真的没什么, 除了做为将来发展的一个踏板.cookie

HTTP/1.0-1996

在1996年, 下一个HTTP版本, 即HTTP/1.0版本被开发, 大大超过了上一个版本.网络

不一样于HTTP/0.9只能定义HTML响应, HTTP/1.0可以定义其余响应格式, 即图片, 视频文件, 普通文本和其余任何的内容类型. 他增长了更多的方法(即, HEADPOST), 请求和响应的格式没有改变, HTTP头部能够在请求和响应都增长, 定义额外的状态码, 引入字符集的支持, 多部分类型, 做者, 缓存, 内容格式化而且支持更多

下面是一个简单的HTTP/1.0的请求和响应看起来大概如此:

GET / HTTP/1.0
Host: kamranahmed.info
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS)
Accept: */*

正如你看到的, 经过这个请求, 客户端也能够发送他的我的信息, 支持的响应类型内容. 在HTTP/0.9中客户端并无办法发送这些信息, 由于没有头部.

对于上面的请求可能有以下的示例响应

HTTP / 1.0 200 OK
Content-Type: text/plain
Content-Length: 137582
Expires: Thu, 05 Dec 1997 16:00:00 GMT
Last-Modified: Wed, 5 August 1996 15:55:28 GMT
Server: Apache 0.84

(response body)
(connection closed)

每个响应的开始都是HTTP/1.0(HTTP后面跟着的是版本号), 而后是状态码200, 后面跟着的事缘由短语(若是你须要的话, 也能够是对于状态码的描述)

在这个新的版本中, 请求和响应头, 依旧都是使用ASCII进行编码, 可是响应主体可使用任何类型, 即, 图片, 视频, 普通文本和其余的任何内容类型. 因此, 如今服务器能够发送任何类型的响应给客户端; 在HTTP引入后不久, "超文本"一词, 在HTTP中变得并不适用. HMTP或者超媒体传输协议也许更加适用于场景, 可是, 我想, 咱们仍是坚持使用生命这个名字.

HTTP/1.0一个主要的缺点是在每个连接中使用不一样的请求方式. 结果就是, 不管何时, 客户端都是为了从服务器得到一些东西, 他会打开一个新的TCP连接, 稍后一个简单的请求会彻底使用这个连接. 而后这个连接就关闭了. 不管下一个请求是什么, 都会打开一个新的连接. 为何坏呢? 很好, 让咱们假设, 你浏览的网站有10张图片, 5个样式文件, 和5个JavaScript文件, 当网页打开的时候, 须要一共进行20次请求. 由于请求一旦被知足, 服务器就会关闭连接. 将会有一系列独立的20个连接, 每一个连接都在本身独立的连接上提供服务. 大量的连接致使严重的性能损失, 由于创建一个新的TCP形成明显的性能损失, 由于三次握手的创建启动很是慢.

三次握手 (Three-way Handshake)

三次握手的简单创建过程: 全部的TCP连接都是经过三次握手开始的, 也就是客户端和服务器在发送应用数据以前, 发送一系列的数据包.

  • SYN - 客户端挑选一个随机数, 咱们称之为x, 而后发送给服务器端.
  • SYN ACK - 服务器接收到请求以后, 发送一个ACK包返回给客户端, 也是一个随机的数字, 咱们把服务器选出的数字称之为y, 而且和x+1, 这里的x是经过客户端发送给服务器的.
  • ACK - 客户端将从服务器收到的数字y增长, 返回一个ACK的数据包, 包含一个数字y+1.

    一次完整的三次握手的过程就完成了, 客户端和服务器端的数据就能够开始传输了. 须要注意的是, 客户端一旦发送完最后一个ACK数据包, 就当即开始发送应用数据, 可是服务器端须要等到最后一个ACK包接受完成才会去响应请求.

    图片

须要注意, 这张图片有一个严重的问题, 最后一次经过客户端发送的数据包ACK, 此次握手应该只包括y+1, 也就是, 应该使用ACK: Y+1替代ACK: x+1, y+1

然而, HTTP / 1.0的一些方案尝试经过增长一个请求头Connection: keep-alive去解决这个问题. 那意味着告诉服务器: "你好, 服务器, 不要关闭这个连接, 我还须要它", 但由于没有普遍使用, 因此这个问题依旧存在.

除了无链接, HTTP也是一个无状态的协议, 也就是服务器不会存储有关客户端的信息, 因此每个请求都必须含有服务器可以完成请求的独立信息, 和任何老的请求没有关系. 致使了, 大量分开的请求在客户端打卡的时候, 须要发送一些多余数据, 增长了网络带宽的使用.

HTTP / 1.1 - 1999

仅仅3年, 就在1999年发布了下一个版本, HTTP / 1.1, 对上个版本进行了大幅改进. 对于HTTP/1.0的主要改进包括:

  • 新的HTTP方法增长了PUT, PATCH, 'OPTIONS', 'DELETE'
  • 主机名表示, 在HTTP/1.0中请求头中的Host并非必须的, 但在HTTP/1.1就是必须的了.
  • 上面提到的持久连接: 在HTTP/1.0中, 每一个连接是惟一一个请求, 只要请求完成了, 就关闭了, 致使了严重的性能浪费和一些潜在的问题. HTTP/1.1引入了持久连接, 也就是连接默认是不关闭的, 会一直保持打开, 容许多个连续的请求. 能够利用请求头上的Connection: close关闭连接. 客户端一般在最后一次请求中发送这个请求头来关闭链接状态.
  • 开始支持管道流Pipelining, 就是客户端能够发送多个请求到服务端, 不须要等待服务器在同一个链接上的响应, 而且服务器端在街道请求以后, 会遵循同样的顺序返回响应. 可是客户端如何知道这是第一个响应下载完成的点. 和下一个响应什么时候开始. 为了解决这个问题, 必须在头部使用Content -Length, 那可让客户端区分出相应结束的地方, 而且能够开始等待下一个响应.
  • 应该注意的是, 为了从持续链接和管道流中获利, 响应中的Content-Length必须是可用的, 由于这可让客户端知道传输完成, 并能够继续开始下一次请求(普通连续的请求方式)或者开始等待下一次响应(当管道流可使用的时候)
  • 当这种方式依旧有个问题: 若数据是动态的, 服务器没有办法提早知道内容大小. 这种请求, 你的确不能使用持续链接. 为了解决这个问题, HTTP/1.1动态引入了动态编码. 在这种状况下, 服务器并不能经过分开编码省略内容长度. 然而, 若是这些方法都不能使用, 连接在最后一次请求后必须关闭.
  • 当服务器并不能在传输开始的时候计算出Content-Length, 会对内容进行分块传输, 那意味着一块一块的发送数据, 而且对发送的每个快添加一个Content-Length, 当全部数据块发送完成的时候, 也就是此次传输完成了, 就会发送一个空的数据块, 就是一个Content-Lenght是0的数据块, 标志这客户端的此次传输完成了. 为了标志出客户端的传输, 服务器端应该在请求头上添加一个Transfer-Encoding: chunked.
  • 不像HTTP/1.0中只有一个基础的认证, HTTP/1.1A还包括了摘要和代理认证.
  • 缓存
  • 字节范围
  • 字符集合
  • 语言谈判(Language negotiation? 这特么是什么啊?)
  • 客户端cookies
  • 支持加强压缩
  • 新的状态码
  • 等等

    我并不许备在这篇文章里面, 彻底展开素有HTTP/1.1的功能. 你能够经过本身去了解更多. 我推荐你阅读Key differences between HTTP/1.0 and HTTP/1.1, 还还有一个不错的original RFC

    HTTP/1.1在1999年发布后, 已经存在不少年了. 即便它可以不错的提升性能, 但网络世界天天都在变化, 它有些力不从心. 现在加载一个网页特别消耗资源. 一个简单的网页至少打开30个连接. 即便HTTP/1.1引入了持久链接, 为何这么链接呢? 由于在HTTP/1.1中任什么时候间, 都只能有一个未完成的连接. 在HTTP/1.1尝试经过管道流水线操做(pipelining)去解决, 但由于**线头阻塞(head-of-line-blocking)**的缘由没能解决问题, 指的是, 若是缓慢和繁重的请求可能会阻塞后面的请求, 一但某个管道中的请求被阻塞了, 那就不能不等到下一次请求被知足. (TODO: 这里没有很好的理解管道流的概念). 为了解决在HTTP/1.1`中的这些缺点, 开发者开始实行变通的方法. 例如, 使用精灵图, 在对CSS中的图片进行编码, 惟一一个极大的CSS/JavaScript文件, 主域分割(domain sharding)等

SPDY - 2009

Google带头开始尝试新的协议, 来提升web速度, 提高web的安全性, 下降网页加载延迟. 在2009年, 他们发布了SPDY.

SPDY, 是谷歌的商标, 并不是是首字母缩写

协议提出, 咱们能够经过提升带宽的方式提升网络的性能, 可是有一个点, 过了这个点, 就没有办法大量的提高性能了. 但若是你对延迟也这么作, 就是咱们继续下降延迟, 延迟是性能提升的常数, 也就是下降延迟, 就能够提升西性能. 在SPDY以后, 关于性能提高有一个重要理念, 下降延迟, 以此提升网站的性能.

当咱们并不请求其中的不一样, 延迟就是延迟, 也就是, 数据在服务器和客户端之间须要的传递时间(使用毫秒计算.) 带宽是指每秒钟数据传输的总量(bit/每秒)

SPDY的功能包括: 多路优化, 压缩, 优先级划分, 安全性等. 在这里并不会深刻讲解SPDY, 由于下面的HTTP/2协议大部分都受到了SPDY的启发.

SPDY并无尝试取代HTTP; 它是HTTP所在应用数据层之上的传输层, 在请求发送到网络以前对其进行修改. 它开始在实际中投入使用, 大部分的浏览器开始使用它.

2015年, Google并不但愿出现竞争的两种协议, 他们决定把它合并到HTTP中, 产生HTTP/2, 再也不使用SPDY.

HTTP/2 - 2015

如今, 你必定确信, 咱们须要另外一个加强版的HTTP协议. HTTP/2是为了下降内容的延迟传输而设计的. 和HTTP/1.1主要区别或者功能, 包括:

  • 使用二进制替代文本
  • 多路复用(Multiplexing) - 多个异步HTTP请求使用同一个连接.
  • 使用HPACK压缩头部
  • 服务端推送 - 对同一个请求的多个响应
  • 请求优化
  • 安全性

    图片

名词解释

这里参考: HTTP/2

  • Message: 逻辑上的request, response.
  • Frame: 数据传输中的最小单位. 每一个Frame都属于一个特定的stream或者整个连接.
    • Length: Frame的长度, 默认最大16kb, 若是要更大须要设置max frame size
    • Type: Frame的类型, 有DATA, HEADRES, PRIORITY等
    • Flag 和 R: 保留位
    • Stream identifier: 标识所属于的stream, 若是为0, 表示这个frame属于整条连接.
    • Frame Payload: 不一样的type, 有不一样的格式.
  • Stream: 一个双向流, 一条连接能够有多个stream.
    • HTTP/2依靠streams实现了多路复用, 提升了连接的利用率.
    • 一条链接能够包含多个streams, 多个streams发送的数据互不影响
    • Stream能够被client和server单方面使用, 也能够共享使用
    • Stream会肯定好发送frame的顺序, 另外一端按照接收到的顺序处理
    • Stream会有惟一的标识.
      • 若是是客户端建立的stream, ID是奇数.若是是server建立的, ID就是偶数. ID 0x00和0x01都有特定用途.
      • Stream不可能被重复利用, 若是一条连接的ID分配完了, client会新建一条链接. 而server则会给clent发送一个GOAWAY frame强制client新建一条连接.
      • 为了更大的一条链接上面的stream并发, 能够考虑调大SETTING_MAX_CONCURRENT_STREAMS

1. 二进制协议

HTTP/2尝试经过二进制协议的方法解决,如今HTTP/1.1的延迟问题. 做为一个二进制协议, 他更容易被解析, 但不容易被人眼所辨识. HTTP/2主要使用Frames和Streams进行构建.

介绍下: Frames和Streams

HTTP中的信息, 如今可以被压缩成为一个或多个frames. HEADRSframe为了元数据(meta data), DATAframe为了有效荷载(payload), 还有存在其余集中类型的frames(HEADRS, DATA, RST_STREAM, SETTINGS, PRIORITY等), 你能够查看the HTTP/2 specs

每个HTTP/2的请求和响应都会生成一个惟一的streamID并分配给frames. Frames只是二进制的数据. 一个frames的连接被称为Stream. 每个frame都有stream id. 用来标记他所属于的stream, 每个frame都有相同的头部. 此外, 除了Stream ID 是惟一的之外, 值得一提的是, 客户端发定义的任何一个请求中的streamID都使用奇数, 服务器的每个响应中的streamID都是用偶数.

除了HEADERSDATA两种类型的frame, 其余类型的frame中, 我想提下, RST_STREAM, 这是一种特殊的类型, 用来中断stream, 即, 客户端发送了这种frame就是告诉服务器, 我不再须要这种stream了. 在HTTP/1.1中, 惟一一种可让服务器端中止发送数据的方法, 就是响应的时候告诉客户端关闭这条链接. 致使了延迟增长, 由于一个新的连接须要很是屡次的请求进行打开. 在HTTP/2中, 客户端可使用RST_STREAM, 去中止接受一个特殊的Stream, 这个连接会一直保持着打开, 另外一个stream会继续使用.

2. 多路复用(Multiplexing)

由于HTTP/2如今是一个二进制的协议, 正如前面所说的, 他使用frames和stream进行请求和响应, 一个TCP连接一旦被打开, 全部的stream均可以使用相同的连接进行异步的发送, 不须要再增长任何连接. 相反, 服务器也能够进行相同的异步响应方法, 即, 响应没有顺序, 客户度使用streamID来进行特殊数据包的区分. 这样就能够解决一个头部阻塞问题(head-of-line-blocking)的问题. 客户端不须要话费时间一直等待请求, 其余请求仍然被正常处理.

3. HPACK头部压缩(HPACK Header Compression)

这是RFC中单独的一部分, 这是RFC针对优化发送的报文头部. 当同一个客户端不断访问着服务器的时候, 会带着不少多余的数据. 咱们一遍又一遍的发送着报文头部, 有时候, 会有cookies增长报文头部的大小, 致使的带宽的使用增长了时间延迟. 为了解决这个问题, HTTP/2使用了头部压缩.

图片

与请求响应不一样的是, 头部信息没法经过gzip或者compress等格式压缩, 这里的头部压缩使用了一种彻底不一样的机制. 文字值使用Huffman编码机, 头部信息表经过客户端和服务端, 而且在客户端和服务端都省略了请求队列中重复的头部信息. 好比: 用户信息等. 使用二者都在维护的头部表进行引用.

当咱们讨论标题的时候, 补充一点, 头部仍然和HTTP/1.1保持相同, 除了添加的一些伪标题, 即::method, :scheme, :host:path.

4. 服务器推送

服务器推送是另外一个在服务器端强大的功能, 都知道当客户端请求一个肯定的资源的时候, 服务器能用把这个资源推送给客户端, 甚至不须要客户端推送. 举个例子: 当浏览器加载一个web页面, 他会格式化整个页面, 找出须要从服务器段获取的内容, 后随之发送请求给服务器获取内容.

服务器端推送, 容许当服务器知道客户端须要的数据时, 经过推送的数据, 来减小往返次数. 他是如何完成的, 服务器发送一个特殊的数据帧, 命名为PUSH_PROMISE通知客户端, "嗨, 我将会把整个资源发送给你, 不须要再询问我了". 这个PUSH_PROMISE数据帧与致使推送发生的流相关, 他其中包括了流ID, 即, 整个数据流就是服务器端将要推送的数据.

5. 请求优化

当stream数据流打开的时候, 客户端向HEADERS数据帧中注入一个优化信息, 来对stream进行优化. 在任什么时候候, 客户度都可以发送一个PRIORITY数据帧来改变steam的优化.

若是不含有优化信息, 服务器异步响应请求. 也就是没有顺序. 若是给stream分配一个优化, 其中至少含有优化信息, 服务器可以决定, 针对整个请求, 须要执行返回多少资源.

6. 安全性

是否应该在HTTP/2中强制使用TLS, 引发了普遍讨论. 最后决定不会强制使用. 然而, 大部分的厂商表示, 他们只会在TLS层面上支持HTTP\2. 因此, 即便HTTP/2规范中不须要加密, 可是已经成为了一种默认的选项. HTTP\2经过TLS实现中的确有一些要求. 必须使用1.2或者更高版本的TLS, 必须含有必定级别的最小秘钥, 须要含有临时秘钥.
HTTP/2在兼容性方面, 已经渐渐超过SPDY. 在许多方面提供了性能优点, 不用多久, 咱们就能够开始用了.

HTTP/2的详细细节感兴趣的人, 能够访问link to specsdemonstrating the performance benefits of HTTP/2.. 欢迎在评论中提出疑问,但愿指出在阅读过程当中遇到的错误点. 下次见.

相关文章
相关标签/搜索