|
|
|
|
|
import httpx |
|
|
from typing import Optional |
|
|
from fastapi import Request, Response, HTTPException, status |
|
|
from fastapi.responses import StreamingResponse |
|
|
import asyncio |
|
|
import logging |
|
|
|
|
|
from config import Config |
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
class OpenCodeProxy: |
|
|
"""OpenCode 代理类""" |
|
|
|
|
|
def __init__(self): |
|
|
self.base_url = Config.get_opencode_url() |
|
|
self.client = httpx.AsyncClient( |
|
|
base_url=self.base_url, |
|
|
timeout=Config.REQUEST_TIMEOUT |
|
|
) |
|
|
|
|
|
async def proxy_request( |
|
|
self, |
|
|
request: Request, |
|
|
path: str, |
|
|
current_user: Optional[dict] = None |
|
|
) -> Response: |
|
|
""" |
|
|
代理请求到 OpenCode 服务 |
|
|
|
|
|
Args: |
|
|
request: FastAPI 请求对象 |
|
|
path: 转发路径 |
|
|
current_user: 当前用户信息(用于认证) |
|
|
|
|
|
Returns: |
|
|
Response: 代理响应 |
|
|
""" |
|
|
try: |
|
|
|
|
|
target_url = f"/{path}" if path else "/" |
|
|
|
|
|
|
|
|
headers = dict(request.headers) |
|
|
|
|
|
|
|
|
headers.pop("host", None) |
|
|
headers.pop("content-length", None) |
|
|
|
|
|
|
|
|
if current_user: |
|
|
headers["X-User"] = current_user.get("username", "") |
|
|
|
|
|
|
|
|
body = await request.body() |
|
|
|
|
|
|
|
|
response = await self.client.request( |
|
|
method=request.method, |
|
|
url=target_url, |
|
|
headers=headers, |
|
|
content=body if body else None, |
|
|
params=request.query_params |
|
|
) |
|
|
|
|
|
|
|
|
response_headers = dict(response.headers) |
|
|
|
|
|
|
|
|
response_headers.pop("content-encoding", None) |
|
|
response_headers.pop("transfer-encoding", None) |
|
|
|
|
|
return Response( |
|
|
content=response.content, |
|
|
status_code=response.status_code, |
|
|
headers=response_headers |
|
|
) |
|
|
|
|
|
except httpx.ConnectError: |
|
|
logger.error(f"无法连接到 OpenCode 服务: {self.base_url}") |
|
|
raise HTTPException( |
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, |
|
|
detail="OpenCode 服务不可用,请检查服务状态" |
|
|
) |
|
|
|
|
|
except httpx.TimeoutException: |
|
|
logger.error(f"OpenCode 服务响应超时: {self.base_url}") |
|
|
raise HTTPException( |
|
|
status_code=status.HTTP_504_GATEWAY_TIMEOUT, |
|
|
detail="OpenCode 服务响应超时" |
|
|
) |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"代理请求失败: {str(e)}") |
|
|
raise HTTPException( |
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
|
|
detail="代理请求失败" |
|
|
) |
|
|
|
|
|
async def proxy_websocket(self, path: str): |
|
|
""" |
|
|
代理 WebSocket 连接(如果需要) |
|
|
目前为占位符实现 |
|
|
""" |
|
|
|
|
|
|
|
|
pass |
|
|
|
|
|
async def check_service_health(self) -> bool: |
|
|
"""检查 OpenCode 服务健康状态""" |
|
|
try: |
|
|
|
|
|
import socket |
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|
|
result = sock.connect_ex(('localhost', 3000)) |
|
|
sock.close() |
|
|
|
|
|
if result != 0: |
|
|
return False |
|
|
|
|
|
|
|
|
response = await self.client.get("/", timeout=5) |
|
|
return response.status_code < 500 |
|
|
except Exception: |
|
|
return False |
|
|
|
|
|
async def get_service_info(self) -> dict: |
|
|
"""获取 OpenCode 服务信息""" |
|
|
try: |
|
|
response = await self.client.get("/", timeout=5) |
|
|
return { |
|
|
"status": "healthy" if response.status_code < 500 else "unhealthy", |
|
|
"status_code": response.status_code, |
|
|
"response_time": response.elapsed.total_seconds() if hasattr(response, 'elapsed') else None |
|
|
} |
|
|
except Exception as e: |
|
|
return { |
|
|
"status": "unreachable", |
|
|
"error": str(e) |
|
|
} |
|
|
|
|
|
|
|
|
opencode_proxy = OpenCodeProxy() |
|
|
|
|
|
async def proxy_to_opencode( |
|
|
request: Request, |
|
|
path: str, |
|
|
current_user: Optional[dict] = None |
|
|
) -> Response: |
|
|
""" |
|
|
便捷函数:代理请求到 OpenCode |
|
|
""" |
|
|
return await opencode_proxy.proxy_request(request, path, current_user) |