Traefik 官方 compress 中间件完全指南:Gzip / Brotli / Zstandard 响应压缩配置与源码解析
【免费下载链接】traefikThe Cloud Native Application Proxy项目地址: https://gitcode.com/GitHub_Trending/tr/traefik
compress是 Traefik(The Cloud Native Application Proxy)内置的一款 HTTP 响应压缩中间件,位于 HTTP 路由与后端服务之间,在响应发送给客户端之前依据Accept-Encoding协商并选择Gzip、Brotli、Zstandard三种编码之一进行压缩。本文以仓库中的官方文档 compress.md 为骨架,结合其源码实现 pkg/middlewares/compress 展开成文,读完你可以掌握:如何在 YAML / TOML / Docker Labels / Swarm Tags / Kubernetes CRD 五种配置形态下启用压缩;如何通过excludedContentTypes、includedContentTypes、minResponseBodyBytes、encodings、defaultEncoding五个选项精准控制压缩行为;以及压缩中间件在何时压缩、何时跳过、如何协商编码的完整判定逻辑。
一、中间件职责与支持能力
compress中间件会对后端返回的响应体进行压缩后再发给客户端。在 Traefik v3 中,它原生支持三种压缩算法:
- Gzip(编码名
gzip) - Brotli(编码名
br) - Zstandard(编码名
zstd)
三种算法的默认优先级为gzip、br、zstd(即默认配置下gzip具有最高优先级)。源码层面的默认值定义在两处:
- pkg/middlewares/compress/compress.go 第 24 行:
var defaultSupportedEncodings = []string{gzipName, brotliName, zstdName} - pkg/config/dynamic/middlewares.go 第 209-211 行:
Compress.SetDefaults()将Encodings默认设为[]string{"gzip", "br", "zstd"}
实际编码由三个成熟的开源库完成:gzip 走github.com/klauspost/compress/gzhttp,Brotli 走github.com/andybalholm/brotli,Zstandard 走github.com/klauspost/compress/zstd(见 compress.go 第 11-13 行的 import)。
二、五种配置形态的启用示例
压缩中间件属于 HTTP 层的动态配置(middleware),先创建,再挂载到 router 上才会真正生效。下面是官方文档给出的五种完全等价的最小启用配置。
2.1 结构化 YAML(文件 Provider)
# Enable compression http: middlewares: test-compress: compress: {}2.2 结构化 TOML(文件 Provider)
# Enable compression [http.middlewares] [http.middlewares.test-compress.compress]2.3 Docker / Swarm Labels
# Enable compression labels: - "traefik.http.middlewares.test-compress.compress=true"2.4 Docker / Swarm Tags(JSON)
// Enable compression { //... "Tags": [ "traefik.http.middlewares.test-compress.compress=true" ] }2.5 Kubernetes CRD
Kubernetes 场景使用traefik.io/v1alpha1的Middleware资源:
# Enable compression apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: name: test-compress spec: compress: {}上述任意一种配置都能在 pkg/server/middleware/middlewares.go 第 163-171 行的构建逻辑中被识别:当检测到config.Compress != nil时调用compress.New(ctx, next, *config.Compress, middlewareName)生成中间件实例。之后把它挂到对应路由(router)上,例如文件动态配置中:
http: routers: my-router: rule: "Host(`example.com`)" middlewares: - test-compress service: my-service三、配置选项详解
官方文档将配置项汇总如下,本节逐一给出实现层面解读。
| Field | Description | Default | Required |
|---|---|---|---|
excludedContentTypes | List of content types to compare theContent-Typeheader of the incoming requests and responses before compressing. The responses with content types defined inexcludedContentTypesare not compressed. Content types are compared in a case-insensitive, whitespace-ignored manner.TheexcludedContentTypesandincludedContentTypesoptions are mutually exclusive. | "" | No |
defaultEncoding | specifies the default encoding if theAccept-Encodingheader is not in the request or contains a wildcard (*). | "" | No |
encodings | Specifies the list of supported compression encodings. At least one encoding value must be specified, and valid entries arezstd(Zstandard),br(Brotli), andgzip(Gzip). The order of the list also sets the priority, the top entry has the highest priority. | gzip, br, zstd | No |
includedContentTypes | List of content types to compare theContent-Typeheader of the responses before compressing. The responses with content types defined inincludedContentTypesare compressed. Content types are compared in a case-insensitive, whitespace-ignored manner.TheexcludedContentTypesandincludedContentTypesoptions are mutually exclusive. | "" | No |
minResponseBodyBytes | Minimum amount of bytes a response body must have to be compressed. Responses smaller than the specified values willnotbe compressed. | 1024 | No |
仓库内完整字段参照请见 docs/content/reference/dynamic-configuration/file.yaml 第 176-188 行,其中
Middleware06.compress示范了五个字段的书写位置。
3.1 excludedContentTypes 与 includedContentTypes:按 Content-Type 收窄压缩范围
excludedContentTypes:黑名单模式。响应Content-Type命中列表中的类型时不压缩,其余类型照常压缩。includedContentTypes:白名单模式。只有Content-Type命中列表中的类型才被压缩。- 两者互斥,同时设置会导致中间件创建失败。
源码中该互斥校验发生在 compress.go 第 47-49 行:
if len(conf.ExcludedContentTypes) > 0 && len(conf.IncludedContentTypes) > 0 { return nil, errors.New("excludedContentTypes and includedContentTypes options are mutually exclusive") }两条规则还需要注意两点实现细节:
- 比较方式:列表项经
mime.ParseMediaType解析为标准媒体类型后再比较,因此类型名大小写、空白差异会被规范化处理;若配置的类型无法被解析为合法 MIME,中间件构造会直接报错(compress.go 第 52-69 行)。 application/grpc永远被排除:源码在构造 excludes 列表时无条件预置了excludes := []string{"application/grpc"}(compress.go 第 51 行),这正是官方文档末尾"GRPC application:application/grpcis never compressed"声明的实现来源。
白名单/黑名单的逐条匹配与included/excluded判定逻辑位于 pkg/middlewares/compress/compression_handler.go 第 234-267 行(Brotli/Zstd 路径)以及 gzip 封装器的gzhttp.ExceptContentTypes / gzhttp.ContentTypes参数(compress.go 第 191-201 行)。其中application/grpc也会在 compress.go 第 147 行按请求的Content-Type被提前拦截,保证对 gRPC/流式请求一律直通不压缩(历史 issue 见源码中引用的 traefik#2576 注释,涉及text/event-stream的同类处理)。
3.2 minResponseBodyBytes:小于阈值不压缩
只有响应体达到该字节数才会启动压缩,过小的响应压缩反而得不偿失。默认值1024(1 KB)定义在 compress.go 第 22 行:const defaultMinSize = 1024,若配置值 >0 则覆盖默认(第 71-74 行)。
实现上是"边收边判断":在 compression_handler.go 第 269-273 行,写入的字节先进入缓冲r.buf,只有当len(r.buf)+len(p) >= r.minSize时才开始真正压缩并写出压缩流;整个请求结束后若缓冲仍未达到阈值,则在close()时把缓冲原样(不压缩)写回(第 416-427 行)。这意味着响应只要实际足够大,就可以"中途切换"到压缩模式,无需后端先完整吐完整个 body。
3.3 encodings 与 defaultEncoding:协商与降级策略
encodings:声明中间件支持的编码及其优先级,列表顺序即优先级,排在最前(top entry)的优先级最高。可取值仅限zstd、br、gzip,且必须至少指定一个——源码 compress.go 第 76-83 行对空列表返回错误"at least one encoding must be specified",对未知编码返回"unsupported encoding: ..."。defaultEncoding:当请求没有携带Accept-Encoding头、或该头只包含通配符*时使用的兜底编码。若它被配置,则必须同时存在于encodings列表中,否则构造报错"unsupported default encoding: ..."(compress.go 第 84-86 行)。
defaultEncoding的两处生效逻辑在 compress.go 第 152-165 行(请求头缺失时直接选用默认编码)与 acceptencoding.go 第 51-57 行(协商结果落到通配符*时返回默认编码;未配置默认编码则回退到encodings的第一项)。
3.4 配置选项的 YAML 完整示例
http: middlewares: test-compress: compress: # 只压缩这三类文本内容(白名单模式) includedContentTypes: - "text/html" - "text/css" - "application/json" # 响应体小于 512 字节不压缩 minResponseBodyBytes: 512 # 优先 gzip,其次 br,其次 zstd encodings: - gzip - br - zstd # 客户端未声明 Accept-Encoding 时按 gzip 压缩 defaultEncoding: gzip注意:excludedContentTypes与includedContentTypes同一时刻只能出现其一,defaultEncoding必须在encodings内。
四、压缩激活条件:何时压缩、何时不压缩
官方文档明确了压缩激活依赖(包含但不限于)请求的Accept-Encoding头。只有以下条件全部满足时响应才会被压缩:
- 请求的
Accept-Encoding头中包含gzip和/或br和/或zstd,可以带 quality values(q 值)(如gzip;q=0.8, br),也可以出现*。- 请求头完全缺失时默认不编码(除非配置了
defaultEncoding,此时仍会编码)。 - 请求头存在但值为空字符串时,压缩被关闭(按 RFC 9110,表示客户端不想要任何内容编码)。
- 请求头完全缺失时默认不编码(除非配置了
- 响应尚未被压缩过,即响应头
Content-Encoding还没有被设置。 - 响应的
Content-Type不在excludedContentTypes中(或命中includedContentTypes白名单)。 - 响应体超过
minResponseBodyBytes配置的最小字节数(默认 1024)。
4.1 内容协商的内部实现
编码选择过程集中在 pkg/middlewares/compress/acceptencoding.go:
- 请求头为空串 → 直接返回
identity(不压缩),代码注释引用了 RFC 9110 关于"空的 Accept-Encoding 表示不接受任何编码"的语义(第 27-31 行)。 - 解析请求头各编码项及 q 值(q=0 表示不可接受,直接剔除;缺省 q 值为 1.0)(第 62-99 行)。
- 先按 q 值降序,再按
encodings配置的顺序(即优先级)排序;同权重时列表靠前者胜出(第 42-49 行)。 - 若胜出项是通配符
*,使用defaultEncoding;未配置则用encodings首项(第 51-57 行)。
对应的行为验证在 pkg/middlewares/compress/compress_test.go 第 25-132 行TestNegotiation中有完整覆盖,例如:
客户端Accept-Encoding | 选中的编码 |
|---|---|
| (头缺失) | 不压缩 |
gzip | gzip |
br | br |
br;q=0.8, gzip;q=0.6 | br(q 值更高) |
gzip;q=1.0, br;q=0.8 | gzip |
gzip;q=0.8, br;q=1.0, zstd;q=0.7 | br |
zstd;q=0.9, br;q=0.8, gzip;q=0.6 | zstd |
gzip, br, zstd(q 全相等) | zstd(当前底层库的倾向性结果) |
4.2 其余跳过压缩的路径
除上述四条件外,还有若干"无条件直通"场景:
- HEAD 请求:
req.Method == http.MethodHead时直接放行不做任何包装(compress.go 第 135-138 行),因为 HEAD 没有响应体可压。对应测试为 compress_test.go 第 283 行TestShouldNotCompressHeadRequest。 - 请求本身 Content-Type 为
application/grpc或text/event-stream等被排除类型时提前放行(compress.go 第 145-150 行),避免破坏流式语义。 - 后端已设置
Content-Encoding:说明上游已经编码,中间件不再二次压缩,直接透传(compression_handler.go 第 227-232 行,测试见 compress_test.go 第 161 行TestShouldNotCompressWhenContentEncodingHeader)。 - 不可解析的
Content-Type:若响应Content-Type无法被解析为合法 MIME,为对齐 gzip handler 行为将禁用压缩(compression_handler.go 第 235-243 行)。 - 1xx 信息性响应:
100-199状态码会被原样转发而不进入压缩缓冲逻辑(第 195-201 行)。
4.3 压缩开启后对响应头的影响
一旦确认压缩,中间件会:
- 添加
Vary: Accept-Encoding响应头(compression_handler.go 第 114 行),确保中间缓存/CDN 能按编码区分缓存副本; - 删除
Content-Length(因为压缩后长度未知),并写入Content-Encoding: gzip | br | zstd(第 280-283 行)。
五、空 Content-Type 的处理:自动 MIME 嗅探
若上游响应没有设置Content-Type或其为空,compress中间件会按 MIME 嗅探标准自动探测内容类型,并把探测到的 MIME 类型写回Content-Type响应头。
这样设计是为了让后续的类型黑/白名单判断(excludedContentTypes/includedContentTypes)有一个可靠的依据,同时也能改善客户端(浏览器)对无类型响应的处理。注意这是 gzip 压缩路径由底层gzhttp库提供的行为(源码中以 TODO 形式标注该行为仍需在 Brotli/Zstd 路径对齐,见 compression_handler.go 第 167 行的注释),因此对缺失Content-Type的响应,压缩判定依据的是嗅探后的结果。
六、gRPC 与流式响应注意事项
官方文档特别强调:application/grpc永远不会被压缩。这一点有两层保障:
- 构造时
application/grpc已被无条件写入排除列表excludes(compress.go 第 51 行),因此 gRPC 的响应Content-Type必然命中排除规则; - 对请求侧,若请求的
Content-Type已是application/grpc(gRPC-Web / gRPC 网关转发场景),则直接在 compress.go 第 145-150 行提前放行,中间件完全不介入。
同样地,text/event-stream(SSE,服务端推送)也不适合压缩——源码在 compress.go 第 146 行的注释中明确指出对这类请求响应不应被压缩(对应历史 issue traefik#2576)。如需禁止压缩 SSE 或其它特殊媒体类型,可在excludedContentTypes中显式补充,例如:
http: middlewares: test-compress: compress: excludedContentTypes: - "text/event-stream" - "application/grpc"七、校验与验证:结合测试理解行为边界
仓库自带的测试对压缩中间件的每个关键行为点都做了断言,是理解边界条件的绝佳教材。除了上文提到的TestNegotiation(编码协商)与TestShouldNotCompressHeadRequest(HEAD 请求)外,还包括:
- compress_test.go 第 186 行
TestShouldNotCompressWhenNoAcceptEncodingHeader:请求无Accept-Encoding时不压缩(未配置defaultEncoding的场景); - 第 207 / 229 行:请求头为
identity或空串时不压缩; - 第 305 行
TestShouldNotCompressWhenSpecificContentType与第 397 行TestShouldCompressWhenSpecificContentType:分别验证黑名单与白名单行为; - 第 592 行
TestMinResponseBodyBytes:验证最小字节阈值生效; - 第 645 行
Test1xxResponses:验证 1xx 信息性响应的透传处理。
若要在本地跑一遍该中间件的单元测试,可在仓库根目录执行:
go test ./pkg/middlewares/compress/...八、小结
compress中间件通过"配置声明 + 内容协商 + 缓冲后决策"三层机制实现透明响应压缩:配置层(encodings/defaultEncoding/ 类型黑白名单 / 最小字节数)决定支持范围与降级策略,协商层依据请求Accept-Encoding与 q 值从zstd、br、gzip中挑选编码,执行层则在响应未达阈值前先缓冲、达标后即时切换到压缩写出,并对已压缩、无类型、gRPC、SSE、HEAD/1xx 等场景做了稳妥的直通保护。理解这些判定顺序,就能在真实网关场景中精准预测某类响应的最终形态,避免对 WebSocket、事件流或二次压缩等问题做出错误假设。
【免费下载链接】traefikThe Cloud Native Application Proxy项目地址: https://gitcode.com/GitHub_Trending/tr/traefik
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考