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
|
@@ -76,4 +76,5 @@ EXPOSE 7860
|
|
| 76 |
ENTRYPOINT []
|
| 77 |
|
| 78 |
# Run FastAPI with uvicorn (module path: stroke_deepisles_demo.api.main:app)
|
| 79 |
-
|
|
|
|
|
|
| 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"]
|
|
@@ -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
|
|
@@ -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)
|
|
@@ -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)
|
|
@@ -804,7 +804,8 @@ if FRONTEND_ORIGIN:
|
|
| 804 |
app.add_middleware(
|
| 805 |
CORSMiddleware,
|
| 806 |
allow_origins=CORS_ORIGINS,
|
| 807 |
-
|
|
|
|
| 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=["*"],
|
|
@@ -28,7 +28,10 @@ if FRONTEND_ORIGIN:
|
|
| 28 |
app.add_middleware(
|
| 29 |
CORSMiddleware,
|
| 30 |
allow_origins=CORS_ORIGINS,
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
| 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=["*"],
|