import os import json import gradio as gr from fastapi import FastAPI, Request, HTTPException from fastapi.responses import JSONResponse, RedirectResponse from gradio_client import Client # ==================== Конфигурация ==================== PRIVATE_SPACE_URL = os.getenv("PRIVATE_SPACE_URL") PRIVATE_TOKEN = os.getenv("PRIVATE_TOKEN") PUBLIC_ENDPOINT = os.getenv("PUBLIC_ENDPOINT") PRIVATE_ENDPOINT = os.getenv("PRIVATE_ENDPOINT") # ==================== Инициализация Клиента ==================== client = None def init_client(): """Ленивая или повторная инициализация клиента""" global client if not PRIVATE_SPACE_URL or not PRIVATE_TOKEN: print("⚠️ Config missing: PRIVATE_SPACE_URL or TOKEN not set.") return None print("🔄 Initializing upstream connection...") try: # Клиент создается new_client = Client(PRIVATE_SPACE_URL, token=PRIVATE_TOKEN, verbose=False) print("✅ Upstream connection established.") return new_client except Exception as e: # Логируем только факт ошибки, не печатаем само исключение, так как оно может содержать URL print(PRIVATE_SPACE_URL) print("❌ Connection failed.") print(e) return None # Пробуем соединиться при старте client = init_client() # ==================== FastAPI (Прокси) ==================== fastapi_app = FastAPI(title="Secure Proxy") @fastapi_app.get("/") async def root_redirect(): return RedirectResponse(url="/ui") @fastapi_app.get("/health") async def health_check(): # Не показываем детали, просто жив сервис или нет return {"status": "ok" if client else "connecting"} @fastapi_app.post(f"/{PUBLIC_ENDPOINT}") async def proxy_webhook(request: Request): global client # Если клиент отвалился или не был создан, пробуем пересоздать if not client: client = init_client() if not client: raise HTTPException(status_code=503, detail="Upstream unavailable") try: # 1. Получаем JSON (без логирования содержимого) payload = await request.json() # 2. Конвертируем в строку для Gradio payload_str = json.dumps(payload, ensure_ascii=False) # 3. Вызываем приватный API # Используем api_name, это безопаснее при изменениях кода. result = client.predict(payload_str, api_name=f"/{PRIVATE_ENDPOINT}") # 4. Возвращаем успех, не показывая ответ приватного сервера (если там есть данные) # Если нужно вернуть ID для отладки Яндекс.Форм, можно вернуть минимальный JSON return JSONResponse( content={ "status": "forwarded", "timestamp": os.urandom(4).hex(), # Просто уникальный маркер } ) except Exception: # В случае ошибки не возвращаем её текст клиенту, чтобы не светить внутренности print("❌ Error during forwarding.") raise HTTPException(status_code=500, detail="Internal forwarding error") # ==================== Безопасный Gradio UI ==================== def create_secure_ui(): with gr.Blocks(title="System Status") as demo: gr.Markdown("## 🛡️ Gateway Status") # Индикатор статуса без деталей status_indicator = gr.Textbox( label="Service State", value="Checking...", interactive=False ) btn = gr.Button("Refresh Status") def check_safe(): if client: return "✅ Online / Connected to Upstream" else: return "⚠️ Offline / Reconnecting..." # При загрузке и нажатии проверяем статус demo.load(check_safe, outputs=status_indicator) btn.click(check_safe, outputs=status_indicator) return demo app = gr.mount_gradio_app(fastapi_app, create_secure_ui(), path="/ui") if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=7860)