cheekeong2025 commited on
Commit
9c712b3
·
verified ·
1 Parent(s): 29e63aa

Update loader.py

Browse files
Files changed (1) hide show
  1. loader.py +50 -44
loader.py CHANGED
@@ -1,5 +1,6 @@
1
- # loader.py — Public proxy loader for private static Hugging Face Space (redirect-safe)
2
  import os
 
3
  from urllib.parse import urljoin
4
 
5
  from fastapi import FastAPI, Request
@@ -8,73 +9,78 @@ import httpx
8
 
9
  app = FastAPI()
10
 
11
- # ========= CONFIG (from Secrets) =========
12
- PRIVATE_STATIC_URL = os.getenv("PRIVATE_STATIC_URL")
13
- HF_TOKEN = os.getenv("HF_TOKEN")
 
 
14
 
15
- if not PRIVATE_STATIC_URL:
16
  raise RuntimeError(
17
- " PRIVATE_STATIC_URL is not set.\n"
18
- "Add it in Settings → Repository secrets, e.g.\n"
19
- "PRIVATE_STATIC_URL=https://cheekeong2025-iip-grading.static.hf.space"
20
  )
21
-
22
  if not HF_TOKEN:
23
- print("[loader] ⚠️ Warning: HF_TOKEN not set — attempting anonymous access.")
 
 
 
 
 
24
 
25
- HEADERS = {"Authorization": f"Bearer {HF_TOKEN}"} if HF_TOKEN else {}
26
- # ========================================
 
27
 
28
- def _with_query(url: str, request: Request) -> str:
29
- """Append the query string (if any) from the incoming request."""
30
  q = str(request.url.query or "")
31
  return f"{url}?{q}" if q else url
32
 
 
 
 
 
 
 
33
  @app.get("/health")
34
  async def health():
35
- return {"status": "ok", "source": PRIVATE_STATIC_URL}
36
 
37
  @app.get("/")
38
  async def root(request: Request):
39
- """
40
- Serve index.html. Many static sites redirect '/' -> '/index.html' (302).
41
- We request index.html directly to avoid the 302, and we also enable
42
- follow_redirects for extra safety.
43
- """
44
- index_url = urljoin(PRIVATE_STATIC_URL.rstrip("/") + "/", "index.html")
45
- index_url = _with_query(index_url, request)
46
-
47
  try:
48
  async with httpx.AsyncClient(timeout=None, follow_redirects=True) as client:
49
- r = await client.get(index_url, headers=HEADERS)
50
- # If upstream doesn't have index.html, fall back to root (may redirect)
51
- if r.status_code == 404:
52
- root_url = _with_query(PRIVATE_STATIC_URL, request)
53
- r = await client.get(root_url, headers=HEADERS)
54
  except Exception as e:
55
- return PlainTextResponse(f"Error fetching root: {e}", status_code=500)
 
 
 
 
 
 
 
 
 
56
 
57
- content_type = r.headers.get("content-type", "text/html; charset=utf-8")
58
- # Use HTMLResponse for better default headers on HTML; Response otherwise
59
- if "text/html" in content_type:
60
- return HTMLResponse(r.text, status_code=r.status_code, media_type=content_type)
61
- return Response(content=r.content, status_code=r.status_code, media_type=content_type)
62
 
63
  @app.get("/{path:path}")
64
  async def proxy(path: str, request: Request):
65
- """
66
- Proxy all other static assets (CSS, JS, images, fonts, etc.).
67
- We honor redirects and forward the query string.
68
- """
69
- base = PRIVATE_STATIC_URL.rstrip("/") + "/"
70
- target = urljoin(base, path) # robust path join
71
- target = _with_query(target, request)
72
 
73
  try:
74
  async with httpx.AsyncClient(timeout=None, follow_redirects=True) as client:
75
  r = await client.get(target, headers=HEADERS)
76
  except Exception as e:
77
- return PlainTextResponse(f"Error fetching {target}: {e}", status_code=500)
78
 
79
- content_type = r.headers.get("content-type", "application/octet-stream")
80
- return Response(content=r.content, media_type=content_type, status_code=r.status_code)
 
 
1
+ # loader.py — Public proxy for PRIVATE Static Space using huggingface.co/resolve
2
  import os
3
+ import mimetypes
4
  from urllib.parse import urljoin
5
 
6
  from fastapi import FastAPI, Request
 
9
 
10
  app = FastAPI()
11
 
12
+ # ==== Config from Secrets ====
13
+ SPACE_ID = os.getenv("PRIVATE_SPACE_ID") # e.g. "cheekeong2025/iip-grading"
14
+ HF_TOKEN = os.getenv("HF_TOKEN") # must have read access
15
+ REVISION = os.getenv("REVISION", "main") # optional: pin a branch/tag/commit
16
+ # =============================
17
 
18
+ if not SPACE_ID:
19
  raise RuntimeError(
20
+ "PRIVATE_SPACE_ID is not set. Go to Settings → Repository secrets and add "
21
+ "PRIVATE_SPACE_ID like 'cheekeong2025/iip-grading'."
 
22
  )
 
23
  if not HF_TOKEN:
24
+ raise RuntimeError(
25
+ "HF_TOKEN is not set. Add a token with READ access to the private Space."
26
+ )
27
+
28
+ BASE_RESOLVE = f"https://huggingface.co/spaces/{SPACE_ID}/resolve/{REVISION}/"
29
+ HEADERS = {"Authorization": f"Bearer {HF_TOKEN}"}
30
 
31
+ def build_resolve_url(path: str) -> str:
32
+ # Ensure trailing slash join is correct
33
+ return urljoin(BASE_RESOLVE, path.lstrip("/"))
34
 
35
+ def with_query(url: str, request: Request) -> str:
 
36
  q = str(request.url.query or "")
37
  return f"{url}?{q}" if q else url
38
 
39
+ def guess_mime(path: str, fallback: str = "application/octet-stream") -> str:
40
+ if path.endswith("/") or path == "":
41
+ return "text/html; charset=utf-8"
42
+ mime, _ = mimetypes.guess_type(path)
43
+ return mime or fallback
44
+
45
  @app.get("/health")
46
  async def health():
47
+ return {"status": "ok", "space": SPACE_ID, "revision": REVISION, "source": "huggingface.co/resolve"}
48
 
49
  @app.get("/")
50
  async def root(request: Request):
51
+ # Serve index.html explicitly (avoid upstream redirects)
52
+ target = with_query(build_resolve_url("index.html"), request)
 
 
 
 
 
 
53
  try:
54
  async with httpx.AsyncClient(timeout=None, follow_redirects=True) as client:
55
+ r = await client.get(target, headers=HEADERS)
 
 
 
 
56
  except Exception as e:
57
+ return PlainTextResponse(f"Error fetching index.html: {e}", status_code=500)
58
+
59
+ # If index.html is missing, try repo root as a fallback (rare)
60
+ if r.status_code == 404:
61
+ try:
62
+ fallback = with_query(build_resolve_url(""), request)
63
+ async with httpx.AsyncClient(timeout=None, follow_redirects=True) as client:
64
+ r = await client.get(fallback, headers=HEADERS)
65
+ except Exception as e:
66
+ return PlainTextResponse(f"Error fetching root: {e}", status_code=500)
67
 
68
+ ctype = r.headers.get("content-type", "text/html; charset=utf-8")
69
+ return HTMLResponse(r.text, status_code=r.status_code, media_type=ctype)
 
 
 
70
 
71
  @app.get("/{path:path}")
72
  async def proxy(path: str, request: Request):
73
+ # Proxy any other static asset (CSS, JS, images, fonts, etc.)
74
+ # Map directories to index.html for SPA-style paths
75
+ resolved_path = path if "." in path.split("/")[-1] else (path.rstrip("/") + "/index.html")
76
+ target = with_query(build_resolve_url(resolved_path), request)
 
 
 
77
 
78
  try:
79
  async with httpx.AsyncClient(timeout=None, follow_redirects=True) as client:
80
  r = await client.get(target, headers=HEADERS)
81
  except Exception as e:
82
+ return PlainTextResponse(f"Error fetching {resolved_path}: {e}", status_code=500)
83
 
84
+ # Prefer upstream header; otherwise guess from file extension
85
+ ctype = r.headers.get("content-type") or guess_mime(resolved_path)
86
+ return Response(content=r.content, media_type=ctype, status_code=r.status_code)