abhiraj12 commited on
Commit
ebcc94a
·
1 Parent(s): 6581d33

Replace Python proxy with Nginx to support Streamlit WebSockets and fix blank screen

Browse files
Dockerfile CHANGED
@@ -4,11 +4,15 @@ FROM python:3.12-slim
4
  RUN apt-get update && apt-get install -y \
5
  gcc g++ \
6
  redis-server \
 
7
  && rm -rf /var/lib/apt/lists/*
8
 
 
 
 
 
9
  # 🚀 CRITICAL FOR HUGGING FACE SPACES 🚀
10
  # HF Spaces run as a non-root user (uid 1000)
11
- # We must create this user and set the home directory
12
  RUN useradd -m -u 1000 user
13
  USER user
14
  ENV HOME=/home/user \
 
4
  RUN apt-get update && apt-get install -y \
5
  gcc g++ \
6
  redis-server \
7
+ nginx \
8
  && rm -rf /var/lib/apt/lists/*
9
 
10
+ # Setup Nginx for non-root user
11
+ RUN mkdir -p /var/lib/nginx /var/log/nginx /run/nginx && \
12
+ chown -R 1000:1000 /var/lib/nginx /var/log/nginx /run/nginx
13
+
14
  # 🚀 CRITICAL FOR HUGGING FACE SPACES 🚀
15
  # HF Spaces run as a non-root user (uid 1000)
 
16
  RUN useradd -m -u 1000 user
17
  USER user
18
  ENV HOME=/home/user \
backend/main.py CHANGED
@@ -9,9 +9,6 @@ from fastapi.responses import FileResponse
9
  from fastapi.staticfiles import StaticFiles
10
  from contextlib import asynccontextmanager
11
  from infra.logger import get_logger
12
- import httpx
13
- from fastapi import Request
14
- from fastapi.responses import StreamingResponse
15
 
16
  log = get_logger(__name__)
17
  csv.field_size_limit(int(1e9))
@@ -115,42 +112,7 @@ def health_check():
115
  return {"status": "ok", "version": "4.0.0"}
116
 
117
 
118
- # ── Streamlit Reverse Proxy ──────────────────────────────────────────────────
119
- # This catch-all route forwards any non-API requests to the Streamlit port (8001).
120
- STREAMLIT_URL = "http://localhost:8001"
121
-
122
- @app.api_route("/{path_name:path}", include_in_schema=False)
123
- async def _proxy_to_streamlit(request: Request, path_name: str):
124
- # Skip if the path is explicitly an API route (though FastAPI should have caught it)
125
- if path_name.startswith("api/") or path_name.startswith("docs") or path_name.startswith("redoc") or path_name.startswith("openapi.json"):
126
- return {"error": "Not Found"}
127
-
128
- target_url = f"{STREAMLIT_URL}/{path_name}"
129
- if request.query_params:
130
- target_url += f"?{request.query_params}"
131
-
132
- async with httpx.AsyncClient() as client:
133
- # Standard proxy logic
134
- method = request.method
135
- headers = dict(request.headers)
136
- # We must remove host and adjust for the internal service
137
- headers.pop("host", None)
138
-
139
- # Handle Streamlit's specific requirements (like websockets if needed, but simple HTTP first)
140
- response = await client.request(
141
- method,
142
- target_url,
143
- content=await request.body(),
144
- headers=headers,
145
- timeout=60.0,
146
- follow_redirects=True,
147
- )
148
-
149
- return StreamingResponse(
150
- response.aiter_bytes(),
151
- status_code=response.status_code,
152
- headers=dict(response.headers),
153
- )
154
 
155
 
156
  if __name__ == "__main__":
 
9
  from fastapi.staticfiles import StaticFiles
10
  from contextlib import asynccontextmanager
11
  from infra.logger import get_logger
 
 
 
12
 
13
  log = get_logger(__name__)
14
  csv.field_size_limit(int(1e9))
 
112
  return {"status": "ok", "version": "4.0.0"}
113
 
114
 
115
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
 
118
  if __name__ == "__main__":
frontend/pages/1_Home.py CHANGED
@@ -9,7 +9,7 @@ from ui_shell import (
9
  render_workspace_banner,
10
  )
11
 
12
- API_URL = "http://localhost:7860/api"
13
 
14
  st.set_page_config(page_title="Home - AutoML Studio", page_icon="🏠", layout="wide")
15
 
 
9
  render_workspace_banner,
10
  )
11
 
12
+ API_URL = "http://localhost:8000/api"
13
 
14
  st.set_page_config(page_title="Home - AutoML Studio", page_icon="🏠", layout="wide")
15
 
frontend/pages/2_Dataset_DNA.py CHANGED
@@ -10,7 +10,7 @@ from ui_shell import (
10
  render_workspace_banner,
11
  )
12
 
13
- API_URL = "http://localhost:7860/api"
14
 
15
  st.set_page_config(page_title="Dataset DNA", page_icon="🧬", layout="wide")
16
 
 
10
  render_workspace_banner,
11
  )
12
 
13
+ API_URL = "http://localhost:8000/api"
14
 
15
  st.set_page_config(page_title="Dataset DNA", page_icon="🧬", layout="wide")
16
 
frontend/pages/3_Training_Lab.py CHANGED
@@ -10,7 +10,7 @@ from ui_shell import (
10
  render_workspace_banner,
11
  )
12
 
13
- API_URL = "http://localhost:7860/api"
14
 
15
  st.set_page_config(page_title="Live Training & Results", page_icon="⚡", layout="wide")
16
 
 
10
  render_workspace_banner,
11
  )
12
 
13
+ API_URL = "http://localhost:8000/api"
14
 
15
  st.set_page_config(page_title="Live Training & Results", page_icon="⚡", layout="wide")
16
 
frontend/pages/4_Results_Console.py CHANGED
@@ -14,7 +14,7 @@ from ui_shell import (
14
  render_workspace_banner,
15
  )
16
 
17
- API_URL = "http://localhost:7860/api"
18
 
19
  st.set_page_config(page_title="Results Console - AutoML Studio", page_icon="📊", layout="wide")
20
 
 
14
  render_workspace_banner,
15
  )
16
 
17
+ API_URL = "http://localhost:8000/api"
18
 
19
  st.set_page_config(page_title="Results Console - AutoML Studio", page_icon="📊", layout="wide")
20
 
frontend/pages/5_Experiment_Tracker.py CHANGED
@@ -9,7 +9,7 @@ from ui_shell import (
9
  render_workspace_banner,
10
  )
11
 
12
- API_URL = "http://localhost:7860/api"
13
 
14
 
15
  def api_json(path: str, timeout: int = 10):
 
9
  render_workspace_banner,
10
  )
11
 
12
+ API_URL = "http://localhost:8000/api"
13
 
14
 
15
  def api_json(path: str, timeout: int = 10):
frontend/pages/6_Drift_Monitor.py CHANGED
@@ -9,7 +9,7 @@ from ui_shell import (
9
  render_workspace_banner,
10
  )
11
 
12
- API_URL = "http://localhost:7860/api"
13
 
14
  st.set_page_config(page_title="Drift Monitor", page_icon="📉", layout="wide")
15
 
 
9
  render_workspace_banner,
10
  )
11
 
12
+ API_URL = "http://localhost:8000/api"
13
 
14
  st.set_page_config(page_title="Drift Monitor", page_icon="📉", layout="wide")
15
 
frontend/pages/7_Smart_AI_Hub.py CHANGED
@@ -10,7 +10,7 @@ from ui_shell import (
10
  render_workspace_banner,
11
  )
12
 
13
- API_URL = "http://localhost:7860/api"
14
 
15
  st.set_page_config(page_title="Smart AI Hub - AutoML Studio", page_icon="🤖", layout="wide")
16
 
 
10
  render_workspace_banner,
11
  )
12
 
13
+ API_URL = "http://localhost:8000/api"
14
 
15
  st.set_page_config(page_title="Smart AI Hub - AutoML Studio", page_icon="🤖", layout="wide")
16
 
frontend/ui_shell.py CHANGED
@@ -4,7 +4,7 @@ from typing import Iterable, Optional
4
  import requests
5
  import streamlit as st
6
 
7
- API_URL = "http://localhost:7860/api"
8
 
9
  SESSION_DEFAULTS = {
10
  "dataset_id": None,
 
4
  import requests
5
  import streamlit as st
6
 
7
+ API_URL = "http://localhost:8000/api"
8
 
9
  SESSION_DEFAULTS = {
10
  "dataset_id": None,
nginx.conf ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ worker_processes 1;
2
+ daemon off;
3
+ pid /tmp/nginx.pid;
4
+
5
+ events {
6
+ worker_connections 1024;
7
+ }
8
+
9
+ http {
10
+ include /etc/nginx/mime.types;
11
+ default_type application/octet-stream;
12
+
13
+ access_log /tmp/nginx_access.log;
14
+ error_log /tmp/nginx_error.log;
15
+
16
+ client_body_temp_path /tmp/nginx_client_body;
17
+ proxy_temp_path /tmp/nginx_proxy;
18
+ fastcgi_temp_path /tmp/nginx_fastcgi;
19
+ uwsgi_temp_path /tmp/nginx_uwsgi;
20
+ scgi_temp_path /tmp/nginx_scgi;
21
+
22
+ sendfile on;
23
+ keepalive_timeout 65;
24
+
25
+ # Gzip settings
26
+ gzip on;
27
+ gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
28
+
29
+ server {
30
+ listen 7860;
31
+ server_name localhost;
32
+
33
+ # ── FastAPI Backend (API & Docs) ────────────────────────
34
+ location ~ ^/(api|docs|redoc|openapi.json) {
35
+ proxy_pass http://localhost:8000;
36
+ proxy_set_header Host $host;
37
+ proxy_set_header X-Real-IP $remote_addr;
38
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
39
+ proxy_set_header X-Forwarded-Proto $scheme;
40
+
41
+ # Increase timeouts for long-running training jobs
42
+ proxy_read_timeout 600s;
43
+ proxy_connect_timeout 600s;
44
+ }
45
+
46
+ # ── Streamlit Websocket (/stream) ───────────────────────
47
+ location /stream {
48
+ proxy_pass http://localhost:8501/stream;
49
+ proxy_http_version 1.1;
50
+ proxy_set_header Upgrade $http_upgrade;
51
+ proxy_set_header Connection "upgrade";
52
+ proxy_set_header Host $host;
53
+ proxy_set_header X-Real-IP $remote_addr;
54
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
55
+ proxy_set_header X-Forwarded-Proto $scheme;
56
+ proxy_read_timeout 86400;
57
+ }
58
+
59
+ # ── Streamlit Static & UI ───────────────────────────────
60
+ location / {
61
+ proxy_pass http://localhost:8501;
62
+ proxy_set_header Host $host;
63
+ proxy_set_header X-Real-IP $remote_addr;
64
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
65
+ proxy_set_header X-Forwarded-Proto $scheme;
66
+
67
+ # Static file caching
68
+ location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
69
+ proxy_pass http://localhost:8501;
70
+ expires 1y;
71
+ add_header Cache-Control "public, no-transform";
72
+ }
73
+ }
74
+ }
75
+ }
start.sh CHANGED
@@ -10,15 +10,14 @@ export PYTHONPATH="$SCRIPT_DIR/backend:$PYTHONPATH"
10
  export HOME="/home/user"
11
 
12
  # 1. Start Redis (Internal Broker)
13
- # We run it in the background using & and point it to /tmp for write access.
14
  echo "Starting Redis..."
15
  redis-server --port 6379 --dir /tmp --dbfilename dump.rdb --daemonize no &
16
  REDIS_PID=$!
17
 
18
- # 2. Start FastAPI Backend (Public Gateway)
19
- echo "Starting FastAPI Backend on port ${PORT:-7860}..."
20
  cd "$SCRIPT_DIR/backend"
21
- python -m uvicorn main:app --host 0.0.0.0 --port "${PORT:-7860}" --timeout-keep-alive 600 &
22
  BACKEND_PID=$!
23
 
24
  # 3. Start Celery Worker (Task Processor)
@@ -27,14 +26,19 @@ cd "$SCRIPT_DIR/backend"
27
  python -m celery -A core.worker.celery_app worker --loglevel=info --concurrency=1 &
28
  WORKER_PID=$!
29
 
30
- # 4. Start Streamlit Frontend (Internal Service)
31
- # This port (8001) will be proxied by FastAPI.
32
- echo "Starting Streamlit Frontend on internal port 8001..."
33
  cd "$SCRIPT_DIR/frontend"
34
- python -m streamlit run app.py --server.port 8001 --server.headless true --server.address 127.0.0.1 &
35
  FRONTEND_PID=$!
36
 
 
 
 
 
 
 
37
  echo "🚀 All services launched!"
38
 
39
- # Keep the script alive. If the Frontend or Backend fails, the container should stop.
40
- wait -n $FRONTEND_PID $BACKEND_PID
 
10
  export HOME="/home/user"
11
 
12
  # 1. Start Redis (Internal Broker)
 
13
  echo "Starting Redis..."
14
  redis-server --port 6379 --dir /tmp --dbfilename dump.rdb --daemonize no &
15
  REDIS_PID=$!
16
 
17
+ # 2. Start FastAPI Backend (Internal API)
18
+ echo "Starting FastAPI Backend on port 8000..."
19
  cd "$SCRIPT_DIR/backend"
20
+ python -m uvicorn main:app --host 127.0.0.1 --port 8000 --timeout-keep-alive 600 &
21
  BACKEND_PID=$!
22
 
23
  # 3. Start Celery Worker (Task Processor)
 
26
  python -m celery -A core.worker.celery_app worker --loglevel=info --concurrency=1 &
27
  WORKER_PID=$!
28
 
29
+ # 4. Start Streamlit Frontend (Internal UI)
30
+ echo "Starting Streamlit Frontend on port 8501..."
 
31
  cd "$SCRIPT_DIR/frontend"
32
+ python -m streamlit run app.py --server.port 8501 --server.headless true --server.address 127.0.0.1 &
33
  FRONTEND_PID=$!
34
 
35
+ # 5. Start Nginx (Public Gateway)
36
+ echo "Starting Nginx on port ${PORT:-7860}..."
37
+ # We use -c to point to our custom config
38
+ nginx -c "$SCRIPT_DIR/nginx.conf" &
39
+ NGINX_PID=$!
40
+
41
  echo "🚀 All services launched!"
42
 
43
+ # Keep the script alive.
44
+ wait -n $NGINX_PID $FRONTEND_PID $BACKEND_PID