mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
feat(aether-proxy): 网络层全面优化与代理耗时追踪
aether-proxy: - 新增 20+ 可配置参数:Aether API/delegate/CONNECT 各阶段超时、连接池、TCP keepalive/nodelay - AetherClient 支持指数退避重试(可配置次数/延迟)和 HTTP/2 - 新增 DNS 缓存(TTL + 容量限制)避免重复解析 - 新增并发连接数限制(Semaphore,默认基于硬件估算) - CONNECT 隧道增加建连超时和升级超时 - TLS 增加握手超时、会话缓存(session ticket + memory cache)、ALPN 协商 - 新增 ProxyMetrics 收集请求计数和延迟,通过心跳上报 - delegate 响应注入 X-Proxy-Timing 头(dns_ms/upstream_ms/total_ms) 后端: - StreamContext 和 handler 提取 X-Proxy-Timing 写入 proxy_info 追踪数据 前端: - 请求时间线展示代理分阶段耗时(DNS/上游) - Endpoint 添加按钮改为文字按钮样式
This commit is contained in:
@@ -40,7 +40,11 @@ from src.api.handlers.base.base_handler import (
|
||||
from src.api.handlers.base.parsers import get_parser_for_format
|
||||
from src.api.handlers.base.request_builder import PassthroughRequestBuilder, get_provider_auth
|
||||
from src.api.handlers.base.response_parser import ResponseParser
|
||||
from src.api.handlers.base.stream_context import StreamContext, is_format_converted
|
||||
from src.api.handlers.base.stream_context import (
|
||||
StreamContext,
|
||||
extract_proxy_timing,
|
||||
is_format_converted,
|
||||
)
|
||||
from src.api.handlers.base.stream_processor import StreamProcessor
|
||||
from src.api.handlers.base.stream_telemetry import StreamTelemetryRecorder
|
||||
from src.api.handlers.base.upstream_stream_bridge import (
|
||||
@@ -975,6 +979,7 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
|
||||
ctx.status_code = resp.status_code
|
||||
ctx.response_headers = dict(resp.headers)
|
||||
ctx.set_proxy_timing(ctx.response_headers)
|
||||
if envelope:
|
||||
envelope.on_http_status(base_url=ctx.selected_base_url, status_code=ctx.status_code)
|
||||
|
||||
@@ -1005,6 +1010,7 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
resp = await http_client.post(**_pkw)
|
||||
ctx.status_code = resp.status_code
|
||||
ctx.response_headers = dict(resp.headers)
|
||||
ctx.set_proxy_timing(ctx.response_headers)
|
||||
if envelope:
|
||||
envelope.on_http_status(
|
||||
base_url=ctx.selected_base_url, status_code=ctx.status_code
|
||||
@@ -1168,6 +1174,7 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
|
||||
ctx.status_code = stream_response.status_code
|
||||
ctx.response_headers = dict(stream_response.headers)
|
||||
ctx.set_proxy_timing(ctx.response_headers)
|
||||
if envelope:
|
||||
envelope.on_http_status(
|
||||
base_url=ctx.selected_base_url,
|
||||
@@ -1643,6 +1650,7 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
|
||||
status_code = stream_resp.status_code
|
||||
response_headers = dict(stream_resp.headers)
|
||||
extract_proxy_timing(sync_proxy_info, response_headers)
|
||||
|
||||
if envelope:
|
||||
envelope.on_http_status(
|
||||
@@ -1695,6 +1703,7 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
|
||||
status_code = resp.status_code
|
||||
response_headers = dict(resp.headers)
|
||||
extract_proxy_timing(sync_proxy_info, response_headers)
|
||||
|
||||
if envelope:
|
||||
envelope.on_http_status(base_url=selected_base_url_cached, status_code=status_code)
|
||||
|
||||
@@ -43,7 +43,11 @@ from src.api.handlers.base.request_builder import PassthroughRequestBuilder, get
|
||||
from src.api.handlers.base.response_parser import (
|
||||
ResponseParser,
|
||||
)
|
||||
from src.api.handlers.base.stream_context import StreamContext, is_format_converted
|
||||
from src.api.handlers.base.stream_context import (
|
||||
StreamContext,
|
||||
extract_proxy_timing,
|
||||
is_format_converted,
|
||||
)
|
||||
from src.api.handlers.base.upstream_stream_bridge import (
|
||||
aggregate_upstream_stream_to_internal_response,
|
||||
)
|
||||
@@ -90,7 +94,7 @@ from src.utils.sse_parser import SSEEventParser
|
||||
from src.utils.timeout import read_first_chunk_with_ttfb_timeout
|
||||
|
||||
# ==============================================================================
|
||||
# SSE 行解析辅助函数
|
||||
# 辅助函数
|
||||
# ==============================================================================
|
||||
|
||||
|
||||
@@ -946,6 +950,7 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
|
||||
ctx.status_code = resp.status_code
|
||||
ctx.response_headers = dict(resp.headers)
|
||||
ctx.set_proxy_timing(ctx.response_headers)
|
||||
if envelope:
|
||||
envelope.on_http_status(base_url=ctx.selected_base_url, status_code=ctx.status_code)
|
||||
|
||||
@@ -978,6 +983,7 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
ctx.set_ttfb_ms(int((time.monotonic() - _connect_start) * 1000))
|
||||
ctx.status_code = resp.status_code
|
||||
ctx.response_headers = dict(resp.headers)
|
||||
ctx.set_proxy_timing(ctx.response_headers)
|
||||
if envelope:
|
||||
envelope.on_http_status(
|
||||
base_url=ctx.selected_base_url, status_code=ctx.status_code
|
||||
@@ -1142,6 +1148,7 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
|
||||
ctx.status_code = stream_response.status_code
|
||||
ctx.response_headers = dict(stream_response.headers)
|
||||
ctx.set_proxy_timing(ctx.response_headers)
|
||||
|
||||
logger.debug(f" └─ 收到响应: status={stream_response.status_code}")
|
||||
|
||||
@@ -3079,6 +3086,7 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
|
||||
status_code = stream_resp.status_code
|
||||
response_headers = dict(stream_resp.headers)
|
||||
extract_proxy_timing(sync_proxy_info, response_headers)
|
||||
|
||||
if envelope:
|
||||
envelope.on_http_status(
|
||||
@@ -3131,6 +3139,7 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
|
||||
status_code = resp.status_code
|
||||
response_headers = dict(resp.headers)
|
||||
extract_proxy_timing(sync_proxy_info, response_headers)
|
||||
|
||||
if envelope:
|
||||
envelope.on_http_status(base_url=selected_base_url_cached, status_code=status_code)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING, Any
|
||||
@@ -18,6 +19,21 @@ if TYPE_CHECKING:
|
||||
from src.core.api_format.conversion.stream_state import StreamState
|
||||
|
||||
|
||||
def extract_proxy_timing(proxy_info: dict[str, Any] | None, headers: dict[str, str]) -> None:
|
||||
"""从响应头中提取代理分阶段耗时(X-Proxy-Timing)写入 proxy_info"""
|
||||
if proxy_info is None:
|
||||
return
|
||||
timing_raw = headers.get("x-proxy-timing")
|
||||
if not timing_raw:
|
||||
return
|
||||
try:
|
||||
timing = json.loads(timing_raw)
|
||||
if isinstance(timing, dict):
|
||||
proxy_info["timing"] = timing
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
|
||||
|
||||
def is_format_converted(
|
||||
provider_api_format: str | None,
|
||||
client_api_format: str | None,
|
||||
@@ -272,6 +288,10 @@ class StreamContext:
|
||||
if self.proxy_info is not None:
|
||||
self.proxy_info["ttfb_ms"] = ms
|
||||
|
||||
def set_proxy_timing(self, headers: dict[str, str]) -> None:
|
||||
"""从代理响应头中提取分阶段耗时信息(X-Proxy-Timing)"""
|
||||
extract_proxy_timing(self.proxy_info, headers)
|
||||
|
||||
def build_response_body(self, response_time_ms: int) -> dict[str, Any]:
|
||||
"""
|
||||
构建响应体元数据
|
||||
|
||||
Reference in New Issue
Block a user