文章目录
介绍
从HTTP的发展开始。介绍了HTTP/1.0、HTTP/1.1 到HTTP2中每个版本的区别对比。还包括HTTP的报文结构,常见的响应状态码。并且针对不同版本HTTP事物的处理方式,例如0.9版本的串行事物处理、浏览器的并行连接事物处理、到1.1版本的Keep-Alive和管道化的原理都有详细的介绍。针对即将普及的HTTP2.0版本的新特性和新提升,例如二进制分帧、头部压缩、多路复用、服务器推送等做了详细的介绍。针对大部分的的面向应用层开发的程序员来说,看着一篇博客对于理解HTTP协议是足够了的。
对于想进一步了解TCP协议的同学,可以参考我的另一篇博客 吐血整理的TCP协议相关原理
一、HTTP协议历史和介绍
1.1 什么是HTTP
HTTP的全称是 HyperText Transfer Protocol ,超文本传输协议。
- 协议 HTTP是一个用在计算机世界里的协议,是一种计算机之间交流通信的规范,包含各种控制和错误处理方式。
- 传输 HTTP是在计算机世界里,专门用来在两点之间传输数据的协议。
- 超文本 HTTP 是在计算机世界里,专门在两点之间传输文字、图片、音频等超文本数据的约定和规范。
1.2 HTTP的历史版本对比
二、HTTP的报文结构
http报文: 是在Http应用程序之间发送的数据块(用于Http协议交互的信息)。请求端的Http报文叫做请求报文,响应端的叫做响应报文
HTTP所有报文都可以分为两类,请求报文和响应报文。HTTP的请求报文和响应报文结构基本相同,不过HTTP是一个纯文本的协议,所有的头数据都是ASCII编码的文本。HTTP的报文可以分为 start line、header、crlf entity/body 四个部分。
起始行 start line | 描述请求或响应的基本信息,在请求报文中称为请求行在响应报文中称为状态行 |
---|---|
头部字段集合(header) | key-value格式的数据项,用来进一步的说明请求或者描述正文。key和value之间用冒号分割,每个请求头用CRLF表示字段结束 |
空行 CRLF | 0D0A0d = "\\r" 、0A="\\n" |
消息正文(entity/body) | 实际传输的数据 可以为空 |
2.1 起始行 start line
起始行描述请求或响应的基本信息,在请求报文中称为请求行、响应报文中称为状态行
2.1.1 请求行
示例:GET /group1/M00/21/D2/wKgBx2O06SWAYUHTAAJDNn8LaEs562.png HTTP/1.1
请求行通常由三部分构成用空格(space)分隔,CRLF(回车换行)标识结尾
- 请求方法:一个动词(GET PUT POST),表示对资源的操作。
- 请求目标:通常是一个URI,标记了请求方法要操作的资源。
GET [http://developer.mozilla.org/en-](http://developer.mozilla.org/en-)US/docs/Web/HTTP/Messages HTTP/1.1
一个完整的URL称为绝对形式,主要用在GET方法连接到代理时使用。CONNECT developer.mozilla.org:80 HTTP/1.1
由域名和可选端口(以’:'为前缀)组成的 URL 的 authority component,称为 authority form。 仅在使用 CONNECT 建立 HTTP 隧道时才使用。OPTIONS * HTTP/1.1
星号形式 (asterisk form),一个简单的星号(‘*’),配合 OPTIONS 方法使用,代表整个服务器
- 版本号:表示报文使用的HTTP协议版本。
2.1.2 状态行
示例:HTTP/1.1 200 OK
状态行结构上和请求行几乎一致,也由三部分组成
- 版本号:表示报文使用的HTTP协议版本。
- 状态码:一个三位整数,标识请求处理的结果,比如200是成功,500是服务器错误;
- 状态文本:作为状态码的补充说明
2.2 头部字段 header
- key-value格式的数据项,用来进一步的说明请求或者描述正文。key和value之间用冒号分割,每个请求头用CRLF表示字段结束
- HTTP 头字段非常灵活,不仅可以使用标准里的 Host、Connection 等已有头,也可以任意添加自定义头,这给 HTTP 协议带来了无限的扩展可能。
- 字段格式,不区分大小写,但推荐首字母大写。字段名不允许空格和下划线,允许连字符"-“。字段名后必须紧跟冒号”:" 不能有空格。字段顺序没有意义,可以任意排列。字段原则上不能重复,除非这个字段语义上允许,例如 Set-Cookie
2.3 实体Body
请求的最后一部分是它的 body。不是所有的请求和响应都有一个 body:例如获取资源的请求,GET,HEAD,DELETE 和 OPTIONS,通常它们不需要 body。 有些请求将数据发送到服务器以便更新数据:常见的的情况是 POST 请求(包含HTML 表单数据)
三、请求方法(HTTP Method)和状态码(Status Code)
请求方法在请求行中用来告诉服务器这个请求希望完成的动作,状态码是在响应结果中告诉客户端本次请求的执行结果。
3.1 请求方法
为什么要求请求方法?
请求方法实际上是一种"动作的指示",表示操作资源的方式。要求服务器对URI定位的资源执行某种操作。方法是一种指示,但是决定权还是在服务器。
方法 | 描述 |
---|---|
GET | 从0.9版本就有了,表示从服务器获取资源。GET方法只是表示请求某个资源,不需要请求体。但是HTTP协议中并没有规定GET不能包含请求体部分。但是带上后服务器会不会处理Body部分得看具体的服务器的策略。 |
HEAD | HEAD方法和GET基本一致,但是在服务器响应中,只返回header部分(且和GET返回的一致),不会包含请求资源的内容。 1. 可以在不获取资源的情况下了解资源的情况(比如类型、大小等) 2. 可以通过响应判断某个资源是否存在 3. 可以通过响应,查看资源的缓存是否有变化,或者位置是否有变更(返回30x) |
PUT | 与GET的操作相反,PUT方法的语义就是让服务器来创建一个请求URI命名的资源,如果已存在就用请求体中的主体来替代它。 |
POST | POST方法起初是用来向服务器输入数据的,通常用来传输HTML的表单。 |
TRACE | TRACE客户端发起请求,请求可能要穿过防火墙、代理、网关等,TRACE方法会在目标服务器中1发起一个**环回诊断,**在响应体中会携带目标服务器收到的原始请求报文。这样客户端就可以查看在中间服务器上原始报文的修改。 |
OPTIONS | 用来询问服务器支持的各种功能。通过请求头返回。 |
DELETE | 请求服务器删除的URI指定的资源 |
3.2 状态码
状态码是在响应报文总,用来表示服务器对该此请求执行的结果的一个code。
状态码分类
范围 | 已定义 | 分类 |
---|---|---|
100~199 | 100~101 | 信息提示 |
200~299 | 200~206 | 成功 |
300~399 | 300~305 | 重定向 |
400~499 | 400~415 | 客户端错误 |
500~599 | 500~505 | 服务端错误 |
常见状态码
状态码 | 原因 | 含义 |
---|---|---|
200 | OK | 请求成功 |
401 | Unauthroized | 未授权,需要先获取认证授权 |
404 | Not Found | 找不到对应URI对应的资源 |
301 | Moved Permanently | 重定向:资源已经被永久转移 转移后的地址在响应头的location中(搜索引擎抓取后,会将旧的地址更新为重定向后的地址) |
302 | MovedTemporarily | 重定向:资源已被临时转移,转移后的地址在响应头的location中(搜索引擎抓取后,不会改动旧的地址,以后的访问也仍按照旧地址访问) |
307&308 | 对应301和302 | 对应301和302,但是不允许修改后续请求的请求方法。 |
四、HTTP的传输层协议TCP
篇幅有限,关于TCP的内容可以参考我的另一篇博文 吐血整理的TCP协议相关原理 这里就不额外介绍了。
五、HTTP的事务处理方式
5.1 串行的事务处理
串行的TCP连接传输事务:对于一个服务端开启一个TCP连接,加载网页时有多个HTTP事务,每次只请求一个HTTP事务。一个HTTP请求完成就关闭连接,而后重新开启一个新的TCP连接来传输数据。
缺点:连接没有复用,且每次都要重新经理TCP的慢启动和握手过程。效率低下且延时增大。
5.2 并行连接事务处理
并行连接: 和串行的基本一致,只是可以同时开启多个TCP连接,同时处理多个Http事务。浏览器发起并行TCP连接的数量也不是无限的,增加并行连接会增大服务器端的资源消耗,也会给服务器端带来更大的压力。浏览器一般会使用并行连接,并且会把连接的数量控制在一个较小的范围内。
5.3 持久连接 keep-Alive
站点局限性(Site locality) :浏览器打开一个站点时,其相关的请求数据大部分将会来自同一个地址,只有少部分会来自其他对象的超链接。所以在浏览这个站点时,对这个站点的服务器发起更多请求的可能性更大。
持久连接:
- HTTP/1.1(以及 HTTP/1.0 的各种增强版本) 允许 HTTP 设备在事务处理结束之后将 TCP 连接保持在打开状态, 以便为未来的 HTTP 请求重用现存的连接。在事务处理结束之后仍然保持在打开状态的 TCP 连接被称为持久连接。通过响应头
Connection: Keep-Alive
开启1.0默认关闭,1.1默认开启。 - 通过持久化连接复用TCP连接是串行传输HTTP事务的,每次需要等待一个http事务响应才能发送下一个HTTP事务请求。这样会有一个弊端,就是假设第一个头部请求是一个耗时的请求,那么这条TCP连接就被阻塞住了 这种现象称为头部阻塞
优点:对连接进行了复用,减少了慢启动和重复握手的消耗。
5.4 管道化连接
Http/1.1 允许在持久化连接的基础上使用管道化技术。这是基于keep-alive 连接的又一性能优化。
pipeline管道化:在确定上一个Http请求已经被服务端接收后,可以不用等待服务器响应直接开始发送下一个Htpp请求。这样在网络情况不好的场景下,可以提高网络的传输效率,提高速度。
管道化的限制规则:
- 服务器必须按照收到请求的顺序返回Http的响应数据,因为Http发送的报文中没有属于哪个请求的序号,所以必须按照顺序来对应请求和响应。这样的话还是会有头部阻塞的影响。
- Http客户端需要时刻准备着连接端开的情况,如果发送了10个请求,返回了五个响应时连接断开了那么需要自动的重发未响应的五个请求。这样的话就需要考虑请求的幂等性
- HTTP 客户端不应该用管道化的方式发送非幂等性的请求,比如 POST。加入使用管道话发送非幂等性请求:如果连接突然断开,如果直接重试可能会产生错误结果,如果不重试可能又会导致请求没有被真正的发送。
六、HTTP2的特性
Http/2对比Http/1.x来说的话,语义是完全兼容的,但是性能有了大幅度的提升。
HTTP2在效率上的改进:
- 采用二进制分帧,请求的数据可以乱序发送,后面再按照帧上面的标识重新组装。同时也从明文传输改成了二进制数据传输
- 头部压缩 对于HTTP请求,请求头的key和value常用的基本是不变的,但是每次传输都带上很浪费资源。引入头部压缩,能减少传输的压力。
- 多路复用,基于二进制分帧和流等概念一个HTTP客户端和一个服务端只通过一个tcp连接来发送数据,但是可以虚拟出多个流来传输数据。这样就避免了TCP的慢启动。
- 服务器推送,服务器可以主动把数据推送到HTTP客户端。
6.1 二进制分帧
HTTP 2.0最大的特点: 不会改动HTTP 的语义,HTTP 方法、状态码、URI 及首部字段,等等这些核心概念上一如往常,却能致力于突破上一代标准的性能限制,改进传输性能,实现低延迟和高吞吐量。而之所以叫2.0,是在于新增的二进制分帧层。
二进制分帧介绍
- HTTP2将所有传输的信息,都分割为更小的消息帧,并且采用二进制的编码。
- Http2有两种数据帧,首部信息被封装成 Headers帧、原来的Body部分会被封装成 Data帧
- 每个数据帧上标识了这个数据帧属于哪个流Stream,所以数据帧可以并行的乱序发送后续再组装,最大限度利用网络带宽。
数据帧的格式
数据帧由Frame Header和Frame Payload两部分组成,所有帧的帧头都是固定的9字节的1大小,不过帧负载长度是可变的。
Frame Header帧头部分 | 长度(共9字节) | 功能 |
---|---|---|
Length | 24位 3字节 | 标识帧负载(Frame Payload)的大小。 |
Type | 8位 1字节 | 有10种类型,用来表示帧类型的。帧类型确定帧的格式和语义。实现方必须忽略并丢弃任何类型未知的帧。 DATA:标识这个数据帧用来传输数据是一个数据帧 HEADERS:标识这个数据帧是一个Header帧 PRIORITY:用户指定或者重新指定引用资源的优先级 RST_STRING:通知流非正常的终止 SETTINGS:用于通知两端通信昂是的数据配置 PUSH_PRORMISE:用于发出创建流和服务器引用资源的邀约 PING :用于计算晚饭时间,执行"活性检查" GOAWAY:用于通知对端停止在当前连接的创建流 WINDOW_UPDATE:用于针对个别流或个别连接实现流量控制 CONTINUATION:用于继续一系列首部块片段 |
Flags | 8位 1字节 | 标志位,常用的标志位有END_HEADERS 标识头数据结束,相当于HTTP/1里的空行 |
R | 1位 | 保留位,语义未定,发送时设置为 0X0,接收时目前忽略。 |
Stream Identifier | 31位 无符号整数 | 流标识符 ,这是Http2多路复用的基石。由于有了流标识符,数据帧才可以在不同流之前乱序发送,最后根据标识符进行数据的组装从而最大化利用TCP的带宽。 1. 由客户端发起的流必须使用基数编号;由服务器发起的流必须使用偶数编号。 2. 一个stream内的消息是有序的 |
6.2 头部压缩(HPACK)
为什么需要头部压缩?
一般而言HTTP请求体会经过各种压缩算法的压缩,例如gzip,或者本身传输的就是压缩后的二进制文件。但是状态行和头部没有任何压缩,直接以纯文本传输。而且每次传输都会带着大量的不会变动的请求头,例如UserAgent Cookie这种,这是一种效率的浪费。
如何进行头部压缩?
HTTP2采用HPACK算法对头部字段进行压缩,消息体有gzip等算法压缩二者一起达到了更好的压缩效果。HPACK压缩的话需要在HTTP客户端和服务端之间维护一份相同的静态表(Static Table)和一份相同的动态表(Dynamic Table)。
静态表Static Table如何工作?
静态表就是HTTP客户端和服务端,都维护一份常用的头部名称,以及常见的头部名称与值的组合。
在发送头部时,如果头部和值存在于静态表中则直接发送静态表中的索引即可。静态表长度为61,已经被提前定义好同时存在于Http客户端和服务端中。
索引 | 头部名称 | 头部值 |
---|---|---|
1 | :authority | |
2 | :method | GET |
3 | :method | POST |
4 | :path | / |
… | … | … |
61 | :www-authenticate |
动态表Dynamic Table如何工作?
- 如果发送数据用到的Header和VALUE在静态表中没有定义,那么HTTP2对这种情况也推出了动态表的机制。
- 动态表是一个先进先出队列维护的有限空间限制的表,同样维护Header和value对应的索引
- 动态表的索引从62开始,当一个header或者value没有出现过的时候,会在第一次发送的时候,使用哈夫曼编码对头部的内容进行压缩,并且定义索引后发送给接收端。这时候只要接收端成功接受了,服务端和发送端就会将定义的索引和头部保存到动态表中,第二次发送相同的头和value时,只要直接发送索引即可。
- 每个动态表,只针对一个TCP连接,并且一个TCP连接的压缩/解压缩的上下文中只有一个动态表。
6.3 多路复用
在HTTP/1.1中,浏览器对同一个域名下的地址,会发起多个并行TCP连接,但因为每个连接都会占用资源,所以连接的数量一般都会限制为4~6个,所以这也是为什么一些站点会有多个静态资源的CDN域名的原因之一。这样就可以使得加载页面静态资源时,可以产生更多的并行TCP连接来加速资源的加载。
HTTP2 如何进行多路复用的?
在HTTP2中,TCP连接都是持久化的(keep-alive)的,并且客户端与服务器之间只需要一个连接即可。这是因为HTTP2的多路复用机制。HTTP的多路复用机制,引入了逻辑上的流,这样一个TCP连接可以承载数十上百个流的复用和并行传输。不同流的数据包,可以混在一起通过同一个连接进行传输,最后再根据数据包中的帧首部的流标识符重新组装。
HTTP2多路复用引入的概念
名词 | 概念 |
---|---|
Connection | 对应着1个TCP连接,包含1个或多个stream流。HTTP2的所有通信,都在一个TCP连接上完成。 |
Stream | 一个双向通信的流,包含1个或多个Message。每个流都有一个唯一的标识符和可选的优先级信息,可以用来承载双线的消息。流是一个虚拟的信道,同时为了防止两端的流的id冲突,HTTP2规定客户端发起的流为奇数ID,从服务端发起的流具有偶数ID。 |
Message | 一个Message指的是逻辑上的HTTP的消息(请求/响应消息)。一个Message又由一系列的二进制数据帧组成。 |
Frame | 数据帧 ,以二进制压缩格式传输数据的数据帧,在HTTP2中,来自不同数据流的帧可以交错发送,最后在根据帧头的流标识符重新组装。 |
多路复用的传输数据的过程
- HTTP客户端三次握手后发起一个连接服务端的TCP连接开始传输数据
- 客户端需要发送多个Message,这时假设客户端开始创建流(通过Frame Header的Type属性创建)
- 流创建成功后,一个Message分为多个Frame并且在Frame Header上标注其所属的流(Stream Identifier)。这个Message的多个Frame将使用同一个Stream Identifier,按照先后顺序发送给服务端。多个流的Frame可以交错发送,只要同一个Message属于同一个流并且有序即可。
- 在数据传输完成后,任意一端都可以发起关闭流的请求。
- 最后TCP连接断开。
需要注意的点
- 单个HTTP2连接,包含多个并发打开的stream流,不论是发送端还是服务端,都能交叉收到来自多个Stream流的数据帧。
- stream流可以单方面建立和单方面使用,也可以客户端和服务端共享。
- 任何一个端都可以关闭stream流
- 在stream上发送的帧的顺序非常重要。接收方会按照收到的帧的顺序来处理帧组装Message。
- stream流的标识是一个整数,stream流的标识由发起流的一方来分配给这个流。
6.4 服务器推送 Server Push
服务推送的场景
HTTP2 的服务端,在知道客户端一定会请求某个数据时,比如客户端请求了index.html,后面一定会请求index.html上的图片、logo、css等资源。这时候服务端可以主动向客户端推送数据,而无需服务端明确的请求,这样就能省省去客户端的主动请求。提高传输效率。服务器推送有一个很大的优势,可以缓存。在遵守同源政策的情况下,不同页面就可以共享缓存资源了。
推送遵循同源策略。服务端的推送是基于客户端的请求响应来确定的。
服务推送的过程
- 服务端发现请求的某个页面上可能包含其他需要客户端请求的数据时,可以主动将关联数据一起推送给浏览器。
- 服务端主动推送某个资源时,会推送一个Frame Type为PUSH_PROMISE的数据帧,里面包含了推送需要新建的stream的ID
- 客户端就知道了,哦!服务端要用这个流来给我主动发送数据,便会通过这个流准备接收服务端即将推送的数据。
七、下一代的HTTP3.0
启用http2.0后会给性能带来很大的提升,但同时也会带来新的性能瓶颈。因为现在所有的压力集中在底层一个TCP连接之上,TCP很可能就是下一个性能瓶颈,比如单个TCP packet丢失导致整个连接阻塞,无法逃避,此时所有消息都会受到影响。
所以下一代的HTTP3.0传输层将会抛弃TCP协议,而是使用UDP协议,新增QUIC协议来实现TCP的可靠传输和流量控制的部分。