mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
fix(proxy): 新增响应头过滤函数,避免透传不兼容的上游头
添加 filter_proxy_response_headers 函数过滤 content-length、content-encoding、 transfer-encoding 等 hop-by-hop 和 body-dependent 头,防止客户端解码失败。 统一在 chat_handler_base、cli_handler_base、stream_telemetry 和 usage recorder 中使用该函数处理响应头透传。
This commit is contained in:
@@ -35,7 +35,7 @@ from src.api.handlers.base.response_parser import ResponseParser
|
||||
from src.api.handlers.base.stream_context import StreamContext
|
||||
from src.api.handlers.base.stream_processor import StreamProcessor
|
||||
from src.api.handlers.base.stream_telemetry import StreamTelemetryRecorder
|
||||
from src.api.handlers.base.utils import build_sse_headers
|
||||
from src.api.handlers.base.utils import build_sse_headers, filter_proxy_response_headers
|
||||
from src.config.settings import config
|
||||
from src.core.error_utils import extract_client_error_message
|
||||
from src.core.exceptions import (
|
||||
@@ -387,7 +387,7 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
|
||||
# 透传提供商的响应头给客户端
|
||||
# 同时添加必要的 SSE 头以确保流式传输正常工作
|
||||
client_headers = dict(ctx.response_headers) if ctx.response_headers else {}
|
||||
client_headers = filter_proxy_response_headers(ctx.response_headers)
|
||||
# 添加/覆盖 SSE 必需的头
|
||||
client_headers.update(build_sse_headers())
|
||||
client_headers["content-type"] = "text/event-stream"
|
||||
@@ -849,7 +849,7 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
|
||||
# 非流式成功时,返回给客户端的是提供商响应头(透传)
|
||||
# JSONResponse 会自动设置 content-type,但我们记录实际返回的完整头
|
||||
client_response_headers = dict(response_headers) if response_headers else {}
|
||||
client_response_headers = filter_proxy_response_headers(response_headers)
|
||||
client_response_headers["content-type"] = "application/json"
|
||||
|
||||
total_cost = await self.telemetry.record_success(
|
||||
@@ -888,7 +888,7 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
return JSONResponse(
|
||||
status_code=status_code,
|
||||
content=response_json,
|
||||
headers=response_headers if response_headers else None,
|
||||
headers=client_response_headers,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -44,6 +44,7 @@ from src.api.handlers.base.utils import (
|
||||
build_sse_headers,
|
||||
check_html_response,
|
||||
check_prefetched_response_error,
|
||||
filter_proxy_response_headers,
|
||||
)
|
||||
from src.config.constants import StreamDefaults
|
||||
from src.config.settings import config
|
||||
@@ -381,7 +382,7 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
|
||||
# 透传提供商的响应头给客户端
|
||||
# 同时添加必要的 SSE 头以确保流式传输正常工作
|
||||
client_headers = dict(ctx.response_headers) if ctx.response_headers else {}
|
||||
client_headers = filter_proxy_response_headers(ctx.response_headers)
|
||||
# 添加/覆盖 SSE 必需的头
|
||||
client_headers.update(build_sse_headers())
|
||||
client_headers["content-type"] = "text/event-stream"
|
||||
@@ -1379,7 +1380,7 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
self._finalize_stream_metadata(ctx)
|
||||
|
||||
# 流式成功时,返回给客户端的是提供商响应头 + SSE 必需头
|
||||
client_response_headers = dict(ctx.response_headers) if ctx.response_headers else {}
|
||||
client_response_headers = filter_proxy_response_headers(ctx.response_headers)
|
||||
client_response_headers.update({
|
||||
"Cache-Control": "no-cache, no-transform",
|
||||
"X-Accel-Buffering": "no",
|
||||
@@ -1769,7 +1770,7 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
actual_request_body = provider_request_body or original_request_body
|
||||
|
||||
# 非流式成功时,返回给客户端的是提供商响应头(透传)
|
||||
client_response_headers = dict(response_headers) if response_headers else {}
|
||||
client_response_headers = filter_proxy_response_headers(response_headers)
|
||||
client_response_headers["content-type"] = "application/json"
|
||||
|
||||
total_cost = await self.telemetry.record_success(
|
||||
@@ -1805,7 +1806,7 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
return JSONResponse(
|
||||
status_code=status_code,
|
||||
content=response_json,
|
||||
headers=response_headers if response_headers else None,
|
||||
headers=client_response_headers,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -15,6 +15,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from src.api.handlers.base.base_handler import MessageTelemetry
|
||||
from src.api.handlers.base.stream_context import StreamContext
|
||||
from src.api.handlers.base.utils import filter_proxy_response_headers
|
||||
from src.config.settings import config
|
||||
from src.core.logger import logger
|
||||
from src.database import get_db
|
||||
@@ -155,7 +156,7 @@ class StreamTelemetryRecorder:
|
||||
) -> None:
|
||||
"""记录成功的请求"""
|
||||
# 流式成功时,返回给客户端的是提供商响应头 + SSE 必需头
|
||||
client_response_headers = dict(ctx.response_headers) if ctx.response_headers else {}
|
||||
client_response_headers = filter_proxy_response_headers(ctx.response_headers)
|
||||
client_response_headers.update({
|
||||
"Cache-Control": "no-cache, no-transform",
|
||||
"X-Accel-Buffering": "no",
|
||||
|
||||
@@ -94,6 +94,40 @@ def build_sse_headers(extra_headers: Optional[Dict[str, str]] = None) -> Dict[st
|
||||
return headers
|
||||
|
||||
|
||||
_PROXY_RESPONSE_HEADER_BLOCKLIST = frozenset(
|
||||
{
|
||||
# Body-dependent headers: 我们会重编码响应体(JSONResponse / SSE),不能透传上游值
|
||||
"content-length",
|
||||
"content-encoding",
|
||||
"transfer-encoding",
|
||||
"content-type",
|
||||
# Hop-by-hop headers (RFC 7230)
|
||||
"connection",
|
||||
"keep-alive",
|
||||
"proxy-authenticate",
|
||||
"proxy-authorization",
|
||||
"te",
|
||||
"trailer",
|
||||
"upgrade",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def filter_proxy_response_headers(headers: Optional[Dict[str, str]]) -> Dict[str, str]:
|
||||
"""
|
||||
过滤上游响应头中不应透传给客户端的字段。
|
||||
|
||||
主要用于“解析/转换后再返回”的场景:
|
||||
- 非流式:我们会 `resp.json()` 后再由 `JSONResponse` 重新序列化
|
||||
- 流式:我们会解析/重组 SSE 行再输出
|
||||
|
||||
如果透传上游的 `content-length/content-encoding/...`,会导致客户端解码失败或等待更多字节。
|
||||
"""
|
||||
if not headers:
|
||||
return {}
|
||||
return {k: v for k, v in headers.items() if k.lower() not in _PROXY_RESPONSE_HEADER_BLOCKLIST}
|
||||
|
||||
|
||||
def check_html_response(line: str) -> bool:
|
||||
"""
|
||||
检查行是否为 HTML 响应(base_url 配置错误的常见症状)
|
||||
|
||||
Reference in New Issue
Block a user