HTTP 协议的历史进化演变
HTTP (Hypertext transfer protocol,超文本传输协议) 是互联网上重要的一个协议,由欧洲核子研究委员会CERN的英国工程师 Tim Berners-Lee 发明的,同时,他也是WWW的发明人,最初的主要是用于传递通过HTML封装过的数据。
在1991年发布了HTTP 0.9版,在1996年发布1.0版,1997年是1.1版,1.1版也是到今天为止传输最广泛的版本(初始RFC 2068 在1997年发布, 然后在1999年被 RFC 2616 取代,再在2014年被 RFC 7230 /7231/7232/7233/7234/7235取代),2015年发布了2.0版,其极大的优化了HTTP/1.1的性能和安全性,而2018年发布的3.0版,继续优化HTTP/2,激进地使用UDP取代TCP协议,目前,HTTP/3 在2019年9月26日 被 Chrome,Firefox,和Cloudflare支持,所以我想写下这篇文章,简单地说一下HTTP的前世今生,让大家学到一些知识,并希望可以在推动一下HTTP标准协议的发展。
HTTP 协议 | 发布年份 | 功能特性 |
HTTP 0.9 | 1991 | 第一版,非常简单,只支持GET,响应为HTML文件 |
HTTP 1.0 | 1996 | 增加了版本、header、状态码、Content-Type |
HTTP 1.1 | 1997 | RFC 2068,使用最广泛,至今都在使用 |
HTTP 1.1 | 1999 | RFC 2616,改进 |
HTTP 1.1 | 2014 | RFC 7230,改进 |
HTTP 2.0 | 2015 | 多路复用,极大优化HTTP/1.1的性能和安全性 |
HTTP 3.0 | 2018 | 激进的用UDP取代TCP协议,修改了底层协议 |
HTTP 3.0 | 2019 | Chrome,Firefox,Cloudflare 支持 HTTP/3 |
HTTP/HTTPS 各版本的协议
HTTP 常用字段描述
请求行 请求行由方法字段、URL 字段 和HTTP 协议版本字段 3 个部分组成,他们之间使用空格隔开。常用的 HTTP 请求方法有 GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT; 请求头部 请求头部由关键字/值对组成,每行一对,关键字和值用英文冒号“:”分隔。请求头部通知服务器有关于客户端请求的信息 典型的请求头有: Accept:用于告诉服务器,客户机支持的数据类型 Accept-Charset:用于告诉服务器,客户机所采用的编码 Accept-Encoding:用于告诉服务器,客户机支持的数据压缩格式 Accept-Language:客户机的语言环境 Host:客户机通过这个头告诉服务器,想访问的主机名 If-Modified-Since:客户机通过这个头告诉服务器,资源的缓存时间 Referer:客户机通过这个头告诉服务器,它是从哪个资源来访问服务器的(防盗链) User-Agent:客户机通过这个头告诉服务器,客户机的软件环境 Cookie:客户机通过这个头可以向服务器带数据 空行 最后一个请求头之后是一个空行,发送回车符和换行符,通知服务器以下不再有请求头; 请求包体 请求包体不在 GET 方法中使用,而是在POST 方法中使用。POST 方法适用于需要客户填写表单的场合。与请求包体相关的最常使用的是包体类型 Content-Type 和包体长度 Content-Length;
状态行 状态行由 HTTP 协议版本字段、状态码和状态码的描述文本 3 个部分组成,他们之间使用空格隔开; 状态码 由三位数字组成,第一位数字表示响应的类型,常用的状态码有五大类如下所示: 1xx:表示服务器已接收了客户端请求,客户端可继续发送请求; 2xx:表示服务器已成功接收到请求并进行处理; 3xx:表示服务器要求客户端重定向; 4xx:表示客户端的请求有非法内容; 5xx:表示服务器未能正常处理客户端的请求而出现意外错误; 响应头部: 典型的响应头部有: Location:这个头配合302状态码使用,用于告诉客户找谁 Server:服务器通过这个头,告诉浏览器服务器的类型 Content-Encoding:服务器通过这个头,数据的压缩格式 Content-Length:服务器通过这个头,告诉浏览器回送数据的长度 Content-Type:服务器通过这个头,告诉浏览器回送数据的类型 Last-Modified:服务器通过这个头,告诉浏览器当前资源缓存时间 Refresh:服务器通过这个头,告诉浏览器隔多长时间刷新一次 Content-Disposition:服务器通过这个头,告诉浏览器以下载方式打开数据 Transfer-Encoding:服务器通过这个头,告诉浏览器数据的传送格式 ETag:… Expires:服务器通过这个头,告诉浏览器把回送的资源缓存多长时间,-1或0,则是不缓存 Cache-Control:no-cache Pragma:no-cache 服务器通过以上两个头,也是控制浏览器不要缓存数据 响应包体 服务器返回给客户端的文本信息;
HTTP 0.9
0.9和1.0这两个版本,就是最传统的 request – response的模式了,HTTP 0.9版本的协议简单到极点,请求时,不支持请求头,只支持 GET
方法,没了。
HTTP 0.9 是最早版本,诞生在 1991 年,这个最早版本和现在比起来极其简单,没有 HTTP 头,没有状态码,甚至版本号也没有,后来它的版本号才被定为 0.9 来和其他版本的 HTTP 区分。
HTTP/0.9 只支持一种方法—— Get,请求只有一行。
GET /hello.html
响应也是非常简单的,只包含 html 文档本身。
<HTML> Hello world </HTML>
当 TCP 建立连接之后,服务器向客户端返回 HTML 格式的字符串。发送完毕后,就关闭 TCP 连接。由于没有状态码和错误代码,如果服务器处理的时候发生错误,只会传回一个特殊的包含问题描述信息的 HTML 文件。这就是最早的 HTTP/0.9 版本。
HTTP 1.0
1996 年,HTTP/1.0 版本发布,大大丰富了 HTTP 的传输内容。
除了文字,还可以发送图片、视频等,这为互联网的发展奠定了基础。
HTTP 1.0 扩展了0.9版,其中主要增加了几个变化:
1)在请求中加入了HTTP版本号,如:GET /coolshell/index.html HTTP/1.0
2)HTTP 开始有 header了,不管是request还是response 都有header了。
3)增加了HTTP Status Code 标识相关的状态码,响应对象的一开始是一个响应状态行。
4)协议版本信息随着请求一起发送,支持GET,HEAD,POST,PUT 等方法
5)支持传输 HTML 文件以外其他类型的内容,例如 Content-Type
可以传输其它的文件了。
我们可以看到,HTTP 1.0 开始让这个协议变得很文明了,一种工程文明。因为:
1)协议有没有版本管理,是一个工程化的象征。
2)header是协议可以说是把元数据和业务数据解耦,也可以说是控制逻辑和业务逻辑的分离。
3)Status Code 的出现可以让请求双方以及第三方的监控或管理程序有了统一的认识。最关键是还是控制错误和业务错误的分离。
注:国内很多公司HTTP无论对错只返回200,这种把HTTP Status Code 全部抹掉完全是一种工程界的倒退
一个典型的 HTTP/1.0 的请求
GET /hello.html HTTP/1.0 User-Agent:NCSA_Mosaic/2.0(Windows3.1) 200 OK Date: Tue, 15 Nov 1996 08:12:31 GMT Server: CERN/3.0 libwww/2.17 Content-Type: text/html <HTML> 一个包含图片的页面 <IMGSRCIMGSRC="/smile.gif"> </HTML>
完整的请求流程
1.0开始支持cache,就是当客户端在规定时间内访问统一网站,直接访问cache即可。 请求行: GET /test/hi-there.txt HTTP/1.0 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) Accept: text/* 回应格式:"头信息 + 一个空行(\r\n) + 数据" HTTP/1.0 200 OK Content-Type: text/plain Content-Length: 19 Expires: Thu, 05 Dec 1997 16:00:00 GMT Last-Modified: Wed, 5 August 1996 15:55:28 GMT Server: Apache 0.84 <html> <body>Hi! I'm a message!</body> </html>
但是,HTTP1.0性能上有一个很大的问题,那就是每请求一个资源都要新建一个TCP链接,而且是串行请求,所以,就算网络变快了,打开网页的速度也还是很慢。所以,HTTP 1.0 应该是一个必需要淘汰的协议了。
HTTP/1.1
在 HTTP/1.0 发布几个月后,HTTP/1.1 就发布了。HTTP/1.1 更多的是作为对 HTTP/1.0 的完善
HTTP/1.1 主要解决了HTTP 1.0的网络性能的问题,以及增加了一些新的东西:
1)可以设置 keepalive
来让HTTP重用TCP链接(长连接、短链接),重用TCP链接可以省了每次请求都要在广域网上进行的TCP的三次握手的巨大开销。这是所谓的“HTTP 长链接” 或是 “请求响应式的HTTP 持久链接”。英文叫 HTTP Persistent connection.
2)然后支持pipeline网络传输,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。(注:非幂等的POST 方法或是有依赖的请求是不能被pipeline化的)
3)支持 Chunked Responses ,chunked 编码将实体分块传送并逐块标明长度,直到长度为 0 块表示传输结束, 这在实体长度未知时特别有用(比如由数据库动态产生的数据),也就是说,在Response的时候,不必说明 Content-Length
,客户端就不能断连接,直到收到服务端的EOF标识。这种技术又叫 “服务端Push模型”,或是 “服务端Push式的HTTP 持久链接”
4)还增加了 etag,cache-control 等机制。
5)协议头注增加了 Language, Encoding, Type 等等头,让客户端可以跟服务器端进行更多的协商。引入内容协商机制,包括语言,编码,类型等,并允许客户端和服务器之间约定以最合适的内容进行交换
6)还正式加入了一个很重要的头—— HOST
这样的话,服务器就知道你要请求哪个网站了。因为可以有多个域名解析到同一个IP上,要区分用户是请求的哪个域名,就需要在HTTP的协议中加入域名的信息,而不是被DNS转换过的IP信息。随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个 IP 地址。因此,Host 头的引入就很有必要了。
7)正式加入了 OPTIONS
方法,其主要用于 CORS – Cross Origin Resource Sharing(跨域) 应用。
HTTP/1.1应该分成两个时代,一个是2014年前,一个是2014年后,因为2014年HTTP/1.1有了一组RFC(7230 /7231/7232/7233/7234/7235),这组RFC又叫“HTTP/2 预览版”。其中影响HTTP发展的是两个大的需求:
一个需要是加大了HTTP的安全性,这样就可以让HTTP应用得广泛,比如,使用TLS(Transport Layer Security,传输层安全)协议。
另一个是让HTTP可以支持更多的应用,在HTTP/1.1 下,HTTP已经支持四种网络协议:
1)传统的短链接。
2)可重用TCP的的长链接模型。
3)服务端push的模型。
4)WebSocket模型。
一个典型的 HTTP/1.1 的请求
$ curl --head 'https://blog.mimvp.com' HTTP/1.1 200 OK Date: Thu, 13 Feb 2020 04:09:07 GMT Server: Apache Vary: Accept-Encoding,Cookie,User-Agent Link: <https://blog.mimvp.com/wp-json/>; rel="https://api.w.org/" Cache-Control: max-age=600 Expires: Thu, 13 Feb 2020 04:19:07 GMT Content-Type: text/html; charset=UTF-8
自从2005年以来,整个世界的应用API越来多,这些都造就了整个世界在推动HTTP的前进,我们可以看到,自2014的HTTP/1.1 以来,这个世界基本的应用协议的标准基本上都是向HTTP看齐了,也许2014年前,还有一些专用的RPC协议,但是2014年以后,HTTP协议的增强,让我们实在找不出什么理由不向标准靠拢,还要重新发明轮子了。
常用的请求方式,详见米扑博客:
GET 请求获取Request-URI所标识的资源
POST 在Request-URI所标识的资源后附加新的数据
HEAD 请求获取由Request-URI所标识的资源的响应消息报头
PUT 请求服务器存储一个资源,并用Request-URI作为其标识
DELETE 请求服务器删除Request-URI所标识的资源
TRACE 请求服务器回送收到的请求信息,主要用于测试或诊断
CONNECT 保留将来使用
OPTIONS 请求查询服务器的性能,或者查询与资源相关的选项和需求
GET方法:在浏览器的地址栏中输入网址的方式访问网页时,浏览器采用GET方法向服务器获取资源,POST方法要求被请求服务器接受附在请求后面的数据,常用于提交表单。GET是用于获取数据的,POST一般用于将数据发给服务器之用。
虽然 HTTP/1.1 已经优化了很多点,作为一个目前使用最广泛的协议版本,已经能够满足很多网络需求,但是随着网页变得越来越复杂,甚至演变成为独立的应用,HTTP/1.1 逐渐暴露出了一些问题:
- 在传输数据时,每次都要重新建立连接,对移动端特别不友好
- 传输内容是明文,不够安全
- header 内容过大,每次请求 header 变化不大,造成浪费
- keep-alive 给服务端带来性能压力
HTTP 响应报文结构
虽然1.1允许复用TCP连接,但是同一个TCP连接里面,所有的数据通信是按次序进行的。服务器只有处理完一个回应,才会进行下一个回应。要是前面的回应特别慢,后面就会有许多请求排队等着。所以跟1.0一样,有"队头堵塞"现象(HOLB)
为了避免这个问题,只有两种方法:一是减少请求数,二是同时多开持久连接。
这导致了很多的网页优化技巧,比如合并脚本和样式表、将图片嵌入CSS代码、域名分片(domain sharding)等等。如果HTTP协议设计得更好一些,这些额外的工作是可以避免的。
为了解决这些问题,HTTPS 和 SPDY 应运而生。
HTTPS
HTTPS 是以安全为目标的 HTTP 通道,简单讲是 HTTP 的安全版,即 HTTP 下加入 SSL 层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL。
HTTPS 协议的主要作用可以分为两种:
一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。
HTTPS 和 HTTP 的区别主要如下:
1)HTTPS 协议使用 ca 申请证书,由于免费证书较少,需要一定费用。
2)HTTP 是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。
3)HTTP 和 HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
HTTP/2
虽然 HTTP/1.1 已经开始变成应用层通讯协议的一等公民了,但是还是有性能问题,虽然HTTP/1.1 可以重用TCP链接,但是请求还是一个一个串行发的,需要保证其顺序。然而,大量的网页请求中都是些资源类的东西,这些东西占了整个HTTP请求中最多的传输数据量。所以,理论上来说,如果能够并行这些请求,那就会增加更大的网络吞吐和性能。
另外,HTTP/1.1传输数据时,是以文本的方式,借助耗CPU的zip、gzip压缩的方式减少网络带宽,但是耗了前端和后端的CPU。这也是为什么很多RPC协议诟病HTTP的一个原因,就是数据传输的成本比较大。
其实,在2010年时,Google 就在搞一个实验型的协议,这个协议叫SPDY(读作“SPeeDY”),这个协议成为了HTTP/2的基础(也可以说成HTTP/2就是SPDY的复刻)。
SPDY(读作“SPeeDY”)是Google开发的基于TCP的会话层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。SPDY并不是一种用于替代HTTP的协议,而是对HTTP协议的增强。新协议的功能包括数据流的多路复用、请求优先级以及HTTP报头压缩。谷歌表示,引入SPDY协议后,在实验室测试中页面加载速度比原先快64%
在 2010 年到 2015 年,谷歌通过实践一个实验性的 SPDY 协议,证明了一个在客户端和服务器端交换数据的另类方式。其收集了浏览器和服务器端的开发者的焦点问题,明确了响应数量的增加和解决复杂的数据传输。在启动 SPDY 这个项目时预设的目标是:
- 页面加载时间 (PLT) 减少 50%。
- 无需网站作者修改任何内容。
- 将部署复杂性降至最低,无需变更网络基础设施。
- 与开源社区合作开发这个新协议。
- 收集真实性能数据,验证这个实验性协议是否有效。
为了达到降低目标,减少页面加载时间的目标,SPDY 引入了一个新的二进制分帧数据层,以实现多向请求和响应、优先次序、最小化及消除不必要的网络延迟,目的是更有效地利用底层 TCP 连接。
浏览器支持 SPDY
Google Chrome和Chromium已经支持SPDY。
Mozilla Firefox自11.0开始内嵌支持SPDY。从Firefox 13开始默认开启对SPDY的支持。
Opera从12.10开始支持SPDY。
Internet Explorer 11开始支持SPDY。
注意:由于SPDY协议已被HTTP/2协议取代,上述浏览器的新版本可能不再支持SPDY协议。
HTTP/2基本上解决了之前的这些性能问题,其和HTTP/1.1最主要的不同是:
1)HTTP/2是一个二进制协议,增加了数据传输的效率。使用二进制分帧层(Binary Framing),在应用层与传输层之间增加一个二进制分帧层,以此达到在不改动 HTTP 的语义,HTTP 方法、状态码、URI 及首部字段的情况下,突破HTTP1.1 的性能限制,改进传输性能,实现低延迟和高吞吐量。在二进制分帧层上,HTTP2.0 会将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码,其中 HTTP1.x 的首部信息会被封装到 Headers 帧,而我们的 request body 则封装到 Data 帧里面。
2)多路复用,对于 HTTP/1.x,即使开启了长连接,请求的发送也是串行发送的,在带宽足够的情况下,对带宽的利用率不够,HTTP/2.0 采用了多路复用的方式,可以并行发送多个请求,提高对带宽的利用率。HTTP/2是可以在一个TCP链接中并发请求多个HTTP请求,移除了HTTP/1.1中的串行请求。
3)HTTP/2会压缩头信息,如果你同时发出多个请求,他们的头是一样的或是相似的,那么,协议会帮你消除重复的部分,这就是所谓的HPACK算法(参看RFC 7541 附录A)使用首部表来跟踪和存储之前发送的键值对,对于相同的内容,不会再每次请求和响应时发送。
4)HTTP/2允许服务端在客户端放cache,又叫服务端push,也就是说,你没有请求的东西,我服务端可以先送给你放在你的本地缓存中。比如,你请求X,我服务端知道X依赖于Y,虽然你没有的请求Y,但我把把Y跟着X的请求一起返回客户端。服务端推送,在 HTTP/2.0 中,服务器可以向客户发送请求之外的内容,比如正在请求一个页面时,服务器会把页面相关的 logo,CSS 等文件直接推送到客户端,而不会等到请求来的时候再发送,因为服务器认为客户端会用到这些东西。这相当于在一个 HTML 文档内集合了所有的资源。
5)数据流优先级:由于请求可以并发发送了,那么如果出现了浏览器在等待关键的 CSS 或者 JS 文件完成对页面的渲染时,服务器却在专注的发送图片资源的情况怎么办呢?HTTP/2.0 对数据流可以设置优先值,这个优先值决定了客户端和服务端处理不同的流采用不同的优先级策略。
对于这些性能上的改善,在Medium上有篇文章你可看一下相关的细节说明和测试“HTTP/2: the difference between HTTP/1.1, benefits and how to use it”
可以看到 HTTP/2.0 的新特点和 SPDY 很相似,
其实 HTTP/2.0 本来就是基于 SPDY 设计的,可以说是 SPDY 的升级版。
但是 HTTP/2.0 仍有和 SPDY 不同的地方,主要有如下两点:
- HTTP/2.0 支持明文 HTTP 传输,而 SPDY 强制使用 HTTPS。
- HTTP/2.0 消息头的压缩算法采用 HPACK,而非 SPDY 采用的 DEFLATE。
当然,还需要注意到的是HTTP/2的协议复杂度比之前所有的HTTP协议的复杂度都上升了许多许多,其内部还有很多看不见的东西,比如其需要维护一个“优先级树”来用于来做一些资源和请求的调度和控制。如此复杂的协议,自然会产生一些不同的声音,或是降低协议的可维护和可扩展性,所以也有一些争议。尽管如此,HTTP/2还是很快地被世界所采用。
一个典型的 HTTP/2 的请求(firefox浏览器请求米扑代理)
Request URL: https://proxy.mimvp.com/ Request Method: GET Remote Address: 47.95.201.113:443 Status Code: 200 Version: HTTP/2.0
HTTP/2 是2015年推出的,其发布后,Google 宣布移除对SPDY的支持,拥抱标准的 HTTP/2。过了一年后,就有8.7%的网站开启了HTTP/2,根据 这份报告:Usage statistics of HTTP/2 for websites ,截止至本文发布时(2019年10月1日 ), 在全世界范围内已经有43%的网站开启了HTTP/2。
HTTP/2的官方组织在 Github 上维护了一份各种语言对HTTP/2的实现列表,大家可以去看看。
我们可以看到,HTTP/2 在性能上对HTTP有质的提高,所以,HTTP/2 被采用的也很快
如果你在你的公司内负责架构的话,HTTP/2是你一个非常重要的需要推动的一个事,除了因为性能上的问题,推动标准落地也是架构师的主要职责,因为,你企业内部的架构越标准,你可以使用到开源软件,或是开发方式就会越有效率,跟随着工业界的标准的发展,你的企业会非常自然的享受到标准所带来的红利。
HTTP/3
然而,这个世界没有完美的解决方案,HTTP/2也不例外,其主要的问题是:若干个HTTP的请求在复用一个TCP的连接,底层的TCP协议是不知道上层有多少个HTTP的请求的,所以,一旦发生丢包,造成的问题就是所有的HTTP请求都必需等待这个丢了的包被重传回来,哪怕丢的那个包不是我这个HTTP请求的。因为TCP底层是没有这个知识了。
这个问题又叫Head-of-Line Blocking问题,这也是一个比较经典的流量调度的问题。这个问题最早主要的发生的交换机上。下图来自Wikipedia。
图中,左边的是输入队列,其中的1,2,3,4表示四个队列,四个队列中的1,2,3,4要去的右边的output的端口号。此时,第一个队列和第三个队列都要写右边的第四个端口,然后,一个时刻只能处理一个包,所以,一个队列只能在那等另一个队列写完后。然后,其此时的3号或1号端口是空闲的,而队列中的要去1和3号端号的数据,被第四号端口给block住了。这就是所谓的HOL blocking问题。
HTTP/1.1中的pipeline中如果有一个请求block了,那么队列后请求也统统被block住了;
HTTP/2 多请求复用一个TCP连接,一旦发生丢包,就会block住所有的HTTP请求。
这样的问题很讨厌,好像基本无解了。
是的,TCP是无解了,但是UDP是有解的 !
于是,HTTP/3破天荒地把HTTP底层的TCP协议改成了UDP(下图黄色 HTTP/3协议 UDP)
然后,又是Google 家的协议进入了标准 – QUIC (Quick UDP Internet Connections)。接下来是QUIC协议的几个重要的特性,为了讲清楚这些特性,我需要带着问题来讲(注:下面的网络知识,如果你看不懂的话,你需要学习一下《TCP/IP详解》一书(在我写blog的这15年里,这本书推荐了无数次了),或是看一下本站的《TCP的那些事》。):
- 首先是上面的Head-of-Line blocking问题,在UDP的世界中,这个就没了。这个应该比较好理解,因为UDP不管顺序,不管丢包(当然,QUIC的一个任务是要像TCP的一个稳定,所以QUIC有自己的丢包重传的机制)
- TCP是一个无私的协议,也就是说,如果网络上出现拥塞,大家都会丢包,于是大家都会进入拥塞控制的算法中,这个算法会让所有人都“冷静”下来,然后进入一个“慢启动”的过程,包括在TCP连接建立时,这个慢启动也在,所以导致TCP性能迸发地比较慢。QUIC基于UDP,使用更为激进的方式。同时,QUIC有一套自己的丢包重传和拥塞控制的协议,一开始QUIC是重新实现一TCP 的 CUBIC算法,但是随着BBR算法的成熟(BBR也在借鉴CUBIC算法的数学模型),QUIC也可以使用BBR算法。这里,多说几句,从模型来说,以前的TCP的拥塞控制算法玩的是数学模型,而新型的TCP拥塞控制算法是以BBR为代表的测量模型,理论上来说,后者会更好,但QUIC的团队在一开始觉得BBR不如CUBIC的算法好,所以没有用。现在的BBR 2.x借鉴了CUBIC数学模型让拥塞控制更公平。这里有文章大家可以一读“TCP BBR : Magic dust for network performance.”
- 接下来,现在要建立一个HTTPS的连接,先是TCP的三次握手,然后是TLS的三次握手,要整出六次网络交互,一个链接才建好,虽说HTTP/1.1和HTTP/2的连接复用解决这个问题,但是基于UDP后,UDP也得要实现这个事。于是,QUIC直接把TCP的和TLS的合并成了三次握手(对此,在HTTP/2的时候,是否默认开启TLS业内是有争议的,反对派说,TLS在一些情况下是不需要的,比如企业内网的时候,而支持派则说,TLS的那些开销,什么也不算了)。
所以,QUIC是一个在UDP之上的伪TCP +TLS +HTTP/2的多路复用的协议。
但是对于UDP还是有一些挑战的,这个挑战主要来自互联网上的各种网络设备,这些设备根本不知道是什么QUIC,他们看QUIC就只能看到的就是UDP,所以,在一些情况下,UDP就是有问题的,
- 比如在NAT的环境下,如果是TCP的话,NAT路由或是代理服务器,可以通过记录TCP的四元组(源地址、源端口,目标地址,目标端口)来做连接映射的,然而,在UDP的情况下不行了。于是,QUIC引入了个叫connection id的不透明的ID来标识一个链接,用这种业务ID很爽的一个事是,如果你从你的3G/4G的网络切到WiFi网络(或是反过来),你的链接不会断,因为我们用的是connection id,而不是四元组。
- 然而就算引用了connection id,也还是会有问题 ,比如一些不够“聪明”的等价路由交换机,这些交换机会通过四元组来做hash把你的请求的IP转到后端的实际的服务器上,然而,他们不懂connection id,只懂四元组,这么导致属于同一个connection id但是四元组不同的网络包就转到了不同的服务器上,这就是导致数据不能传到同一台服务器上,数据不完整,链接只能断了。所以,你需要更聪明的算法(可以参看 Facebook 的 Katran 开源项目 )
好了,就算搞定上面的东西,还有一些业务层的事没解,这个事就是 HTTP/2的头压缩算法 HPACK,HPACK需要维护一个动态的字典表来分析请求的头中哪些是重复的,HPACK的这个数据结构需要在encoder和decoder端同步这个东西。在TCP上,这种同步是透明的,然而在UDP上这个事不好干了。所以,这个事也必需要重新设计了,基于QUIC的QPACK就出来了,利用两个附加的QUIC steam,一个用来发送这个字典表的更新给对方,另一个用来ack对方发过来的update。
目前看下来,HTTP/3目前看上去没有太多的协议业务逻辑上的东西,更多是HTTP/2 + QUIC协议。
HTTP/3 因为动到了底层协议,例如UDP替代了TCP,所以,在普及方面上可能会比 HTTP/2要慢的多的多。
但是,可以看到QUIC协议的强大,细思及恐,QUIC这个协议真对TCP是个威胁,如果QUIC成熟了,TCP是不是会有可能成为历史呢?
问题与思考
1、NAT 问题
为了解决 IP 地址不足的问题,NAT 给一个局域网络只分配一个 IP 地址,这个网络内的主机,则分配私有地址,这些私有地址对外是不可见的,他们对外的通信都要借助那个分配的 IP 地址。所有离开本地网络去往 Internet 的数据报的源 IP 地址需替换为相同的 NAT,区别仅在于端口号不同。
TCP 和 UDP 的报文头部不同导致 NAT 问题的出现。
NAT 设备的端口记忆问题
对于基于 TCP 的 HTTP、HTTPS 传输,NAT 设备可以根据 TCP 报文头的 SYN/FIN 状态位,知道通信什么时候开始,什么时候结束,对应记忆 NAT 映射的开始和结束。
但是基于 UDP 传输的 HTTP3 ,不存在 SYN/FIN 状态位。NAT 设备的记忆如果短于用户会话时间,则用户会话会中断。NAT 设备的记忆时间如果长于用户会话时间,则意味着 NAT 设备的端口资源会被白白占用。
最直接的解决方案是,在 QUIC 的头部模仿 TCP 的 SYN/FIN 状态,让沿途的 NAT 设备知道会话什么时候开始、什么时候结束。但这需要升级全球所有的 NAT 设备的软件。
另外一个可行的方案是,让 QUIC 周期性地发送 Keepalive 消息,刷新 NAT 设备的记忆,避免 NAT 设备自动释放。
NAT 设备禁用 UDP
在一些 NAT 网络环境下(如某些校园网),UDP 协议会被路由器等中间网络设备禁止,这时客户端会直接降级,选择 HTTPS 等备选通道,保证正常业务请求。
2、NGINX 负载均衡问题
QUIC 客户端存在网络制式切换,就算是同一个移动机房,可能第一次业务请求时会落到 A 这台服务器,后续再次连接,就会落到 B 实例上,重复走 1-RTT 的完整握手流程。
全局握手缓存
为所有 QUIC 服务器实例建立一个全局握手缓存。当用户网络发生切换时,下一次的业务请求无论是落到哪一个机房或哪一台实例上,握手建连都会是 0-RTT。
总结
HTTP 速度测试对比
Http 0.9:客户端只能用GET,服务器只能回送HTML格式的字符串,其他数据不支持。非持续连接,每个连接只处理一个请求响应事务,已过时。
Http 1.0:短连接, 默认是非持续短连接,请求头可以设置Connection:Keep-Alive,可以在一定时间内复用连接,具体复用时间的长短可以由服务器控制,一般在15s左右。连接无法复用、线头阻塞(head of line blocking)、带宽和延迟、支持cache,相比0.9增加了 GET、POST、HEAD
Http 1.1:长连接,默认使用持续长连接,不必为每一个WEB对象建立一个新的连接,一个连接可以传送多个对象。客户端pipelining,服务端线头阻塞,Host字段、断电续传、相比1.0增加了PUT、PATCH、DELETE
Http 2.0:多路复用、通信单位为二进制帧、并行通信、数据流模式(无序,编号+ID+(优先权))、首部压缩(索引号查数据)、服务器推送。多路复用(一个域只要一个TCP连接)实现真正的并发请求,降低延时,提高了带宽的利用率。
HTTP 3.0 因为动到了底层协议,UDP替代了TCP,所以,在普及方面上可能会比 HTTP/2要慢的多的多。
实时数据传输(音频、视频、游戏等)都面临卡顿、延迟等问题,而 QUIC 基于 UDP 的架构和改进的重传等特性,能够有效的提升用户体验。目前
B 站 也已经接入 QUIC。
如果想要自己体验 QUIC,可以使用 Libquic、Caddy 等。
另外 github 上面也有 C++版本的 QUIC 实现,利用 Nodejs 的 C++ 模块,前端工程师也可以快速实现一个 node-quic。
未来十年,让我们看看UDP是否能够逆袭TCP……
HTTP 1.X 网络传输优化方法
1、多个资源合并成一个请求连接,如前端Spriting雪碧图,JS/CSS压缩成一个文件,使用Tengine(Nginx)等
2、Inlining内联的方式,采用inline css/inline js等并入html中,减少对css/js文件的多次请求
3、CDN资源配置多域名转发(cdn-img,cdn-js,cdn-css),静态资源分布存储在多个域下。
以上三种方法虽然能使HTTP1.X协议传输速度提高,但也有对应的不足。
如合并的Spriting雪碧图,将多个小图合并成一张大图,降低多张小图请求的高延迟,但是如果我只想要两个icon小图,却需要加载一整张大图,就会造成资源冗余。合并的JS/CSS文件也有类似的问题,文件太大造成冗余。
内联的方式,会让代码变得难以维护,不够统一管理,让html文件变得更大,代码混合严重。
多域名下可缓解Max-Connection,但不同域会让Cookie信息无法彼此共享。
上述是HTTP 1.X的一些痛点,于是催生出了新一代的HTTP协议 HTTP/2
HTTP/2起源于SPDY, SPDY是由Google牵头开发,Nginx / Apache 配置 HTTP/2请见米扑博客:
Apache 和 Nginx 编译、配置、验证 HTTP/2 协议
Let’s Encrypt 加密SSL证书并强制启用HTTPS访问 (Nginx 编译配置 HTTP/2)
HTTP1.0和1.1虽然存在这么多问题,业界也想出了各种优化的手段,但这些方法手段都是在尝试绕开协议本身的缺陷,都有种隔靴搔痒,治标不治本的感觉。直到2012年google如一声惊雷提出了SPDY的方案,大家才开始从正面看待和解决老版本http协议本身的问题,这也直接加速了http2.0的诞生。
实际上,HTTP2.0是以SPDY为原型进行讨论和标准化的。为了给HTTP2.0让路,google已决定在2016年不再继续支持SPDY开发,但在HTTP2.0出生之前,SPDY已经有了相当规模的应用,作为一个过渡方案恐怕在还将一段时间内继续存在。现在不少app客户端和server都已经使用了SPDY来提升体验,HTTP2.0在老的设备和系统上还无法使用(iOS系统只有在iOS9+上才支持),所以可以预见未来几年SPDY将和http2.0共同服务的情况。
检测网站是否支持 HTTP/2 方法如下:
1)Firefox 浏览器查看:开发者模式 —> Network —> 网页头信息 Headers —> Version: HTTP/2.0
2)Safari 浏览器查看:开发者模式 —> Resources —> 网页头信息 —> Resource —> Protocol HTTP/2
3)Chrome 浏览器查看:开发者模式 —> Console —> 输入 window.chrome.loadTimes() —> 结果中查看 connectionInfo: "h2"
4)浏览器插件:
Chrome 插件:HTTP/2 and SPDY indicator ,HTTP Indicator
Firefox 插件:HTTP/2 Indicator ,Web Site HTTP/2.0 detector
本文参考:
HTTP的前世今生 (酷壳)
HTTP0.9、HTTP1.0、HTTP1.1、HTTP/2
HTTP0.9、HTTP1.0、HTTP1.1、HTTP2的区别
HTTP 的前世今生:一次性搞懂 HTTP、HTTPS、SPDY、HTT
参考推荐:
HTTP协议中POST、GET、HEAD、PUT等请求方法总结
Let’s Encrypt 在2018年1月发行通配符域名https证书
Let’s Encrypt 加密SSL证书并强制启用HTTPS访问 (推荐)
Apache 和 Nginx 编译、配置、验证 HTTP/2 协议
https SSL 免费证书服务申请 (推荐)
Mac 安装Nginx with-http_ssl_module
版权所有: 本文系米扑博客原创、转载、摘录,或修订后发表,最后更新于 2020-04-17 05:30:13
侵权处理: 本个人博客,不盈利,若侵犯了您的作品权,请联系博主删除,莫恶意,索钱财,感谢!
转载注明: HTTP 协议的历史进化演变 (米扑博客)