Spaces:
Sleeping
Sleeping
| # app.py (Lightweight Reverse Proxy for Runflare) | |
| import os | |
| import ssl | |
| import asyncio | |
| import httpx | |
| import websockets | |
| from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect | |
| from fastapi.responses import StreamingResponse, HTMLResponse, FileResponse | |
| app = FastAPI() | |
| # 🟢 آدرس اسپیس هاگینگ فیس شما (مغز متفکر اصلی) | |
| # میتوانید این آدرس را تغییر دهید تا به اسپیس مد نظر شما اشاره کند | |
| HF_SPACE_URL = os.environ.get("HF_SPACE_URL", "https://opera8-runflare.hf.space").strip() | |
| HF_WS_URL = HF_SPACE_URL.replace("https://", "wss://") + "/ws" | |
| # هدرهای امنیتی جهت اعطای دسترسی دوربین و میکروفون به مرورگر و وبویو موبایل | |
| PERMISSION_HEADERS = { | |
| "Permissions-Policy": "camera=*, microphone=*, geolocation=*, autoplay=*", | |
| "Feature-Policy": "camera *; microphone *; autoplay *", | |
| "Access-Control-Allow-Origin": "*", | |
| "Access-Control-Allow-Methods": "GET, POST, OPTIONS", | |
| "Access-Control-Allow-Headers": "*" | |
| } | |
| # غیرفعال کردن بررسی سختگیرانه SSL برای جلوگیری از خطاهای احتمالی ارتباط داخلی داکر رانفلر | |
| ssl_context = ssl.create_default_context() | |
| ssl_context.check_hostname = False | |
| ssl_context.verify_mode = ssl.CERT_NONE | |
| # ================================================================= | |
| # ۱. مانیتورینگ و پروکسی اتصالهای وبسوکت صوتی (/ws) به هاگینگ فیس | |
| # ================================================================= | |
| async def websocket_proxy_route(client_ws: WebSocket): | |
| await client_ws.accept() | |
| # شبیهسازی دستتکانی وبسوکت برای عبور از سد فایروال هاگینگفیس | |
| headers = { | |
| "Host": HF_SPACE_URL.replace("https://", ""), | |
| "Origin": HF_SPACE_URL, | |
| "User-Agent": client_ws.headers.get("user-agent", "Mozilla/5.0") | |
| } | |
| ws_kwargs = {} | |
| try: | |
| ws_version = getattr(websockets, "__version__", "10.0") | |
| try: | |
| major_version = int(ws_version.split('.')[0]) | |
| except ValueError: | |
| major_version = 10 | |
| if major_version >= 14: | |
| ws_kwargs["additional_headers"] = headers | |
| else: | |
| ws_kwargs["extra_headers"] = headers | |
| except Exception: | |
| ws_kwargs["extra_headers"] = headers | |
| try: | |
| # اتصال مستقیم و پایدار وبسوکت به هاگینگفیس | |
| async with websockets.connect( | |
| HF_WS_URL, | |
| ssl=ssl_context, | |
| **ws_kwargs | |
| ) as server_ws: | |
| async def client_to_server(): | |
| try: | |
| while True: | |
| data = await client_ws.receive() | |
| if "text" in data and data["text"] is not None: | |
| await server_ws.send(data["text"]) | |
| elif "bytes" in data and data["bytes"] is not None: | |
| await server_ws.send(data["bytes"]) | |
| except Exception: | |
| pass | |
| async def server_to_client(): | |
| try: | |
| async for message in server_ws: | |
| if isinstance(message, str): | |
| await client_ws.send_text(message) | |
| else: | |
| await client_ws.send_bytes(message) | |
| except Exception: | |
| pass | |
| await asyncio.gather(client_to_server(), server_to_client()) | |
| except WebSocketDisconnect: | |
| pass | |
| except Exception as e: | |
| print(f"[WS Proxy Error]: {e}") | |
| finally: | |
| try: | |
| await client_ws.close() | |
| except: | |
| pass | |
| # ================================================================= | |
| # ۲. مسیر یاب و پروکسی سراسری و استریمینگ تمام درخواستهای وب و API | |
| # ================================================================= | |
| async def reverse_proxy_route(request: Request, path: str): | |
| # کپی کردن هدرهای درخواست کاربر | |
| headers = dict(request.headers) | |
| # حذف هدرهای محلی رانفلر برای جلوگیری از تداخل مسیر در هاگینگفیس | |
| headers.pop("host", None) | |
| headers.pop("content-length", None) | |
| # فوروارد کردن آیپی واقعی کاربر جهت پایداری سهمیه اعتبارات | |
| cf_ip = request.headers.get("CF-Connecting-IP") | |
| if cf_ip: | |
| headers["X-Forwarded-For"] = cf_ip | |
| url = f"{HF_SPACE_URL}/{path}" | |
| body = await request.body() | |
| # دریافت استریم دادهها از هاگینگفیس و تحویل آنی به کاربر (بدون بافر کردن در رم رانفلر) | |
| async def generate_response(): | |
| async with httpx.AsyncClient(verify=False, timeout=300.0) as client_local: | |
| async with client_local.stream( | |
| request.method, | |
| url, | |
| content=body, | |
| headers=headers, | |
| params=dict(request.query_params) | |
| ) as resp: | |
| async for chunk in resp.aiter_bytes(): | |
| yield chunk | |
| # دریافت متادیتای هدرها از هاگینگفیس | |
| async with httpx.AsyncClient(verify=False, timeout=300.0) as client_headers: | |
| resp_headers = await client_headers.request( | |
| request.method, | |
| url, | |
| content=body, | |
| headers=headers, | |
| params=dict(request.query_params) | |
| ) | |
| # آمادهسازی هدرهای پاسخ برای بازگرداندن به مرورگر کاربر | |
| headers_to_return = dict(resp_headers.headers) | |
| headers_to_return.pop("content-encoding", None) | |
| headers_to_return.pop("content-length", None) | |
| headers_to_return.pop("transfer-encoding", None) | |
| # تزریق هدرهای دسترسی و کرس (CORS) | |
| for k, v in PERMISSION_HEADERS.items(): | |
| headers_to_return[k] = v | |
| return StreamingResponse( | |
| generate_response(), | |
| status_code=resp_headers.status_code, | |
| headers=headers_to_return, | |
| media_type=resp_headers.headers.get("content-type") | |
| ) |