VibecoderMcSwaggins commited on
Commit
1efb3e0
·
unverified ·
1 Parent(s): c0bb818

fix(deploy): CORS and proxy-headers for HF Spaces (#35)

Browse files

* feat(docs): add comprehensive specifications for NiiVue integration and JavaScript loading issues

- Introduced multiple new documentation files detailing the proposed Gradio Custom Component for NiiVue, addressing the "Loading..." issue on HuggingFace Spaces, and providing a root cause analysis of JavaScript loading failures.
- Included a detailed audit of JavaScript loading issues, diagnostic steps for the "Loading..." bug, and an analysis of Gradio's compatibility with WebGL.
- Documented the decision-making process and next steps for implementing the custom component approach, emphasizing the need for a structured solution to integrate NiiVue effectively.

These additions aim to enhance understanding and provide a clear path forward for developers working with Gradio and NiiVue.

* fix(docs): update technical debt documentation and add root cause analysis for NiiVue loading issue

- Revised the TECHNICAL_DEBT.md to reflect the current status of the NiiVue/WebGL integration, marking it as a P0 BLOCKER due to issues on HuggingFace Spaces.
- Added detailed sections outlining the critical issue with NiiVue not loading, including root causes and proposed solutions.
- Introduced new specifications for the Gradio Custom Component approach to resolve the loading issue, emphasizing the need for a structured solution.
- Documented the findings from the recent audits and analyses, providing a clear path forward for developers addressing the integration challenges.

These updates aim to enhance clarity and provide actionable insights for future development efforts.

* fix(cors): correct regex pattern for HF Spaces URLs

The CORS allow_origin_regex expected double hyphens (--) but
HuggingFace Spaces direct URLs use single hyphens:

Before: https://.*--stroke-viewer-frontend(--.*)?\.hf\.space
After: https://.*stroke-viewer-frontend.*\.hf\.space

This was causing "Failed to fetch" errors when NiiVue tried to
load NIfTI files from the backend - the static file requests
were being blocked by CORS.

* fix(deploy): add proxy-headers and document HF Spaces bugs

- Add --proxy-headers to uvicorn CMD in Dockerfile
This ensures request.base_url returns https:// on HF Spaces
(fixes potential mixed-content issues with NIfTI file URLs)

- Add docs/bugs/ directory with comprehensive documentation:
- 001: CORS regex pattern mismatch (fixed)
- 002: HTTP vs HTTPS behind proxy (fixed)
- README: Common HF Spaces pitfalls and prevention

Sources:
- https://github.com/fastapi/fastapi/discussions/6670
- https://medium.com/@na.mazaheri/deploying-a-fastapi-app-on-hugging-face-spaces

Dockerfile CHANGED
@@ -76,4 +76,5 @@ EXPOSE 7860
76
  ENTRYPOINT []
77
 
78
  # Run FastAPI with uvicorn (module path: stroke_deepisles_demo.api.main:app)
79
- CMD ["uvicorn", "stroke_deepisles_demo.api.main:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
76
  ENTRYPOINT []
77
 
78
  # Run FastAPI with uvicorn (module path: stroke_deepisles_demo.api.main:app)
79
+ # --proxy-headers: Trust X-Forwarded-Proto from HF Spaces proxy (ensures https:// in request.base_url)
80
+ CMD ["uvicorn", "stroke_deepisles_demo.api.main:app", "--host", "0.0.0.0", "--port", "7860", "--proxy-headers"]
docs/bugs/001-cors-static-files-hf-spaces.md ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Bug 001: CORS Blocking Static File Requests on HuggingFace Spaces
2
+
3
+ **Status**: FIXED
4
+ **Date Found**: 2025-12-11
5
+ **Severity**: Critical (blocks core functionality)
6
+
7
+ ---
8
+
9
+ ## Symptoms
10
+
11
+ 1. Frontend loads successfully, case dropdown populates
12
+ 2. Segmentation API call succeeds (200 OK, ~34s processing time)
13
+ 3. Results panel shows metrics (Dice Score, Volume, Time)
14
+ 4. NiiVue viewer shows "loading..." then error: **"Failed to load volume: Failed to fetch"**
15
+
16
+ ## Root Cause
17
+
18
+ The CORS `allow_origin_regex` pattern in `src/stroke_deepisles_demo/api/main.py` was incorrect:
19
+
20
+ ```python
21
+ # WRONG - expects double hyphens
22
+ allow_origin_regex=r"https://.*--stroke-viewer-frontend(--.*)?\.hf\.space"
23
+
24
+ # Actual frontend URL uses single hyphen:
25
+ # https://vibecodermcswaggins-stroke-viewer-frontend.hf.space
26
+ # ^ single hyphen
27
+ ```
28
+
29
+ The regex expected `--` (double hyphen) between username and space name, but HuggingFace Spaces direct URLs use single hyphens.
30
+
31
+ ## HuggingFace Spaces URL Formats
32
+
33
+ | Format | Pattern | Example |
34
+ |--------|---------|---------|
35
+ | **Direct** | `{username}-{spacename}.hf.space` | `vibecodermcswaggins-stroke-viewer-frontend.hf.space` |
36
+ | **Proxy/Embed** | `{username}--{spacename}--{hash}.hf.space` | `vibecodermcswaggins--stroke-viewer-frontend--abc123.hf.space` |
37
+
38
+ The original regex only matched the proxy format, not the direct format.
39
+
40
+ ## Fix
41
+
42
+ ```python
43
+ # CORRECT - matches both formats
44
+ allow_origin_regex=r"https://.*stroke-viewer-frontend.*\.hf\.space"
45
+ ```
46
+
47
+ ## Logs Evidence
48
+
49
+ ```
50
+ INFO: 10.16.13.79:42834 - "POST /api/segment HTTP/1.1" 200 OK
51
+ ```
52
+
53
+ The API call succeeded, but subsequent static file fetches for NIfTI volumes were blocked by CORS (browser silently blocks and shows "Failed to fetch").
54
+
55
+ ## Files Changed
56
+
57
+ - `src/stroke_deepisles_demo/api/main.py` - Fixed regex
58
+ - `docs/specs/frontend/36-frontend-without-gradio-hf-spaces.md` - Updated spec
59
+
60
+ ## Verification
61
+
62
+ After fix:
63
+ 1. Redeploy backend to HF Spaces
64
+ 2. Refresh frontend
65
+ 3. Run segmentation
66
+ 4. NiiVue should load and display the DWI + prediction overlay
67
+
68
+ ## Prevention
69
+
70
+ - Test CORS configuration with actual production URLs before deployment
71
+ - Add integration test that verifies static file CORS headers
72
+ - Document HF Spaces URL formats in spec
docs/bugs/002-http-vs-https-proxy-headers.md ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Bug 002: HTTP vs HTTPS URL Mismatch Behind HF Spaces Proxy
2
+
3
+ **Status**: FIXED
4
+ **Date Found**: 2025-12-11
5
+ **Severity**: High (may cause mixed content errors or fetch failures)
6
+
7
+ ---
8
+
9
+ ## Symptoms
10
+
11
+ NiiVue viewer fails to load NIfTI files with "Failed to fetch" even after CORS is fixed.
12
+ Browser console may show mixed content warnings (HTTPS page loading HTTP resources).
13
+
14
+ ## Root Cause
15
+
16
+ HuggingFace Spaces runs containers behind a reverse proxy that handles SSL termination.
17
+ When the app constructs URLs using `request.base_url`, it may return `http://` instead of `https://`
18
+ because uvicorn doesn't trust the proxy's `X-Forwarded-Proto` header by default.
19
+
20
+ **Reference**: [FastAPI Static Files over HTTPS Discussion](https://github.com/fastapi/fastapi/discussions/6670)
21
+
22
+ > "Starlette (FastAPI) returns http instead of https only inside containers"
23
+
24
+ ## The Code Path
25
+
26
+ ```python
27
+ # routes.py
28
+ def get_backend_base_url(request: Request) -> str:
29
+ env_url = os.environ.get("BACKEND_PUBLIC_URL", "").rstrip("/")
30
+ if env_url:
31
+ return env_url
32
+ return str(request.base_url).rstrip("/") # May return http:// behind proxy!
33
+ ```
34
+
35
+ ## Fix
36
+
37
+ Add `--proxy-headers` flag to uvicorn in Dockerfile:
38
+
39
+ ```dockerfile
40
+ # BEFORE (broken)
41
+ CMD ["uvicorn", "...:app", "--host", "0.0.0.0", "--port", "7860"]
42
+
43
+ # AFTER (fixed)
44
+ CMD ["uvicorn", "...:app", "--host", "0.0.0.0", "--port", "7860", "--proxy-headers"]
45
+ ```
46
+
47
+ This tells uvicorn to trust headers like:
48
+ - `X-Forwarded-Proto: https`
49
+ - `X-Forwarded-For: client-ip`
50
+
51
+ ## Alternative Fixes
52
+
53
+ 1. **Set BACKEND_PUBLIC_URL**: Explicitly set the public URL in HF Space settings
54
+ ```
55
+ BACKEND_PUBLIC_URL=https://vibecodermcswaggins-stroke-deepisles-demo.hf.space
56
+ ```
57
+
58
+ 2. **Force HTTPS in code**: Override scheme detection
59
+ ```python
60
+ def get_backend_base_url(request: Request) -> str:
61
+ base = str(request.base_url).rstrip("/")
62
+ # Force HTTPS in production
63
+ if os.environ.get("HF_SPACES"):
64
+ base = base.replace("http://", "https://")
65
+ return base
66
+ ```
67
+
68
+ ## Files Changed
69
+
70
+ - `Dockerfile` - Added `--proxy-headers` to uvicorn CMD
71
+
72
+ ## Verification
73
+
74
+ 1. Deploy to HF Spaces
75
+ 2. Run segmentation
76
+ 3. Check Network tab - file URLs should be `https://`
77
+ 4. NiiVue should load volumes successfully
78
+
79
+ ## Sources
80
+
81
+ - [FastAPI/Starlette HTTPS Discussion](https://github.com/fastapi/fastapi/discussions/6670)
82
+ - [Deploying FastAPI on HuggingFace Spaces](https://medium.com/@na.mazaheri/deploying-a-fastapi-app-on-hugging-face-spaces-and-handling-all-its-restrictions-d494d97a78fa)
docs/bugs/README.md ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Bug Tracker: HuggingFace Spaces Deployment
2
+
3
+ This directory tracks bugs found during deployment to HuggingFace Spaces.
4
+
5
+ ## Active Bugs
6
+
7
+ None currently.
8
+
9
+ ## Fixed Bugs
10
+
11
+ | ID | Title | Severity | Status |
12
+ |----|-------|----------|--------|
13
+ | [001](./001-cors-static-files-hf-spaces.md) | CORS regex blocking static file requests | Critical | FIXED |
14
+ | [002](./002-http-vs-https-proxy-headers.md) | HTTP vs HTTPS URL mismatch behind proxy | High | FIXED |
15
+
16
+ ## Common HuggingFace Spaces Pitfalls
17
+
18
+ Based on research and experience, here are common issues to watch for:
19
+
20
+ ### 1. CORS Configuration
21
+ - HF Spaces URLs use single hyphens: `{username}-{spacename}.hf.space`
22
+ - Proxy/embed URLs may use double hyphens: `{username}--{spacename}--{hash}.hf.space`
23
+ - Always use a permissive regex that matches both formats
24
+
25
+ ### 2. HTTPS Behind Proxy
26
+ - HF Spaces terminates SSL at their proxy
27
+ - Uvicorn sees HTTP internally
28
+ - Add `--proxy-headers` to trust `X-Forwarded-Proto`
29
+ - Or explicitly set `BACKEND_PUBLIC_URL` environment variable
30
+
31
+ ### 3. File System Restrictions
32
+ - Only `/tmp` is writable
33
+ - Use `/tmp/stroke-results` for output files
34
+ - Ensure directories are created with proper permissions
35
+
36
+ ### 4. Static Files
37
+ - Mount static files AFTER directory exists
38
+ - Ensure CORS allows file fetches from frontend origin
39
+ - Files served from `/files/...` must be accessible
40
+
41
+ ### 5. Environment Variables
42
+ - `HF_SPACES=1` indicates running on HF Spaces
43
+ - `SPACE_ID` contains the space identifier
44
+ - Use these to detect production environment
45
+
46
+ ## Sources
47
+
48
+ - [Deploying FastAPI on HuggingFace Spaces](https://huggingface.co/blog/HemanthSai7/deploy-applications-on-huggingface-spaces)
49
+ - [HF Spaces Restrictions](https://medium.com/@na.mazaheri/deploying-a-fastapi-app-on-hugging-face-spaces-and-handling-all-its-restrictions-d494d97a78fa)
50
+ - [FastAPI HTTPS Discussion](https://github.com/fastapi/fastapi/discussions/6670)
51
+ - [HF Docker Spaces Docs](https://huggingface.co/docs/hub/en/spaces-sdks-docker)
docs/specs/frontend/36-frontend-without-gradio-hf-spaces.md CHANGED
@@ -804,7 +804,8 @@ if FRONTEND_ORIGIN:
804
  app.add_middleware(
805
  CORSMiddleware,
806
  allow_origins=CORS_ORIGINS,
807
- allow_origin_regex=r"https://.*--stroke-viewer-frontend(--.*)?\\.hf\\.space",
 
808
  allow_credentials=True,
809
  allow_methods=["*"],
810
  allow_headers=["*"],
 
804
  app.add_middleware(
805
  CORSMiddleware,
806
  allow_origins=CORS_ORIGINS,
807
+ # Match HF Spaces URLs in both formats (direct and proxy)
808
+ allow_origin_regex=r"https://.*stroke-viewer-frontend.*\\.hf\\.space",
809
  allow_credentials=True,
810
  allow_methods=["*"],
811
  allow_headers=["*"],
src/stroke_deepisles_demo/api/main.py CHANGED
@@ -28,7 +28,10 @@ if FRONTEND_ORIGIN:
28
  app.add_middleware(
29
  CORSMiddleware,
30
  allow_origins=CORS_ORIGINS,
31
- allow_origin_regex=r"https://.*--stroke-viewer-frontend(--.*)?\.hf\.space",
 
 
 
32
  allow_credentials=True,
33
  allow_methods=["*"],
34
  allow_headers=["*"],
 
28
  app.add_middleware(
29
  CORSMiddleware,
30
  allow_origins=CORS_ORIGINS,
31
+ # Match HF Spaces URLs in both formats:
32
+ # - Direct: https://username-spacename.hf.space
33
+ # - Proxy: https://username--spacename--hash.hf.space
34
+ allow_origin_regex=r"https://.*stroke-viewer-frontend.*\.hf\.space",
35
  allow_credentials=True,
36
  allow_methods=["*"],
37
  allow_headers=["*"],