Spaces:
Running
Running
asemxin commited on
Commit ·
6b10e4d
1
Parent(s): c3431ec
Use Python aiohttp for reverse proxy + static files
Browse files- Dockerfile +7 -43
- server.py +102 -0
Dockerfile
CHANGED
|
@@ -12,55 +12,19 @@ RUN git clone https://github.com/ycvk/monica-proxy.git .
|
|
| 12 |
RUN go mod download
|
| 13 |
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o monica-proxy main.go
|
| 14 |
|
| 15 |
-
# Stage 2: Runtime with
|
| 16 |
-
FROM
|
| 17 |
|
| 18 |
-
RUN apk --no-cache add ca-certificates tzdata
|
| 19 |
|
| 20 |
WORKDIR /app
|
| 21 |
|
| 22 |
COPY --from=builder /app/monica-proxy .
|
| 23 |
-
COPY index.html
|
|
|
|
| 24 |
|
| 25 |
-
|
| 26 |
-
RUN cat > /etc/nginx/http.d/default.conf << 'EOF'
|
| 27 |
-
server {
|
| 28 |
-
listen 7860;
|
| 29 |
-
|
| 30 |
-
root /usr/share/nginx/html;
|
| 31 |
-
index index.html;
|
| 32 |
-
|
| 33 |
-
# Exact match for root path - highest priority
|
| 34 |
-
location = / {
|
| 35 |
-
default_type text/html;
|
| 36 |
-
alias /usr/share/nginx/html/index.html;
|
| 37 |
-
}
|
| 38 |
-
|
| 39 |
-
# API endpoints - proxy to monica-proxy
|
| 40 |
-
location /v1 {
|
| 41 |
-
proxy_pass http://127.0.0.1:8080;
|
| 42 |
-
proxy_http_version 1.1;
|
| 43 |
-
proxy_set_header Host $host;
|
| 44 |
-
proxy_set_header X-Real-IP $remote_addr;
|
| 45 |
-
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
| 46 |
-
proxy_set_header X-Forwarded-Proto $scheme;
|
| 47 |
-
proxy_buffering off;
|
| 48 |
-
proxy_read_timeout 300s;
|
| 49 |
-
}
|
| 50 |
-
}
|
| 51 |
-
EOF
|
| 52 |
-
|
| 53 |
-
# Start script
|
| 54 |
-
RUN cat > /app/start.sh << 'EOF'
|
| 55 |
-
#!/bin/sh
|
| 56 |
-
# Start monica-proxy in background on port 8080
|
| 57 |
-
SERVER_PORT=8080 SERVER_HOST=0.0.0.0 ./monica-proxy &
|
| 58 |
-
# Start nginx in foreground
|
| 59 |
-
nginx -g 'daemon off;'
|
| 60 |
-
EOF
|
| 61 |
-
|
| 62 |
-
RUN chmod +x /app/start.sh
|
| 63 |
|
| 64 |
EXPOSE 7860
|
| 65 |
|
| 66 |
-
CMD ["
|
|
|
|
| 12 |
RUN go mod download
|
| 13 |
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o monica-proxy main.go
|
| 14 |
|
| 15 |
+
# Stage 2: Runtime with Python proxy
|
| 16 |
+
FROM python:3.11-alpine
|
| 17 |
|
| 18 |
+
RUN apk --no-cache add ca-certificates tzdata
|
| 19 |
|
| 20 |
WORKDIR /app
|
| 21 |
|
| 22 |
COPY --from=builder /app/monica-proxy .
|
| 23 |
+
COPY index.html .
|
| 24 |
+
COPY server.py .
|
| 25 |
|
| 26 |
+
RUN pip install --no-cache-dir aiohttp
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
EXPOSE 7860
|
| 29 |
|
| 30 |
+
CMD ["python", "server.py"]
|
server.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Simple reverse proxy + static file server for Monica Proxy
|
| 3 |
+
"""
|
| 4 |
+
import asyncio
|
| 5 |
+
import subprocess
|
| 6 |
+
import os
|
| 7 |
+
from aiohttp import web, ClientSession, ClientTimeout
|
| 8 |
+
|
| 9 |
+
BACKEND_PORT = 8080
|
| 10 |
+
FRONTEND_PORT = 7860
|
| 11 |
+
|
| 12 |
+
async def proxy_handler(request: web.Request):
|
| 13 |
+
"""Proxy requests to monica-proxy backend"""
|
| 14 |
+
backend_url = f"http://127.0.0.1:{BACKEND_PORT}{request.path_qs}"
|
| 15 |
+
|
| 16 |
+
async with ClientSession(timeout=ClientTimeout(total=300)) as session:
|
| 17 |
+
try:
|
| 18 |
+
headers = {k: v for k, v in request.headers.items()
|
| 19 |
+
if k.lower() not in ('host', 'content-length')}
|
| 20 |
+
|
| 21 |
+
body = await request.read() if request.can_read_body else None
|
| 22 |
+
|
| 23 |
+
async with session.request(
|
| 24 |
+
method=request.method,
|
| 25 |
+
url=backend_url,
|
| 26 |
+
headers=headers,
|
| 27 |
+
data=body,
|
| 28 |
+
) as resp:
|
| 29 |
+
response_body = await resp.read()
|
| 30 |
+
return web.Response(
|
| 31 |
+
status=resp.status,
|
| 32 |
+
headers={k: v for k, v in resp.headers.items()
|
| 33 |
+
if k.lower() not in ('content-encoding', 'transfer-encoding', 'content-length')},
|
| 34 |
+
body=response_body
|
| 35 |
+
)
|
| 36 |
+
except Exception as e:
|
| 37 |
+
return web.json_response(
|
| 38 |
+
{"error": str(e), "message": "Backend connection failed"},
|
| 39 |
+
status=502
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
async def index_handler(request: web.Request):
|
| 43 |
+
"""Serve the status page"""
|
| 44 |
+
return web.FileResponse('index.html')
|
| 45 |
+
|
| 46 |
+
async def start_backend():
|
| 47 |
+
"""Start monica-proxy in background"""
|
| 48 |
+
env = os.environ.copy()
|
| 49 |
+
env['SERVER_PORT'] = str(BACKEND_PORT)
|
| 50 |
+
env['SERVER_HOST'] = '0.0.0.0'
|
| 51 |
+
|
| 52 |
+
process = await asyncio.create_subprocess_exec(
|
| 53 |
+
'./monica-proxy',
|
| 54 |
+
env=env,
|
| 55 |
+
stdout=asyncio.subprocess.PIPE,
|
| 56 |
+
stderr=asyncio.subprocess.STDOUT
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
# Log backend output
|
| 60 |
+
async def log_output():
|
| 61 |
+
while True:
|
| 62 |
+
line = await process.stdout.readline()
|
| 63 |
+
if not line:
|
| 64 |
+
break
|
| 65 |
+
print(f"[backend] {line.decode().strip()}")
|
| 66 |
+
|
| 67 |
+
asyncio.create_task(log_output())
|
| 68 |
+
return process
|
| 69 |
+
|
| 70 |
+
async def main():
|
| 71 |
+
# Start backend
|
| 72 |
+
print(f"Starting monica-proxy on port {BACKEND_PORT}...")
|
| 73 |
+
backend = await start_backend()
|
| 74 |
+
|
| 75 |
+
# Give backend time to start
|
| 76 |
+
await asyncio.sleep(2)
|
| 77 |
+
|
| 78 |
+
# Setup web server
|
| 79 |
+
app = web.Application()
|
| 80 |
+
app.router.add_get('/', index_handler)
|
| 81 |
+
app.router.add_route('*', '/v1/{path:.*}', proxy_handler)
|
| 82 |
+
app.router.add_route('*', '/{path:.*}', proxy_handler)
|
| 83 |
+
|
| 84 |
+
runner = web.AppRunner(app)
|
| 85 |
+
await runner.setup()
|
| 86 |
+
|
| 87 |
+
site = web.TCPSite(runner, '0.0.0.0', FRONTEND_PORT)
|
| 88 |
+
print(f"Starting frontend on port {FRONTEND_PORT}...")
|
| 89 |
+
await site.start()
|
| 90 |
+
|
| 91 |
+
print(f"Server ready!")
|
| 92 |
+
print(f" Status page: http://0.0.0.0:{FRONTEND_PORT}/")
|
| 93 |
+
print(f" API: http://0.0.0.0:{FRONTEND_PORT}/v1/...")
|
| 94 |
+
|
| 95 |
+
# Keep running
|
| 96 |
+
try:
|
| 97 |
+
await asyncio.Event().wait()
|
| 98 |
+
finally:
|
| 99 |
+
backend.terminate()
|
| 100 |
+
|
| 101 |
+
if __name__ == '__main__':
|
| 102 |
+
asyncio.run(main())
|