refactor(compression): 请求压缩策略改为跟随客户端行为,响应支持gzip压缩

- 移除全局 ENABLE_REQUEST_COMPRESSION 配置,改为根据客户端 Content-Encoding
  决定是否对上游请求体进行 gzip 压缩
- 非流式响应根据客户端 Accept-Encoding 返回 gzip 压缩的 JSON
- ApiRequestContext 记录客户端编码偏好并透传至 handler 链路
- 新增 http_compression 模块统一处理压缩相关判断逻辑
- 上游请求头丢弃列表新增 content-encoding 防止客户端值泄露
- ensure_json_body 支持解压 gzip 编码的请求体
This commit is contained in:
fawney19
2026-03-01 15:52:32 +08:00
parent 2dcccc9820
commit 2c1e51a490
16 changed files with 469 additions and 47 deletions

View File

@@ -76,6 +76,7 @@ UPSTREAM_DROP_HEADERS: frozenset[str] = frozenset(
"connection",
# 编码头 - 丢弃客户端值,由 BROWSER_FINGERPRINT_HEADERS 统一设置
"accept-encoding",
"content-encoding",
# 反向代理 / 网关注入的头部 - 属于本站基础设施,不应泄露给上游
"x-real-ip",
"x-real-proto",

View File

@@ -0,0 +1,46 @@
"""HTTP 压缩相关辅助函数。"""
from __future__ import annotations
def normalize_content_encoding(value: str | None) -> str | None:
"""标准化 Content-Encoding 值(仅做清洗,不做兼容扩展)。"""
if not isinstance(value, str):
return None
normalized = value.strip().lower()
return normalized or None
def is_gzip_content_encoding(value: str | None) -> bool:
"""判断 Content-Encoding 是否为 gzip。"""
return normalize_content_encoding(value) == "gzip"
def accepts_gzip(accept_encoding: str | None) -> bool:
"""判断 Accept-Encoding 是否可接受 gzip。"""
if not isinstance(accept_encoding, str):
return False
for item in accept_encoding.split(","):
token_and_params = [part.strip() for part in item.split(";") if part.strip()]
if not token_and_params:
continue
encoding = token_and_params[0].lower()
if encoding not in {"gzip", "*"}:
continue
quality = 1.0
for param in token_and_params[1:]:
if not param.lower().startswith("q="):
continue
try:
quality = float(param[2:])
except ValueError:
quality = 0.0
break
if quality > 0:
return True
return False