[翻译]Dropbox Web 服务启用 HTTP/2:经验和观察

Haowei Yuan | 2016年5月11日

最近,为了启用 HTTP/2,我们 Dropbox 的流量团队升级了前端的 Nginx 服务器。本文将分享我们迁移到 HTTP/2 的过程中的体验和新发现。整个过程较为顺利,但还是有发现一些坑,也许对大家有帮助。 继续阅读[翻译]Dropbox Web 服务启用 HTTP/2:经验和观察

平滑升级 nginx 使之支持 HTTP2

1. 得到 HTTPS 证书

本站用的是在 StartSSL.COM 申请的免费证书。请用 PC 浏览器访问,只支持根域名和一个二级域名。WoSign 的免费 SSL 证书也不错,全中文,不用翻墙,还支持多个域名。

不介意 Windows XP 用户无法建立 HTTPS 连接的请参考 Let’s Encrypt,免费好用的 HTTPS 证书 :支持多个域名、高大上的自动化部署。

2. 平滑升级 nginx

所谓平滑升级,保持网站可以访问的情况下,只为新开启的连接采用新的 nginx 服务,参考 nginx 平滑升级的详细操作方法

继续阅读平滑升级 nginx 使之支持 HTTP2

HTTP/2 十分钟速知

升级到 HTTP/2 后,那些针对HTTP/1.x 的优化手段需要如何变化?

答:总结来说,除了多域名增加并行 TCP 连接数不再适用以外,启用 HTTP/2 几乎不用考虑太多。

首先,由于 HTTP/2 是复用了一个 TCP 连接进行多次传输,所以适用于 HTTP/1.x 的多域名增加并发 TCP 连接数的策略已经不再适用了。不仅如此,如果你的 CDN 和主站不是指向同一 IP 且共用同一个 https 证书的话,HTTP/2 就不会在同一个 TCP 连接中也完成来自 CDN 的资源的传递,而是会为 CDN 徒增一个额外的 TCP 连接。

继续阅读HTTP/2 十分钟速知

《图解HTTP》读书笔记 – 第6章 HTTP 首部

本章学习 HTTP 首部的结构,以及首部中各字段的用法。

 

后记

这是最厚的一章了。HTTP 协议最有讲头的就是首部了。接下来五章的篇幅都比较短。

博客只发读书笔记显得人有点 low:只知道学习,不知道产出。每当想到这一点,博主就有点没有继续写笔记的动力了。但是,每当思考寻求解决方案时,第一反应还是会想起当初的笔记。所以,既然是自己挖的坑,哪怕很勉强,也还是要填完。

如何用 nginx 配置 gzip 和长缓存

这是一篇笔记,记录博主实际配置一个老生常谈的问题的过程。

Gzip 可以使资源加载时,大小缩减到原来的 1/3 。长时间缓存可以让浏览器不加载没有变化的文件。这两者都是前端性能优化的范畴,大家都懂的。

 

资源文件长缓存

将 Cache-Control 改为 public ,并加上一年的长缓存,配合 MD5 版本号实现长缓存 + 按版本号自动更新:

location /res{
    expires      1y;
    add_header   Cache-Control public;
}

为保证缓存效果,先去掉 Etag (Entity Tag)

浏览器会根据 Entity Tag 判断文件是否被修改过。而观星台用的是多台服务器。由于不同的服务器可能对同一个文件生成不同的 Entity Tag,用户第一次从服务器 A 拿过的文件,第二次从服务器 B 拿时,服务器给出的 Entity Tag 有可能不同。这就会造成重复下载,缓存形同虚设。由于已经有 MD5 插件机制起到了类似 Entity Tag 的效果,所以暂时先禁用掉 Entity Tag。

http{
    etag   off;
}

开启 Gzip 压缩

  1. 设置最小 1kb 开启 gzip 压缩;
  2. 开启 gzip_vary ,当用户使用代理服务器的时候,代理服务器会缓存压缩前后两个版本的文件,方便不同浏览器支持情况的用户;
  3. 将 gzip_http_version 设为 1.0,表明为 HTTP/1.0 以上的协议启用 gzip 压缩;
  4. 开启 js 、 css 、 xml 和 txt 文件的 gzip 压缩。其中网络图片(png/jpeg/gif)格式本身的压缩率一般都已经够高,不用再加 gzip 压缩,没什么效果还占用资源。对于观星台某些过大的 jpeg 图片,可以选择自动加 gzip 压缩作为折衷方案。但最主要的还是在做图的时候就设置好质量压缩比最高的压缩程度。
  5. 压缩比例设为 3。最小是 1 ,最大是 10。一般都设为 3。
  6. 对 IE1-6 不启用压缩,因为对 gzip 压缩支持的不太好,可能会出现闪跳问题(当然我们几乎没有这个类型的访客,可加可不加)。
  7. 其它未提到的设置保持默认。
http{
    gzip        on;
    gzip_min_length 1k;
    gzip_vary       on;
    gzip_http_version   1.0;
    gzip_types      text/plain application/x-javascript text/css application/xml image/jpeg;
    gzip_comp_level 3;
    gzip_disable    "MSIE [1-6].";
}

200 OK (from cache) 与 304 Not Modified

为什么有的缓存是 200 OK (from cache),有的缓存是 304 Not Modified 呢?很简单,看运维是否移除了 Entity Tag。移除了,就总是 200 OK (from cache)。没有移除,就两者交替出现。

最近在做百度云观测的 nginx 配置优化。从知乎上看到这个问题:“阿里云存储如何让浏览器始终以200 (from cache)缓存图片?”,提问者强调 200 OK (from cache) 和 304 Not Modified 的区别,有感而发。

其实, 200 OK (from cache)  是浏览器没有跟服务器确认,直接用了浏览器缓存;而 304 Not Modified 是浏览器和服务器多确认了一次缓存有效性,再用的缓存。

它们都是在设置了缓存的情况下触发的。

那么,两者触发的时机有什么区别呢?200 OK (from cache) 是直接点击链接访问,输入网址按回车访问也能触发;而 304 Not Modified 是刷新页面时触发,或是设置了长缓存、但 Entity Tags 没有移除时触发。这是经过查阅资料得出的结论。博主实际测试了一下,结论与之相符:

直接访问有缓存的网站都触发 200 OK (from cache)
图1 – 直接访问有缓存的网站都触发 200 OK (from cache)
刷新浏览器会触发 304 Not Modified
图2 – 刷新浏览器则会触发 304 Not Modified
同一域名下,没有 Entity Tag 的资源直接访问,是 200 OK (from cache) 的结果
图3 – 同一域名下,没有 Entity Tag 的资源直接访问,是 200 OK (from cache) 的结果
同一域名下,有 Entity Tag ,直接访问就会触发 304 Not Modified
图4 – 同一域名下,有 Entity Tag ,直接访问就会触发 304 Not Modified

现在一般都会设置长时间的缓存,正确设置方式参考这两篇笔记:

参考文献

后记

搜索了一下,发现这个问题,在网络上并还没有定论,也没发现有人去实测。

想想也对,现在网络那么快,304 Not Modified 还是 200 OK (from cache),如果不是较真地追求速度,可能大家都觉得区别不大,从而也就没发现这个问题了。

博主截图的域名是某著名 IT 公司的 CDN(已反馈)。可见犯这个错误的运维 GG 还真不少呢!

本文并不是说影响浏览器缓存只有 ETag 这一个因素的意思,请大家不要误解。只是就“为什么我加了缓存,有的却是 304 Not Modified, 而不是 200 OK(from cache)”这件事给出一个一针见血的原因和解答。本文也发在了 div.io 上,详细的 ETag 补充知识可以看 @hefangshi 补充的评论

《图解HTTP》读书笔记 – 第5章 与 HTTP 协作的 Web 服务器

一台 Web 服务器可搭建多个独立域名的 Web 网站,也可作为通信路径上的中转服务器提升传输效率。

搭建 Web 网站的服务器

HTTP/1.1 规范允许一台 HTTP 服务器搭建多个 Web 站点。访客通过 DNS 将域名解析成服务器 IP 地址,并访问该 IP 对应的服务器。这时,客户端发送 HTTP 请求会完整指出主机名或域名的 URI,服务器端就知晓具体要的是哪个网站。

同一台服务器建立多个 Web 网站
同一台服务器可能建立了多个 Web 网站,所以发 HTTP 请求必须准确指出域名或 IP 地址的 URI

代理

接收客户端的请求并转发给服务器,同时也接受服务器返回的响应并转发给客户端。

通过代理服务器转发请求或相应的示意图
通过代理服务器转发请求或相应的示意图

缓存代理

预先将资源的副本(缓存)保存在代理服务器上,下次同样请求就直接返回缓存的资源。

缓存服务器

缓存服务器是缓存代理的一种。优势在于利用缓存可以避免多次从源服务器转发资源。因此客户端可以就近从缓存服务器上获取资源,而源服务器也不必多次处理相同的请求。

缓存服务器示意图
缓存服务器示意图

缓存服务器判断缓存失效后,会再次从源服务器上获取新资源。
缓存服务器从源服务器更新过期的资源示意图
缓存服务器从源服务器更新过期的资源示意图

透明代理

转发请求或响应时,不做任何加工处理。

网关

转发其它服务器通信数据给客户端,但不再发客户端的请求转发给其它服务器端。

网关原理示意图
网关原理示意图

隧道

在相隔甚远的客户端和服务器两者之间进行中转,并保持双方通信连接。隧道不会解析 HTTP 请求,请求会保持原样中转给之后的服务器。隧道的目的是确保客户端与服务器进行安全的通信。

隧道示意图
隧道示意图

客户端缓存

缓存不仅可以存在于缓存服务器内,还可以存在客户端浏览器中(例如 Internet Explorer 的临时网络文件,Temporary Internet File)。
浏览器判断缓存过期后,会向服务器确认资源的有效性。若浏览器判断缓存失效,浏览器会再次请求新资源。

浏览器向服务器更新过期客户端缓存
浏览器向服务器更新过期客户端缓存

《图解HTTP》读书笔记 – 第4章 返回结果的 HTTP 状态码

状态码如 200 OK,以 3 位数字和原因短语组成。

数字中的第一位指定了响应类别,后两位数字无分类。响应类别有以下 5 种:

类别 原因短语
1XX Infomational (信息性状态码) 接收的请求正在处理
2XX Success (成功状态码) 请求正常处理完毕
3XX Redirection (重定向状态码) 需要进行附加操作以完成请求
4XX Client Error(客户端错误状态码) 服务器无法处理请求
5XX Server Error(服务器错误状态码) 服务器处理请求出错

最常用且最具代表性的 14 个状态码如下:

2XX 成功

  • 200 OK
  • 204 No Content
  • 206 Partial Content

200 表示从客户端发来的请求在服务器端被正常处理了。
204 一般在不需要对客户端发送新内容的情况下使用。表明服务器接受的请求已经成功处理,但返回的响应报文中不含实体的主体部分;
206 表示服务器成功执行了客户端发来的范围请求。响应报文中包含指定范围的实体内容。

3XX 重定向

  • 301 Moved Permanently
  • 302 Found
  • 303 See Other
  • 304 Not Modified
  • 307 Temporary Redirect

301 表示资源已经分配了新的 URI,以后都应该使用新的 URI。
302 表示资源已经分配了新的 URI,仅仅这次用新的 URI 访问。
303 和 302 一样,只是要求必须用 GET 方法访问新的 URI。
304 和重定向没有关系,也不返回实体,只是表明没有满足请求的附带条件。常见于浏览器判断缓存是否过期。
307 和 302 一样。302 标准禁止 POST 变成 GET,实际大家并不遵守。307 会遵守这个标准,但处理时每种浏览器的行为有所不同。

4XX 客户端错误

  • 400 Bad Request
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not Found

400 表示请求报文中有语法错误。
401 表示请求需要通过 HTTP 认证。若之前已经进行过一次请求,则表示用户认证失败。
403 表示请求的资源访问被服务器拒绝了。服务器不一定会给出拒绝的详细理由。
404 表示请求的资源不存在。也可以在服务器拒绝请求但不想说明理由的时候使用。

5XX 服务器错误

  • 500 Internal Server Error
  • 503 Service Unavailable

500 表示服务器在执行请求过程中发生了错误。
503 表示服务器暂时处于超负荷或者停机维护,暂时无法处理请求。

状态码和状况的不一致

有时候,不少返回的状态码响应都是错误的,但用户可能察觉不到这点。比如 Web 应用程序内部发生错误,状态码依然返回 200 OK,这种情况也经常遇到。

改变状态码

只要遵守状态码类别的定义,即使改变 RFC2616 中定义的状态码,或服务器自行创建状态码都没有问题。

《图解HTTP》读书笔记 – 第3章 HTTP报文内的 HTTP 信息

HTTP 报文的组成

HTTP 报文都分为首部、空行(回车符 + 换行符)和主体三部分。

HTTP 报文都由首部、空行和报文主体组成
HTTP 报文都由首部、空行(CR + LF)和报文主体组成

其中,HTTP 请求报文和 HTTP 响应报文只是报文首部不同。

请求报文(上)和响应报文(下)的结构
请求报文(上)和响应报文(下)的结构

HTTP 报文的实例

看一个 HTTP 报文的具体的实际例子,如下图:

起始行是方法(GET)、URI(/) 和协议版本(HTTP/1.1),这是 HTTP 首部必须的字段。
第二行开始是非必需的 HTTP 首部字段(Host、User-Agent 等)。
空一行以后,是 HTTP 请求的内容实体。HTTP 请求的内容实体也是非必需的(本例中就没有)。

HTTP 响应报文和 HTTP 请求报文的区别不大。HTTP 响应报文的起始行是服务器 HTTP 版本(HTTP/1.1)、状态码(200)和状态码原因(OK)的短语。第二行开始和 HTTP 请求报文一样,是非必需的首部和内容实体。

请求报文(上)和响应报文(下)的实例
请求报文(上)和响应报文(下)的实例

编码传输

HTTP 报文(message)一般和 HTTP实体(entity)是同一个东西。只有当传输过程中发生编码操作时,实体主体的内容发生变化,才导致它和报文主体产生差异。常用的内容编码有:

  • gzip (GNU zip)
  • compress (UNIX 系统的标准压缩)
  • deflate (zlib)
  • identity (不进行编码)

博主见得最多的是 gzip 和 identity 这两种编码。

分块传输

在 HTTP 通信过程中,请求的编码实体资源尚未全部传输完成之前,浏览器无法显示请求页面。在传输大容量数据时,通过把数据分割成多块,能够让浏览器逐步显示页面。

这种把实体主体分块的功能称为分块传输编码(Chunked Transfer Coding)。

范围请求

客户端也可以只请求实体的一部分内容,叫做范围请求(Range Request),如下图所示:

范围请求(Range Request)的例子。客户端用首字段 Range 来指定资源的 byte 范围。
范围请求(Range Request)的例子。客户端用首字段 Range 来指定资源的 byte 范围。

《图解HTTP》读书笔记 – 第2章 简单的 HTTP 协议

HTTP 规定,请求由客户端发出。

客户端发出请求报文,服务器发出响应报文,如下图:

HTTP 请求具体示例 - 客户端发出 HTTP 报文,服务器端也响应一份报文
HTTP 请求具体示例 – 客户端发出 HTTP 报文,服务器端也响应一份报文

HTTP 请求报文中,使用不同的方法(Method)可以用以告知服务器意图。例如 GET 是获取, PUT 是上传,DELETE 是删除,OPTIONS 是询问支持的方法, TRACE 是追踪路径等。具体可以看下图中的表格:

HTTP/1.0 和 HTTP/1.1 支持的方法HTTP/1.0 和 HTTP/1.1 支持的方法
HTTP/1.0 和 HTTP/1.1 支持的方法

HTTP 协议是不保存状态的协议,如下图所示:

HTTP 协议自身不具备保存之前发送过的请求或响应的功能
HTTP 协议自身不具备保存之前发送过的请求或响应的功能

HTTP 协议是不保存状态的协议,因此引入了 Cookie 技术。服务器端可以往客户端浏览器写 Cookie,客户端会自动在每次请求时都往服务器发这个 Cookie

最初的 HTTP 协议是每进行一次 HTTP 通信就要断开一次 TCP 连接。由于 TCP 协议的(三次握手等)保证可靠性的机制,很多时间浪费在连接和断开上了。

这个问题的解决方案是持久连接(HTTP Persistent Connections,也称为 HTTP keep-alive 或 HTTP connecttion reuse)的方法。其特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。HTTP/1.1 中,所有的连接默认都是持久连接,但在 HTTP/1.0 内未标准化。现在的服务器端基本都是 HTTP/1.1 了。除了服务器端,客户端也需要支持持久连接。

持久连接还使得管线化(pipelining)方式发送成为可能。从前发送一个请求需等待并收到响应,才能发送下一个请求。管线化出现后,不用等待响应便可发送下一个请求。

还是上图说明持久连接和管线化,如下:

早期的HTTP协议中,每次都要断开TCP连接
早期的HTTP协议中,每次都要断开TCP连接
早期的HTTP协议中,每次都要断开TCP连接。增加通信量的开销。
早期的HTTP协议中,每次都要断开TCP连接。增加通信量的开销。
持久连接旨在建立1次TCP连接后进行多次请求和响应的交互
持久连接旨在建立1次TCP连接后进行多次请求和响应的交互
管线化连接不用等待响应,直接发送下一个请求。效率大大提升。
管线化连接不用等待响应,直接发送下一个请求。效率大大提升。