聊一聊 HTTP 协议的发展史

785 阅读38分钟

什么是HTTP

Hypertext Transfer Protocol -- HTTP/1.1 中对于 HTTP 的定义如下:

The Hypertext Transfer Protocol (HTTP) is an application-levelprotocol for distributed, collaborative, hypermedia information systems. It is a generic, stateless, protocol which can be used for many tasks beyond its use for hypertext, such as name servers and distributed object management systems, through extension of its request methods, error codes and headers

即:
超文本传输协议 (HyperText Transfer Protocol, HTTP) 是一种用于分布式、协作式和超媒体信息系统应用层协议。这是自 1990 年以来万维网(即互联网)数据通信的基础。它是一个通用的、无状态的协议,可用于除了用于超文本之外的许多任务,例如名称服务器和分布式对象管理系统,通过扩展其请求方法、错误代码和标头。

从官方的定义来看,简单来说,HTTP 就是用于传输超文本数据应用层协议。

那我们将 HyperText Transfer Protocol 拆开来看,每个单词又意味着什么呢?

image.png

  • HyperText(超文本): 是一种可以显示在电脑显示器或电子设备上的文本,现时超文本普遍以电子文档的方式存在,其中的文字包含有可以链接到其他字段或者文档的超链接,允许从当前阅读的文字直接切换到超链接所指向的所有文字

超媒体(Hypermedia)是超文本的延伸,是一种包含图形、音效、视频、纯文字和超链接的非线性消息媒体。

我们常见的 HTML(超文本标记语言)就是一种超文本格式。

  • Transfer(传输): 在电信中,传输是通过物理点对点或点对多点传输介质(有线,光纤或无线)发送和传播模拟或数字信息信号的过程。

简单来说,就是把数据从一个点发送到另一个点。

  • Protocol(协议): 协议是定义了数据如何在计算机内和之间进行交换的规则的系统。设备之间通信要求设备接受正在交换的数据的格式。定义格式的一组规则称之为协议。

所以,HTTP 是 计算机领域用于点对点之间传输超文本(图形、音效、视频、纯文字和超链接等)数据的一组规则。

基本上,HTTP 是一种基于 TCP/IP 的通信协议,用于在万维网上传递数据(HTML 文件、图像文件、查询结果等)。默认端口是 TCP 80,但也可以使用其他端口。它为计算机之间的通信提供了一种标准化的方式。 HTTP 规范指定了客户端的请求数据将如何构造并发送到服务器,以及服务器如何响应这些请求。

HTTP 协议的基本特点

  • 无连接(connectionless):HTTP 客户端发起一个HTTP请求,请求发出后,客户端等待响应。服务器处理请求并发送响应,然后客户端断开连接。因此,客户端和服务器只在当前的请求和响应期间了解对方。进一步的请求是在新的连接上提出的,就像客户和服务器对彼此都是新的
  • 媒体独立(media independent):这意味着,只要客户端和服务器都知道如何处理数据内容,任何类型的数据都可以通过HTTP发送。客户端和服务器都需要使用适当的 MIME 类型来指定内容类型
  • 无状态(stateless):HTTP 是无状态协议 决定了 HTTP 是无连接 。服务器和客户端仅在当前请求期间才知道彼此。之后,两个人都忘记了对方。由于协议的这种性质,客户端和浏览器都不能保留跨网页的不同请求之间的信息

HTTP中的 客户端 和 服务器

  • 客户端(Client) HTTP 客户端以请求方法、URI 和协议版本的形式向服务器发送请求,随后是一个类似于 MIME 类型的消息,其中包含请求修饰符、客户端信息以及可能包含通过TCP/IP连接的可能的主体内容。

  • 服务器(Server) HTTP 服务器以状态行进行响应,包括消息的协议版本和成功或错误代码,然后是类似于 MIME 的消息,其中包含服务器信息、实体元信息以及可能包含的的实体主体内容。

注意:以上介绍的是 HTTP 协议中规定的客户端和服务器的基本功能,但是客户端(Client)并不等同于网页浏览器(Web Browser)服务器(Server)也不等同于 Web 服务器(Web Server)。因为 Web 服务器是通过 HTTP 协议将网页传输给网页浏览器,所以通常介绍 HTTP 协议时,会将这些概念混淆。此外,还可以了解一下 C/S 架构B/S 架构的区别。

一、HTTP 的发展

1969年11月21日,美国国防部高等研究计划署(ARPA)建立了第一个 APRA 网连接,这是如今互联网的前身。

196911211.jpg

1989 年,任职于欧洲核子研究中心(CERN)的蒂姆·伯纳斯 - 李(Tim Berners-Lee)在一篇名为《Information Management: A Proposal》 的文章中阐述了关于建立一个通过网络传输超文本系统的设想。这个系统起初被命名为 Mesh,在随后的1990年项目实施期间被更名为万维网(World Wide Web)。 它在现有的TCP和IP协议基础之上建立。

1990年10月,Tim Berners-Lee 编写了网页的三大关键技术:

  • HTML(超文本标记语言),也就是页面的的标记(格式化)语言。
  • URI(统一资源标识符),一种用来标识各网络资源的唯一“地址”,通常也称作URL。
  • HTTP(超文本传输协议),用于实现链接资源在网络上的提取。 同年,Tim Berners-Lee 还开发了第一个网络浏览器 WorldWideWeb 和 第一个服务器 httpd

1991年8月16日,Tim Berners-Lee 在公开的超文本新闻组上发表的文章《World-Wide Web: The Information Universe》被视为是万维网公共项目的开始。

Tim-Berners-Lee-1024x576.jpg.jpg 蒂姆·伯纳斯 - 李(Tim Berners-Lee)

1.1 HTTP 0.9 —— 单行协议

最初版本的HTTP协议并没有版本号,后来它的版本号被定位在 0.9 以区分后来的版本。 蒂姆·伯纳斯 - 李最初设想的系统里的文档都是只读的,所以只允许用GET动作从服务器上获取 HTML 文档,并且在响应请求之后立即关闭连接,功能非常有限。

HTTP/0.9 极其简单:请求由单行指令构成,以唯一可用方法GET开头,其后跟目标资源的路径(一旦连接到服务器,协议、服务器、端口号这些都不是必须的)。例如:

GET /page.html

服务器的响应也是极其简单的,即文档本身:

<HTML>
    ...
</HTML>

HTTP/0.9虽然简单,但充分验证了 Web 服务的可行性。当然也会存在一些弊端:

  • HTTP/0.9 的响应内容并不包含HTTP头,这意味着只有HTML文件可以传送,无法传输其他类型的文件;
  • 没有状态码或错误代码:一旦出现问题,一个特殊的包含问题描述信息的HTML文件将被发回,供人们查看。

1.2 HTTP/1.0 —— 构建可扩展性

1993 年,NCSA(美国国家超级计算应用中心)开发出了 Mosaic,是第一个可以图文混排的浏览器,随后又在 1995 年开发出了服务器软件 Apache,简化了 HTTP 服务器的搭建工作。
同一时期,计算机多媒体技术也有了新的发展:1992 年发明了 JPEG 图像格式,1995 年发明了 MP3 音乐格式。 这些新技术的出现促进了 HTTP 的发展。

在这些已有实践的基础上,经过一系列的草案,HTTP/1.0 版本在 1996 年正式发布。相较于 HTTP/0.9,HTTP/1.0 做了如下改变:

  • 引入了 HEAD、POST 等新方法使客户能确定其想要执行操作的类型。例如,引入 POST 是为了允许客户端将数据发送到服务器以处理和存储;
  • 引入了协议版本号概念,协议版本信息现在会随着每个请求发送。
  • 增加了响应状态码,状态码会在响应开始时发送,使浏览器能了解请求执行成功或失败,并相应调整行为。
  • 引入了HTTP Header(头部) 的概念,无论是对于请求还是响应,允许传输元数据,使协议变得非常灵活更具扩展性
  • 传输的数据不再仅限于文本,在 HTTP Header 的帮助下,具备了传输除纯文本HTML文件以外其他类型文档的能力(凭借Content-Type头)。 一个典型的请求和响应如下所示:
    请求:
GET /mypage.html HTTP/1.0
User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)

响应:

200 OK
Date: Tue, 15 Nov 1994 08:12:31 GMT
Server: CERN/3.0 libwww/2.17
Content-Type: text/html
<HTML>
一个包含图片的页面
  <IMG SRC="/myimage.gif">
</HTML>

但 HTTP/1.0 并不是一个“标准”,只是记录已有实践和模式的一份参考文档,不具有实际的约束力,相当于一个“备忘录”。所以 HTTP/1.0 的发布对于当时正在蓬勃发展的互联网来说并没有太大的实际意义,各方势力仍然按照自己的意图继续在市场上奋力拼杀。

HTTP/1.0 存在的问题

HTTP/1.0 存在的主要问题是,客户端每发送一个请求,都要新建立一次 TCP 连接,等待服务器响应后再断开连接。而且,客户端必须在前一个请求接收到服务器的响应后,才能发送下一个请求。这样不仅会造成不必要的开销,也会造成延时。随着网页加载的外部资源越来越多,这个问题就愈发突出了。

1.3 HTTP/1.1 —— 标准化协议

HTTP/1.0 多种不同的实现方式在实际运用中显得有些混乱,自1995年开始,即HTTP/1.0文档发布的下一年,就开始修订HTTP的第一个标准化版本。在1997年初,HTTP1.1 标准发布,就在HTTP/1.0 发布的几个月后。

HTTP/1.1 消除了大量歧义内容并引入了多项改进:

  • 连接可以复用(持久连接),节省了多次打开TCP连接加载网页文档资源的时间。
  • 增加管线化(pipelining)技术,允许在第一个应答被完全发送之前就发送第二个请求,以降低通信延迟。
  • 允许响应数据分块(chunked),利于传输大文件;
  • 引入额外的缓存控制机制
  • 引入内容协商机制,包括语言,编码,类型等,并允许客户端和服务器之间约定以最合适的内容进行交换。
  • 强制 Host,能够使不同域名配置在同一个IP地址的服务器上。

请求:

GET /en-US/docs/Glossary/Simple_header HTTP/1.1
Host: developer.mozilla.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://developer.mozilla.org/en-US/docs/Glossary/Simple_header

响应:

200 OK
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Wed, 20 Jul 2016 10:55:30 GMT
Etag: "547fa7e369ef56031dd3bff2ace9fc0832eb251a"
Keep-Alive: timeout=5, max=1000
Last-Modified: Tue, 19 Jul 2016 00:59:33 GMT
Server: Apache
Transfer-Encoding: chunked
Vary: Cookie, Accept-Encoding

(content) 

(1)持久连接 —— Connection:keep-alive

HTTP/1.0 中。客户端每发送一个请求,都要新建一次 TCP 连接,等待服务器处理请求并发送响应后客户端断开连接。我们知道建立 TCP 连接需要三次握手,断开 TCP 连接需要四次握手,如果每发送一个请求都需要建立和断开一次 TCP 连接,势必会增加了通信开销。

为了减少不必要的 TCP 连接和断开,HTTP/1.1 默认采用持节连接,只要任意一端没有明确提出断开连接,则保持 TCP 的连接状态。如下图所示:

image.png

Connection 字段

  • 通常,客户端发送带有 Connection:keep-alive 标头的请求,以表明意图为后续请求保持 TCP 连接的打开状态。如果服务器理解此标头并同意遵守该标头,则其响应还将包含 Connection:keep-alive 标头。这样,双方都保持 TCP 通道打开并使用它进行后续通信,直到任何一方决定关闭它为止。
  • 如果客户端或服务器想要关闭连接,则在请求头或响应头中带上 Connection: close
  • 此外,过多的持久连接会占用服务器资源,所以服务器会用一些策略有选择地关闭持久连接;

(2)管线化(pipelining)

**HTTP/1.0 **中客户端的请求是串行的,这意味着必须等待前一个请求接收到服务器的响应后,才能发送下一个请求。

但是 HTTP/1.1 中增加了管线化技术,允许在同⼀个 TCP 连接里面,客户端可以发起多个请求,只要第⼀个请求发出去了,不必等其回来,就可以发第⼆个请求出去,可以减少整体的响应时间

image.png

Content-Length 字段

由于管线化的引入,所以在一个TCP连接中,服务器可以传送多个回应,这便需要一种机制,区分数据包时属于哪一个回应的。Content-Length 字段就是用于声明本次回应的数据长度。例如:Content-Length: 3945 告诉浏览器,本次回应的长度是3945个字节,后面的字节属于下一个回应。

但是在HTTP/1.0 中 Content-Length 字段不是必须的,因为一次TCP连接只能发送一个请求,服务器也只传送一个回应

(3)响应数据分块 —— Transfer-Encoding: chunked

使用Content-Length字段的前提条件是,服务器发送回应之前,必须知道回应的数据长度。但是,对于一些很耗时的动态操作来说,这意味着,服务器要等到所有操作完成,才能发送数据,显然这样的效率不高。更好的处理方法是,产生一块数据,就发送一块,采用"流模式"(stream)取代"缓存模式"(buffer)。所以 HTTP/1.1 中允许使用"分块传输编码"(chunked transfer encoding)。只要请求或回应的头信息有Transfer-Encoding字段,就表明回应将由数量未定的数据块组成。

每个非空的数据块之前,会有一个16进制的数值,表示这个块的长度。最后是一个大小为0的块,就表示本次回应的数据发送完了。例如:

HTTP/1.1 200 OK 
Content-Type: text/plain 
Transfer-Encoding: chunked 
25 
This is the data in the first chunk 

1C 
and this is the second one 

8
sequence 

0

(4)缓存控制机制 —— Cache-control

HTTP/1.1 中缓存的目标是在许多情况下消除发送请求的需要,并在许多其他情况下消除发送完整响应的需要。

HTTP/1.1 中的基本缓存机制是对服务器指定过期时间和验证器的缓存的隐式指令。为此,我们使用 Cache-Control 标头。Cache-Control头允许客户端或服务器在请求或响应中传输各种指令。这些指令通常覆盖默认的缓存算法。详细介绍可见 HTTP caching

  • 客户端可以在其 HTTP 请求中使用以下缓存请求指令 | 缓存请求指令 | 描述 | | ---- | -----------| | no-cache | 未经源服务器成功重新验证,缓存不得使用响应来满足后续请求 | | no-store | 缓存中不得存储任何关于客户端请求和服务端响应的内容。每次由客户端发起的请求都会下载完整的响应内容。 | | max-age = seconds | 表示资源能够被缓存(保持新鲜)的最大时间。| | max-stale [ = seconds ] |表示客户端愿意接受已超过其过期时间的响应。如果给出秒数,则它的过期时间不得超过该时间 | | min-fresh = seconds | 表示客户端愿意接受其新鲜度不小于其当前存在时间加上指定时间(以秒为单位)的响应| | no-transform | 不转换实体主体 | | only-if-cached |不检索新数据。仅当文档在缓存中时,缓存才能发送文档,并且不应联系原始服务器以查看是否存在较新的副本 |

  • 服务器可以在其 HTTP 响应中使用以下缓存响应指令 | 缓存响应指令 | 描述 | | ---- | ---------- | | public | 表示响应可能被任何缓存缓存。 | | private | 表示响应消息的全部或部分是针对单个用户的,不得由共享缓存缓存 | | no-cache | 未经源服务器成功重新验证,缓存不得使用响应来满足后续请求 | | no-store | 缓存不应存储有关客户端请求或服务器响应的任何内容 | | no-transform | 不转换实体主体 | | must-revalidate | 表示缓存在考虑使用一个陈旧的资源时,必须先验证它的状态,已过期的缓存将不被使用 | | proxy-revalidate | proxy-revalidate 指令与 must-revalidate 指令具有相同的含义,只是它不适用于非共享用户代理缓存 | | max-age = seconds | 表示资源能够被缓存(保持新鲜)的最大时间。| | s-maxage = seconds | 此指令指定的最长期限将覆盖 max-age 指令或 Expires 标头指定的最长期限。 s-maxage 指令总是被私有缓存忽略 |

(5)内容协商机制

在 HTTP 协议中,内容协商是这样一种机制,通过为同一 URI 指向的资源提供不同的展现形式,可以使用户代理选择与用户需求相适应的最佳匹配。 HTTP/1.0 规范制定了一系列的标准消息头用于内容协商。详细介绍可见 HTTP content negotiation

数据类型 —— 表示实体数据的内容是什么,使用 MIME type 表示

  • Accept (客户端):告诉服务器希望接收什么样的数据,并且可以用 “,” 分隔,表示可以接收的多种类型。
Accept: text/html,application/xml,image/webp,image/png
  • Content-Type (服务器):告诉客户端实际发送了什么样的数据
Content-Type: text/html 
Content-Type: image/png

补充\color{red}{补充}MIME type(Multipurpose Internet Mail Extensions , 媒体类型)

MIME的组成结构非常简单;由类型与子类型两个字符串中间用'/'分隔而组成。不允许空格存在。type 表示可以被分多个子类的独立类别。subtype 表示细分后的每个类型。

type/subtype
类型描述典型示例
text表明文件是普通文本,理论上是人类可读text/plain, text/html, text/css, text/javascript
image表明是某种图像。不包括视频,但是动态图(比如动态gif)也使用image类型image/gif, image/png, image/jpeg, image/bmp, image/webp, image/x-icon, image/vnd.microsoft.icon
audio表明是某种音频文件audio/midi, audio/mpeg, audio/webm, audio/ogg, audio/wav
video表明是某种视频文件video/webm, video/ogg
application表明是某种二进制数据application/octetstream, application/pkcs12, application/vnd.mspowerpoint, application/xhtml+xml, application/xmlapplication/pdf

数据编码 —— 表示实体数据的压缩方式

  • Accept-Encoding(客户端):客户端支持的压缩格式, 也可以用 “,”列出多个
Accept-Encoding: gzip, deflate, br

- Content-Encoding(服务器):服务器实际使用的压缩格式

Content-Encoding: gzip

压缩方式主要有以下三种:

  1. gzip:GNU zip 压缩格式,也是互联网上最流行的压缩格式;
  2. deflate:zlib(deflate)压缩格式,流行程度仅次于 gzip;
  3. br:一种专门为 HTTP 优化的新压缩算法(Brotli)。

语言类型 —— 表示实体数据的自然语言, 使用“type-subtype”的形式

  • Accept-Language(客户端):标记了客户端可理解的自然语言,也允许用 “,“ 分隔符列出多个
Accept-Language: zh-CN, zh, en
  • Content-Language(服务器):实体数据实际使用的语言类型。
Content-Language: zh-CN

例如:en 表示任意的英语,en-US 表示美式英语,en-GB 表示英式英语,而 zh-CN 就表示我们最常使用的汉语。

字符集 —— 表示实体数据的编码方式

  • Accept-Charset(客户端)
Accept-Charset: gbk, utf-8
  • 与之对应的服务器字符集是在Content-Type字段的数据类型后面用 “charset=xxx” 来表示
Content-Type: text/html; charset=utf-8

image.png

权重参数 —— q:

在 HTTP 协议里用 Accept、Accept-Encoding、Accept-Language 等请求头字段进行内容协商的时候,还可以用一种特殊的“q”参数表示权重来设定优先级,这里的“q”是“quality factor”的意思。

权重的最大值是 1,最小值是 0.01,默认值是 1,如果值是 0 就表示拒绝。具体的形式是在数据类型或语言代码后面加一个“;”,然后是“q=value”。

Accept: text/html,application/xml;q=0.9,*/*;q=0.8

内容协商的结果 —— vary:

内容协商的过程是不透明的,每个 Web 服务器使用的算法都不一样。但有的时候,服务器会在响应头里多加一个Vary字段,记录服务器在内容协商时参考的请求头字段,给出一点信息,例如:

Vary: Accept-Encoding,User-Agent,Accept

这个 Vary 字段表示服务器依据了 Accept-Encoding、User-Agent 和 Accept 这三个头字段,然后决定了发回的响应报文。

(6)Host 头部

客户端请求的头信息新增了Host 字段,用来指定服务器的域名

Host: www.example.com

有了Host字段,就可以将请求发往同一台服务器上的不同网站,为虚拟主机的兴起打下了基础。

同一个服务器上可以部署多个不同的网站,这意味着访问不同的域名都转发到同一服务器IP,那么Host字段就是用于区分请求的是服务器上的哪个网站。

HTTP1.1 存在的问题 —— 队头阻塞

虽然管道网络传输使得客户端可以不必等待前面请求响应就可以发送下一个请求,但是服务器仍然需要按请求的顺序进行响应。加入前面的请求处理的特别慢,那么后面的请求必须等待,这就造成了队头阻塞问题。

image.png

为了避免这个问题,可以通过减少请求数同时多开持节连接,这也导致了很多网页优化技巧的出现,例如:合并脚本和样式表、将图片嵌入CSS代码、并发连接、域名分片(domain sharding)等等

1.4 HTTP/2 —— 为了更优异的表现

HTTP/1.1 发布之后,整个互联网世界呈现出了爆发式的增长,度过了十多年的“快乐时光”,更涌现出了 Facebook、Twitter、淘宝、京东等互联网新贵。随之网页也愈渐变得的复杂,甚至演变成了独有的应用,可见媒体的播放量,增进交互的脚本大小也增加了许多:更多的数据通过HTTP请求被传输。HTTP/1.1链接需要请求以正确的顺序发送,理论上可以用一些并行的链接(尤其是5到8个),带来的成本和复杂性堪忧。比如,HTTP管线化(pipelining)就成为了Web开发的负担。

2008 年谷歌发布了 Chrome 浏览器,这种浏览器因其快速和创新而迅速流行。它使谷歌在互联网技术问题上获得了强大的话语权。在2010年到2015年,谷歌通过实践了一个实验性的 SPDY 协议,证明了一个在客户端和服务器端交换数据的另类方式。其收集了浏览器和服务器端的开发者的焦点问题。明确了响应数量的增加和解决复杂的数据传输,SPDY成为了HTTP/2协议的基础

互联网标准化组织以 SPDY 为基础开始制定新版本的 HTTP 协议,最终在 2015 年发布了 HTTP/2,RFC 编号 7540。

HTTP/2 主要做了以下改进:

  • 二进制协议:HTTP/2是二进制协议而不是文本协议。不再可读,也不可无障碍的手动创建,改善的优化技术现在可被实施。
  • 复用协议:这是一个复用协议。并行的请求能在同一个连接中处理,移除了HTTP/1.x中顺序和阻塞的约束。(废弃了HTTP/1.1中的管线化)
  • 压缩头部:因为headers在一系列请求中常常是相似的,其移除了重复和传输重复数据的成本。
  • 服务器推送:其允许服务器在客户端缓存中填充数据,通过一个叫服务器推送的机制来提前请求。
  • 增强了安全性,即要求加密通信

(1)二进制协议

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

截图.png

二进制协议的好处是:

  • 可以定义额外的帧。HTTP/2 定义了近十种帧,为将来的高级应用打好了基础。
  • 计算机只懂二进制,那么收到报文后,⽆需再将明⽂的报⽂转成二进制,⽽是直接解析二进制报⽂,这增加了数据传输的效率

HTTP/2 中帧的结构如下图所示: 截图.png

  • 帧长度:帧开头是 3 个字节的长度(但不包括头的 9 个字节),默认上限是 2^14,最大是 2^24,也就是说 HTTP/2 的帧通常不超过 16K,最大是 16M。

  • 帧类型:大致可以分成数据帧控制帧两类,HEADERS 帧和 DATA 帧属于数据帧,存放的是 HTTP 报文,而 SETTINGS、PING、PRIORITY 等则是用来管理流的控制帧。

HTTP/2 总共定义了 10 种类型的帧,但一个字节可以表示最多 256 种,所以也允许在标准之外定义其他类型实现功能扩展。

  • 帧标志:,可以保存 8 个标志位,携带简单的控制信息。 常用的标志位有END_HEADERS表示头数据结束,相当于 HTTP/1 里头后的空行(“\r\n”),END_STREAM表示单方向数据发送结束(即 EOS,End of Stream),相当于 HTTP/1 里 Chunked 分块结束标志(“0\r\n\r\n”)。
  • 流标识符:也就是帧所属的“流”,接收方使用它就可以从乱序的帧里识别出具有相同流 ID 的帧序列,按顺序组装起来就实现了虚拟的“流” 流标识符虽然有 4 个字节,但最高位被保留不用,所以只有 31 位可以使用,也就是说,流标识符的上限是 2^31,大约是 21 亿。

(2)数据流(straem)— 二进制帧的双向传输序列

HTTP/2 的数据包(帧)不是按顺序发送的,同⼀个连接连接能属于不同的回应。因此,必须要对数据包(帧)做标记,指出它属于哪个回应。每个请求或回应的所有数据包,称为⼀个数据流( Stream )。每个数据流都标记着⼀个独一无二的编号,其中规定客户端发出的数据流编号为奇数, 服务器发出的数据流编号为偶数。客户端还可以指定数据流的优先级。优先级⾼的请求,服务器就先响应该请求。

如下图所示,需要注意的是,虽然帧是乱序收发的,但只要它们都拥有相同的流 ID,就都属于一个流,而且在这个流里帧不是无序的,而是有着严格的先后顺序

截图.png HTTP/2 中的流有以下特点:

  1. 流是可并发的,一个 HTTP/2 连接上可以同时发出多个流传输数据,也就是并发多请求,实现“多路复用”;
  2. 客户端和服务器都可以创建流,双方互不干扰;
  3. 流是双向的,一个流里面客户端和服务器都可以发送或接收数据帧,也就是一个“请求 - 应答”来回;
  4. 流之间没有固定关系,彼此独立,但流内部的帧是有严格顺序的;
  5. 流可以设置优先级,让服务器优先处理,比如先传 HTML/CSS,后传图片,优化用户体验;
  6. 流 ID 不能重用,只能顺序递增,客户端发起的 ID 是奇数,服务器端发起的 ID 是偶数;流ID有上限,若ID用完了,则会发送控制帧“GOAWAY”,真正关闭 TCP 连接。
  7. 在流上发送 “RST_STREAM”帧 可以随时终止流,取消接收或发送,而长连接会继续保持。
  8. 第 0 号流比较特殊,不能关闭,也不能发送数据帧,只能发送控制帧,用于流量控制。

(3)多路复用

HTTP/2 可以在⼀个连接中并发多个请求或回应,⽽不⽤按照顺序⼀⼀对应。例如下图中,客户端发送了 A、B、C 三个请求,但是服务器处理 A 请求非常耗时,所以先回应 A 请求处理好的部分,接着回应 B、C 请求。B、C 请求回应完后,再回应 A 请求剩下的部分。这就解决了队头阻塞问题,降低了延迟,大幅度提高了连接的利用率

截图.png

HTTP/2 中并发多个请求和回应是基于上述的数据流实现的。从数据流的角度来看,消息是一些有序的“帧”序列,而从连接的层面是上来看,消息是乱序收发的“帧”。 多个请求/响应之间没有了顺序关系,不需要排队等待,也就不会出现“队头阻塞”问题。例如下图:

从数据流的角度来看: image.png 从连接的角度来看:

image.png

(4)头部压缩(header compression)

HTTP 协议是无状态的,每次请求都必须附上所有头部信息。所以,请求的很多字段都是重复的,比如Cookie和User Agent,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。

HTTP/2 对这一点做了优化,引入了头部压缩机制(header compression)

  • 一方面,头信息使用 gzip 或 compress 压缩后再发送;
  • 另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号(用索引号表示重复的字符串),以后就不发送同样字段了,只发送索引号,这样就提高速度了。(HPACK 算法)

此外,HTTP/2 中对响应和请求报文也作了一些修改:

  • HTTP/2 废除了原有的起始行,并把起始行中的请求方法、URI、状态码统一转换成了头字段的形式,并称为 “伪头字段”(pseudo-header fields) 。为了区别“真头字段”和“伪头字段”,会在名字前添加一个 ":" 。例如“:authority” “:method” “:status”,分别表示的是域名、请求方法和状态码。

image.png

  • 响应报文中起始行的版本号和错误原因短语也废除了
  • HTTP/2 就为一些最常用的头字段定义了一个只读的“静态表”(Static Table)。如数字“2”代表“GET”,数字“8”代表状态码 200。

如果表里只有 Key 没有 Value,或者是自定义字段找不到时,就要用到“动态表”(Dynamic Table),它添加在静态表后面,结构相同,但会在编码解码的时候随时更新

截图.png

(5)服务器推送

HTTP/2 在⼀定程度上改善了传统的「请求 - 应答」⼯作模式,服务不再是被动地响应,也可以主动向客户端发送消息

举例来说,在浏览器刚请求 HTML 的时候,就提前把可能会⽤到的 JS、CSS ⽂件等静态资源主动发给客户端,减少延时的等待,也就是服务器推送(Server Push,也叫 Cache Push)

HTTP/2 存在的问题

HTTP/2 主要的问题在于:HTTP/2 对一个域名只开一个连接多个 HTTP 请求在复⽤⼀个 TCP 连接,下层的 TCP 协议是不知道有多少个HTTP 请求的。所以⼀旦发⽣了丢包现象,就会触发 TCP 的重传机制,这样在⼀个 TCP 连接中的所的 HTTP 请求都必须等待这个丢了的包被重传回来。

此外,在移动网络中发生 IP 地址切换的时候,下层的 TCP 必须重新建连,要再次“握手”,经历“慢启动”,而且之前连接里积累的 HPACK 字典也都消失了,必须重头开始计算,导致带宽浪费和时延。

1.5 HTTP/3 —— 未来发展趋势

由上述可知,HTTP/2 的问题不能仅靠应用层来解决,因此协议的新迭代必须更新传输层,所以 HTTP/3 把 HTTP 下层的 TCP 协议改成了 UDP

QUIC 协议(Quick UDP Internet Connections)

QUIC (Quick UDP Internet Connections)是谷歌开发的一种新的网络传输协议。QUIC 是一种建立在 UDP 之上的新的多路复用传输。 HTTP/3 旨在利用 QUIC 的功能,包括流之间没有 Head-Of-Line 阻塞。

QUIC 项目最初是作为 TCP+TLS+HTTP/2 的替代方案,旨在改善用户体验,尤其是页面加载时间。 IETF 的 QUIC 工作组定义了传输 (QUIC) 层和应用程序 (HTTP/3) 层之间的明确界限,以及从 QUIC Crypto 迁移到 TLS 1.3。

由于 TCP 是在操作系统内核和中间盒中实现的,因此对 TCP 进行广泛的重大更改几乎是不可能的。然而,由于 QUIC 是建立在 UDP 之上的,并且传输功能是加密的,所以它没有这样的限制。

基于 TCP+TLS 和 HTTP/2 的 QUIC 和 HTTP/3 的主要特性包括:

  • 减少建立时间(CReduced connection establishment time)
  • 改进拥塞控制(Improved congestion control)
  • 没有行首阻塞的多路复用(Multiplexing without head-of-line blocking)
  • 前向纠错(Forward error correction)
  • 传输的可扩展性(Transport extensibility)
  • 连接迁移(Connection migration)

有关 QUIC 的详细介绍可见此文档

截图.png

以上就是HTTP协议的发展史了,但其实为了保证安全传输,其实还经历了 HTTPS 协议阶段,这里就暂不阐述了。

二、HTTP 基础知识

2.1 HTTP 消息(HTTP message)

HTTP消息(也称:报文)是服务器和客户端之间交换数据的方式。有两种类型的消息︰ 请求(requests)--由客户端发送用来触发一个服务器上的动作;响应(responses)--来自服务器的应答。

通常 HTTP 消息的结构就是:起始行 + 头部 + 空行 + 实体

截图.png

(1)HTTP 请求

  • 起始行 —— 简要描述客户端想要如何操作服务器端的资源
    • 请求方法:表示对资源的操作,如GET/POST
    • 请求目标:通常是一个URI,标记了请求方法要操作的资源
    • 版本号:表示报文使用的 HTTP 协议版本

截图.png

注意:三部分通常使用空格分隔,最后用 CRLF 换行表示结束

  • 请求头

image.png

但是在 HTTP/2 中,进行了头部压缩,废除了请求行。并把起请求中的请求方法、URI、状态码统一转换成了头字段的形式,并称为 “伪头字段”(pseudo-header fields)

(2)HTTP 响应

  • 状态行 —— 服务器响应的状态
    • 版本号:表示报文使用的 HTTP 协议版本
    • 状态码:一个三位数,用于表示处理的结果,例如 200 是成功
    • 错误原因:作为数字状态码补充,是更详细的解释文字,帮助理解

截图.png

  • 响应头

image.png

但是在 HTTP/2 中,状态行中起始行的版本号和错误原因短语也废除了

小结

通常请求/响应报文由以下内容组成:

  • 请求行,例如:GET /logo.gif HTTP/1.1或状态码行,例如:HTTP/1.1 200 OK
  • HTTP头字段
  • 空行
  • 可选的HTTP报文主体数据

请求/状态行和标题必须以<CR> <LF>结尾(即回车=后跟一个换行符=)。 空行必须只包含<CR> <LF>,而不能包含其他空格。

但是,随着 HTTP 的发展,为了压缩传输数据的大小,HTTP 报文会有所变化。

2.2 HTTP 请求方法(HTTP request methods)

HTTP 定义了一组请求方法,以表明要对给定资源执行的操作。指示针对给定资源要执行的期望动作。主要方法如下所示:

  • GET:GET方法请求一个指定资源的表示形式,使用GET的请求应该只被用于获取数据
GET /index.html
  • HEAD:HEAD方法请求资源的头部信息, 并且这些头部与 HTTP GET方法请求时返回的一致,但没有响应体。
HEAD /index.html
  • POST: POST方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用。
POST /test
  • PUT:PUT方法用请求有效载荷替换目标资源的所有当前表示。
PUT /new.html HTTP/1.1
  • DELETE:DELETE方法删除指定的资源。
DELETE /file.html HTTP/1.1
  • CONNECT:CONNECT方法建立一个到由目标资源标识的服务器的隧道。
CONNECT www.example.com:443 HTTP/1.1
  • OPTIONS:OPTIONS方法用于描述目标资源的通信选项
OPTIONS /index.html HTTP/1.1
OPTIONS * HTTP/1.1
  • TRACE:TRACE方法沿着到目标资源的路径执行一个消息环回测试,提供了一种实用的 debug 机制。
TRACE /index.html
  • PATCH:PATCH方法用于对资源应用部分修改
PATCH /file.txt HTTP/1.1

(1)安全和幂等

  • 安全:指请求方法不会“破坏”服务器上的资源,即不会对服务器上的资源造成实质的修改
  • 幂等:多次执行相同的操作,结果也是相同的
请求方法安全的幂等的
GET 【只读操作】
HEAD【只读操作】
POST 【新增或提交数据】
PUT 【替换或更新数据】
DELETE 【可以多次删除同一个资源,效果都是“资源不存在”】
CONNECT
OPTION 【只读操作】
TRACE
PATCH 【修改部分资源】

(2) GET 和 POST 的区别

  • GET :请求从服务器获取资源,这个资源可以是静态的文本、页面、图片视频等

GET ⽅法就是安全且幂等的,因为它是「只读」操作,⽆论操作多少次,服务器上的数据都是安全的,且每次的结果都是相同的

  • POST:向 URI 指定的资源提交数据,数据就放在报文的 body 里面

POST 因为是「新增或提交数据」的操作,会修改服务器上的资源,所以是不安全的,且多次提交数据就会创建多个资源,所以不是幂等的

2.3 HTTP 响应状态码(HTTP response status codes)

HTTP 响应状态码用来表明特定 HTTP 请求是否成功完成。 响应被归为以下五大类:

(1)信息响应 —— 1xx

这类状态码属于提示信息,是协议处理中的⼀种中间状态,实际⽤到的⽐较少

(2)成功响应 —— 2xx

这类状态码表示服务器成功处理了客户端的请求,也是我们最愿意看到的状态。

  • 200(OK):一切正常。如果是非HEAD请求,服务器响应头会有body数据
  • 204(No Content):响应头没有body数据
  • 206(Partial Content):是应⽤于 HTTP 分块下载或断点续传,表示响应返回的 body 数据并不是资源的全部,⽽是其中的⼀部分,也是服务器处理成功的状态

(3)重定向消息 —— 3xx

这类状态码表示客户端请求的资源发送了变动,需要客户端⽤新的 URL 新发送请求获取资源,也就是重定向

  • 301(Moved Permanently):表示永久重定向,说明请求的资源已经不存在了,需改⽤新的 URL 再次访问。
  • 302(Found):表示临时᯿定向,说明请求的资源还在,但暂时需要⽤另⼀个 URL 来访问
  • 304(Not Modified):不具有跳转的含义,表示资源未修改,重定向已存在的缓冲⽂件,也称缓存定向,⽤于缓存控制。

(4)客户端错误响应 —— 4xx

这类状态码表示客户端发送的报⽂有误,服务器⽆法处理,也就是错误码的含义

  • 400(Bad Request):表示客户端请求的报⽂有错误,但只是个笼统的错误
  • 403(Forbidden):表示服务器禁⽌访问资源,并不是客户端的请求出错。
  • 404(Not Found):表示请求的资源在服务器上不存在或未找到,所以⽆法提供给客户端。

(5)服务端错误响应 —— 5xx

这类状态码表示客户端请求报⽂正确,但是服务器处理时内部发⽣了错误,属于服务器端的错误码。

  • 500(Internal Server Error):与 400 类型,是个笼统通⽤的错误码,服务器发⽣了什么错误,我们并不知道
  • 501(Not Implement):表示客户端请求的功能还不⽀持,类似“即将开业,敬请期待”的意思
  • 502(Bad Gateaway):通常是服务器作为⽹关或代理时返回的错误码,表示服务器⾃身⼯作正常,访问后端服务器发⽣了错误。
  • 503(Service Unavailable):表示服务器当前很忙,暂时⽆法响应服务器,类似“⽹络服务正忙,请稍后试”的意思

2.4 统一资源标识符(Uniform Resourse Locator,URI)

统一资源标识符URI ):本质上是一个字符串,这个字符串的作用是唯一地标记资源的位置或者名字

URI 的基本组成

截图.png

  • scheme协议名 —— 表示资源应该使用那种协议来访问,例如http、https、ftp、ldap、file、news

  • authority:表示资源所在的主机名,通常的形式是 “host:port”,即 主机名:端口号

    • 主机名可以是IP地址或者域名的形式,不可省略
    • 端口号可以省略,省略时浏览器等客户端会依据 scheme 使用默认的端口号,例如 HTTP 的默认端口号是 80, HTTPS默认端口号是443
  • path:标记资源所在的位置

    • URI的path必须以 “/ “ 开始
  • query查询参数,以 ? 开头,key=value 形式的字符串,多个参数用 & 连接。

完整的URI

image.png 相对于基本组成,多了以下两个部分:

  • 第一个多出的部分是协议名之后、主机名之前的身份信息“user:passwd@”,表示登录主机时的用户名和密码,但现在已经不推荐使用这种形式了(RFC7230),因为它把敏感信息以明文形式暴露出来,存在严重的安全隐患。

  • 第二个多出的部分是查询参数后的片段标识符“#fragment”,它是 URI 所定位的资源内部的一个“锚点”或者说是“标签”,浏览器可以在获取资源后直接跳转到它指示的位置。但片段标识符仅能由浏览器这样的客户端使用,服务器是看不到的。也就是说,浏览器永远不会把带“#fragment”的URI 发送给服务器,服务器也永远不会用这种方式去处理资源的片段。(这与我们常说的哈希路由相关)

URI 编码

对于 ASCII 码以外的字符集和特殊字符做一个特殊的操作,把它们转换成与 URI 语义不冲突的形式。URI 转义的规则有点“简单粗暴”,直接把非 ASCII 码或特殊字符转换成十六进制字节值,然后前面再加上一个“%”。

在 URI 里对“@&/”等特殊字符和汉字必须要做编码,否则服务器收到 HTTP 报文后会无法正确处理。

有关 HTTP 的发展就介绍到这里了,很多细节并未介绍清楚,大家可以去查看 MDN 文档 或 RFC 文档了解。

参考
[1] HTTP 基础
[2] HTTP - Overview
[3] 透视 HTTP 协议
[4] 图解网络