HTTP 概述: HTTP/1.1 与HTTP/2
HTTP(HyperTextTransferProtocol) 是超文本传输协议的缩写,它用于传送WWW 方式的数据,关于HTTP 协议的详细内容请参考RFC2616 。HTTP 协议采用了请求/ 响应模型。客户端向服务器发送一个请求,请求头包含请求的方法、URI 、协议版本、以及包含请求修饰符、客户 信息和内容的类似于MIME 的消息结构。服务器以一个状态行作为响应,相应的内容包括消息协议的版本,成功或者错误编码加上包含服务器信息、实体元信息以及可能的实体内容。HTTP 是一种无状态性的协议。这是因为此种协议不要求浏览器在每次请求中标明它自己的身份,并且浏览器以及服务器之间并没有保持一个持久性的连接用于多个页面之间的访问。当一个用户访问一个站点的时候,用户的浏览器发送一个HTTP 请求到服务器,服务器返回给浏览器一个HTTP 响应。其实很简单的一个概念,客户端一个请求,服务器端一个回复,这就是整个基于HTTP 协议的通讯过程。
随着网络技术的发展,1999 年设计的HTTP/1.1 已经不能满足需求,所以Google 在2009 年设计了基于TCP 的SPDY ,后来SPDY 的开发组推动SPDY 成为正式标准,不过最终没能通过。不过SPDY 的开发组全程参与了HTTP/2 的制定过程,参考了SPDY 的很多设计,因此一般认为SPDY 就是HTTP/2 的前身。无论SPDY 还是HTTP/2 ,都是基于TCP 的,TCP 与UDP 相比效率上存在天然的劣势,所以2013 年Google 开发了基于UDP 的名为QUIC 的传输层协议,QUIC 全称Quick UDP Internet Connections ,希望它能替代TCP ,使得网页传输更加高效。后经提议,互联网工程任务组正式将基于QUIC 协议的HTTP (HTTP over QUIC)重命名为HTTP/3 。
HTTP 0.9
最早版本是1991 年发布的0.9 版。该版本极其简单,只有一个命令 GET
。
GET /index.html
上面命令表示,TCP 连接(connection ) 建立后,客户端向服务器请求(request ) 网页index.html
。协议规定,服务器只能回应HTML 格式的字符串,不能回应别的格式。
<html >
<body > Hello World</body >
</html >
服务器发送完毕,就关闭TCP 连接。
HTTP 1.0
1996 年5 月,HTTP/1.0 版本发布,内容大大增加。首先,任何格式的内容都可以发送。这使得互联网不仅可以传输文字,还能传输图像、视频、二进制文件。这为互联网的大发展奠定了基础。其次,除了GET
命令,还引入了POST
命令和HEAD
命令,丰富了浏览器与服务器的互动手段。再次,HTTP 请求和回应的格式也变了。除了数据部分,每次通信都必须包括头信息(HTTP header ) ,用来描述一些元数据。其他的新增功能还包括状态码(status code ) 、多字符集支持、多部分发送(multi-part type ) 、权限(authorization ) 、缓存(cache ) 、内容编码(content encoding ) 等。
短暂连接的缺陷
HTTP 1.0 规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP 连接,服务器完成请求处理后立即断开TCP 连接,服务器不跟踪每个客户也不记录过去的请求。但是,这也造成了一些性能上的缺陷,例如,一个包含有许多图像的网页文件中并没有包含真正的图像数据内容,而只是指明了这些图像的URL 地址,当WEB 浏览器访问这个网页文件时,浏览器首先要发出针对该网页文件的请求,当浏览器解析WEB 服务器返回的该网页文档中的HTML 内容时,发现其中的img 图像标签后,浏览器将根据img 标签中的src 属性所指定的URL 地址再次向服务器发出下载图像数据的请求:
显然,访问一个包含有许多图像的网页文件的整个过程包含了多次请求和响应,每次请求和响应都需要建立一个单独的连接,每次连接只是传输一个文档和图像,上一 次和下一次请求完全分离。即使图像文件都很小,但是客户端和服务器端每次建立和关闭连接却是一个相对比较费时的过程,并且会严重影响客户机和服务器的性 能。当一个网页文件中包含Applet ,JavaScript 文件,CSS 文件等内容时,也会出现类似上述的情况。
HTTP 1.1
持久连接
在HTTP1.0 中,每对Request/Response 都使用一个新的连接。HTTP 1.1 则支持持久连接Persistent Connection, 并且默认使用persistent connection. 在同一个tcp 的连接中可以传送多个HTTP 请求和响应. 多个请求和响应可以重叠,多个请求和响应可以同时进行. 更加多的请求头和响应头( 比如HTTP1.0 没有host 的字段).HTTP 1.1 的持续连接,也需要增加新的请求头来帮助实现。
例如,Connection 请求头的值为Keep-Alive 时,客户端通知服务器返回本次请求结果后保持连接;Connection 请求头的值为close 时,客户端通知服务器返回本次请求结果后关闭连接。HTTP 1.1 还提供了与身份认证、状态管理和Cache 缓存等机制相关的请求头和响应头。HTTP 1.0 规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP 连接,服务器完成请求处理后立即断开TCP 连接,服务器不跟踪 每个客户也不记录过去的请求。此外,由于大多数网页的流量都比较小,一次TCP 连接很少能通过slow-start 区,不利于提高带宽利用率。
管道机制
1.1 版还引入了管道机制(pipelining) ,即在同一个TCP 连接里面,客户端可以同时发送多个请求。这样就进一步改进了HTTP 协议的效率。举例来说,客户端需要请求两个资源。以前的做法是,在同一个TCP 连接里面,先发送A 请求,然后等待服务器做出回应,收到后再发出B 请求。管道机制则是允许浏览器同时发出A 请求和B 请求,但是服务器还是按照顺序,先回应A 请求,完成后再回应B 请求。
分块传输编码
分块传输编码(Chunked transfer encoding) 是超文本传输协议(HTTP ) 中的一种数据传输机制,允许HTTP 由应用服务器发送给客户端应用( 通常是网页浏览器) 的数据可以分成多个部分。分块传输编码只在HTTP 协议1.1 版本(HTTP/1.1 ) 中提供。通常,HTTP 应答消息中发送的数据是整个发送的,Content-Length 消息头字段表示数据的长度。数据的长度很重要,因为客户端需要知道哪里是应答消息的结束,以及后续应答消息的开始。然而,使用分块传输编码,数据分解成一系列数据块,并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的总大小。通常数据块的大小是一致的,但也不总是这种情况。
HTTP 1.1 引入分块传输编码提供了以下几点好处:
HTTP 分块传输编码允许服务器为动态生成的内容维持HTTP 持久连接。通常,持久链接需要服务器在开始发送消息体前发送Content-Length 消息头字段,但是对于动态生成的内容来说,在内容创建完之前是不可知的。[ 动态内容,content-length 无法预知]
分块传输编码允许服务器在最后发送消息头字段。对于那些头字段值在内容被生成之前无法知道的情形非常重要,例如消息的内容要使用哈希进行签名,哈希的结果通过HTTP 消息头字段进行传输。没有分块传输编码时,服务器必须缓冲内容直到完成后计算头字段的值并在发送内容前发送这些头字段的值。[ 哈希签名,需缓冲完成才能计算]
HTTP 服务器有时使用压缩(gzip 或deflate) 以缩短传输花费的时间。分块传输编码可以用来分隔压缩对象的多个部分。在这种情况下,块不是分别压缩的,而是整个负载进行压缩,压缩的输出使用本文描述的方案进行分块传输。在压缩的情形中,分块编码有利于一边进行压缩一边发送数据,而不是先完成压缩过程以得知压缩后数据的大小。[gzip 压缩,压缩与传输同时进行]
一般情况HTTP 的Header 包含Content-Length 域来指明报文体的长度。有时候服务生成HTTP 回应是无法确定消息大小的,比如大文件的下载,或者后台需要复杂的逻辑才能全部处理页面的请求,这时用需要实时生成消息长度,服务器一般使用chunked 编码。在进行Chunked 编码传输时,在回复消息的Headers 有transfer-coding 域值为chunked ,表示将用chunked 编码传输内容。使用chunked 编码的Headers 如下( 可以利用FireFox 的FireBug 插件或HttpWatch 查看Headers 信息) :
Chunked-Body=*chunk
"0" CRLF
footer
CRLF
chunk=chunk-size[chunk-ext]CRLF
chunk-dataCRLF
hex -no -zero=<HEXexcluding"0" >
chunk-size=hex -no -zero*HEX
chunk-ext=*(";" chunk-ext-name["=" chunk-ext-value])
chunk-ext-name=token
chunk-ext-val=token|quoted-string
chunk-data=chunk-size(OCTET)
footer=*entity-header
编码使用若干个Chunk 组成,由一个标明长度为0 的chunk 结束,每个Chunk 有两部分组成,第一部分是该Chunk 的长度和长度单位( 一般不 写) ,第二部分就是指定长度的内容,每个部分用CRLF 隔开。在最后一个长度为0 的Chunk 中的内容是称为footer 的内容,是一些没有写的头部内容。下面给出一个Chunked 的解码过程(RFC 文档中有) :
length :=0
readchunk-size,chunk-ext(ifany)andCRLF
while (chunk-size>0 ){
readchunk-dataandCRLF
appendchunk-datatoentity-body
length :=length +chunk-size
readchunk-sizeandCRLF
}
readentity-header
while (entity-headernotempty){
appendentity-headertoexistingheaderfields
readentity-header
}
Content-Length:=length
Remove"chunked" fromTransfer-Encoding
HTTP 2
HTTP/2 采用二进制格式传输数据,而非HTTP/1.x 的文本格式。二进制格式在协议的解析和优化扩展上带来更多的优势和可能。
HTTP/2 对消息头采用HPACK 进行压缩传输,能够节省消息头占用的网络的流量。而HTTP/1.x 每次请求,都会携带大量冗余头信息,浪费了很多带宽资源。头压缩能够很好的解决该问题。
多路复用,直白的说就是所有的请求都是通过一个TCP 连接并发完成。HTTP/1.x 虽然通过 pipeline 也能并发请求,但是多个请求之间的响应会被阻塞 的,所以 pipeline 至今也没有被普及应用,而HTTP/2 做到了真正的并发请求。同时,流还支持优先级和流量控制。
Server Push:服务端能够更快的把资源推送给客户端。例如服务端可以主动把JS 和CSS 文件推送给客户端,而不需要客户端解析HTML 再发送这些请求。当客户端需要的时候,它已经在客户端了。
二进制协议支持
HTTP/1.1 版的头信息肯定是文本(ASCII 编码) ,数据体可以是文本,也可以是二进制。HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为" 帧 “(frame ):头信息帧和数据帧。二进制协议的一个好处是,可以定义额外的帧。HTTP/2 定义了近十种帧,为将来的高级应用打好了基础。如果使用文本实现这种功能,解析数据将会变得非常麻烦,二进制解析则方便得多。
多工复用
HTTP/2 复用TCP 连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应,这样就避免了" 队头堵塞 “。举例来说,在一个TCP 连接里面,服务器同时收到了A 请求和B 请求,于是先回应A 请求,结果发现处理过程非常耗时,于是就发送A 请求已经处理好的部分,接着回应B 请求,完成后,再发送A 请求剩下的部分。这样双向的、实时的通信,就叫做多工(Multiplexing ) 。
数据流
因为HTTP/2 的数据包是不按顺序发送的,同一个连接里面连续的数据包,可能属于不同的回应。因此,必须要对数据包做标记,指出它属于哪个回应。HTTP/2 将每个请求或回应的所有数据包,称为一个数据流(stream ) 。每个数据流都有一个独一无二的编号。数据包发送的时候,都必须标记数据流ID ,用来区分它属于哪个数据流。另外还规定,客户端发出的数据流,ID 一律为奇数,服务器发出的,ID 为偶数。数据流发送到一半的时候,客户端和服务器都可以发送信号(RST_STREAM 帧) ,取消这个数据流。1.1 版取消数据流的唯一方法,就是关闭TCP 连接。这就是说,HTTP/2 可以取消某一次请求,同时保证TCP 连接还打开着,可以被其他请求使用。客户端还可以指定数据流的优先级。优先级越高,服务器就会越早回应。
头信息压缩
HTTP 协议不带有状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的,比如Cookie 和User Agent ,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。HTTP/2 对这一点做了优化,引入了头信息压缩机制(header compression ) 。一方面,头信息使用gzip 或compress 压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。
服务器推送
HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送(server push ) 。常见场景是客户端请求一个网页,这个网页里面包含很多静态资源。正常情况下,客户端必须收到网页后,解析HTML 源码,发现有静态资源,再发出静态资源请求。其实,服务器可以预期到客户端请求网页后,很可能会再请求静态资源,所以就主动把这些静态资源随着网页一起发给客户端了。