This post was published in 2021-10-03. Obviously, expired content is less useful to users if it has already pasted its expiration date.
由于Cloudflare经常更新自己的技术,本文的部分内容可能已经过期,比如origin server到cloudflare edge server的HTTP/2协议(2022年引入)和brotli压缩编码(2023~2024年引入)。如果你对某项技术的实现有执念,建议搜索最新的Cloudflare文档再确认一遍。
Cloudflare对平民用户确实非常友好,它包装了很多(原本复杂的)技术,让用户仅仅通过点击鼠标就能完成——比如Brotli压缩,比如QUIC. 但是,如果有的时候我们想更深入了解Cloudflare的一些机制,就会发现很多细节并不会被Cloudflare显眼地标示出来,比如Cloudflare edge cache,一个让我曾经花了大量时间试图绕过这个机制的东西:🔗 Sometimes Cloudflare don't cache page via CURL request - Truxton's blog .
首先是一个非常简化的Cloudflare工作原理图:
(排除cloudflare partner/CNAME接入等操作,这些内容不在本文讨论范畴内)可以看到,Cloudflare接管了一个网站的相当一部分配置:name server,DNS解析,SSL证书,内容缓存,HTTP请求,WAF...全都要经过Cloudflare.
Table of Contents
默认
注明:本文所有客户端都发送 Accept-Encoding: gzip,deflate,br ,就像一个现代Chrome一样。
现在假设服务器上有一个20M的hello.html文件,由于Cloudflare默认不缓存html的特性,每当一个客户端请求这个.html的时候,它都会走这个网络流程:
服务器发送20M hello.html ➟ Cloudflare接收20M hello.html ➟ Cloudflare制作并发送10M hello.html.gz ➟ 客户端接收10M hello.html.gz ➟ 解压缩
等等,那个10M的hello.html.gz是什么回事?因为Cloudflare会自动执行特定类型文件的gzip压缩(即使你什么都没做,即使你在服务器里关闭了gzip,它也会压缩):What will Cloudflare compress?
开启Cloudflare brotli
现在假设你观察了Cloudflare的用户设置面板,发现了 brotli 是可以开启的:
那么现在,那个20M的.html文件的传输就变成了:
服务器发送20M hello.html ➟ Cloudflare 20M hello.html ➟ Cloudflare 5M hello.html.br ➟ 客户端接收5M hello.html.br ➟ 解压缩
(其实大多数情况下gzip和brotli的压缩比没有10M~5M那么大的差距,这里只是做一个简单的区分)
NGINX gzip, Cloudflare brotli
我猜,绝大部分人的NGINX的配置里都会开启 gzip . 现在假设服务器上运行了NGINX,我们也把它的 gzip 模块打开,那么这个文件传输就变成了:
服务器存放20M hello.html ➟ 服务器发送10M hello.html.gz ➟ Cloudflare 10M hello.html.gz ➟ Cloudflare 20M hello.html ➟ Cloudflare 5M hello.html.br ➟ 客户端接收5M hello.html.br ➟ 解压缩
这很合理,对吧?我们的NGINX只提供了gzip,而我们的客户端想要请求更加高效的brotli,Cloudflare自然会把我们的gzip转换为brotli .
NGINX gzip, Cloudflare gzip
现在假设我们在Cloudflare控制面板里关掉 brotli ,那么我们的NGINX会发送gzip,而Cloudflare会向客户端发送gzip,看起来一个.gz文件会从头走到尾,对吗?实际上并不是你所想的这样:
服务器存放20M hello.html ➟ 服务器发送10M hello.html.gz ➟ Cloudflare 10M hello.html.gz ➟ 客户端接收10M hello.html.gz ➟ 解压缩 (并不是这样)
而是这样:
服务器存放20M hello.html ➟ 服务器发送10M hello.html.gz ➟ Cloudflare 10M hello.html.gz ➟ Cloudflare 20M hello.html ➟ Cloudflare 10M hello.html.gz ➟ 客户端接收10M hello.html.gz ➟ 解压缩
等等,我们的NGINX不是提供了gzip吗,为什么Cloudflare还要多套一层 gzip解压缩➟gzip重新压缩 呢?
事实上就是这样。为了验证这个令人不愉快的事实,你可以在服务器本机(127.0.0.1)跑这样的命令(你需要修改html文件的url以及Host:example.com):
$ curl --insecure -H'Accept-Encoding: gzip,deflate,br' --header 'Host:example.com' https://127.0.0.1:443/your-path-to-hello.html --output /dev/null
这个命令会绕过Cloudflare;
再执行:
$ curl -H'Accept-Encoding: gzip,deflate,br' https://example.com/your-path-to-hello.html --output /dev/null
这个命令会让文件经过Cloudflare.
你会发现,这两个命令得到的文件体积大概率(接近100%)会不一样 (如果真的遇到了体积相等的情况,那就修改 gzip_comp_level 的值再跑一次)。这就说明了即使是向Cloudflare提供gzip文件,它也会给这个文件重新解压并压缩。为了多次验证这个猜想,你可以任意更改NGINX gzip的压缩参数: gzip_comp_level 1~9 ,最后你会发现:无论你怎么修改这些参数,只要文件经过Cloudflare,就一定会被“重新塑形”,变成Cloudflare gzip的形状。
NGINX gzip_static on
你可以在NGINX上开启 gzip_static ,让文件传输变成这个样子:
服务器存放10M hello.html.gz ➟ 服务器发送10M hello.html.gz ➟ Cloudflare 10M hello.html.gz ➟ Cloudflare 20M hello.html ➟ Cloudflare 5M hello.html.br ➟ 客户端接收5M hello.html.br ➟ 解压缩
从理论上来说,这样做可以节省一些服务器的流量,或者降低服务器的cpu负载,前提是你把 .gz 文件都提前准备好了。但事实上这样也没什么用,因为Cloudflare会缓存你的JS和CSS,而gzip对.png, .jpg, .mp4等压缩格式的媒体文件完全没用。你并不能因此而节省多少流量。
补充:如果你使用了wp-rocket, wp super page cache, wp fastest cache...类似的磁盘文件缓存插件(而不是NGINX fastcgi),那么开启 gzip_static 可能会有一些作用:可以省去服务器gzip压缩带来的cpu负载,同时节省相当一部分(请求html文件的)流量。
P.S. Fastcgi可以提供precompressed(gzip_static)的http服务吗?目前我没有找到解决方案。
NGINX brotli
2024年2月补充:根据cloudflare的博客(写于2023年6月23日):🔗 [All the way up to 11: Serve Brotli from origin and Introducing Compression Rules] https://blog.cloudflare.com/this-is-brotli-from-origin ,Cloudflare宣称已经支持origin server的brotli(甚至还支持origin server的brotli直接发送到用户端而无需中途重新解压-压缩);但我无论如何都没法复现(永远都只能发送gzip),所以暂时还不知道这个功能到底是个什么情况。(根据这个社区问答,似乎推出这个功能后遇到了一些问题,所以暂时又禁用了origin server brotli,还没重新放开) 虽然2月的时候没有复现成功,但我还是把nginx brotli功能打开放在了那里,直到4月的时候我偶然检查nginx log,又在最近的日志里发现了 Accept-Encoding: gzip, br ,说明Cloudflare还是逐步放开了这个功能。
下面的内容是2021年的,已经失效:
你试图在gzip的进度上更进一步,使用brotli(无论是动态的brotli on还是提前准备静态文件的brotli_static on)节省更多的流量,或者依赖于更先进的brotli算法来节省更多的cpu。首先根据前面已知的Cloudflare双层嵌套机制,无论你给Cloudflare发送什么东西,都会被解压并重新压缩一遍。我们现在已经可以向Cloudflare发送 uncompressed 和 gzip 文件了,那么我们也可以发送 brotli 文件吗?
是不行的。因为:https://support.cloudflare.com/hc/en-us/articles/200168396-What-will-Cloudflare-compress- 里面有一句话:
Cloudflare only supports the content types gzip towards your origin server and can also only deliver content either gzip compressed, brotli compressed, or not compressed.
所以你在NGINX里编译的brotli module,开启的brotli_static,通通无效。Cloudflare根本就不吃brotli这一套,它只吐brotli.
NGINX QUIC
HTTP/3看起来也挺好的,全新的协议配上更高的性能,Cloudflare提供了选项让用户决定是否开启HTTP/3:
但这还不太够,有的时候你可能希望你的服务器到Cloudflare edge server的这段传输也能走HTTP/3协议。所以很自然地你会发现Cloudflare开源的quiche(注:quiche官方只支持到了NGINX 1.16.1):
GitHub - cloudflare/quiche: 🥧 Savoury implementation of the QUIC transport protocol and HTTP/3
* 补充:2023-02-08,Nginx官方也开始提供支持QUIC的Nginx:🔗 [NGINX QUIC Preview] https://quic.nginx.org/
为了测试是否生效,还需要在本地编译支持HTTP/3协议的 curl .
但是最后,你会发现虽然本地跑编译出来的HTTP/3 NGINX用 curl 测试通过了,但这个NGINX就是不被Cloudflare识别。如果你在NGINX配置里仅允许HTTP/3,Cloudflare就会直接抛出521 web server is down的错误。这是因为:
* 2022-11-03更新:下面这句话被cloudflare悄悄删掉了。事实上,现在cloudflare允许在origin server和edge server之间使用HTTP/2协议 ,具体设置见这个官方文档。
所以和brotli一样,对cf edge server而言HTTP/3也是行不通的。Cloudflare根本就不吃HTTP/3这一套,它只吐HTTP/3 .