mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-01 12:56:34 +08:00
57 lines
2.0 KiB
Python
57 lines
2.0 KiB
Python
"""
|
||
HTTP 请求关联 ID 的 ASGI 适配器。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from starlette.types import ASGIApp, Message, Receive, Scope, Send
|
||
|
||
from app.runtime.correlation import (
|
||
CORRELATION_ID_HEADER,
|
||
correlation_scope,
|
||
normalize_correlation_id,
|
||
)
|
||
|
||
|
||
class CorrelationIdMiddleware:
|
||
"""
|
||
验证入口 ID、绑定请求上下文并把同一 ID 写回响应。
|
||
"""
|
||
|
||
def __init__(self, app: ASGIApp) -> None:
|
||
"""
|
||
保存下游 ASGI 应用。
|
||
"""
|
||
self._app = app
|
||
|
||
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
||
"""
|
||
为 HTTP 请求绑定关联 ID,绑定范围覆盖完整流式响应生命周期。
|
||
|
||
其他连接类型直接转交下游;请求结束或抛出异常后恢复原有上下文。
|
||
"""
|
||
if scope.get("type") != "http":
|
||
await self._app(scope, receive, send)
|
||
return
|
||
raw_headers = dict(scope.get("headers") or [])
|
||
candidate = raw_headers.get(CORRELATION_ID_HEADER.lower().encode("ascii"))
|
||
correlation_id = normalize_correlation_id(
|
||
candidate.decode("ascii", errors="ignore") if candidate else None
|
||
)
|
||
scope.setdefault("state", {})["request_id"] = correlation_id
|
||
|
||
async def send_with_correlation(message: Message) -> None:
|
||
"""
|
||
在响应开始帧中覆盖为当前请求的安全关联 ID。
|
||
"""
|
||
if message.get("type") == "http.response.start":
|
||
headers = list(message.get("headers") or [])
|
||
header_name = CORRELATION_ID_HEADER.lower().encode("ascii")
|
||
headers = [item for item in headers if item[0].lower() != header_name]
|
||
headers.append((header_name, correlation_id.encode("ascii")))
|
||
message["headers"] = headers
|
||
await send(message)
|
||
|
||
with correlation_scope(correlation_id):
|
||
await self._app(scope, receive, send_with_correlation)
|