55 lines
1.9 KiB
Python
55 lines
1.9 KiB
Python
"""请求上下文透传(迭代第 3 轮)。
|
||
|
||
**问题**:审计需要 IP / User-Agent,但业务服务层(`sales.services.confirm_sales_bill` 等)
|
||
拿不到 `request` 对象——它们是被视图调用的纯函数,签名里加 request 会污染业务 API。
|
||
|
||
**方案**:中间件在请求进入时把 request 存进 `contextvars.ContextVar`,
|
||
服务层通过 `current_request()` 读取。用 contextvars 而非 threading.local 是为了
|
||
在 ASGI(Graniot/asyncio)下也能正确隔离协程——threading.local 在异步场景会串请求。
|
||
|
||
安全性:只读用途(审计取 IP/UA),不做鉴权决策;请求结束即清除。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from contextvars import ContextVar
|
||
from typing import Optional
|
||
|
||
_current_request: ContextVar[Optional[object]] = ContextVar(
|
||
"dealerhub_current_request", default=None
|
||
)
|
||
|
||
|
||
def set_current_request(request) -> None:
|
||
"""中间件调用:绑定当前请求。"""
|
||
_current_request.set(request)
|
||
|
||
|
||
def clear_current_request() -> None:
|
||
"""中间件调用:解绑(防止跨请求泄漏)。"""
|
||
_current_request.set(None)
|
||
|
||
|
||
def current_request():
|
||
"""服务层调用:取当前请求(可能为 None,如后台任务/测试)。"""
|
||
return _current_request.get()
|
||
|
||
|
||
class RequestContextMiddleware:
|
||
"""把 request 放进 contextvar,供服务层读取(审计用)。
|
||
|
||
注意:本中间件**不**做鉴权或租户解析(那些由 TenantMiddleware / DRF 负责),
|
||
仅提供"当前请求"的只读视图。
|
||
"""
|
||
|
||
def __init__(self, get_response):
|
||
self.get_response = get_response
|
||
|
||
def __call__(self, request):
|
||
token = _current_request.set(request)
|
||
try:
|
||
return self.get_response(request)
|
||
finally:
|
||
# 用 reset 精确恢复到进入前的值,避免异步并发下互相覆盖
|
||
_current_request.reset(token)
|