from fastapi import FastAPI, Request, Response
from fastapi.responses import HTMLResponse
import httpx
from bs4 import BeautifulSoup
from urllib.parse import urljoin, quote
app = FastAPI()
HTML_INDEX = """
HF Proxy Browser
Proxy URL:
"""
@app.get("/", response_class=HTMLResponse)
async def index():
return HTML_INDEX
async def fetch_url(url: str, request: Request) -> httpx.Response:
"""
Fetch target URL via httpx, forwarding some useful headers
(like Range for video/audio).
"""
client_headers = request.headers
headers = {
"User-Agent": client_headers.get(
"user-agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0 Safari/537.36",
),
"Accept": client_headers.get("accept", "*/*"),
"Accept-Language": client_headers.get("accept-language", "en-US,en;q=0.9"),
}
# Forward Range header for video/audio seeking
range_header = client_headers.get("range")
if range_header:
headers["Range"] = range_header
async with httpx.AsyncClient(follow_redirects=True, timeout=30) as client:
resp = await client.get(url, headers=headers)
return resp
def rewrite_html(html: str, base_url: str) -> str:
"""
Rewrite links in HTML so sub-resources (scripts, css, images, video, etc.)
go through /proxy as well.
"""
soup = BeautifulSoup(html, "html.parser")
def proxify(attr: str, tag):
if attr not in tag.attrs:
return
original = tag.attrs.get(attr)
if not original:
return
absolute = urljoin(base_url, original)
tag.attrs[attr] = f"/proxy?url={quote(absolute, safe='')}"
# Tags that can contain URLs
for tag in soup.find_all(
[
"a",
"img",
"script",
"link",
"form",
"iframe",
"video",
"audio",
"source",
]
):
if tag.name in ("a", "link"):
proxify("href", tag)
if tag.name in ("img", "script", "iframe", "video", "audio", "source"):
proxify("src", tag)
if tag.name == "form":
proxify("action", tag)
# video poster attribute (thumbnail)
if tag.name == "video":
proxify("poster", tag)
# Optional: add a small banner so you know it's proxied
banner = soup.new_tag("div")
banner.string = f"Proxied via HF Space — {base_url}"
banner["style"] = (
"position:fixed;bottom:0;left:0;right:0;"
"background:#111827;color:#e5e7eb;"
"font-size:12px;padding:4px 8px;z-index:9999;"
)
if soup.body:
soup.body.append(banner)
return str(soup)
@app.get("/proxy")
async def proxy(url: str, request: Request):
"""
Reverse-proxy endpoint: /proxy?url=https://example.com
Supports:
- HTML (rewritten)
- Images
- JS / CSS
- Video / audio (with Range header forwarded)
"""
try:
upstream = await fetch_url(url, request)
except Exception as e:
return HTMLResponse(
f"Error
Could not fetch {url}
{e}",
status_code=502,
)
content_type = upstream.headers.get("content-type", "")
# HTML: rewrite links so further requests go via /proxy
if "text/html" in content_type:
rewritten = rewrite_html(upstream.text, base_url=url)
return HTMLResponse(content=rewritten, status_code=upstream.status_code)
# Non-HTML (images, videos, audio, JS, CSS, fonts...): pass through
safe_headers = {}
for k, v in upstream.headers.items():
lk = k.lower()
# Strip hop-by-hop and encoding headers (let FastAPI handle compression)
if lk in ("content-encoding", "transfer-encoding", "connection"):
continue
safe_headers[k] = v
return Response(
content=upstream.content,
status_code=upstream.status_code,
headers=safe_headers,
media_type=content_type or None,
)