File size: 4,981 Bytes
d99a1a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b020ec1
 
 
 
 
 
 
 
 
 
d99a1a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# 代理模块 - 处理 OpenCode 请求转发
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:
            # 构建目标 URL
            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 连接(如果需要)
        目前为占位符实现
        """
        # TODO: 实现 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
            
            # 端口开放,再检查 HTTP 响应
            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)