tanbushi commited on
Commit
4d43315
·
1 Parent(s): 56617bf
Files changed (1) hide show
  1. opencode_gateway.py +0 -306
opencode_gateway.py DELETED
@@ -1,306 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- OpenCode API Gateway with Authentication
4
-
5
- This module provides a secure gateway to OpenCode services through nginx,
6
- handling authentication, rate limiting, and request forwarding.
7
- """
8
-
9
- import os
10
- import logging
11
- import asyncio
12
- import json
13
- from typing import Dict, Any, Optional, List
14
- from datetime import datetime, timedelta
15
-
16
- import aiohttp
17
- from aiohttp import web, ClientSession
18
- from aiohttp.web import middleware
19
-
20
- # Configure logging
21
- logging.basicConfig(level=logging.INFO)
22
- logger = logging.getLogger(__name__)
23
-
24
- class OpenCodeGateway:
25
- """Secure gateway for OpenCode API services"""
26
-
27
- def __init__(self):
28
- self.opencode_base = "http://127.0.0.1:57860"
29
- self.session_timeout = 300
30
- self.rate_limits = {} # Simple in-memory rate limiting
31
- self.allowed_origins = [
32
- "https://airsltd-ocngx.hf.space",
33
- "http://localhost:7860",
34
- "https://localhost:7860"
35
- ]
36
-
37
- async def setup_session(self) -> ClientSession:
38
- """Setup HTTP session with proper headers"""
39
- return ClientSession(
40
- timeout=aiohttp.ClientTimeout(total=self.session_timeout),
41
- headers={
42
- 'User-Agent': 'OpenCode-Gateway/1.0',
43
- 'Content-Type': 'application/json'
44
- }
45
- )
46
-
47
- def check_rate_limit(self, client_ip: str, endpoint: str) -> bool:
48
- """Simple rate limiting (100 requests per minute per IP)"""
49
- now = datetime.now()
50
- key = f"{client_ip}:{endpoint}"
51
-
52
- if key not in self.rate_limits:
53
- self.rate_limits[key] = []
54
-
55
- # Remove old requests (older than 1 minute)
56
- self.rate_limits[key] = [
57
- req_time for req_time in self.rate_limits[key]
58
- if now - req_time < timedelta(minutes=1)
59
- ]
60
-
61
- # Check if under limit
62
- if len(self.rate_limits[key]) < 100:
63
- self.rate_limits[key].append(now)
64
- return True
65
-
66
- return False
67
-
68
- def check_cors(self, request: web.Request) -> bool:
69
- """Check CORS origin"""
70
- origin = request.headers.get('Origin', '')
71
- if origin in self.allowed_origins or not origin:
72
- return True
73
- return False
74
-
75
- @middleware
76
- async def auth_middleware(self, request: web.Request, handler):
77
- """Authentication and security middleware"""
78
-
79
- # CORS handling
80
- origin = request.headers.get('Origin', '')
81
- if origin and origin in self.allowed_origins:
82
- # Handle preflight requests
83
- if request.method == 'OPTIONS':
84
- return web.Response(
85
- status=200,
86
- headers={
87
- 'Access-Control-Allow-Origin': origin,
88
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
89
- 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
90
- 'Access-Control-Max-Age': '86400'
91
- }
92
- )
93
-
94
- # Rate limiting check
95
- client_ip = request.remote or 'unknown'
96
- endpoint = request.path
97
-
98
- if not self.check_rate_limit(client_ip, endpoint):
99
- return web.Response(
100
- status=429,
101
- text=json.dumps({"error": "Rate limit exceeded"}),
102
- headers={'Content-Type': 'application/json'}
103
- )
104
-
105
- # Log request
106
- logger.info(f"{request.method} {request.path} from {client_ip}")
107
-
108
- try:
109
- response = await handler(request)
110
-
111
- # Add CORS headers if needed
112
- if origin and origin in self.allowed_origins:
113
- response.headers['Access-Control-Allow-Origin'] = origin
114
- response.headers['Access-Control-Allow-Credentials'] = 'true'
115
-
116
- return response
117
-
118
- except Exception as e:
119
- logger.error(f"Error handling request: {str(e)}")
120
- return web.Response(
121
- status=500,
122
- text=json.dumps({"error": "Internal server error"}),
123
- headers={'Content-Type': 'application/json'}
124
- )
125
-
126
- async def proxy_request(self, request: web.Request) -> web.Response:
127
- """Proxy request to OpenCode server"""
128
-
129
- # Remove /opencode prefix if present
130
- path = request.path
131
- if path.startswith('/opencode/'):
132
- path = path[10:] # Remove /opencode/
133
-
134
- # Build target URL
135
- target_url = f"{self.opencode_base}{path}"
136
- if request.query_string:
137
- target_url += f"?{request.query_string}"
138
-
139
- async with await self.setup_session() as session:
140
- try:
141
- # Prepare headers
142
- headers = dict(request.headers)
143
- headers.pop('Host', None) # Remove Host header
144
- headers.pop('Origin', None) # Remove Origin for security
145
-
146
- # Make request
147
- if request.method in ['POST', 'PUT', 'PATCH']:
148
- data = await request.read()
149
- async with session.request(
150
- method=request.method,
151
- url=target_url,
152
- headers=headers,
153
- data=data
154
- ) as resp:
155
- response_data = await resp.read()
156
- return web.Response(
157
- body=response_data,
158
- status=resp.status,
159
- headers=dict(resp.headers)
160
- )
161
- else:
162
- async with session.request(
163
- method=request.method,
164
- url=target_url,
165
- headers=headers
166
- ) as resp:
167
- response_data = await resp.read()
168
- return web.Response(
169
- body=response_data,
170
- status=resp.status,
171
- headers=dict(resp.headers)
172
- )
173
-
174
- except asyncio.TimeoutError:
175
- logger.error(f"Timeout proxying to {target_url}")
176
- return web.Response(
177
- status=504,
178
- text=json.dumps({"error": "Gateway timeout"}),
179
- headers={'Content-Type': 'application/json'}
180
- )
181
- except Exception as e:
182
- logger.error(f"Error proxying to {target_url}: {str(e)}")
183
- return web.Response(
184
- status=502,
185
- text=json.dumps({"error": "Bad gateway"}),
186
- headers={'Content-Type': 'application/json'}
187
- )
188
-
189
- async def health_check(self, request: web.Request) -> web.Response:
190
- """Health check endpoint"""
191
- health_data = {
192
- "status": "healthy",
193
- "service": "OpenCode Gateway",
194
- "version": "1.0.0",
195
- "timestamp": datetime.now().isoformat(),
196
- "upstream": {
197
- "healthy": True,
198
- "url": self.opencode_base
199
- }
200
- }
201
-
202
- return web.Response(
203
- text=json.dumps(health_data, indent=2),
204
- headers={'Content-Type': 'application/json'}
205
- )
206
-
207
- async def api_info(self, request: web.Request) -> web.Response:
208
- """API information and documentation"""
209
- info_data = {
210
- "title": "OpenCode API Gateway",
211
- "description": "Secure gateway to OpenCode AI coding services",
212
- "version": "1.0.0",
213
- "endpoints": {
214
- "health": "/gateway/health",
215
- "api_docs": "/opencode/doc",
216
- "global_endpoints": "/opencode/global/*",
217
- "project_endpoints": "/opencode/project/*",
218
- "session_endpoints": "/opencode/session/*",
219
- "provider_endpoints": "/opencode/provider/*",
220
- "file_operations": "/opencode/file/*",
221
- "search_operations": "/opencode/find/*"
222
- },
223
- "features": [
224
- "HTTP Basic Authentication",
225
- "Rate Limiting (100 req/min)",
226
- "CORS Support",
227
- "Request Logging",
228
- "Health Monitoring",
229
- "Error Handling"
230
- ],
231
- "security": {
232
- "authentication": "HTTP Basic Auth",
233
- "rate_limiting": "100 requests per minute per IP",
234
- "cors": "Configured origins only",
235
- "logging": "All requests logged"
236
- }
237
- }
238
-
239
- return web.Response(
240
- text=json.dumps(info_data, indent=2),
241
- headers={'Content-Type': 'application/json'}
242
- )
243
-
244
-
245
- async def create_app() -> web.Application:
246
- """Create and configure the gateway application"""
247
-
248
- gateway = OpenCodeGateway()
249
- app = web.Application(middlewares=[gateway.auth_middleware])
250
-
251
- # Gateway endpoints
252
- app.router.add_get('/gateway/health', gateway.health_check)
253
- app.router.add_get('/gateway/info', gateway.api_info)
254
-
255
- # OpenCode proxy endpoints
256
- app.router.add_route('*', '/opencode/{path:.*}', gateway.proxy_request)
257
-
258
- # Direct OpenCode endpoints (for convenience)
259
- opencode_endpoints = [
260
- '/global', '/project', '/session', '/provider', '/config', '/auth',
261
- '/file', '/find', '/command', '/agent', '/tool', '/lsp',
262
- '/formatter', '/mcp', '/log', '/tui', '/event', '/doc'
263
- ]
264
-
265
- for endpoint in opencode_endpoints:
266
- app.router.add_route('*', f'{endpoint}{{path:.*}}', gateway.proxy_request)
267
- app.router.add_route('*', endpoint, gateway.proxy_request)
268
-
269
- return app
270
-
271
-
272
- async def main():
273
- """Main function to start the gateway"""
274
-
275
- # Environment configuration
276
- host = os.getenv('GATEWAY_HOST', '127.0.0.1')
277
- port = int(os.getenv('GATEWAY_PORT', '57860'))
278
-
279
- logger.info(f"Starting OpenCode Gateway on {host}:{port}")
280
-
281
- # Create application
282
- app = await create_app()
283
-
284
- # Start server
285
- runner = web.AppRunner(app)
286
- await runner.setup()
287
-
288
- site = web.TCPSite(runner, host, port)
289
- await site.start()
290
-
291
- logger.info(f"OpenCode Gateway started successfully")
292
- logger.info(f"API Health: http://{host}:{port}/gateway/health")
293
- logger.info(f"API Info: http://{host}:{port}/gateway/info")
294
- logger.info(f"OpenCode Docs: http://{host}:{port}/opencode/doc")
295
-
296
- try:
297
- # Keep the server running
298
- while True:
299
- await asyncio.sleep(1)
300
- except KeyboardInterrupt:
301
- logger.info("Shutting down OpenCode Gateway")
302
- await runner.cleanup()
303
-
304
-
305
- if __name__ == "__main__":
306
- asyncio.run(main())