Litestar 响应压缩中间件(CompressionMiddleware)完全指南:gzip / brotli / zstd 的配置、回退机制与实现原理
【免费下载链接】litestarLight, flexible and extensible ASGI framework | Built to scale项目地址: https://gitcode.com/GitHub_Trending/li/litestar
本篇技术指南围绕 docs/reference/middleware/compression.rst 所对应的litestar.middleware.compression模块展开,完整讲解 Litestar 内置的响应压缩能力:如何通过CompressionConfig启用 gzip、brotli、zstd 三种压缩后端,如何理解参数校验、客户端协商与 gzip 回退机制,以及CompressionMiddleware在 ASGI 层面完成字节流压缩的底层实现。读完本文,你将能够为应用正确配置压缩中间件、按路由排除压缩、并掌握压缩器与响应缓存协同工作的原理,可直接运用于生产环境。
一、模块定位:面向 API 参考页面的压缩组件
docs/reference/middleware/compression.rst是 Litestar 文档中对该模块的 API 参考入口,通过 Sphinx 的automodule指令把litestar.middleware.compression的全部公开成员渲染为参考文档。这一模块在仓库中由以下几个文件组成(litestar/middleware/compression/):
middleware.py:核心的CompressionMiddleware,负责 ASGI 调用链上的压缩包装;facade.py:定义统一的CompressionFacade协议,抽象不同压缩库的差异;gzip_facade.py:基于标准库gzip的GzipCompression实现;brotli_facade.py:基于brotli第三方库的BrotliCompression实现;zstd_facade.py:基于backports.zstd(Python 3.14+ 为compression.zstd)的ZstdCompression实现。
模块对外只暴露两个符号(见 litestar/middleware/compression/init.py):CompressionFacade与CompressionMiddleware,而真正的用户配置入口是位于 litestar/config/compression.py 的CompressionConfig数据类。
二、启用压缩:把CompressionConfig交给应用
要开启响应压缩,只需在创建Litestar应用时传入compression_config参数,传入一个CompressionConfig实例即可(CompressionConfig的 docstring 明确说明此用法,见 litestar/config/compression.py):
from litestar import Litestar from litestar.config.compression import CompressionConfig app = Litestar(route_handlers=[...], compression_config=CompressionConfig(backend="gzip"))CompressionConfig中唯一必填的参数是backend,取值可以是"gzip"、"brotli"或"zstd"(类型标注为Literal["gzip", "brotli", "zstd"] | str),它决定使用哪种压缩算法。其余参数均有默认值,可以按需覆盖。
2.1 完整参数表
以下参数均定义在 litestar/config/compression.py,默认值与取值范围以源码为准:
| 参数 | 默认值 | 说明 |
|---|---|---|
backend | (必填) | 压缩后端,"gzip"/"brotli"/"zstd"三者之一 |
minimum_size | 500 | 触发压缩的最小响应体大小(字节),对所有后端生效;必须大于 0 |
gzip_compress_level | 9 | gzip 压缩级别,范围[0, 9],语义与 Python 标准库gzip一致 |
zstd_compress_level | 0 | zstd 压缩级别,大于等于 0 的整数;0表示使用库默认压缩级别 |
brotli_quality | 5 | brotli 质量参数,范围[0, 11],值越高压缩率越高、速度越慢 |
brotli_mode | "text" | brotli 模式,"generic"/"text"(UTF-8 文本,默认)/"font"(WOFF 2.0 字体) |
brotli_lgwin | 22 | 滑动窗口大小的以 2 为底的对数,范围10到24 |
brotli_lgblock | 0 | 最大输入块大小的以 2 为底的对数,范围16到24;0表示按 quality 自动决定 |
brotli_gzip_fallback | True | 客户端不支持 brotli 时是否回退到 gzip |
zstd_gzip_fallback | True | 客户端不支持 zstd 时是否回退到 gzip |
gzip_fallback | True | 所选后端不被客户端支持时是否回退到 gzip(brotli/zstd 后端下由上面两个参数推导) |
middleware_class | CompressionMiddleware | 使用的中间件类,必须是CompressionMiddleware的子类 |
exclude | None | 需要跳过压缩的路径模式(字符串或字符串列表) |
exclude_opt_key | None | 路由上用于单独关闭压缩的标识键 |
compression_facade | GzipCompression | 实际执行压缩的 facade 类,默认 gzip |
backend_config | None | 与后端相关的额外配置 |
2.2 参数校验与后端联动
CompressionConfig通过__post_init__在实例化时执行校验(litestar/config/compression.py):
minimum_size <= 0会抛出ImproperlyConfiguredException;- 选择
"gzip"时校验gzip_compress_level必须在[0, 9]; - 选择
"brotli"时校验brotli_quality在[0, 11]、brotli_lgwin在[10, 24],并把compression_facade自动切换为BrotliCompression,同时用brotli_gzip_fallback覆盖gzip_fallback; - 选择
"zstd"时校验zstd_compress_level不超过ZstdCompression.upper_bound(取自底层库CompressionParameter.compression_level的边界),并把compression_facade切换为ZstdCompression,用zstd_gzip_fallback覆盖gzip_fallback。
也就是说,compression_facade与gzip_fallback通常不需要手动设置,而是由backend自动推导;只有自定义压缩后端时才需要直接配置这两个字段。
三、三种后端 facade:统一接口背后的差异
3.1CompressionFacade协议
为了让中间件不关心具体压缩库,模块定义了一个协议类CompressionFacade(litestar/middleware/compression/facade.py),它要求实现:
- 类属性
encoding:当前压缩器的编码字符串; __init__(buffer, compression_encoding, config):接收一个BytesIO缓冲区、编码值和CompressionConfig;write(body, final=False):把待压缩字节写入缓冲区,final=True表示这是最后一块数据,压缩器可冲刷内部缓冲;close():关闭压缩流。
3.2 GzipCompression(标准库实现)
litestar/middleware/compression/gzip_facade.py 基于 Python 标准库的gzip.GzipFile实现:write()写入数据并flush(),close()关闭压缩器。encoding = CompressionEncoding.GZIP,即"gzip"。由于它只依赖标准库,因此是默认 facade,无需额外安装依赖。
3.3 BrotliCompression(第三方 brotli 包)
litestar/middleware/compression/brotli_facade.py 在模块导入时尝试from brotli import MODE_FONT, MODE_GENERIC, MODE_TEXT, Compressor,若未安装brotli会抛出MissingDependencyException("brotli")。它把配置中的brotli_mode字符串映射为 brotli 库的MODE_TEXT/MODE_FONT/MODE_GENERIC,并用quality、lgwin、lgblock构造Compressor;write()依次调用process与flush,close()调用finish()。因此,选择backend="brotli"前需要先安装brotli依赖。
3.4 ZstdCompression(backports.zstd / 标准库 zstd)
litestar/middleware/compression/zstd_facade.py 在 Python 3.14 及以上使用标准库compression.zstd,更早版本则导入backports.zstd,缺失时抛出MissingDependencyException("backports.zstd", extra="zstd")。实现上通过zstd.ZstdCompressor(level=...)构造压缩器:非最终块使用FLUSH_BLOCK模式、最终块使用FLUSH_FRAME模式,close()会在尚未冲刷帧时调用flush()。upper_bound类属性用于CompressionConfig的级别校验。同样,使用该后端需要安装backports.zstd(或运行在 Python 3.14+)。
四、中间件运行机制:协商、回退与按需压缩
CompressionMiddleware继承自AbstractMiddleware,仅作用于 HTTP 作用域(scopes={ScopeType.HTTP}),并把config.exclude、config.exclude_opt_key透传给父类用于路由排除(litestar/middleware/compression/middleware.py)。
4.1 客户端协商(content negotiation)
每次请求进入中间件时,先从 ASGI scope 构造请求头并读取accept-encoding(litestar/middleware/compression/middleware.py),决策顺序为:
- 若
accept-encoding中包含当前配置后端对应的编码(如br或zstd),则用该编码包装send; - 否则若
gzip_fallback为True且客户端接受gzip,则改用 gzip 包装send; - 否则原样透传,不做任何压缩。
其中编码字符串来自CompressionEncoding枚举(litestar/enums.py):GZIP = "gzip"、BROTLI = "br"、ZSTD = "zstd"。注意 brotli 的编码标识是br而非brotli。
4.2 流式压缩的 ASGI 包装
create_compression_send_wrapper创建一个新的send闭包(litestar/middleware/compression/middleware.py),其关键逻辑如下:
- 维护一个
BytesIO缓冲区;如果是 gzip 回退场景会新建GzipCompression,否则复用self.config.compression_facade构造压缩器; - 拦截
http.response.start消息暂存为initial_message,等待首个http.response.body消息再决定是否真正压缩; - 若响应已被缓存(读取 scope 状态
connection_state.is_cached),则跳过压缩、直接透传原始消息并关闭压缩器,避免重复压缩; - 对首个 body 块:若
more_body为真(流式响应)则直接压缩并改写头;若单块且长度大于等于minimum_size则压缩并回写Content-Length;否则放弃压缩、原样发送; - 压缩路径上会改写响应头:设置
Content-Encoding为所选编码、更新Content-Length、通过extend_header_value("vary", "Accept-Encoding")追加Vary: Accept-Encoding,并在 scope 状态上标记response_compressed = True; - 后续 body 块持续写入压缩器并按块发送,最后一块调用
facade.close()并关闭缓冲区。
由此可见minimum_size只在响应非流式(单一 body 块)时起作用:小于阈值的响应不会被压缩,从而避免对小响应做无谓的 CPU 开销。
4.3 与响应缓存协同
通过读取 scope 的connection_state.is_cached状态(由缓存中间件写入),压缩中间件能识别命中缓存的响应并直接透传。这一点在仓库的端到端测试tests/e2e/test_response_caching.py中有覆盖,缓存与压缩叠加时不会对已缓存的字节流做二次压缩。
五、路由级排除与定制
CompressionConfig提供了两种排除方式:
exclude:传入路径模式字符串或模式列表,中间件对匹配路径的请求不执行压缩;exclude_opt_key:为路由设置一个标识键,在路由处理器上通过该键标记后即可单独关闭该路由的压缩。
若需要替换压缩实现,可以继承CompressionMiddleware并设置middleware_class,或提供自定义的CompressionFacade实现并通过compression_facade注入——这保持了中间件对压缩算法细节的解耦。
六、验证与测试
仓库在 tests/unit/test_middleware/test_compression_middleware.py 中提供针对压缩中间件的单元测试,覆盖了不同后端、回退逻辑、minimum_size阈值、响应头改写等场景;tests/e2e/test_response_caching.py则验证了压缩与缓存组合的行为。读者可以基于这些测试文件快速复现中间件的各种分支行为,将其作为理解实现细节的入口。
七、实战建议小结
- 最省事的入门选择是
CompressionConfig(backend="gzip"):不引入任何第三方依赖,gzip_compress_level=9提供标准库下最高的压缩比; - 追求更优压缩率时选用
backend="brotli"(需安装brotli),并通过brotli_quality权衡速度与体积;面向 WOFF 字体资源可设置brotli_mode="font"; - 需要更高吞吐或与既有基础设施一致时可选用
backend="zstd"(需安装backports.zstd或运行于 Python 3.14+); - 默认开启的 gzip 回退(
gzip_fallback/brotli_gzip_fallback/zstd_gzip_fallback)能保证老客户端仍可解压,不建议在生产环境关闭; - 对高频小响应可调大
minimum_size减少无意义压缩,对明确不需要压缩的路由使用exclude或exclude_opt_key排除,从而在带宽与 CPU 之间取得平衡。
【免费下载链接】litestarLight, flexible and extensible ASGI framework | Built to scale项目地址: https://gitcode.com/GitHub_Trending/li/litestar
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考