feat:新增大屏存取发布接口
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
"""限制请求体大小:声明的 Content-Length 超限直接 413,没声明(分块)或声明得不实的,边读边数
|
||||
|
||||
FastAPI 会先把整个请求体收完再调用接口函数,超大的请求体已经占满了带宽和内存
|
||||
和数据文件上传的 UploadSizeLimit 同一出发点;限制随路径而不同,所以用规则表,第一条命中的生效
|
||||
"""
|
||||
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
|
||||
from fastapi.responses import JSONResponse
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.types import ASGIApp, Message, Receive, Scope, Send
|
||||
|
||||
from app.common.response import error_content
|
||||
|
||||
|
||||
class BodyTooLarge(HTTPException):
|
||||
"""边读边数时超限;message 由全局 HTTP 异常处理放进信封,和声明超限的响应文案一致"""
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(413)
|
||||
self.message = message
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BodyLimitRule:
|
||||
methods: frozenset[str]
|
||||
path: re.Pattern[str]
|
||||
max_bytes: int
|
||||
message: str
|
||||
|
||||
|
||||
class BodyLimit:
|
||||
def __init__(self, app: ASGIApp, rules: list[BodyLimitRule]):
|
||||
self.app = app
|
||||
self.rules = rules
|
||||
|
||||
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
||||
rule = self._match(scope) if scope["type"] == "http" else None
|
||||
if rule is None:
|
||||
await self.app(scope, receive, send)
|
||||
return
|
||||
length = dict(scope["headers"]).get(b"content-length", b"")
|
||||
if length.isdigit() and int(length) > rule.max_bytes:
|
||||
response = JSONResponse(error_content(413, rule.message), 413)
|
||||
await response(scope, receive, send)
|
||||
return
|
||||
received = 0
|
||||
|
||||
async def counted() -> Message:
|
||||
nonlocal received
|
||||
message = await receive()
|
||||
if message["type"] == "http.request":
|
||||
received += len(message.get("body", b""))
|
||||
if received > rule.max_bytes:
|
||||
# 抛出的异常由应用里统一的 HTTP 异常处理转成 413 信封
|
||||
raise BodyTooLarge(rule.message)
|
||||
return message
|
||||
|
||||
await self.app(scope, counted, send)
|
||||
|
||||
def _match(self, scope: Scope) -> BodyLimitRule | None:
|
||||
return next(
|
||||
(
|
||||
rule
|
||||
for rule in self.rules
|
||||
if scope["method"] in rule.methods
|
||||
and rule.path.fullmatch(scope["path"])
|
||||
),
|
||||
None,
|
||||
)
|
||||
@@ -50,6 +50,8 @@ async def handle_http_exception(_request: Request, exc: HTTPException) -> JSONRe
|
||||
message = HTTPStatus(exc.status_code).phrase
|
||||
except ValueError:
|
||||
message = "请求失败"
|
||||
# 个别异常自带面向用户的文案,例如请求体超限
|
||||
message = getattr(exc, "message", None) or message
|
||||
return JSONResponse(
|
||||
status_code=exc.status_code,
|
||||
content=error_content(exc.status_code, message),
|
||||
|
||||
Reference in New Issue
Block a user