HTTP/2之旅 (翻译)
距离我上一次通过博客写作以来, 经过了很长的一段安静的时间. 因为一直没有足够的时间投入其中. 直到现在有了一些空闲的时间, 我想利用他们写一些HTTP相关的文章.
HTTP是一种协议, 每一个web开发者都应该知道他是如何推进整个网络的 并应该清楚的知道他是如何帮助你开发更好的应用.
什么是HTTP
首先, 什么是HTTP? HTTP是一种基于TCP/IP
的应用层的传输协议, 规定了客户端和服务器端如何进行通信的. 定义了在物联网中是请求和传送的内容. 对于应用层的协议, 我理解的只是一层抽象的协议, 让主机(客户端和服务器)之间的交流标准化, 并且依赖于TCP/IP
来完成客户端之间的请求和响应.TCP默认使用80
端口, 也可以使用其他的端口. HTTPS使用过的是443
端口.
HTTP/0.9
一个班机(开始的协议)(1991)
第一个HTTP的版本是HTTP/0.9
在1991年之前推出. 那是一种非常简单的协议, 含有一个简单的被称为GET
的方法. 如果一个客户端通过访问服务器上的一些网页, 他会发出一个下面这种的简单请求.
GET /index.html
服务器返回的内容如下面展示的
(response body)
(connection closed)
这就是服务器获得的请求, 在响应中返回一个HTML, 只要内容开始传输, 那响应就会关闭. 他是
无头响应
GET
只是一个请求方法响应一个HTML
正如你看到的, 协议真的没什么, 除了作为未来发展的一个踏板.
HTTP/1.0
-1996
在1996年, 下一个HTTP版本, 即HTTP/1.0
版本被开发, 大大超过了上一个版本.
不同于HTTP/0.9
只能定义HTML响应, HTTP/1.0
能够定义其他响应格式, 即图片, 视频文件, 普通文本和其他任何的内容类型. 他增加了更多的方法(即, HEAD
和POST
), 请求和响应的格式没有改变, 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.1
A还包括了摘要和代理认证.缓存
字节范围
字符集合
语言谈判(Language negotiation? 这特么是什么啊?)
客户端cookies
支持增强压缩
新的状态码
等等
我并不准备在这篇文章里面, 完全展开素有
HTTP/1.1
的功能. 你可以通过自己去了解更多. 我推荐你阅读Key differences between HTTP/1.0 and HTTP/1.1, 还还有一个不错的original RFCHTTP/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. HEADRS
frame为了元数据(meta data), DATA
frame为了有效荷载(payload), 还有存在其他集中类型的frames(HEADRS, DATA, RST_STREAM, SETTINGS, PRIORITY等), 你可以查看
每一个HTTP/2
的请求和响应都会生成一个唯一的streamID并分配给frames. Frames只是二进制的数据. 一个frames的链接被称为Stream. 每一个frame都有stream id. 用来标记他所属于的stream, 每一个frame都有相同的头部. 此外, 除了Stream ID 是唯一的以外, 值得一提的是, 客户端发定义的任何一个请求中的streamID都使用奇数, 服务器的每一个响应中的streamID都是用偶数.
除了HEADERS
和DATA
两种类型的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 specs和demonstrating the performance benefits of HTTP/2.. 欢迎在评论中提出疑问,希望指出在阅读过程中遇到的错误点. 下次见.
HTTP/2之旅 (翻译)的更多相关文章
- CQRS之旅——前言(翻译)
探索CQRS和Event Sourcing 本项目聚焦在使用命令和查询分离模式和事件溯源(CQRS+Event Sourcing)构建一个具有高扩展,高可用和高维护性的应用程序. 本项目定位为一个学习 ...
- Linq之旅:Linq入门详解(Linq to Objects)
示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...
- 【Knockout.js 学习体验之旅】(3)模板绑定
本文是[Knockout.js 学习体验之旅]系列文章的第3篇,所有demo均基于目前knockout.js的最新版本(3.4.0).小茄才识有限,文中若有不当之处,还望大家指出. 目录: [Knoc ...
- 【Knockout.js 学习体验之旅】(2)花式捆绑
本文是[Knockout.js 学习体验之旅]系列文章的第2篇,所有demo均基于目前knockout.js的最新版本(3.4.0).小茄才识有限,文中若有不当之处,还望大家指出. 目录: [Knoc ...
- 【Knockout.js 学习体验之旅】(1)ko初体验
前言 什么,你现在还在看knockout.js?这货都已经落后主流一千年了!赶紧去学Angular.React啊,再不赶紧的话,他们也要变out了哦.身旁的90后小伙伴,嘴里还塞着山东的狗不理大蒜包, ...
- 《Entity Framework 6 Recipes》中文翻译系列 目录篇 -持续更新
为了方便大家的阅读和学习,也是响应网友的建议,在这里为这个系列做一个目录.在目录开始这前,我先来回答之前遇到的几个问题. 1.为什么要学习EF? 这个问题很简单,项目需要.这不像学校,没人强迫你学习! ...
- F#之旅4 - 小实践之快排
参考文章:https://swlaschin.gitbooks.io/fsharpforfunandprofit/content/posts/fvsc-quicksort.html F#之旅4 - 小 ...
- F#之旅0 - 开端
F#之旅0 - 开端 UWP的学习告一段落,CozyRSS的UWP版本并没有做.UWP跟wpf开发几乎一模一样,然后又引入了很多针对移动设备的东西,这部分有点像android.没啥太大的意思,不难,估 ...
- 【翻译】Express web应用开发 第一章
本章节是一个对初学者友好的Express介绍.你将学习到Express的基础知识.核心概念和实现一个Express应用的组成部分.现阶段我们不需要做太多的编码,本章节会让你熟悉和习惯Express,为 ...
随机推荐
- 用python 实现录入学生作业情况的小程序
写一个录入学生作业情况的一个程序 1.查看学生作业情况 2.录入学生作业情况 3.可以让输入3次,需要为空的情况 homeworks = { ‘张流量’: {‘2018.3.22’:”未交”,’201 ...
- ctypes模块与pywin32模块
ctypes模块: 主要用于调用c动态链接库. 1.聊聊Python ctypes模块 2.ctypes模块管理 相关网址: pywin32模块: 用于访问win32API函数(win32api模块) ...
- ACM学习历程——HDU5137 How Many Maos Does the Guanxi Worth(14广州10题)(单源最短路)
Problem Description "Guanxi" is a very important word in Chinese. It kind of means &quo ...
- bzoj 2216: Lightning Conductor 单调队列优化dp
题目大意 已知一个长度为\(n\)的序列\(a_1,a_2,...,a_n\)对于每个\(1\leq i\leq n\),找到最小的非负整数\(p\)满足: 对于任意的\(j\), \(a_j \le ...
- 'xxx' declared `static' but never defined
'xxx' declared `static' but never defined [问题描述] uart.c文件中有函数read_sample的实现: [plain] view plain copy ...
- msfvenom 摄像头
4.13 莫名其妙的心情不好 又回到了那个不想打游戏不想聊天不想说话的日子. 不用想.vm——>kali 很早以前看过用msfvenom生成木马的文章.然后……然后我的浏览器就崩溃了.Firef ...
- 用idea工具对java打包:命令 mvn clear package,报错
用idea工具对java打包:命令 mvn clear package,报错 网上都是eclipse的,要么是project structure和setting的(当然这俩也要用) 我都试了,每一个能 ...
- JSP编译指令、JSP动作指令
JSP编译指令:通过指令中的属性配置来向JSP容器发出指令,用来控制JSP页面的某些特征 JSP指令格式:<%@ 指令名 [一个或多个指令属性]%> 1.page:用于对JSP页面中的 ...
- Robot Framework基础学习(五)
Selenium2Library 常用关键字介绍 关于Selenium2Library 的关键字,我们可以参考:http://rtomac.github.io/robotframework-selen ...
- Al-Qaeda affiliate in&…
By Greg Miller, Updated: Friday, February 1, 4:13 AM New intelligence on al-Qaeda's affiliate in Nor ...