fix(backend): API 文本输入加 max_length + 请求体大小中间件(CR-H9)

This commit is contained in:
Yaojia Wang
2026-07-08 10:53:44 +02:00
parent 26f10d023d
commit e1a24a2c2c
13 changed files with 223 additions and 30 deletions

View File

@@ -12,10 +12,14 @@ from collections.abc import Awaitable, Callable
import structlog
from starlette.requests import Request
from starlette.responses import Response
from starlette.responses import JSONResponse, Response
from ww_shared import ErrorBody, ErrorCode, ErrorEnvelope
REQUEST_ID_HEADER = "x-request-id"
# 请求体大小上限2 MiB超过即 413防超大 body 撑爆内存/成本CR-H9
MAX_BODY_BYTES = 2 * 1024 * 1024
# 放行安全字符集(字母数字 . _ -1128 位):覆盖 uuid4().hex、常见 trace id 与客户端
# 自定义短 id含空白/控制字符/换行/注入序列/超长的一律丢弃改生成新 uuid——防止把未经
# 校验的客户端值原样写进日志/响应头(日志注入防护)。
@@ -38,3 +42,29 @@ async def request_id_middleware(
response = await call_next(request)
response.headers[REQUEST_ID_HEADER] = request_id
return response
async def body_size_limit_middleware(
request: Request, call_next: Callable[[Request], Awaitable[Response]]
) -> Response:
"""按 `Content-Length` 拦截超大请求体,超限直接 413CR-H9
用户中间件跑在 FastAPI 的 AppError 处理器**之外**,故必须自建错误信封响应(不能靠抛
AppError。分块编码无 `Content-Length`)无法在此拦截——单用户原型可接受的限制。
"""
content_length = request.headers.get("content-length")
if content_length is not None:
try:
size = int(content_length)
except ValueError:
size = 0 # 非法头防御性视作 0交由下游正常解析/报错。
if size > MAX_BODY_BYTES:
envelope = ErrorEnvelope(
error=ErrorBody(
code=ErrorCode.PAYLOAD_TOO_LARGE,
message="请求体过大",
request_id=_sanitize_request_id(request.headers.get(REQUEST_ID_HEADER)),
)
)
return JSONResponse(status_code=413, content=envelope.model_dump())
return await call_next(request)