HTTP 压缩
Brotli 是一个开源数据压缩库,由 IETF 草案正式规范。它可以用来压缩发送到浏览器的 HTTPS 响应,替代 gzip 或 deflate。
对 Brotli 内容编码的支持已最近加入,现在可以在 Firefox 开发者版(Firefox 44)中进行测试。在这篇文章中,我们将向您展示如何设置一个简单的 HTTPS 服务器,在客户端支持的情况下利用 Brotli 进行压缩。
在通过网络提供内容时,一个简单的优化或低垂的果实是开启服务器端压缩。有点违反直觉的是,在服务器端额外进行 HTTP 响应压缩,并在客户端解压缩,比不进行额外工作要快。这是由于网络带宽的限制。当内容很大、未经压缩(重新压缩不会带来任何好处,除非你是Pied Piper)并且通信成本相对较高时,添加压缩可以缩短传输时间。
用户代理、客户端或 Web 浏览器向服务器发出其可以解压缩哪些压缩内容的信号的方式是通过 `Accept-Encoding` 头。让我们看看 Firefox 43(在支持 Brotli 之前)中的此类头可能是什么样子,开发者工具。
客户端支持这些编码并不意味着它们就会收到这些编码。服务器负责决定选择哪种编码。服务器甚至可能不支持任何形式的压缩。
然后,服务器会使用 `Content-Encoding` 头进行响应,指定使用了哪种压缩形式,如果没有则不指定。
虽然客户端发送其支持的编码列表,但服务器选择一个进行响应。使用不支持的内容编码进行响应,或使用与内容的实际编码不匹配的头可能会导致解压缩错误,并召唤Z͈͈̩͔̹͙͂͆ͨ̂͒́̌͋ͩ͑̄̔̏́̕ͅĄ̸̢̤͚̜̰̺̉͗̂͋̈͋̏̎̌ͬ͊̾͟L̵͈̠̯͙̠̩͚̰̖̬̩̂̐͐̐̇͑ͥͩ̇͐̄̇̀̕͡G̵̡͋̄͛̈́̓҉̶͉̳̮̣́Ő̐̈̀͐̅ͦ̔͊̽́̅̏͏̦̫̹̖̯͕̙̝̹̳͕͢͜。
大多数浏览器都支持 gzip 和 deflate(当然还有未压缩的内容)。基于 Gecko 的浏览器(如 Firefox 44 及更高版本)现在支持“br”表示 brotli。Opera Beta 33 支持 lzma(注意:lzma1 而不是 lzma2)和 sdch。这里是 Brotli 支持的相关 Chromium 错误。
创建我们的服务器
这是一个简单的 Node.js 服务器,它使用5 段生成的 Lorem Ipsum 文本进行响应。注意:你需要安装 Node.js,我使用的是 Node v0.12.7。你需要安装 C++ 编译器才能安装我使用的原生插件
npm install accepts iltorb lzma-native
最后,你需要生成一些 TLS 证书来进行测试,因为 Firefox 44 及更高版本支持通过 HTTPS 进行 Brotli 压缩,但不支持 HTTP。如果你在家跟着操作,并且没有看到 Accept-Encoding:“br”,请确保你通过 HTTPS 连接。
你可以按照这里的教程生成自签名证书。请注意,你需要安装 openssl,并且浏览器会发出警告,因为你新生成的证书未被它们或其受信任的证书颁发机构识别。在使用自己生成的并信任的证书进行本地开发时,可以安全地忽略这些警告,但在浏览网络时不要随意忽略证书错误。
这是我们简单服务器的代码。
#!/usr/bin/env node
var accepts = require('accepts');
var fs = require('fs');
var https = require('https');
var brotli = require('iltorb').compressStream;
var lzma = require('lzma-native').createStream.bind(null, 'aloneEncoder');
var gzip = require('zlib').createGzip;
var filename = 'lorem_ipsum.txt';
function onRequest (req, res) {
res.setHeader('Content-Type', 'text/html');
var encodings = new Set(accepts(req).encodings());
if (encodings.has('br')) {
res.setHeader('Content-Encoding', 'br');
fs.createReadStream(filename).pipe(brotli()).pipe(res);
} else if (encodings.has('lzma')) {
res.setHeader('Content-Encoding', 'lzma');
fs.createReadStream(filename).pipe(lzma()).pipe(res);
} else if (encodings.has('gzip')) {
res.setHeader('Content-Encoding', 'gzip');
fs.createReadStream(filename).pipe(gzip()).pipe(res);
} else {
fs.createReadStream(filename).pipe(res);
}
};
var certs = {
key: fs.readFileSync('./https-key.pem'),
cert: fs.readFileSync('./https-cert.pem'),
};
https.createServer(certs, onRequest).listen(3000);
然后,我们可以在浏览器中导航到 https://localhost:3000。让我们看看我在不同浏览器中访问服务器时会发生什么。
Firefox 45 使用 Brotli
我们可以使用 Firefox 开发者工具(在网络选项卡下)比较压缩前后资源的大小,方法是比较“已传输”和“大小”列。已传输列显示通过网络传输的压缩内容的字节数,大小列显示资源的解压缩大小。对于未经任何形式压缩发送的内容,这两者应该相同。
我们还可以使用 curl 命令行实用程序进行验证
$ curl https://localhost:3000 --insecure -H 'Accept-Encoding: br' -w '%{size_download}' -so /dev/null
1333
$ curl https://localhost:3000 --insecure -H 'Accept-Encoding: lzma' -w '%{size_download}' -so /dev/null
1502
$ curl https://localhost:3000 --insecure -H 'Accept-Encoding: gzip' -w '%{size_download}' -so /dev/null
1408
$ curl https://localhost:3000 --insecure -w '%{size_download}' -so /dev/null
3484
关于压缩与性能的说明
使用哪种压缩方案会产生影响。Node.js 自带 zlib,但是包含 lzma 和 brotli 的原生 Node 插件会稍微增加分发大小。各种压缩引擎运行所需的时间可能差异很大,并且在压缩内容时使用的内存可能会在处理大量请求时达到物理限制。
在前面的示例中,您可能已经注意到,lzma 在开箱即用时并没有超过 gzip 的压缩率,brotli 也只略微超过。您应该注意,所有压缩引擎都有许多配置选项,可以对其进行调整以权衡性能、内存使用等方面。我们将接下来看看响应时间、内存使用和 Weissman 得分的变化情况。
以下数字是从运行中收集的
$ /usr/bin/time -l node server.js &
$ wrk -c 100 -t 6 -d 30s -H 'Accept-Encoding: <either br lzma gzip or none>' https://localhost:3000
$ fg
ctrl-c
以下测量是在以下机器上进行的:2013 年初 Apple MacBook Pro OSX 10.10.5 16GB 1600 MHz DDR3 2.7 GHz Core i7 4 核,支持超线程。
压缩方法 | 每秒请求数 | 已传输字节数 (MB/s) | 最大 RSS (MB) | 平均延迟 (ms) |
---|---|---|---|---|
br-stream | 203 | 0.25 | 3485.54 | 462.57 |
lzma | 233 | 0.37 | 330.29 | 407.71 |
gzip | 2276 | 3.44 | 204.29 | 41.86 |
无 | 4061 | 14.06 | 125.1 | 23.45 |
br-static | 4087 | 5.85 | 105.58 | 23.3 |
一些需要注意的数字
- 除了 gzip 之外的压缩方法,每秒请求数都会出现性能下降。
- 压缩流的内存使用量明显更高。brotli 的峰值 RSS 为
9.8 GB3.4 GB,看起来像是内存泄漏,已在 upstream 报告(当我看到这个数字时,我的单片眼镜都掉了)。 - 测量的延迟仅来自本地主机,在互联网上的延迟至少会这么高,甚至可能更高。这是开发者工具 > 网络 > 定时中显示的等待时间。
- 如果我们使用从源代码构建的 brotli 预先压缩静态资源,则可以获得极佳的结果。注意:我们只能对静态响应使用此技巧。
- 提供静态 Brotli 压缩响应的性能与提供静态未压缩资源的性能一样好,同时使用的内存略少。这是有道理的,因为传输的字节数更少!每秒传输的字节数越少,使得该变量似乎与要传输的文件中的字节数无关。
为了预先压缩静态资源,我们可以从源代码构建 brotli,然后运行
$ ./bro --input lorem_ipsum.txt --output lorem_ipsum.txt.br
并修改我们的服务器
4d3
< var brotli = require('iltorb').compressStream;
8c7
< var filename = 'lorem_ipsum.txt'; --- > var filename = 'lorem_ipsum.txt.br';
17c16
< fs.createReadStream(filename).pipe(brotli()).pipe(res); --- > fs.createReadStream(filename).pipe(res);
BREACH 攻击
与其他 HTTP 压缩机制一样,在 HTTPS 中使用 Brotli 可能会使您容易受到BREACH 攻击。如果你想使用它,你应该应用其他 BREACH 缓解措施。
结论
对于 5 段 Lorem Ipsum 文本,Brotli 比 gzip 的压缩率高 5%。如果我对 2015 年 10 月 1 日的 reddit.com 首页运行相同的实验,Brotli 比 gzip 的压缩率高 22%!请注意,这两个测量值都使用开箱即用的压缩器,没有任何配置值的调整。
你的用户群中是否有很大一部分用户使用支持 Brotli 作为内容编码的浏览器、增加的延迟和内存成本是否值得、你的 HTTPS 服务器或 CDN 是否支持 Brotli 则是另一个问题。但如果你正在寻找比 gzip 更佳的性能,Brotli 看起来是一个可能的竞争者。
6 条评论