Cdf1add49fa85b17ee80b891c04894e2
HTTP/1.1 优化之路

好久没更新文章了,实在抱歉!一是最近工作比较忙,二是为祖国母亲庆祝生日。累并快乐着!

HTTP协议经历了0.9、1.0、1.1、2.0等几个版本的发展,每个版本都针对上一个版本的痛点进行了优化。目前性能最好的版本肯定是2.0,但是国内乃至国外的一些大公司网站还是使用HTTP/1.1,这里面除了基础设施的原因,还说明了HTTP/1.1目前还是可以Hold住绝大部分的应用场景的。本篇文章将详细整理HTTP/1.1相对于HTTP/1.0所做的十大优化。更多内容可以直接参考权威的RFC 2616

目录

  1. 扩展性
  2. 缓存
  3. 带宽优化
  4. 长链接(Keep-Alive)
  5. Chunk编码
  6. 内容协商(Content Negotiation)
  7. 请求流水线(Pipeling)
  8. 域名分片 (Domain Sharding)
  9. 图片/文件合并(Spriting/Concatenation)
  10. 资源内嵌(Inlining)

1. 扩展性

HTTP/1.1的制定花了四年多时间,这期间已经有厂商开始使用这个新协议了,即使在最终版还未定稿之前,因此最终版需要与之前未完善的版本保持兼容。同时,HTTP/1.1还要与未来的新版本保持兼容。好在HTTP规定了只要收到不能识别的请求头,就直接忽略,这在一定程度上起到了保护措施,但并不代码它拥有了扩展能力。
HTTP/1.1使用了三种技术手段来实现扩展性:

  • 版本号

    在消息中增加版本号来推断发送方的兼容能力。这个版本号是用来推断逐段(hop-by-hop)的兼容性而不是端到端(end-to-end)的,即网络的每一跳可以推断上一跳的兼容性,但服务器不知道客户端使用的协议版本。于是HTTP/1.1增加了Via字段,记录消息转发的路径,它记录了整个路径上所有发送方使用的版本号。

  • OPTIONS

    HTTP/1.1 引入了OPTIONS方法,使得客户端知道服务器支持的方法列表。

  • Upgrade

    未来兼容未来的协议版本,HTTP/1.1在请求头部增加了一个 Upgrade字段,使得客户端可以通知服务端它能支持的备用通讯协议,服务器就可以切换协议进行通讯。

2. 缓存

HTTP/1.0的Cache机制比较简单: 服务端在Response头指定一个Expires 头字段,标示这个response的缓存过期时间。以后客户端发送一个带条件的请求,包括If-Modified-Since头字段以及Response 头的 Last-Modified字段,服务端据此返回304(使用缓存数据)或者200(使用新数据)。 同时,HTTP/1.0也使用Pragma: no-cache来告诉服务器不使用缓存。
一切看起来很好,但还是有缺点的。

HTTP/1.0使用If-Modified-Since来校验缓存有效性。这个字段是使用绝对时间戳的,精度是秒。因此不管是时钟同步错误还是精度问题都可能导致缓存出错。HTTP/1.1引入了entity tag(ETags),这是由服务器产生,保证唯一性的一个值。ETags可以是一个随机的字符串、内容的版本好或者校验和。如果两个资源响应中的ETags一样,那一定是同一份资源。因此客户端可以通过比较缓存和响应的ETags来校验缓存有效性。服务端在响应头中使用ETag字段来传递这个tag值。

HTTP/1.1 还引入了If-None-Match,允许客户端可以保留一个资源的多份cache,对应多个ETags。如果服务端检测所有这些ETags都与响应的不匹配,服务端才返回一个响应;否则,返回304,并携带一个ETag字段用来表明哪一个缓存项是有效的。可以看出,这个机制可以让服务端遍历一系列的ETags来决定哪个是有效缓存,而If-Modified-Since只产生一个缓存项来做校验。

If-None-Match: "v2.6"
If-None-Match: "v2.4","v2.5","v2.6"
If-None-March: "foobar","A34FAC0095","Profiles in Courage"

另外,HTTP/1.1为了扩展缓存机制,还引入了Cache-Control字段。比如HTTP/1.0的Expires字段可能因为时钟问题不准,那HTTP/1.1就可以使用相对过期时间,通过设置max-age字段来达到目的。
请求和响应都可以使用Cache-Control来实现缓存机制,其指令集比较大:

请求时的指令集
Cache-Control: max-age=
Cache-Control: max-stale[=]
Cache-Control: min-fresh=
Cache-control: no-cache
Cache-control: no-store
Cache-control: no-transform
Cache-control: only-if-cached

响应中的指令集
Cache-control: must-revalidate
Cache-control: no-cache
Cache-control: no-store
Cache-control: no-transform
Cache-control: public
Cache-control: private
Cache-control: proxy-revalidate
Cache-Control: max-age=
Cache-control: s-maxage=

3. 带宽优化

HTTP/1.0存在很多浪费带宽资源的场景,比如说不支持断点续传,那么如果客户端只请求部分数据,服务端却把整个对象下发了;又比如客户端发送很大的实体请求,服务端可能无法接受这么大的数据而返回错误,但此时请求已经发出去了,带宽被浪费了。

HTTP/1.1对此做了各种优化:

3.1 Range Request

如果客户端只想请求一部分预览数据或者需要断点续传的时候,HTTP/1.1允许在Request Header中包含 Range字段。

首先我们可以检测一下服务端是否支持Range Request,如果HTTP Response有 Accept-Ranges字段并且值不是"none"则说明服务端不支持Range Request。

curl -I https://www.youtube.com/watch?v=EwTZ2xpQwpA

HTTP/1.1 200 OK
...
Accept-Ranges: none

否则,表示服务端支持Range Request,并且其值代表数据范围的单位,HTTP/1.1只支持bytes单位。下面的请求表示服务端支持Range Request,本次请求的数据范围是0~1023个字节,总的数据大小为146515个字节,本次请求返回的部分数据大小是1024个字节。

curl http://i.imgur.com/z4d4kWk.jpg -i -H "Range: bytes=0-1023"

HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Range: bytes 0-1023/146515
Content-Length: 1024

值得注意的是,此时返回码是206,表示的是返回内容是部分数据。这样跟200区分开,避免有些HTTP/1.0的代理Cache把数据当作一个完整的数据,然后下次直接把这部分数据返回给相同的请求。

我们还可以同时指定多个字节范围,用逗号来分割。

curl http://www.example.com -i -H "Range: bytes=0-50, 100-150"

HTTP/1.1 206 Partial Content
Content-Type: multipart/byteranges; boundary=3d6b6a416f9b5
Content-Length: 282

--3d6b6a416f9b5
Content-Type: text/html
Content-Range: bytes 0-50/1270

<!doctype html>
<html>
<head>
    <title>Example Do
--3d6b6a416f9b5
Content-Type: text/html
Content-Range: bytes 100-150/1270

eta http-equiv="Content-type" content="text/html; c
--3d6b6a416f9b5--
top Created with Sketch.