initial commit
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Serve a SPA's dist/ directory + proxy API paths to a backend.
|
||||
|
||||
Usage: python3 proxy-server.py [--port PORT] [--dist DIST_DIR] [--backend BACKEND_URL]
|
||||
|
||||
Proxies:
|
||||
/pb/* → BACKEND/api/* (PocketBase SDK path)
|
||||
/api/* → BACKEND/api/* (direct API)
|
||||
/deepseek/* → https://api.deepseek.com/* (with API key from /tmp/deepseek_key.txt)
|
||||
/* → SPA static files (fallback to index.html for client-side routes)
|
||||
|
||||
Environment:
|
||||
DEEPSEEK_KEY — API key for DeepSeek (overrides /tmp/deepseek_key.txt)
|
||||
"""
|
||||
|
||||
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
||||
import urllib.request, os, sys, argparse
|
||||
|
||||
def parse_args():
|
||||
p = argparse.ArgumentParser(description='SPA dev server with API proxy')
|
||||
p.add_argument('--port', type=int, default=4173)
|
||||
p.add_argument('--dist', default=os.path.join(os.path.dirname(__file__), 'dist'))
|
||||
p.add_argument('--backend', default='http://127.0.0.1:8091')
|
||||
return p.parse_args()
|
||||
|
||||
ARGS = parse_args()
|
||||
DIST = ARGS.dist
|
||||
PB = ARGS.backend
|
||||
DEEPSEEK_KEY = os.environ.get('DEEPSEEK_KEY')
|
||||
if not DEEPSEEK_KEY:
|
||||
try:
|
||||
with open('/tmp/deepseek_key.txt') as f:
|
||||
DEEPSEEK_KEY = f.read().strip()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
class Handler(SimpleHTTPRequestHandler):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, directory=DIST, **kwargs)
|
||||
|
||||
def end_headers(self):
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
|
||||
self.send_header('Access-Control-Allow-Headers', 'Authorization, Content-Type')
|
||||
super().end_headers()
|
||||
|
||||
def do_OPTIONS(self):
|
||||
self.send_response(204)
|
||||
self.end_headers()
|
||||
|
||||
def do_GET(self):
|
||||
if self._is_api_path():
|
||||
self._proxy()
|
||||
else:
|
||||
full = os.path.join(DIST, self.path.lstrip('/'))
|
||||
if not os.path.exists(full) or (os.path.isdir(full) and self.path != '/'):
|
||||
self.path = '/index.html'
|
||||
super().do_GET()
|
||||
|
||||
def do_POST(self):
|
||||
if self._is_api_path():
|
||||
self._proxy()
|
||||
else:
|
||||
self.send_error(405)
|
||||
|
||||
def do_PUT(self): self._proxy()
|
||||
def do_PATCH(self): self._proxy()
|
||||
def do_DELETE(self): self._proxy()
|
||||
|
||||
def _is_api_path(self):
|
||||
return self.path.startswith(('/pb', '/api', '/deepseek', '/llm', '/vision'))
|
||||
|
||||
def _proxy(self):
|
||||
if self.path.startswith('/deepseek'):
|
||||
url = 'https://api.deepseek.com' + self.path[len('/deepseek'):]
|
||||
elif self.path.startswith('/llm') or self.path.startswith('/vision'):
|
||||
url = 'http://127.0.0.1:11434' + self.path.split('/', 2)[-1] if '/' in self.path[1:] else self.path
|
||||
url = f'http://127.0.0.1:11434/{url}' if not url.startswith('http') else url
|
||||
elif self.path.startswith('/pb'):
|
||||
rest = self.path[3:] # /pb/api/collections/... → /api/collections/...
|
||||
url = PB + rest
|
||||
if not rest.startswith('/api'):
|
||||
url = PB + '/api' + rest
|
||||
else:
|
||||
url = PB + self.path
|
||||
|
||||
body = None
|
||||
if self.headers.get('Content-Length'):
|
||||
body = self.rfile.read(int(self.headers['Content-Length']))
|
||||
|
||||
req = urllib.request.Request(url, data=body, method=self.command)
|
||||
for k, v in self.headers.items():
|
||||
if k.lower() not in ('host', 'connection', 'origin', 'referer'):
|
||||
req.add_header(k, v)
|
||||
|
||||
if self.path.startswith('/deepseek') and DEEPSEEK_KEY:
|
||||
req.add_header('Authorization', f'Bearer {DEEPSEEK_KEY}')
|
||||
|
||||
try:
|
||||
resp = urllib.request.urlopen(req)
|
||||
self.send_response(resp.status)
|
||||
for k, v in resp.headers.items():
|
||||
if k.lower() not in ('transfer-encoding', 'connection'):
|
||||
self.send_header(k, v)
|
||||
self.end_headers()
|
||||
self.wfile.write(resp.read())
|
||||
except urllib.error.HTTPError as e:
|
||||
self.send_response(e.code)
|
||||
self.end_headers()
|
||||
self.wfile.write(e.read())
|
||||
|
||||
if __name__ == '__main__':
|
||||
server = HTTPServer(('0.0.0.0', ARGS.port), Handler)
|
||||
print(f'Serving {DIST} on http://0.0.0.0:{ARGS.port} (API → {PB})')
|
||||
server.serve_forever()
|
||||
Reference in New Issue
Block a user