势→趋势, 技术变革, 术→技巧, 研发

HTTP协议演进与各版本特性

钱魏Way · · 947 次浏览
!文章内容如有错误或排版问题,请提交反馈,非常感谢!

最近一段时间在学习TCP/IP相关的知识点,在学习到HTTP相关内容时发现大部分都是非常老旧的。很多的资料并没有随着HTTP版本的更新而更新。于是花时间做了些简单的整理。

HTTP的前世传奇

HTTP协议确定之前,伯纳斯-李已经提出了超文本构想,并最终实现了最早期的超文本系统。

1980年——超文本构想的诞生

1980年6月至12月间,伯纳斯-李在日内瓦的CERN(欧洲核子研究中心)担任独立承包人。在那段时间里,他提出了一个构想:创建一个以超文本系统为基础的项目,目的是为了方便研究人员分享及更新讯息。

同时他也开发出了最早的原型系统,并命名为ENQUIRE。这个系统允许一个存储信息片断,并以任何方式链接相关的部分。要找到信息,通过从一张纸到另一张纸的链接进行,就像在旧电脑游戏”冒险”中一样。他用它来记录个人和模块的个人记录。它类似于Apple原来为Macintosh制作的应用程序Hypercard。不同的是,查询尽管缺乏花哨的图形,却运行在多用户系统上,并允许许多人访问相同的数据。

就这样,最早的超文本系统原型诞生了(那时候还没有HTTP协议)。也正是因为ENQUIRE系统的经验,为万维网的诞生埋下了一颗坚实的种子。

1989年——万维网的诞生

1980年,离开CERN的伯纳斯-李在辗转4年之后又重新回到了CERN。1989年,伯纳斯-李为了解决CERN的信息访问的问题,他使用与ENQUIRE系统相似的概念来创建万维网

当时CERN信息访问的存在着很多的问题,也包括了:

  • 人员流动,信息不断丢失?
  • 这个模块在哪里使用?
  • 谁写了这段代码?他在哪里工作?
  • 关于这个概念的文件有哪些?
  • 该项目包括哪些实验室?
  • 哪些系统依赖于此设备?
  • 什么文件涉及这一个?

伯纳斯-李意识到存在的问题后,他写了一份提案 试图说服CERN管理层,全球超文本系统是符合CERN的现状并且还是有益的。1989年的时候,CERN是全欧洲最大的互联网节点。这份提案里面详细的说出了CERN信息访问问题的产生,并且详细的描述了解决方案的演变。并说出了最终的解决方案就是使用超文本系统。

这也第一次提出使用链接资源而非分层系统,而非关键字定位资源。这里的链接资源也就是后面鼎鼎有名的全球网络资源唯一认证的系统,统一资源标识符(URI)。

那为什么伯纳斯-李不用分层系统和关键字定位资源呢。其实他也是有考虑过的,在他写的那份提案里面详细的描述了分层系统和关键字定位资源的存在的问题。主要的问题有:

  • 分层系统的问题:树形结构的问题,使用链接的话没有限制。
  • 关键字的问题:两个人从未选择过相同的关键字。这些关键字仅适用于已经熟悉应用程序的人员。

正是出于这些原因,伯纳斯-李第一次建立了一个小型的链接信息系统,但他并没有意识到已经为这个想法创造了一个术语:”超文本”。

最终,在1990年伯纳斯-李重新开发配置系统后被他的经理麦克·森德尔(Mike Sendall)所接受。

伯纳斯-李重新开发配置系统大概的系统就是如图展示的样子。用户通过超文本浏览器通过超文本网关查看数据。

也就是因为这个系统,不仅为HTTP/0.9协议奠定了基础,也诞生了后面大家熟悉的万维网

HTTP协议诞生

其实说了这么多并没有说明HTTP协议的必要性,为什么需要创造一个新的协议(HTTP协议),难道使用其它协议不行吗?

当时现有的协议涵盖许多不同的任务:

  • 邮件协议允许将单个作者的短暂信息传输给少量收件人。
  • 文件传输协议允许根据发送者或接收者的请求传输数据,但在响应端不允许处理数据。
  • 新闻协议允许向广泛的受众广播瞬态数据。
  • 搜索和检索协议允许进行索引搜索,并允许文档访问。

考虑到很少存在可以根据需要进行扩展的协议,仅有的Z39.50算是一个可行的吧。

HTTP协议必须提供:

  • 文件传输功能的一个子集
  • 能够请求索引搜索
  • 自动格式协商
  • 将客户端引用到另一台服务器的能力

由于很难在原来现有的协议上进行扩展,所以HTTP协议诞生了。

HTTP协议简介

HTTP超文本传输协议(HyperText Transfer Protocol)是当今互联网上应用最为广泛的一种网络协议。所有的WWW(万维网)文件都必须遵守这个标准。HTTP和TCP/IP协议簇中的众多协议一样,用于客户端和服务器端之间的通信。

所谓”超文本”,指的是超越了普通文本的文本,它是文字、图片、视频等的混合体。最关键有「超链接」,能从一个超文本跳转到另外一个超文本。

HTTP协议规定,在两台计算机之间使用HTTP协议进行通信时,在一条通信线路上必定有一端是客户端,另一端则是服务端。当在浏览器中输入网址访问某个网站时,你的浏览器(客户端)会将你的请求封装成一个HTTP请求发送给服务器站点,服务器接收到请求后会组织响应数据封装成一个HTTP响应返回给浏览器。换句话说,肯定是先从客户端开始建立通信的,服务器端在没有接收到请求之前不会发送响应。

HTTP请求报文

HTTP请求报文由3大部分组成:

  • 请求行(必须在HTTP请求报文的第一行)
  • 请求头(从第二行开始,到第一个空行结束。请求头和请求体之间存在一个空行)
  • 请求体(通常以键值对{key:value}方式传递数据)

请求行开头的 POST 表示请求访问服务器的类型,称为方法(method)。随后的字符串 /form/login 指明了请求访问的资源对象,也叫做请求 URI(request-URI)。最后的 HTTP/1.1 即 HTTP 的版本号,用来提示客户端使用的 HTTP 协议功能。

综上来看,这段请求的意思就是:请求访问某台 HTTP 服务器上的 /form/login 页面资源,并附带参数 name=veal、age=37。

注意,无论是 HTTP 请求报文还是 HTTP 响应报文,请求头/响应头和请求体/响应体之间都会有一个“空行”,且请求体/响应体并不是必须的。

HTTP 请求方法

请求行中的方法的作用在于可以指定请求的资源按照期望产生某种行为,即使用方法给服务器下命令。包括(HTTP1.1):GET、POST、PUT、HEAD、DELETE、OPTIONS、CONNECT、TRACE。当然,我们在开发中最常见也最常使用的就只有前面 2 个。

HTTP 请求头

请求头用于补充请求的附加信息、客户端信息、对响应内容相关的优先级等内容。以下列出常见请求头:

  • Referer:表示这个请求是从哪个 URI 跳过来的。比如说通过百度来搜索淘宝网,那么在进入淘宝网的请求报文中,Referer 的值就是:baidu.com。如果是直接访问就不会有这个头。这个字段通常用于防盗链。
  • Accept:告诉服务端,该请求所能支持的响应数据类型。(对应的,HTTP 响应报文中也有这样一个类似的字段 Content-Type,用于表示服务端发送的数据类型,如果 Accept 指定的类型和服务端返回的类型不一致,就会报错)
  • Host:告知服务器请求的资源所处的互联网主机名和端口号。该字段是 HTTP/1.1 规范中唯一一个必须被包含在请求头中的字段。
  • Cookie:客户端的 Cookie 就是通过这个报文头属性传给服务端的!
  • Connection:表示客户端与服务连接类型;Keep-Alive 表示持久连接,close 已关闭
  • Content-Length:请求体的长度
  • Accept-Language:浏览器通知服务器,浏览器支持的语言
  • Range:对于只需获取部分资源的范围请求,包含首部字段 Range 即可告知服务器资源的指定范围

HTTP 响应报文

HTTP 的响应报文也由三部分组成:

  • 响应行(必须在 HTTP 响应报文的第一行)
  • 响应头(从第二行开始,到第一个空行结束。响应头和响应体之间存在一个空行)
  • 响应体

在响应行开头的 HTTP1.1 表示服务器对应的 HTTP 版本。紧随的 200 OK 表示请求的处理结果的“状态码”和“原因短语”。

HTTP 状态码

HTTP 状态码负责表示客户端 HTTP 请求的的返回结果、标记服务器端处理是否正常、通知出现的错误等工作。状态码由 3 位数字组成,第一个数字定义了响应的类别。

HTTP 响应头

响应头也是用键值对 k:v,用于补充响应的附加信息、服务器信息,以及对客户端的附加要求等。

HTTP 连接管理

短连接(非持久连接)

在 HTTP 协议的初始版本(HTTP/1.0)中,客户端和服务器每进行一次 HTTP 会话,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个 HTML 或其他类型的 Web 页中包含有其他的 Web 资源(如 JavaScript 文件、图像文件、CSS 文件等),每遇到这样一个 Web 资源,浏览器就会重新建立一个 HTTP 会话。这种方式称为短连接(也称非持久连接)。

也就是说每次 HTTP 请求都要重新建立一次连接。由于 HTTP 是基于 TCP/IP 协议的,所以连接的每一次建立或者断开都需要 TCP 三次握手或者 TCP 四次挥手的开销。

显然,这种方式存在巨大的弊端。比如访问一个包含多张图片的 HTML 页面,每请求一张图片资源就会造成无谓的 TCP 连接的建立和断开,大大增加了通信量的开销。

长连接(持久连接)

从 HTTP/1.1 起,默认使用长连接也称持久连接 keep-alive。使用长连接的 HTTP 协议,会在响应头加入这行代码:Connection: keep-alive

在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输 HTTP 数据的 TCP 连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive 不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如 Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。

HTTP 协议的长连接和短连接,实质上是 TCP 协议的长连接和短连接。

流水线(管线化)

默认情况下,HTTP 请求是按顺序发出的,下一个请求只有在当前请求收到响应之后才会被发出。由于受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。

持久连接使得多数请求以“流水线”(管线化 pipeline)方式发送成为可能,即在同一条持久连接上连续发出请求,而不用等待响应返回后再发送,这样就可以做到同时并行发送多个请求,而不需要一个接一个地等待响应了。

无状态的 HTTP

HTTP 协议是无状态协议。也就是说他不对之前发生过的请求和响应的状态进行管理,即无法根据之前的状态进行本次的请求处理。这样就会带来一个明显的问题,如果 HTTP 无法记住用户登录的状态,那岂不是每次页面的跳转都会导致用户需要再次重新登录?

当然,不可否认,无状态的优点也很显著,由于不必保存状态,自然就减少了服务器的 CPU 及内存资源的消耗。另一方面,正式由于 HTTP 简单,所以才会被如此广泛应用。

在保留无状态协议这个特征的同时,又要解决无状态导致的问题。方案有很多种,其中比较简单的方式就是使用Cookie 技术Cookie通过在请求和响应报文中写入Cookie信息来控制客户端的状态。具体来说,Cookie会根据从服务器端发送的响应报文中的一个叫作Set-Cookie的首部字段信息,通知客户端保存Cookie。当下次客户端再往服务器发送请求时,客户端会自动在请求报文中加入Cookie值发送出去。服务器端收到客户端发来的Cookie后,会去检查究竟是哪一个客户端发来的连接请求,然后对比服务器上的记录,最后得到之前的状态信息。

形象来说,在客户端第一次请求后,服务器会下发一个装有客户信息的身份证,后续客户端请求服务器的时候,带上身份证,服务器就能认得了。

下图展示了发生Cookie交互的情景:

没有Cookie信息状态下的请求:

第2次以后的请求(存有Cookie信息状态)

HTTP与HTTPS

HTTPS协议可以理解为HTTP协议的升级,就是在HTTP的基础上增加了数据加密。在数据进行传输之前,对数据进行加密,然后再发送到服务器。这样,就算数据被第三者所截获,但是由于数据是加密的,所以你的个人信息仍然是安全的。这就是HTTP和HTTPS的最大区别。

互联网的通信安全,建立在SSL/TLS协议之上,SSL“安全套接层”协议,TLS“安全传输层”协议,都属于是加密协议,在其网络数据传输中起到保护隐私和数据的完整性。保证该网络传输的信息不会被未经授权的元素拦截或修改,从而确保只有合法的发送者和接收者才能完全访问并传输信息。

  • SSL:(Secure Socket Layer,安全套接字层),位于可靠的面向连接的网络层协议和应用层协议之间的一种协议层。SSL通过互相认证、使用数字签名确保完整性、使用加密确保私密性,以实现客户端和服务器之间的安全通讯。该协议由两层组成:SSL记录协议和SSL握手协议。
  • TLS:(Transport Layer Security,传输层安全协议),用于两个应用程序之间提供保密性和数据完整性。该协议由两层组成:TLS记录协议和TLS握手协议。

不使用SSL/TLS的HTTP通信,就是不加密的通信。所有信息明文传播,带来了三大风险。

  • 窃听风险(eavesdropping):第三方可以获知通信内容。
  • 篡改风险(tampering):第三方可以修改通信内容。
  • 冒充风险(pretending):第三方可以冒充他人身份参与通信。

SSL/TLS协议是为了解决这三大风险而设计的,希望达到:

  • 所有信息都是加密传播,第三方无法窃听。
  • 具有校验机制,一旦被篡改,通信双方会立刻发现。
  • 配备身份证书,防止身份被冒充。

互联网是开放环境,通信双方都是未知身份,这为协议的设计带来了很大的难度。而且,协议还必须能够经受所有匪夷所思的攻击,这使得SSL/TLS协议变得异常复杂。

HTTP协议演变

截止到现在,IETF已经发布了5个HTTP协议了,包括HTTP0.9、HTTP1.0、HTTP1.1、HTTP2、HTTP3。下面讲一下各个版本的区别:

HTTP/0.9

最初版本的HTTP协议并没有版本号,后来它的版本号被定位在0.9以区分以后的版本。已过时的HTTP/0.9是HTTP协议的第一个版本,诞生于1989年。它的组成极其简单,只允许客户端发送GET这一种请求,而且不支持请求头。由于没有协议头,所以HTTP/0.9只能支持一种内容——纯文本。服务器只能回应HTML格式的字符串,不能回应别的格式。服务器发送完毕后,就会关闭TCP连接。HTTP/0.9具有典型的无状态性,每个访问都独立处理,处理完成后就会断开连接。如果请求的页面不存在,也不会返回任何错误码。

HTTP/1.0

1996年11月,一份新文档(RFC1945)被发表出来,文档RFC1945定义了HTTP/1.0,但它是狭义的,并不是官方标准。HTTP1.0是HTTP协议的第二个版本,至今仍被广泛采用。它在HTTP/0.9的基础上做了大量的扩充和改进,包括:

  • 具备了传输除纯文本HTML文件以外其他类型文档的能力,如图像、视频、二进制文件,不仅仅局限于文本
  • 除了GET命令,还引入了POST命令和HEAD命令,丰富了浏览器与服务器的互动手段。
  • HTTP请求和回应的格式也变了。除了数据部分,每次通信都必须包括头信息(HTTP header),用来描述一些元数据。
  • 其他的新增功能还包括状态码(status code)、多字符集支持、多部分发送(multi-part type)、权限(authorization)、缓存(cache)、内容编码(content encoding)等。

请求头

一个简单请求的头信息

GET / HTTP/1.0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5)
Accept: */*

可以看到在请求方法之后有请求资源的位置+请求协议版本,之后是一些客户端的信息配置响应头

一个简单响应的头信息(v1.0)

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
//这是一个空行
...数据内容

服务端的响应头第一个就是请求协议版本,后面紧跟着是这次请求的状态码、以及状态码的描述,之后的内容是一些关于返回内容的描述。

Content-Type字段

在HTTP1.0的时候,任何的资源都可以被传输,传输的格式呢也是多种多样的,客户端在收到响应体的内容的时候就是根据这个Content-Type去进行解析的。关于字符的编码,1.0版规定,头信息必须是ASCII码,后面的数据可以是任何格式。因此,服务器回应的时候,必须告诉客户端,数据是什么格式,所以服务端返回时候必须带着这个字段。

常见Content-Type字段的值:

  • text/html
  • text/css
  • image/png
  • image/gif
  • application/javascript
  • application/octet-stream

这些Content-Type有一个总称叫做MIME type。

这些数据类型总称为MIME type,MIME type还可以在尾部使用分号,添加参数。

Content-Type: text/html; charset=utf-8

客户端请求的时候也可以使用Accept声明自己接收的数据格式

accept: */*

Content-Encoding字段由于传输的数据可能比较大,一次可以将数据压缩后进行传输。Content-Encoding字段就是说明数据压缩的方法。

Content-Encoding: gzip
Content-Encoding: compress
Content-Encoding: deflate

客户端在请求时,使用Accept-Encoding字段说明自己可以接受哪些压缩方法。

Accept-Encoding: gzip, deflate

状态码(status code)

状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:

  • 1xx:指示信息–表示请求已接收,继续处理
  • 2xx:成功–表示请求已被成功接收、理解、接受
  • 3xx:重定向–要完成请求必须进行更进一步的操作
  • 4xx:客户端错误–请求有语法错误或请求无法实现
  • 5xx:服务器端错误–服务器未能实现合法的请求

HTTP/1.0的特性

  • 无状态:服务器不跟踪不记录请求过的状态(可以借助cookie/session机制来做身份认证和状态记录)
  • 无连接:浏览器每次请求都需要建立tcp连接

HTTP/1.0的缺点

1.0版本的HTTP最明显的缺点,与0.9相同,那就是短连接,这会造成两个问题,分别是影响HTTP通信的两个方面:速度与带宽。连接无法复用,导致每次请求都要进行TCP三次握手等流程,导致速度变慢;由此导致的带宽不足,会使后面的响应被阻塞。随着网页加载的外部资源(图片、CSS、JavaScript文件等)越来越多,这个问题就愈发突出了。

为了解决这个问题,有些浏览器在请求时,用了一个非标准的Connection字段。

Connection: keep-alive

这个字段要求服务器不要关闭TCP连接,以便其他请求复用。服务器同样回应这个字段。

Connection: keep-alive

一个可以复用的TCP连接就建立了,直到客户端或服务器主动关闭连接。但是,这不是标准字段,不同实现的行为可能不一致,因此不是根本的解决办法。

另外,HTTP1.0所保持的TCP每次只能处理一个请求,虽然能一次性接收多个请求,但是还是得按顺序一次处理一个请求,这样在后续请求等待前序请求完成时,很容易造成阻塞。同时,它还不支持断点续传。

HTTP/1.1

HTTP协议的第三个版本是HTTP1.1,HTTP1.1是在1.0发布之后的半年就推出了,完善了1.0版本。是目前使用最广泛的协议版本。HTTP1.1是目前主流的HTTP协议版本。

HTTP1.1引入了许多关键性能优化:

1)持久连接/长连接

1.1版本最大变化,引入了持久连接(persistent connection),即TCP连接默认不关闭,可以被多个请求复用,不用声明Connection: keep-alive。客户端和服务端发现对方一段时间没有活动,可以主动关闭连接。

Connection: Keep-Alive
Keep-Alive: max=5, timeout=120

客户端如果想要关闭连接,可以在最后一个请求的请求头中,加上Connection: close来安全关闭这个连接。

目前对于同一个域名,大多数浏览器允许同时建立6个持久连接。

2)管道机制

管道机制(pipelining):即在同一个TCP链接里面,客户端可以同事发送多个请求。从而进一步改善HTTP协议的效率问题。例如:客户端需要请求两个资源。以前的做法是,在同一个TCP连接里面,先发送A请求,然后等待服务器做出回应,收到后再发出B请求。管道机制则是允许浏览器同时发出A请求和B请求,但是服务器还是按照顺序,先回应A请求,完成后再回应B请求,通过下图可以很清晰的看出区别:

这里有一个问题,即客户端如何区分哪里是第一个响应的内容,哪里是下一个响应的内容呢?HTTP1.1为此添加了 Content-Length首部,用它来标记响应内容的结束位置,以及下一个响应内容的开始位置。

Content-Length: 1234

上面代码告诉浏览器,本次回应的长度是1234个字节,后面的字节就属于下一个回应了。

管道机制解决了上面提到的并行请求限制问题,因为管道机制支持一次性发送多个请求,但是仍然存在问题:服务器仍然按照请求顺序给与响应,因此如果前面的请求回应得很慢,后面就会有很多请求排队,这称为“队头阻塞(Head-of-line blocking)”。

3)分块传输

使用Content-Length字段的前提条件是,服务器发送回应之前,必须知道回应的数据长度。对于一些很耗时的动态操作来说,这意味着,服务器要等到所有操作完成,才能发送数据,显然这样的效率不高。更好的处理方法是,产生一块数据,就发送一块,采用”流模式”(stream)取代”缓存模式”(buffer)。即产生一块数据,就发送一块。

具体的形式是,在响应头部返回Transfer-Encoding字段,表明数据是有未定的数据块组成。

Transfer-Encoding: chunked

每一个分块,都添加一个Content-Length,当所有的数据块都传输完成时,返回一个空的chunked,Content-Length设为0,来表示传输完成。

1.1版规定可以不使用Content-Length字段,而使用”分块传输编码”(chunked transfer encoding)。只要请求或回应的头信息有Transfer-Encoding字段,就表明回应将由数量未定的数据块组成。

4)新增HTTP方法

新增HTTP方法,包括PUT,PATCH,OPTIONS,DELETE。

  • GET请求获取Request-URI所标识的资源
  • POST在Request-URI所标识的资源后附加新的数据
  • HEAD请求获取由Request-URI所标识的资源的响应消息报头
  • PUT请求服务器存储一个资源,并用Request-URI作为其标识
  • DELETE请求服务器删除Request-URI所标识的资源
  • TRACE请求服务器回送收到的请求信息,主要用于测试或诊断
  • CONNECT保留将来使用
  • OPTIONS请求查询服务器的性能,或者查询与资源相关的选项和需求

5)新增了24个状态响应码

在HTTP1.1中新增24个状态响应码,如:

  • 409(Conflict)表示请求的资源与资源当前状态冲突;
  • 410(Gone)表示服务器上的某个资源被永久性的删除

6)强制HOST头HTTP 1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP 1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)。有了Host字段,就可以将请求发往同一台服务器上的不同网站,为虚拟主机的兴起打下了基础。

7)引入了range头域/断点续传

HTTP/1.1还支持RANGE方法,只传送内容的一部分。这样当客户端已经有一部分的资源后,只需要跟服务器请求另外的部分资源即可。这是支持文件断点续传的基础。该功能通过在请求消息中引入了range头域来实现,它允许只请求资源的某个部分。在响应消息中Content-Range头域声明了返回的这部分对象的偏移值和长度。如果服务器相应地返回了对象所请求范围的内容,则响应码206(Partial Content)。在上传/下载资源时,如果资源过大,将其分割为多个部分,分别上传/下载,如果遇到网络故障,可以从已经上传/下载好的地方继续请求,不用从头开始,提高效率。

8)缓存处理

HTTP/1.0使用Pragma: no-cache + Last-Modified/If-Modified-Since + Expire来作为缓存判断的标准;HTTP/1.1引入了更多的缓存控制策略:Cache-Control、Etag/If-None-Match

所有HTTP 1.1请求里的响应头都包含了“Date:”标头,因此每个Response都为缓存添加了时间戳。

9)增加了type、language、encoding等header

10)增加了TLS支持,即支持https传输

11)支持更多的连接模型

支持四种模型:短连接,可重用tcp的长链接,服务端push模型(服务端主动将数据推送到客户端cache中),websocket模型。

SYPD

2009年,谷歌公开了自行研发的SPDY协议,主要解决HTTP/1.1效率不高的问题。这个协议在Chrome浏览器上证明可行以后,就被当作HTTP/2的基础,主要特性都在HTTP/2之中得到继承。

SPDY的方案优化了HTTP 1.X的请求延迟,解决了HTTP 1.X的安全性,具体如下:

  • 降低延迟,针对HTTP高延迟的问题,SPDY优雅的采取了多路复用(multiplexing)。多路复用通过多个请求stream共享一个tcp连接的方式,解决了HOL blocking的问题,降低了延迟同时提高了带宽的利用率。
  • 请求优先级(request prioritization)。多路复用带来一个新的问题是,在连接共享的基础之上有可能会导致关键请求被阻塞。SPDY允许给每个request设置优先级,这样重要的请求就会优先得到响应。比如浏览器加载首页,首页的html内容应该优先展示,之后才是各种静态资源文件,脚本文件等加载,这样可以保证用户能第一时间看到网页内容。
  • header压缩。前面提到x的header很多时候都是重复多余的。选择合适的压缩算法可以减小包的大小和数量。
  • 基于HTTPS的加密协议传输,大大提高了传输数据的可靠性。
  • 服务端推送(server push),采用了SPDY的网页,例如我的网页有一个css的请求,在客户端收到sytle.css数据的同时,服务端会将sytle.js的文件推送给客户端,当客户端再次尝试获取sytle.js时就可以直接从缓存中获取到,不用再发请求了。
  • SPDY位于HTTP之下,TCP和SSL之上,这样可以轻松兼容老版本的HTTP协议(将x的内容封装成一种新的frame格式),同时可以使用已有的SSL功能。

SPDY与HTTP/1.1的区别

SPDY并不用于取代HTTP,它只是修改了HTTP的请求与应答在网络上传输的方式;这意味着只需增加一个SPDY传输层,现有的所有服务端应用均不用做任何修改。当使用SPDY的方式传输,HTTP请求会被处理、标记简化和压缩。比如,每一个SPDY端点会持续跟踪每一个在之前的请求中已经发送的HTTP报文头部,从而避免重复发送还未改变的头部。而还未发送的报文的数据部分将在被压缩后被发送。

左边是普通HTTPS加载,右边是SPDY加载。

SPDY协议只是在性能上对HTTP做了很大的优化,其核心思想是尽量减少连接个数,而对于HTTP的语义并没有做太大的修改。具体来说是,SPDY使用了HTTP的方法和页眉,但是删除了一些头并重写了HTTP中管理连接和数据转移格式的部分,所以基本上是兼容HTTP的。

Google在SPDY白皮书里表示要向协议栈下面渗透并替换掉传输层协议(TCP),但是因为这样无论是部署起来还是实现起来暂时相当困难,因此Google准备先对应用层协议HTTP进行改进,先在SSL之上增加一个会话层来实现SPDY协议,而HTTP的GET和POST消息格式保持不变,即现有的所有服务端应用均不用做任何修改。

因此在目前,SPDY的目的是为了加强HTTP,是对HTTP一个更好的实现和支持。

SPDY的核心是帧管理层(framing layer),它管理两个端点间的连接和数据的传输。两个端点之间可以有多个数据流。在帧管理层的顶部,SPDY实现了HTTP请求/响应处理。这使得我们不需要对现有网站做太大的更改或不更改就可以使用SPDY。

HTTP 1.1协议的不足

  • 单路连接请求低效。HTTP协议的最大弊端就是每个TCP连接只能对应一个HTTP请求,即每个HTTP连接只请求一个资源,浏览器只能通过建立多个连接来解决。此外在HTTP中对请求是严格的先入先出(FIFO)进行的,如果中间某个请求处理时间较长会阻塞后面的请求。
  • HTTP只允许由客户端主动发起请求。服务端只能等待客户端发送一个请求,在可以满足预加载的现状是一种桎梏。
  • HTTP头冗余。HTTP头在同一个会话里是反复发送的,中间的冗余信息,比如User-Agent、Host等不需要重复发送的信息也在反复发送,浪费带宽和资源。

SPDY协议的优点:

  • 多路复用请求优化。SPDY规定在一个SPDY连接内可以有无限个并行请求,即允许多个并发HTTP请求共用一个TCP会话。这样SPDY通过复用在单个TCP连接上的多次请求,而非为每个请求单独开放连接,这样只需建立一个TCP连接就可以传送网页上所有资源,不仅可以减少消息交互往返的时间还可以避免创建新连接造成的延迟,使得TCP的效率更高。此外,SPDY的多路复用可以设置优先级,而不像传统HTTP那样严格按照先入先出一个一个处理请求,它会选择性的先传输CSS这样更重要的资源,然后再传输网站图标之类不太重要的资源,可以避免让非关键资源占用网络通道的问题,提升TCP的性能。
  • 支持服务器推送技术。服务器可以主动向客户端发起通信向客户端推送数据,这种预加载可以使用户一直保持一个快速的网络。
  • SPDY压缩了HTTP头。舍弃掉了不必要的头信息,经过压缩之后可以节省多余数据传输所带来的等待时间和带宽。
  • 强制使用SSL传输协议。Google认为Web未来的发展方向必定是安全的网络连接,全部请求SSL加密后,信息传输更加安全。

2015年,Google决定将SPDY合并到HTTP标准中,并命名为HTTP/2。

HTTP/2

HTTP/2.0是HTTP协议自1999年HTTP/1.1发布后的首个更新,主要基于SPDY协议,于2015年2月17日被批准。主要特点:

  • HTTP/2采用二进制格式而非文本格式
  • HTTP/2是完全多路复用的,而非有序并阻塞的——只需一个连接即可实现并行
  • HTTP/2使用报头压缩,降低了开销
  • HTTP/2让服务器可以将响应主动“推送”到客户端缓存中
  • HTTP/2相比HTTP/1.1的修改并不会破坏现有程序的工作,但是新的程序可以借由新特性得到更好的速度。
  • HTTP/2保留了HTTP/1.1的大部分语义,例如请求方法、状态码乃至URI和绝大多数HTTP头部字段一致。而HTTP/2采用了新的方法来编码、传输客户端——服务器间的数据。

HTTP 2.0可以说是 SPDY 的升级版(其实原本也是基于 SPDY 设计的),但是,HTTP 2.0 跟 SPDY 仍有不同的地方,如下:

  • HTTP 2.0 支持明文 HTTP 传输,而 SPDY 强制使用 HTTPS
  • HTTP 2.0 消息头的压缩算法采用HPACK,而非 SPDY 采用的DEFLATE

使用HTTP 2.0 测试 便可看出 HTTP 2.0 比之前的协议在性能上有很大的提升。下面总结了 HTTP 2.0 协议的几个特性。

1)二进制传输

HTTP/1.1 版的头信息肯定是文本(ASCII 编码),数据体可以是文本,也可以是二进制。HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为”帧”(frame):头信息帧和数据帧。

HTTP/2 在应用层 (HTTP/2) 和传输层 (TCP or UDP) 之间增加一个二进制分帧层。在不改动 HTTP/1.x 的语义、方法、状态码、URI 以及首部字段的情况下, 解决了 HTTP 1.1 的性能限制,改进传输性能,实现低延迟和高吞吐量。在二进制分帧层中,HTTP/2 会将所有传输的信息分割为更小的消息和帧(frame), 并对它们采用二进制格式的编码,其中 HTTP 1.x 的首部信息会被封装到 HEADER frame,而相应的 Request Body 则封装到 DATA frame 里面。

二进制协议的一个好处是,可以定义额外的帧。HTTP/2 定义了近十种帧,为将来的高级应用打好了基础。如果使用文本实现这种功能,解析数据将会变得非常麻烦,二进制解析则方便得多。

2)多路复用 (Multiplexing)/二进制分帧

多路复用允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。在 HTTP/1.1 协议中浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞。这也是为何一些站点会有多个静态资源 CDN 域名的原因之一,而 HTTP/2 的多路复用 (Multiplexing) 则允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。因此 HTTP/2 可以很容易的去实现多流并行而不用依赖建立多个 TCP 连接,HTTP/2 把 HTTP 协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息。并行地在同一个 TCP 连接上双向交换消息。

HTTP 2.0 中,有两个概念非常重要:帧(frame)和流(stream)。帧是最小的数据单位,每个帧会标识出该帧属于哪个流,流是多个帧组成的数据流。

所谓多路复用,即在一个 TCP 连接中存在多个流,即可以同时发送多个请求,对端可以通过帧中的表示知道该帧属于哪个请求。在客户端,这些帧乱序发送,到对端后再根据每个帧首部的流标识符重新组装。通过该技术,可以避免 HTTP 旧版本的队头阻塞问题,极大提高传输性能

因为 HTTP/2 的数据包是不按顺序发送的,同一个连接里面连续的数据包,可能属于不同的回应。因此,必须要对数据包做标记,指出它属于哪个回应。

HTTP/2 将每个请求或回应的所有数据包,称为一个数据流(stream)。每个数据流都有一个独一无二的编号。数据包发送的时候,都必须标记数据流 ID,用来区分它属于哪个数据流。另外还规定,客户端发出的数据流,ID 一律为奇数,服务器发出的,ID 为偶数。

数据流发送到一半的时候,客户端和服务器都可以发送信号(RST_STREAM 帧),取消这个数据流。1.1 版取消数据流的唯一方法,就是关闭 TCP 连接。这就是说,HTTP/2 可以取消某一次请求,同时保证 TCP 连接还打开着,可以被其他请求使用。

客户端还可以指定数据流的优先级。优先级越高,服务器就会越早回应。

过分帧给每个帧打上流的 ID 去避免依次响应的问题,对方接收到帧之后根据 ID 拼接出流,这样就可以做到乱序响应从而避免请求时的队首阻塞问题。但是 TCP 层面的队首阻塞是 HTTP/2 无法解决的(HTTP 只是应用层协议,TCP 是传输层协议),TCP 的阻塞问题是因为传输阶段可能会丢包,一旦丢包就会等待重新发包,阻塞后续传输,这个问题虽然有滑动窗口(Sliding Window)这个方案,但是只能增强抗干扰,并没有彻底解决。

3)首部压缩(Header Compression)

HTTP/1.1 并不支持 HTTP 首部压缩,为此 SPDY 和 HTTP/2 应运而生,SPDY 使用的是通用的 DEFLATE 算法,而 HTTP/2 则使用了专门为首部压缩而设计的 HPACK 算法。

当一个客户端向相同服务器请求许多资源时,像来自同一个网页的图像,将会有大量的请求看上去几乎同样的,这就需要压缩技术对付这种几乎相同的信息。HTTP 协议不带有状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的,比如 Cookie 和 User Agent,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。所以 HTTP2 可以维护一个头部信息字典,差量进行更新头信息,减少头部信息传输占用的资源。

4)服务端推送(Server Push)

HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送(server push)。

常见场景是客户端请求一个网页,这个网页里面包含很多静态资源。正常情况下,客户端必须收到网页后,解析 HTML 源码,发现有静态资源,再发出静态资源请求。其实,服务器可以预期到客户端请求网页后,很可能会再请求静态资源,所以就主动把这些静态资源随着网页一起发给客户端了。

在 HTTP 2.0 中,服务端可以在客户端某个请求后,主动推送其他资源。可以想象一下,某些资源客户端是一定会请求的,这时就可以采取服务端 push 的技术,提前给客户端推送必要的资源,就可以相对减少一点延迟时间。在浏览器兼容的情况下也可以使用 prefetch。

QUIC

针对,先前已经整理过一篇网络通讯协议之 QUIC 的文章。这里再做一些简单的介绍。

QUIC 产生的背景

HTTP 2.0 虽然性能已经不错了,但还是有很多不足:

  • 建立连接时间长(本质上是 TCP 的问题)
  • 队头阻塞问题
  • 移动互联网领域表现不佳(弱网环境)
  • ……

这些缺点基本都是由于 TCP 协议引起的。

TCP是面向连接、可靠的传输层协议,当前几乎所有重要的协议和应用都是基于TCP来实现的。

网络环境的改变速度很快,但是TCP协议相对缓慢,正是这种矛盾促使谷歌做出了一个看似出乎意料的决定 – 基于UDP来开发新一代HTTP协议。

单纯地看看TCP协议的不足和UDP的一些优点

  • 基于TCP开发的设备和协议非常多,兼容困难
  • TCP协议栈是Linux内部的重要部分,修改和升级成本很大
  • UDP本身是无连接的、没有建链和拆链成本
  • UDP的数据包无队头阻塞问题
  • UDP改造成本小

谷歌要想从TCP上进行改造升级绝非易事,但是UDP虽然没有TCP为了保证可靠连接而引发的问题,但是UDP本身不可靠,又不能直接用。

综合而知,谷歌决定在UDP基础上改造一个具备TCP协议优点的新协议也就顺理成章了,这个新协议就是QUIC协议。

QUIC简介

QUIC其实是 Quick UDP Internet Connections 的缩写,直译为快速UDP互联网连接。

维基百科对于QUIC协议的一些介绍:QUIC协议最初由Google的Jim Roskind设计,实施并于2012年部署,在2013年随着实验的扩大而公开宣布,并向IETF进行了描述。QUIC提高了当前正在使用TCP的面向连接的Web应用程序的性能。它在两个端点之间使用用户数据报协议(UDP)建立多个复用连接来实现此目的。QUIC的次要目标包括减少连接和传输延迟,在每个方向进行带宽估计以避免拥塞。它还将拥塞控制算法移动到用户空间,而不是内核空间,此外使用前向纠错(FEC)进行扩展,以在出现错误时进一步提高性能。

HTTP/3.0

HTTP 3.0又称为HTTP Over QUIC,其弃用TCP协议,改为使用基于UDP协议的QUIC协议来实现。

HTTP 3.0既然选择了QUIC协议,也就意味着HTTP 3.0基本继承了HTTP 2.0的强大功能,并且进一步解决了HTTP 2.0存在的一些问题,同时必然引入了新的问题。

HTTP 2协议虽然大幅提升了HTTP/1.1的性能,然而,基于TCP实现的HTTP 2遗留下3个问题:

  • 有序字节流引出的队头阻塞(Head-of-line blocking),使得HTTP 2的多路复用能力大打折扣;
  • TCP与TLS叠加了握手时延,建链时长还有1倍的下降空间;
  • 基于TCP四元组确定一个连接,这种诞生于有线网络的设计,并不适合移动状态下的无线网络,这意味着IP地址的频繁变动会导致TCP连接、TLS会话反复握手,成本高昂。

HTTP 3协议解决了这些问题:

  • HTTP 3基于UDP协议重新定义了连接,在QUIC层实现了无序、并发字节流的传输,解决了队头阻塞问题(包括基于QPACK解决了动态表的队头阻塞);
  • HTTP 3重新定义了TLS协议加密QUIC头部的方式,既提高了网络攻击成本,又降低了建立连接的速度(仅需1个RTT就可以同时完成建链与密钥协商);
  • HTTP 3将Packet、QUIC Frame、HTTP 3 Frame分离,实现了连接迁移功能,降低了5G环境下高速移动设备的连接维护成本。

1)队头阻塞问题

队头阻塞 Head-of-line blocking (缩写为HOL blocking)是计算机网络中是一种性能受限的现象,通俗来说就是:一个数据包影响了一堆数据包,它不来大家都走不了。

队头阻塞问题可能存在于HTTP层和TCP层,在HTTP 1.x时两个层次都存在该问题。

 

HTTP 2.0协议的多路复用机制解决了HTTP层的队头阻塞问题,但是在TCP层仍然存在队头阻塞问题。

TCP协议在收到数据包之后,这部分数据可能是乱序到达的,但是TCP必须将所有数据收集排序整合后给上层使用,如果其中某个包丢失了,就必须等待重传,从而出现某个丢包数据阻塞整个连接的数据使用。

QUIC协议是基于UDP协议实现的,在一条链接上可以有多个流,流与流之间是互不影响的,当一个流出现丢包影响范围非常小,从而解决队头阻塞问题。

2)0RTT建链

衡量网络建链的常用指标是RTT Round-Trip Time,也就是数据包一来一回的时间消耗。

RTT包括三部分:往返传播时延、网络设备内排队时延、应用程序数据处理时延。

一般来说HTTPS协议要建立完整链接包括: TCP握手和TLS握手,总计需要至少2-3个RTT,普通的HTTP协议也需要至少1个RTT才可以完成握手。

然而,QUIC协议可以实现在第一个包就可以包含有效的应用数据,从而实现0RTT,但这也是有条件的。

简单来说,基于TCP协议和TLS协议的HTTP 2.0在真正发送数据包之前需要花费一些时间来完成握手和加密协商,完成之后才可以真正传输业务数据。

但是QUIC则第一个数据包就可以发业务数据,从而在连接延时有很大优势,可以节约数百毫秒的时间。

QUIC的0RTT也是需要条件的,对于第一次交互的客户端和服务端0RTT也是做不到的,毕竟双方完全陌生。

因此,QUIC协议可以分为首次连接和非首次连接,两种情况进行讨论。

使用QUIC协议的客户端和服务端要使用1RTT进行密钥交换,使用的交换算法是DH(Diffie-Hellman)迪菲-赫尔曼算法。

首次连接

简单来说一下,首次连接时客户端和服务端的密钥协商和数据传输过程,其中涉及了DH算法的基本过程:

非首次连接

前面提到客户端和服务端首次连接时服务端传递了config包,里面包含了服务端公钥和两个随机数,客户端会将config存储下来,后续再连接时可以直接使用,从而跳过这个1RTT,实现0RTT的业务数据交互。

客户端保存config是有时间期限的,在config失效之后仍然需要进行首次连接时的密钥交换。

3)前向安全问题前向安全或前向保密 Forward Secrecy 是密码学中通讯协议的安全属性,指的是长期使用的主密钥泄漏不会导致过去的会话密钥泄漏。前向安全能够保护过去进行的通讯不受密码或密钥在未来暴露的威胁,如果系统具有前向安全性,就可以保证在主密钥泄露时历史通讯的安全,即使系统遭到主动攻击也是如此。通俗来说,前向安全指的是密钥泄漏也不会让之前加密的数据被泄漏,影响的只有当前,对之前的数据无影响。

前面提到 QUIC 协议首次连接时先后生成了两个加密密钥,由于 config 被客户端存储了,如果期间服务端私钥泄漏,那么可以根据 K=modp 计算出密钥 K。如果一直使用这个密钥进行加解密,那么就可以用 K 解密所有历史消息,因此后续又生成了新密钥,使用其进行加解密,当时完成交互时则销毁,从而实现了前向安全。

4)前向纠错

前向纠错也叫前向纠错码 Forward Error Correction 简称 FEC 是增加数据通讯可信度的方法,在单向通讯信道中,一旦错误被发现,其接收器将无权再请求传输。FEC 是利用数据进行传输冗余信息的方法,当传输中出现错误,将允许接收器再建数据。

QUIC 每发送一组数据就对这组数据进行异或运算,并将结果作为一个 FEC 包发送出去,接收方收到这一组数据后根据数据包和 FEC 包即可进行校验和纠错。

5)连接迁移

网络切换几乎无时无刻不在发生。

TCP 协议使用五元组来表示一条唯一的连接,当我们从 4G 环境切换到 wifi 环境时,手机的 IP 地址就会发生变化,这时必须创建新的 TCP 连接才能继续传输数据。

QUIC 协议基于 UDP 实现摒弃了五元组的概念,使用 64 位的随机数作为连接的 ID,并使用该 ID 表示连接。

基于 QUIC 协议之下,我们在日常 wifi 和 4G 切换时,或者不同基站之间切换都不会重连,从而提高业务层的体验。

参考链接:

One Reply to “HTTP协议演进与各版本特性”

回复 lin 取消回复

您的邮箱地址不会被公开。 必填项已用 * 标注