fix: 处理客户端断开连接情况,以防止出现虚假的系统错误报告

捕获 starlette.requests.ClientDisconnect 异常,避免客户端主动断开连接时被当作系统未知错误(500)处理。
- 在读取请求体阶段捕获 ClientDisconnect,返回 499 状态码
- 在 adapter.handle 阶段捕获 ClientDisconnect,记录审计日志并返回 499
- 日志级别从 ERROR 降为 WARNING,减少误报告警噪音
This commit is contained in:
AAEE86
2026-03-09 10:38:14 +08:00
parent 4dbfeb87b8
commit 258be3b640

View File

@@ -5,8 +5,10 @@ from enum import Enum
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
from fastapi import HTTPException, Request from fastapi import HTTPException, Request
from fastapi.responses import JSONResponse
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from starlette.requests import ClientDisconnect
from src.config.settings import config from src.config.settings import config
from src.core.enums import UserRole from src.core.enums import UserRole
@@ -144,6 +146,16 @@ class ApiRequestPipeline:
status_code=408, status_code=408,
detail=f"Request timeout: body not received within {timeout_sec} seconds", detail=f"Request timeout: body not received within {timeout_sec} seconds",
) )
except ClientDisconnect:
logger.warning(
"[Pipeline] 客户端在读取请求体期间断开连接: {} {}",
http_request.method,
http_request.url.path,
)
return JSONResponse(
status_code=499,
content={"error": "client_disconnected", "message": "Client closed request"},
)
context_start = PerfRecorder.start(force=perf_sampled) context_start = PerfRecorder.start(force=perf_sampled)
context = ApiRequestContext.build( context = ApiRequestContext.build(
request=http_request, request=http_request,
@@ -199,6 +211,25 @@ class ApiRequestPipeline:
error=err_detail, error=err_detail,
) )
raise raise
except ClientDisconnect:
handle_duration = PerfRecorder.stop(handle_start, "pipeline_handle", labels=perf_labels)
_record_perf_metric("handle_ms", handle_duration)
logger.warning(
"[Pipeline] 客户端在处理期间断开连接: {} {}",
context.request.method,
context.request.url.path,
)
self._record_audit_event(
context,
adapter,
success=False,
status_code=499,
error="client_disconnected",
)
return JSONResponse(
status_code=499,
content={"error": "client_disconnected", "message": "Client closed request"},
)
except Exception as exc: except Exception as exc:
handle_duration = PerfRecorder.stop(handle_start, "pipeline_handle", labels=perf_labels) handle_duration = PerfRecorder.stop(handle_start, "pipeline_handle", labels=perf_labels)
_record_perf_metric("handle_ms", handle_duration) _record_perf_metric("handle_ms", handle_duration)