2025-12-10 20:52:44 +08:00
|
|
|
|
"""
|
|
|
|
|
|
请求处理工具函数
|
|
|
|
|
|
提供统一的HTTP请求信息提取功能
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-01-30 14:30:57 +08:00
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
2026-03-31 19:19:04 +08:00
|
|
|
|
from dataclasses import asdict, dataclass
|
|
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
from fastapi import Request
|
|
|
|
|
|
|
2026-03-31 19:19:04 +08:00
|
|
|
|
TRACE_ID_HEADER = "x-trace-id"
|
|
|
|
|
|
_MISSING = object()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
|
class RequestIdentityMetadata:
|
|
|
|
|
|
"""请求身份元数据(最小集合)。"""
|
|
|
|
|
|
|
|
|
|
|
|
request_id: str | None
|
|
|
|
|
|
client_ip: str
|
|
|
|
|
|
user_agent: str
|
|
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
def get_client_ip(request: Request) -> str:
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取客户端真实IP地址
|
|
|
|
|
|
|
|
|
|
|
|
按优先级检查:
|
2026-01-06 16:29:03 +08:00
|
|
|
|
1. X-Real-IP 头(最可靠,由最外层可信 Nginx 直接设置)
|
|
|
|
|
|
2. X-Forwarded-For 头的第一个 IP(原始客户端)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
3. 直接客户端IP
|
|
|
|
|
|
|
2025-12-18 19:07:20 +08:00
|
|
|
|
安全说明:
|
2026-01-06 16:29:03 +08:00
|
|
|
|
- X-Real-IP 优先级最高,因为它通常由最外层 Nginx 设置为 $remote_addr,
|
|
|
|
|
|
Nginx 会直接覆盖这个头,不会传递客户端伪造的值
|
|
|
|
|
|
- 只要最外层 Nginx 配置了 proxy_set_header X-Real-IP $remote_addr; 即可正确获取真实 IP
|
2025-12-18 19:07:20 +08:00
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
Args:
|
|
|
|
|
|
request: FastAPI Request 对象
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
str: 客户端IP地址,如果无法获取则返回 "unknown"
|
|
|
|
|
|
"""
|
2026-01-06 16:29:03 +08:00
|
|
|
|
# 优先检查 X-Real-IP 头(由最外层 Nginx 设置,最可靠)
|
|
|
|
|
|
real_ip = request.headers.get("X-Real-IP")
|
|
|
|
|
|
if real_ip:
|
|
|
|
|
|
return real_ip.strip()
|
2025-12-18 19:07:20 +08:00
|
|
|
|
|
2026-01-06 16:29:03 +08:00
|
|
|
|
# 检查 X-Forwarded-For 头,取第一个 IP(原始客户端)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
forwarded_for = request.headers.get("X-Forwarded-For")
|
|
|
|
|
|
if forwarded_for:
|
2025-12-18 19:07:20 +08:00
|
|
|
|
# X-Forwarded-For 格式: "client, proxy1, proxy2"
|
|
|
|
|
|
ips = [ip.strip() for ip in forwarded_for.split(",") if ip.strip()]
|
2026-01-06 16:29:03 +08:00
|
|
|
|
if ips:
|
2025-12-18 19:07:20 +08:00
|
|
|
|
return ips[0]
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
# 回退到直接客户端IP
|
|
|
|
|
|
if request.client and request.client.host:
|
|
|
|
|
|
return request.client.host
|
|
|
|
|
|
|
|
|
|
|
|
return "unknown"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_user_agent(request: Request) -> str:
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取用户代理字符串
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
request: FastAPI Request 对象
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
str: User-Agent 字符串,如果无法获取则返回 "unknown"
|
|
|
|
|
|
"""
|
|
|
|
|
|
return request.headers.get("User-Agent", "unknown")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def get_request_id(request: Request) -> str | None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""
|
|
|
|
|
|
获取请求ID(如果存在)
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
request: FastAPI Request 对象
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Optional[str]: 请求ID,如果不存在则返回 None
|
|
|
|
|
|
"""
|
2026-03-31 19:19:04 +08:00
|
|
|
|
request_id = getattr(request.state, "request_id", None)
|
|
|
|
|
|
if request_id:
|
|
|
|
|
|
return request_id
|
|
|
|
|
|
|
|
|
|
|
|
trace_id = request.headers.get(TRACE_ID_HEADER)
|
|
|
|
|
|
if trace_id:
|
|
|
|
|
|
return trace_id.strip() or None
|
|
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def update_request_state(
|
|
|
|
|
|
request: Request,
|
|
|
|
|
|
*,
|
|
|
|
|
|
request_id: object = _MISSING,
|
|
|
|
|
|
user_id: object = _MISSING,
|
|
|
|
|
|
api_key_id: object = _MISSING,
|
|
|
|
|
|
management_token_id: object = _MISSING,
|
|
|
|
|
|
user_session_id: object = _MISSING,
|
|
|
|
|
|
prefetched_balance_remaining: object = _MISSING,
|
|
|
|
|
|
gateway_execution_path: object = _MISSING,
|
|
|
|
|
|
rate_limit_scope: object = _MISSING,
|
|
|
|
|
|
) -> None:
|
|
|
|
|
|
"""集中维护请求级 runtime state,减少散点赋值。"""
|
|
|
|
|
|
|
|
|
|
|
|
if request_id is not _MISSING:
|
|
|
|
|
|
request.state.request_id = request_id
|
|
|
|
|
|
if user_id is not _MISSING:
|
|
|
|
|
|
request.state.user_id = user_id
|
|
|
|
|
|
if api_key_id is not _MISSING:
|
|
|
|
|
|
request.state.api_key_id = api_key_id
|
|
|
|
|
|
if management_token_id is not _MISSING:
|
|
|
|
|
|
request.state.management_token_id = management_token_id
|
|
|
|
|
|
if user_session_id is not _MISSING:
|
|
|
|
|
|
request.state.user_session_id = user_session_id
|
|
|
|
|
|
if prefetched_balance_remaining is not _MISSING:
|
|
|
|
|
|
request.state.prefetched_balance_remaining = prefetched_balance_remaining
|
|
|
|
|
|
if gateway_execution_path is not _MISSING:
|
|
|
|
|
|
request.state.gateway_execution_path = gateway_execution_path
|
|
|
|
|
|
if rate_limit_scope is not _MISSING:
|
|
|
|
|
|
request.state.rate_limit_scope = rate_limit_scope
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_request_metadata(request: Request) -> dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取请求的完整元数据
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
request: FastAPI Request 对象
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
dict: 包含请求元数据的字典
|
|
|
|
|
|
"""
|
2026-03-31 19:19:04 +08:00
|
|
|
|
identity = get_request_identity_metadata(request)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
return {
|
2026-03-31 19:19:04 +08:00
|
|
|
|
**asdict(identity),
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"method": request.method,
|
|
|
|
|
|
"path": request.url.path,
|
|
|
|
|
|
"query_params": str(request.query_params) if request.query_params else None,
|
|
|
|
|
|
"content_type": request.headers.get("Content-Type"),
|
|
|
|
|
|
"content_length": request.headers.get("Content-Length"),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-31 19:19:04 +08:00
|
|
|
|
def get_request_identity_metadata(request: Request) -> RequestIdentityMetadata:
|
|
|
|
|
|
"""集中读取 request_id/client_ip/user_agent,避免散点访问。"""
|
|
|
|
|
|
|
|
|
|
|
|
return RequestIdentityMetadata(
|
|
|
|
|
|
request_id=get_request_id(request),
|
|
|
|
|
|
client_ip=get_client_ip(request),
|
|
|
|
|
|
user_agent=get_user_agent(request),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-06 16:29:03 +08:00
|
|
|
|
def extract_ip_from_headers(headers: dict) -> str:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""
|
|
|
|
|
|
从HTTP头字典中提取IP地址(用于中间件等场景)
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
headers: HTTP头字典
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
str: 客户端IP地址
|
|
|
|
|
|
"""
|
2026-01-06 16:29:03 +08:00
|
|
|
|
# 优先检查 X-Real-IP(由最外层 Nginx 设置,最可靠)
|
|
|
|
|
|
real_ip = headers.get("x-real-ip", "")
|
|
|
|
|
|
if real_ip:
|
|
|
|
|
|
return real_ip.strip()
|
2025-12-18 19:07:20 +08:00
|
|
|
|
|
2026-01-06 16:29:03 +08:00
|
|
|
|
# 检查 X-Forwarded-For,取第一个 IP
|
2025-12-10 20:52:44 +08:00
|
|
|
|
forwarded_for = headers.get("x-forwarded-for", "")
|
|
|
|
|
|
if forwarded_for:
|
2025-12-18 19:07:20 +08:00
|
|
|
|
ips = [ip.strip() for ip in forwarded_for.split(",") if ip.strip()]
|
2026-01-06 16:29:03 +08:00
|
|
|
|
if ips:
|
2025-12-18 19:07:20 +08:00
|
|
|
|
return ips[0]
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
return "unknown"
|