压缩
OxPHP 使用 Brotli、Zstandard 或 gzip 压缩 HTTP 响应,具体取决于客户端接受哪一种。它会自动应用于一份固定的内容类型列表(文本、JSON、XML、SVG、WebAssembly 以及若干字体格式),因此无需改动应用代码,传输体积就会下降。
工作原理
客户端的 Accept-Encoding 请求头只读取一次,发生在请求进入路由管线之前,并与服务器提供的编码进行比对。接下来会发生什么,取决于字节来自哪里。
已有存储压缩副本的缓存静态文件会直接用该副本作答。请求期间不做任何压缩,下面的检查也都不适用。参见缓存的静态文件。
其他所有响应(PHP 输出、自定义错误页、尚无存储副本的静态文件)都会在响应构建完成后,按顺序经过以下相同的检查:
- 跳过部分响应。
206永远不会被编码,因为它的Content-Range偏移量描述的是未编码的表示形式。 - 内容类型检查。 在可压缩类型列表中查找响应的 MIME 类型。
- 已编码检查。 已带有
Content-Encoding响应头的响应保持原样。 - 大小检查。 只有介于 256 字节和 3 MiB(3,145,728 字节)之间的响应体才会被压缩,两端均包含在内。响应带有
Content-Length头时使用该头的值;PHP 响应通常不带,此时改为测量缓冲后的响应体。 - 压缩。 以配置的级别应用协商得到的编码。如果压缩后的输出并不比原始内容更小,则改为发送未压缩的响应。
压缩发生在 PHP 执行之后、静态文件服务之后,以及自定义错误页替换之后,因此超过 256 字节的自定义 404 响应体会像其他响应一样被压缩。内置的错误响应体(404 Not Found、500 Internal Server Error)只有几十个字节,低于大小下限。除此之外,状态码从不参与判断。
小的响应体直接内联压缩;超过某个阈值后,这项工作会转移到阻塞线程上,以免拖住异步运行时。阈值为 64 KiB,一旦级别高到成本曲线开始陡升,就降到 4 KiB:Brotli 为质量 4 以上,gzip 为级别 6 以上,Zstandard 为级别 9 以上。在出厂默认值下 Brotli 已处于陡升的那一半,因此超过 4 KiB 的 Brotli 响应体都在异步线程之外压缩。
flowchart TD
AE["Accept-Encoding,每个请求读取一次"]
W["按权重排序。服务器<br/>不提供的编码得分为零并被淘汰"]
Q{"是否提供<br/>存储副本?"}
A["平局时依次取 br、zstd、gzip。<br/>以 br 11、zstd 19、gzip 9 一次性构建"]
P["平局时依次取 zstd、br、gzip。<br/>以配置的 zstd 6、br 5、gzip 6<br/>当场压缩"]
H["Content-Encoding、重新计算的 Content-Length、<br/>Vary、弱化的 ETag、无 Accept-Ranges"]
AE --> W
W --> Q
Q -->|是| A
Q -->|否| P
A --> H
P --> H
选择编码
Accept-Encoding 按权重对编码排序(RFC 9110 §12.5.3),OxPHP 也按这种方式读取它:
- 编码名称按不区分大小写的方式匹配,未知的编码会被忽略。
br;q=0表示拒绝 Brotli。这与不写该编码不同:*仍可覆盖被省略的编码,但覆盖不了被明确拒绝的编码。*以赋予它的权重,覆盖请求头中未明确列出的任何编码。- 无法解析的权重会让该编码保持默认权重;格式错误的参数不会让该编码被丢弃。这一点对
q=0很重要。qvalue 最多有三位小数,因此q=0.000表示拒绝该编码,而q=0.0000根本不是有效权重,会被当作完全接受。同一编码出现两次时,按第一次的权重读取。 - 在客户端接受且服务器提供的编码中,权重最高的胜出。被配置撤下的编码在排序前就得分为零,因此如果客户端最偏好的编码服务器不提供,它会退到下一个选择,而不是得到未压缩的响应。
- 什么都不接受的请求头(为空,或
*;q=0)得到未编码的响应。OxPHP 从不因内容编码而返回406,也从不把identity当作一个标记来读取。
通常情况下权重是相同的,因为浏览器会不带权重地发送它支持的所有编码。OxPHP 根据压缩后字节的去向来打破这种平局:
| 响应属于 | 首选编码 | 原因 |
|---|---|---|
| 由存储副本提供 | Brotli,然后 Zstandard,然后 gzip | 字节以最高质量压缩一次,此后都从内存提供,因此只有体积重要,而在其范围顶端,Brotli 是三者中最小的 |
| 其他所有情况 | Zstandard,然后 Brotli,然后 gzip | 字节在客户端等待时压缩、用完即弃,因此每个请求都要付出成本。在请求负担得起的级别上,实测 Zstandard 的体积与 Brotli 相差不过几个百分点,CPU 却远不到后者的一半 |
第一行只在存储副本存在之后才适用。此前的命中,以及所有过大而无法缓存的文件,都走第二行,这就是为什么静态资源通常先以 Zstandard 作答,之后才以 Brotli 作答。
这种偏好只用于打破平局。写了 br;q=0.2, gzip;q=0.9 的客户端在两条路径上都会得到 gzip,为它构建的存储副本也是 gzip 的。
三种都不接受的客户端会收到未编码的响应。实际上 gzip 是兜底选择:过去二十年里的每一个 HTTP 客户端都接受它,而 Zstandard 需要 2024 年或之后发布的浏览器。
浏览器是否启用较新的编码,看的是连接,而不仅仅是自身的新旧:br 和 zstd 在 HTTPS 上会被声明,在明文 HTTP 上则不会。一个不使用 TLS 提供服务的站点,无论访问者用的是什么浏览器,都是 gzip 站点。
Vary: Accept-Encoding
由于结果取决于请求头,压缩窗口内的每个响应都带有 Vary: Accept-Encoding,无论是否压缩,无论是静态还是动态。服务器会为某些客户端编码的表示形式,即使当前客户端拿到的是原始字节,也随该请求头而变化;如果共享缓存得到的信息与此相反,它就会把那些原始字节转交给一个请求了某种编码的客户端。
有两种情况不在这条规则之内:
- 服务器从未完整持有的动态响应(任何随 PHP 产出而流式发送的内容)不带
Vary:这条路径在任何请求头下都无法压缩,所以响应确实不会变化。大到需要流式发送的静态文件不属于这一类,它会带上该头,因为同一个文件在客户端协商了编码时会被完整读取并压缩。 304既不带Vary也不带Content-Encoding:它刷新的是客户端已持有的表示形式,而不是描述一个新的表示形式。
Vary 会作为独立的一行头部添加,而不是合并到应用已发送的那一行中,因此设置了 Vary: Cookie 的响应发出时会带有两行 Vary。这是合法的,而且 OxPHP 在追加之前会读取所有这些行,因此 Accept-Encoding 永远不会重复;但只读取第一行的中间层会漏掉它。
缓存的静态文件
放得进内容缓存(1 MiB 或更小)且可压缩(属于列表中的类型,至少 256 字节)的静态文件只压缩一次,而不是每个请求都压缩。
- 1第一个请求:缓存未命中。 从磁盘读取文件并存入缓存,然后像其他响应一样为该请求压缩。
- 2下一个请求:触发构建。 文件已被缓存,如果该请求协商出的编码还没有存储副本,服务器会请求在后台线程上构建一份。触发构建的请求按每请求的方式提供,构建期间到达的每个请求也是如此。
- 3副本就绪。 之后协商出相同编码的请求都由存储副本作答。
这里没有命中计数:触发条件是该编码首次在没有存储副本的情况下命中缓存,因此一个只服务过 gzip 客户端的文件,会在第一个命中它的 Brotli 客户端到来时得到 Brotli 副本。
存储副本以每种编码的最高级别构建(Brotli 11、Zstandard 19、gzip 9),与配置的每请求级别无关,存储副本从不使用后者。它们没有单独的设置:阻止构建某个副本的唯一办法,是彻底撤下该编码。最高质量通常比 Brotli 自身的每请求级别少产生 8% 到 12% 的字节。
每种编码都有自己的存储副本,按需构建,因此一个只服务过 gzip 客户端的文件永远不会付出 Brotli 压缩的成本。它们都共享被缓存文件的验证器,并与缓存的字节一起被丢弃:当条目为保持在 64 MiB 缓存预算之内而被淘汰时(存储副本也计入该预算),或者当 STATIC_REVALIDATE 开启并发现磁盘上的文件已变化时。该设置默认关闭,因此在默认部署中,淘汰和重启是存储副本被丢弃的仅有两种方式。无法压缩的字节会被标记为不可压缩并且不再重试,304 既不会触发构建,也不会提供存储副本。
由于两种平局规则不同,同时接受 Brotli 和 Zstandard 的客户端通常会看到每个文件的编码变化一次:最初的命中以 Zstandard 作答,为该请求当场压缩;存储副本就绪之后的命中则以 Brotli 作答。两者都是同一资源的有效表示形式,也都带有 Vary,所以不会出问题。它们还共享同一个弱 ETag,因此持有 Zstandard 变体的缓存能成功重新校验并保留它;这次切换影响的是新客户端,而不是已存储副本的客户端。
过大无法缓存的文件
在缓存上限与压缩窗口上限之间(1 MiB 到 3 MiB,框架打包文件、source map 和 WebAssembly 模块就落在这里),只要客户端协商了编码且类型可压缩,文件就会被完整读取并为每个请求压缩。它不会被缓存,也永远不会得到存储副本,因此这是服务器每个请求所做的最昂贵的压缩。
在 1 到 3 MiB 范围内的资源前面放一个 CDN,或把它们控制在 1 MiB 缓存上限以下,这项工作就只做一次。
读取这样的文件时,它会一直驻留在内存中,直到响应写完,因此同一时间只会读取有限数量的此类文件。超出该限制到达的请求不会等待空位,而是以流式、未编码的方式得到文件。因此在大型资源承受持续负载时,有些客户端拿到的是压缩版本,有些则不是;两种答复对该 URL 来说都正确,而且都带有 Vary: Accept-Encoding。
除了这种情况,超过 1 MiB 的文件都会从磁盘原样流式发送:不可压缩的类型、不接受任何编码的客户端,或任何超过 3 MiB 的文件。
静态文件按大小的成本如下:
flowchart TD
F["静态文件"]
C1{"256 B 或更大?"}
C2{"1 MiB 或更小?"}
C3{"3 MiB 或更小?"}
N1["原样发送,并被缓存。<br/>编码帧的开销会超过节省"]
N2["被缓存,首次缓存命中时<br/>在后台一次性构建存储副本"]
N3["每个请求都完整读取并压缩。<br/>太大无法缓存,因此不保留副本"]
N4["从磁盘流式发送,从不压缩。<br/>超出压缩窗口"]
F --> C1
C1 -->|否| N1
C1 -->|是| C2
C2 -->|是| N2
C2 -->|否| C3
C3 -->|是| N3
C3 -->|否| N4
该路径假定类型可压缩,且客户端协商了编码。去掉其中任一条件,文件在 1 MiB 以内会被缓存,超过则流式发送,范围请求全程有效,任何地方都不做编码。
配置
| 变量 | 默认值 | 描述 |
|---|---|---|
COMPRESSION_ENCODINGS |
br,zstd,gzip |
服务器提供哪些编码,以逗号分隔。接受 br(或 brotli)、zstd 和 gzip,不区分大小写,允许前后有空白。这里书写的顺序会被忽略;服务器针对每个响应自行选择(参见选择编码)。将整个值设为 off(或 none)可完全关闭压缩。off 不是列表中的标记,因此 br,off 会被当作错误拒绝 |
COMPRESSION_BROTLI_LEVEL |
5 |
Brotli 质量(0 到 11) |
COMPRESSION_ZSTD_LEVEL |
6 |
Zstandard 级别(0 到 19) |
COMPRESSION_GZIP_LEVEL |
6 |
Gzip 级别(0 到 9) |
COMPRESSION_LEVEL |
(未设置) | COMPRESSION_BROTLI_LEVEL 的已弃用名称。设置它会在启动时记录一条警告。显式设置的 COMPRESSION_BROTLI_LEVEL 会覆盖它的非零值,但覆盖不了 COMPRESSION_LEVEL=0,后者无论其他设置如何都会关闭全部压缩 |
一种编码只有在 COMPRESSION_ENCODINGS 中列出且其级别不为 0 时才会被提供;任一条件不满足都会撤下它。被撤下的编码会从整个功能中消失:它永远不会被协商,永远不会在请求路径上被压缩,也永远不会被构建为存储副本。
未知的编码名称、超出范围的级别,或不是数字的级别,都会以一条指明该变量的错误中止启动。不会做任何截断,也不会静默丢弃任何值。空赋值(COMPRESSION_ENCODINGS=,即宿主变量缺失时 ${VAR:-} 产生的形式)被视为未设置,并恢复默认值。它并不表示 off。
从 COMPRESSION_LEVEL 迁移
早期版本只有一种编码和一个旋钮:COMPRESSION_LEVEL,一个默认为 4 的 Brotli 质量,设为 0 时关闭压缩。该变量仍会被读取,两种含义都保留,因此现有部署可以继续工作,但会记录一条弃用警告。
COMPRESSION_LEVEL=N(非零)改为COMPRESSION_BROTLI_LEVEL=N。COMPRESSION_LEVEL=0改为COMPRESSION_ENCODINGS=off。
保留旧值同样会让 Zstandard 和 gzip 以默认值开启,这通常正是你想要的:过去不接受 Brotli 的客户端得到的是未压缩响应,现在则得到 gzip。
为什么是这些默认值
- Brotli 选 5,而不是 4。 Brotli 的质量拐点(哈希器的切换)就在这两者之间。在 4 时,对于 4 KB 以上的 JSON 和真实的压缩后资源,它产生的字节多于 gzip 在其默认级别下的输出,还花了更多 CPU,这样就完全没有理由优先选它而不是 gzip。在 5 时,它在每一个实测的响应体上都比 gzip 小,CPU 大约是 gzip 的两倍。
- Gzip 选 6。 这是 zlib 自己的默认值,接近收益递减点。级别 9 的成本大约是它的两倍,只换来一两个百分点。
- Zstandard 选 6,而不是它自己的默认值 3。 在几 KB 以上的响应体上,级别 6 花的时间比早期版本压缩一切所用的 Brotli 质量还少,产生的字节也更少,因此与那些版本发送的内容相比没有任何退步。
级别 9 到 11 更适合离线或构建期压缩,而不是每请求的工作;缓存的静态文件仍然使用它们,因为那份成本只付一次。
可压缩的内容类型
这份列表是封闭的:没有前缀或通配规则,因此不在列表中的类型永远不会被压缩,哪怕它压缩效果很好。在列表内部,匹配是宽松的。参数和前后空白会被去掉,类型本身按不区分大小写的方式比较,因此 text/html; charset=utf-8 和 Application/JSON 都能匹配。
Text 类型:
text/htmltext/csstext/plaintext/xmltext/javascript
Application 类型:
application/javascriptapplication/jsonapplication/xmlapplication/xhtml+xmlapplication/rss+xmlapplication/atom+xmlapplication/manifest+jsonapplication/ld+jsonapplication/wasm
其他类型:
image/svg+xmlfont/ttffont/otfapplication/x-font-ttfapplication/x-font-opentypeapplication/vnd.ms-fontobject
不会被压缩的情况
当满足以下任一条件时,响应将不经压缩直接发送:
- 客户端在
Accept-Encoding请求头中br、zstd和gzip都不接受,或只以零权重接受它们(br;q=0, zstd;q=0, gzip;q=0),或根本没有发送该请求头 - 客户端接受的每一种编码都已被
COMPRESSION_ENCODINGS或0级别撤下 - 响应已带有
Content-Encoding响应头(例如应用生成的预压缩内容) - 响应体小于 256 字节或大于 3 MiB
- 响应是
206 Partial Content:范围偏移量描述的是未编码的表示形式,因此部分响应体永远不会被编码 - 内容类型不在上面的列表中。
image/png、image/jpeg、font/woff2和application/zip被排除,是因为这些格式本身已带有压缩;text/event-stream被排除,是因为事件流必须逐个事件到达客户端。text/csv、application/pdf、image/x-icon以及完全没有Content-Type的响应也被排除,因为它们不在列表中 - 服务器没有完整持有响应体:使用
oxphp_stream_flush()的 PHP 脚本、Server-Sent Events,以及边从磁盘读取边发送的静态文件。OxPHP 以整体方式压缩响应。Brotli、Zstandard 和 gzip 都能增量编码,因此这是服务器的限制,而不是编码的限制 - 响应来自在管线运行之前就已作答的插件,例如被限流的
429,它会在压缩步骤之前返回 - 响应来自内部监听器(
INTERNAL_ADDR),它提供的/metrics和健康检查端点始终不编码,无论抓取方接受什么
响应头
服务器编码的响应会按如下方式改写,无论是为该请求当场压缩的,还是由存储副本提供的:
| 响应头 | 值 |
|---|---|
Content-Encoding |
br、zstd 或 gzip,取协商得到的那一种 |
Content-Length |
更新为压缩后的响应体大小 |
Vary |
追加 Accept-Encoding,使 HTTP 缓存为每种编码分别存储版本 |
ETag |
强标签会被弱化为 W/"…"。编码后的字节是另一种表示形式,如果与原始字节共用一个强标签,客户端就可能用原始片段来续传编码后的下载 |
Accept-Ranges |
被移除。字节偏移量对编码后的响应体毫无意义 |
同一个静态文件的所有编码共享这个弱标签,它由原始大小和修改时间派生而来。区分各个变体靠的是 Vary;标签只决定新鲜度,因此重新校验某个变体的缓存无法告诉服务器它持有的是哪一个。
对于每一个将会为该客户端压缩的响应,范围处理都会在响应构建之前被关闭。对于同时发送 Range 和 Accept-Encoding 的客户端,压缩窗口内的可压缩静态文件会以完整的 200 作答;来自这类客户端的无法满足的范围请求得到的也是这个 200,而不是 416。不接受任何编码的客户端保留范围功能,可续传的下载正是在这里。
可观测性
指标端点上的两个计数器报告压缩实际做了什么:
oxphp_compressed_responses_total:以某种内容编码发送的响应数oxphp_compression_bytes_saved_total:原始大小减去压缩后大小的累计值
两者都会计入存储副本的命中和每请求的压缩,因此拿它们与 oxphp_requests_total 求比值,比单独一次 curl -I 更能可靠地回答"压缩是否作用到了我的流量上"。
疑难排查
响应没有被压缩
确认客户端确实发送了 Accept-Encoding 请求头。浏览器会发送,但某些 HTTP 测试工具默认不发送,而没有该请求头的请求会得到未编码的响应。
零权重表示拒绝该编码:Accept-Encoding: br;q=0, zstd;q=0, gzip;q=0 会为该请求禁用压缩,与不发送该请求头的效果相同。
使用 curl 检查:
curl -H "Accept-Encoding: br, zstd, gzip" -I http://localhost/在响应头中查找 Content-Encoding。如果缺失,请检查:
COMPRESSION_ENCODINGS仍然列出了你请求的编码,且其级别不为0- 响应体至少有 256 字节,且不超过 3 MiB
- 响应的
Content-Type在上面的可压缩列表中。匹配时忽略大小写和参数 - 处理程序没有流式输出。调用
oxphp_stream_flush()、发送 Server-Sent Events,或以其他方式在长度确定之前就开始写出的脚本会被原样透传,在会 flush 的应用中这是最常见的原因
不同客户端得到不同的编码
协商本来就会产生这种结果,而且几乎所有情况都可以归入三种。浏览器在明文 HTTP 上不声明 br 和 zstd:它们在那里发送 Accept-Encoding: gzip, deflate,无论多新都以 gzip 作答。命令行工具往往根本不发送 Accept-Encoding,因此以未编码方式作答。而在 HTTPS 上,新到会发送 zstd 的浏览器在动态响应上得到 Zstandard,较旧的浏览器则得到 Brotli。这些情况都不是配置错误,而且被压缩的响应都带有 Vary: Accept-Encoding,缓存会将它们区分开。
同一个静态文件的编码发生了变化
这是预期行为:对可缓存静态文件的最初几次命中会用 Zstandard 压缩以回应该请求,而一旦存储的 Brotli 副本在后台构建完成,之后的命中就由它作答。参见缓存的静态文件。
范围请求返回了整个文件
当客户端同时发送 Accept-Encoding 时,对于压缩窗口内的可压缩文件,这是预期行为:提供给它的表示形式是编码过的,原始字节的偏移量无法描述它。不带 Accept-Encoding 的同一请求会得到它的 206。参见响应头。
压缩反而让响应变大了
无法压缩的响应体(base64 数据块、由随机标识符组成的载荷、被包装成 image/svg+xml 的已压缩图像)经过三种编码中的任何一种之后,都可能不比原来小。OxPHP 会比较两者,只要压缩没有带来好处就发送原始内容,因此无需任何配置。这不可能是小响应的问题:256 字节是尝试压缩的下限。
开启压缩后吞吐量下降了
压缩既可能提高也可能降低吞吐量。往哪个方向走,取决于开启压缩之前服务器缺的是什么;同一台服务器上的同一个设置,可能让吞吐量朝任一方向变化好几倍。
面对饱和的链路,它是能拿到的最大收益:缩小到八分之一的文本响应,送达速度就快八倍,而它花掉的 CPU 原本就在空闲地等待线路。
面对狭窄的异步运行时,它是一种成本。低于移交阈值的响应体在异步工作线程上压缩,而运行时默认只占用进程可用 CPU 数的一半,因此这项工作是从承载所有打开连接的线程池中拿走的,而机器的其余部分可能还有余力。它并不需要多少就会占满:两个运行时线程光是压缩 1.5 KB 的 JSON 响应体就会饱和。识别它的症状是:吞吐量下降,同时机器和 PHP 工作进程池都变得更闲,因为用尽的并不是它们。
需要检查的是: 运行时是否比机器窄。TOKIO_WORKERS 默认为可用核心数的一半,调高它能让这项工作用上机器的更多部分。能多用多少取决于机器以及上面还运行着什么,所以要实测而不是假设,并且在实测的同时观察工作进程池:它的线程与运行时的线程是分开的,因此在已经受 PHP CPU 限制的部署上,更宽的运行时是从应用那里抢走周期,而不是找到新的周期。
改为把小响应体送到阻塞线程池并不能解决这个问题。在双线程运行时上,一个内联压缩的 1.5 KB JSON 响应体大约占用运行时线程 7 µs;交给阻塞线程池再取回来,则会占用 13 到 19 µs,因为提交工作、挂起和唤醒本身就是运行时的工作。这个运行时上的吞吐量会降到原来的三分之一到二分之一,运行时上其他所有工作的等待时间不降反升。大小阈值正是考虑到这一成本而设定的。
压缩导致 CPU 占用过高
在每种编码范围的顶端(Brotli 8 到 11、gzip 8 和 9、Zstandard 12 到 19),压缩成本都会急剧上升,而每请求的默认值远低于这一区间。如果你观察到压缩带来的高 CPU 消耗:
修复方法: 将 COMPRESSION_ZSTD_LEVEL 降到 3,将 COMPRESSION_GZIP_LEVEL 降到 4。Brotli 是三者中最昂贵的编码,但把 COMPRESSION_BROTLI_LEVEL 降到 4 并不是节省这部分 CPU 的办法:在 4 时,它产生的字节比 gzip 在其默认级别下还多,成本却仍然高于 gzip。应当改为从 COMPRESSION_ENCODINGS 中去掉 br。新到会发送 zstd 的客户端会退到 Zstandard 而不是 gzip,因此这一改动对它们毫无损失;只有接受 Brotli 但不接受 Zstandard 的客户端(Safari、2024 年之前的浏览器、大多数中间层)会转向 gzip,而在真实响应体上 gzip 大 2% 到 6%,CPU 约为一半。
缓存的静态文件不在这部分成本之内:无论剩下哪些编码,它们的压缩副本都只在后台构建一次。不过 1 MiB 到 3 MiB 之间的文件在其中。它们每个请求都要压缩,而且无法缓存;参见过大无法缓存的文件。
预压缩的资源被再次压缩
OxPHP 不会在原始文件旁查找 .br、.zst 或 .gz 文件:对 /app.js 的请求永远不会由 /app.js.br 作答,而直接请求压缩文件得到的是一个浏览器不会解码的不透明下载。静态资源的构建期压缩没有可接入的地方;服务器自己的存储副本承担了这一角色,质量同样是最高级别,而且不需要构建步骤。
跳过重复压缩只适用于已带有 Content-Encoding 响应头的响应,实际上就是指自行编码响应体并自行设置该头的 PHP 脚本。
Docker 示例
services:
app:
image: ghcr.io/oxphp/oxphp:0.13.0
ports:
- "8080:80"
volumes:
- ./src:/var/www/html
environment:
- DOCUMENT_ROOT=/var/www/html/public
- ENTRY_FILE=index.php
- COMPRESSION_ENCODINGS=br,zstd,gzip
- COMPRESSION_BROTLI_LEVEL=5
- COMPRESSION_ZSTD_LEVEL=6
- COMPRESSION_GZIP_LEVEL=6给你的智能体的提示词
这些提示词假定你的智能体已连接 OxPHP 文档 MCP 服务器——需要哪些页面它会自己去读。
弄清楚 OxPHP 对我的应用到底会压缩哪些东西,并调好各个编码。通过 OxPHP 文档 MCP 服务器读取 `features/compression` 页面。
过一遍这个仓库返回的内容类型和响应大小,告诉我其中哪些落在可压缩类型列表或 256 B 到 3 MiB 的范围之外,标出介于 1 MiB 和 3 MiB 之间、每个请求都要重新压缩的静态资源,并为它承载的流量结构推荐 `COMPRESSION_ENCODINGS` 和各编码的级别。如果配置里仍在设置已弃用的 `COMPRESSION_LEVEL`,把它迁移掉。