vaka.pro commited on
Commit
7f857aa
·
1 Parent(s): 19e521c

refactor: refactor code for gradio client

Browse files
Files changed (3) hide show
  1. README.md +1 -26
  2. app.py +84 -100
  3. requirements.txt +3 -2
README.md CHANGED
@@ -10,32 +10,7 @@ pinned: false
10
 
11
  # Proxy Service
12
 
13
- 🔒 **Сервисное приложение для проброса вебхуков Яндекс.Форм**
14
-
15
- ## ⚠️ ВНИМАНИЕ
16
-
17
- Это **сервисное приложение**, предназначенное исключительно для системной интеграции.
18
-
19
- ## 📋 Назначение
20
-
21
- Этот прокси-сервер перенаправляет входящие вебхуки в приватное пространство.
22
-
23
- ## 📊 Доступные эндпоинты
24
-
25
- | Метод | Путь | Назначение |
26
- |-------|------|------------|
27
- | `GET` | `/ui` | **Интерфейс администратора** (Проверка статуса) |
28
- | `POST` | `/{SECRET_PATH}` | **Прием вебхуков** (Только известный путь) |
29
- | `GET` | `/health` | Проверка работоспособности (Health Check) |
30
- | `GET` | `/api` | Техническая информация о сервисе |
31
-
32
- > **Примечание:** Вебхуки принимаются только на путь, указанный в переменной окружения `PUBLIC_ENDPOINT`. Все остальные запросы возвращают 404.
33
-
34
- ## 🛠 Конфигурация
35
-
36
- Для работы требуются Secrets:
37
- - `PRIVATE_SPACE_URL`: Полный URL целевого сервиса
38
- - `PUBLIC_ENDPOINT`: Секретный путь для приема вебхуков
39
 
40
  ## 📞 Поддержка
41
 
 
10
 
11
  # Proxy Service
12
 
13
+ 🔒 **Сервисное приложение для проброса вебхуков**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  ## 📞 Поддержка
16
 
app.py CHANGED
@@ -1,35 +1,44 @@
1
  import os
2
- import requests
3
  import gradio as gr
4
  from fastapi import FastAPI, Request, HTTPException
5
  from fastapi.responses import JSONResponse, RedirectResponse
 
6
 
7
- # Конфигурация
8
- PRIVATE_SPACE_URL = os.getenv("PRIVATE_SPACE_URL")
 
9
  PUBLIC_ENDPOINT = os.getenv("PUBLIC_ENDPOINT")
 
10
 
11
- # ==================== FastAPI приложение ====================
12
- fastapi_app = FastAPI(title="Proxy Service")
13
 
14
 
15
- # Health check
16
- @fastapi_app.get("/health")
17
- async def health_check():
18
- """Безопасная проверка работоспособности."""
19
- is_configured = bool(PRIVATE_SPACE_URL and PUBLIC_ENDPOINT)
20
- return JSONResponse(
21
- content={
22
- "status": "healthy" if is_configured else "degraded",
23
- "service": "proxy",
24
- "timestamp": "...",
25
- }
26
- )
 
 
 
 
 
 
27
 
 
 
28
 
29
- # Корневой эндпоинт
30
- @fastapi_app.get("/api")
31
- async def root():
32
- return JSONResponse(content={"service": "API Gateway", "status": "operational"})
33
 
34
 
35
  @fastapi_app.get("/")
@@ -37,100 +46,75 @@ async def root_redirect():
37
  return RedirectResponse(url="/ui")
38
 
39
 
40
- # ==================== Безопасный Gradio интерфейс ====================
41
- def create_gradio_interface():
42
- with gr.Blocks(title="Proxy Service") as demo:
43
- gr.Markdown("# 🔒 Защищенный прокси-сервис")
44
- gr.Markdown(
45
- """
46
- ### ⚠️ Сервисное приложение
47
- Этот сервис предназначен исключительно для системной интеграции.
48
-
49
- ---
50
-
51
- ### 📊 Системный статус
52
- """
53
- )
54
-
55
- # Безопасный статус
56
- status_box = gr.Textbox(label="Статус", interactive=False, lines=4)
57
-
58
- def get_safe_status():
59
- """Возвращает безопасный статус без конфиденциальной информации."""
60
- if not PUBLIC_ENDPOINT or not PRIVATE_SPACE_URL:
61
- return "🔧 Требуется настройка\n\nУстановите необходимые переменные окружения."
62
-
63
- return """✅ Сервис готов к работе
64
 
65
- Статус: Активен
66
- Вебхук: Настроен
67
- Целевой сервис: Доступен
68
 
69
- Для настройки интеграции обратитесь к администратору системы."""
 
 
70
 
71
- # Связываем кнопку со статусом
72
- refresh_btn = gr.Button("Обновить статус")
73
- refresh_btn.click(get_safe_status, outputs=status_box)
 
 
74
 
75
- # Загружаем статус при загрузке
76
- demo.load(get_safe_status, outputs=status_box)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
- return demo
 
 
 
79
 
80
 
81
- # Создаем интерфейс
82
- demo = create_gradio_interface()
 
 
83
 
84
- # Принудительно прописываем путь корня (нужно для корректной работы JS/CSS)
85
- demo.root_path = "/ui"
 
 
86
 
87
- # КЛЮЧЕВОЕ ИЗМЕНЕНИЕ: монтируем Gradio на /ui, а не на /
88
- app = gr.mount_gradio_app(fastapi_app, demo, path="/ui", root_path="/ui")
89
 
 
 
 
 
 
90
 
91
- # Универсальный обработчик
92
- @fastapi_app.post("/{path:path}")
93
- async def proxy_webhook(path: str, request: Request):
94
- """Обработчик вебхуков с проверкой пути."""
95
- # Проверяем секретный путь
96
- if not PUBLIC_ENDPOINT or path != PUBLIC_ENDPOINT:
97
- raise HTTPException(status_code=404, detail="Not found")
98
 
99
- if not PRIVATE_SPACE_URL:
100
- raise HTTPException(status_code=500, detail="Service error")
101
 
102
- try:
103
- body_bytes = await request.body()
104
-
105
- # Минимальные заголовки
106
- headers = {
107
- "Content-Type": request.headers.get("content-type", "application/json"),
108
- }
109
-
110
- # Только заголовки
111
- incoming_headers = ["X-FORM-ID", "X-DELIVERY-ID", "X-FORM-ANSWER-ID"]
112
- for header in incoming_headers:
113
- if value := request.headers.get(header):
114
- headers[header] = value
115
-
116
- # Отправляем запрос
117
- response = requests.post(
118
- PRIVATE_SPACE_URL, data=body_bytes, headers=headers, timeout=25
119
- )
120
- print("data:", body_bytes)
121
- print("headers:", headers)
122
- print("PRIVATE_SPACE_URL:", PRIVATE_SPACE_URL)
123
- print("JSONResponse:", JSONResponse(response.json()))
124
- # Возвращаем ответ
125
- return JSONResponse(response.json())
126
-
127
- except requests.exceptions.Timeout:
128
- raise HTTPException(status_code=504, detail="Service timeout")
129
- except Exception:
130
- raise HTTPException(status_code=500, detail="Internal error")
131
 
 
132
 
133
- # Локальный запуск
134
  if __name__ == "__main__":
135
  import uvicorn
136
 
 
1
  import os
2
+ import json
3
  import gradio as gr
4
  from fastapi import FastAPI, Request, HTTPException
5
  from fastapi.responses import JSONResponse, RedirectResponse
6
+ from gradio_client import Client
7
 
8
+ # ==================== Конфигурация ====================
9
+ PRIVATE_SPACE_ID = os.getenv("PRIVATE_SPACE_ID")
10
+ PRIVATE_TOKEN = os.getenv("PRIVATE_TOKEN")
11
  PUBLIC_ENDPOINT = os.getenv("PUBLIC_ENDPOINT")
12
+ PRIVATE_ENDPOINT = os.getenv("PRIVATE_ENDPOINT")
13
 
14
+ # ==================== Инициализация Клиента ====================
15
+ client = None
16
 
17
 
18
+ def init_client():
19
+ """Ленивая или повторная инициализация клиента"""
20
+ global client
21
+ if not PRIVATE_SPACE_ID or not PRIVATE_TOKEN:
22
+ print("⚠️ Config missing: PRIVATE_SPACE_ID or HF_TOKEN not set.")
23
+ return None
24
+
25
+ print("🔄 Initializing upstream connection...")
26
+ try:
27
+ # Клиент создается
28
+ new_client = Client(PRIVATE_SPACE_ID, hf_token=PRIVATE_TOKEN, verbose=False)
29
+ print("✅ Upstream connection established.")
30
+ return new_client
31
+ except Exception as e:
32
+ # Логируем только факт ошибки, не печатаем само исключение, так как оно может содержать URL
33
+ print("❌ Connection failed.")
34
+ return None
35
+
36
 
37
+ # Пробуем соединиться при старте
38
+ client = init_client()
39
 
40
+ # ==================== FastAPI (Прокси) ====================
41
+ fastapi_app = FastAPI(title="Secure Proxy")
 
 
42
 
43
 
44
  @fastapi_app.get("/")
 
46
  return RedirectResponse(url="/ui")
47
 
48
 
49
+ @fastapi_app.get("/health")
50
+ async def health_check():
51
+ # Не показываем детали, просто жив сервис или нет
52
+ return {"status": "ok" if client else "connecting"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
 
 
 
54
 
55
+ @fastapi_app.post(f"/{PUBLIC_ENDPOINT}")
56
+ async def proxy_webhook(request: Request):
57
+ global client
58
 
59
+ # Если клиент отвалился или не был создан, пробуем пересоздать
60
+ if not client:
61
+ client = init_client()
62
+ if not client:
63
+ raise HTTPException(status_code=503, detail="Upstream unavailable")
64
 
65
+ try:
66
+ # 1. Получаем JSON (без логирования содержимого)
67
+ payload = await request.json()
68
+
69
+ # 2. Конвертируем в строку для Gradio
70
+ payload_str = json.dumps(payload, ensure_ascii=False)
71
+
72
+ # 3. Вызываем приватный API
73
+ # Используем api_name, это безопаснее при изменениях кода.
74
+ result = client.predict(payload_str, api_name=f"/{PRIVATE_ENDPOINT}")
75
+
76
+ # 4. Возвращаем успех, не показывая ответ приватного сервера (если там есть данные)
77
+ # Если нужно вернуть ID для отладки Яндекс.Форм, можно вернуть минимальный JSON
78
+ return JSONResponse(
79
+ content={
80
+ "status": "forwarded",
81
+ "timestamp": os.urandom(4).hex(), # Просто уникальный маркер
82
+ }
83
+ )
84
 
85
+ except Exception:
86
+ # В случае ошибки не возвращаем её текст клиенту, чтобы не светить внутренности
87
+ print("❌ Error during forwarding.")
88
+ raise HTTPException(status_code=500, detail="Internal forwarding error")
89
 
90
 
91
+ # ==================== Безопасный Gradio UI ====================
92
+ def create_secure_ui():
93
+ with gr.Blocks(title="System Status") as demo:
94
+ gr.Markdown("## 🛡️ Gateway Status")
95
 
96
+ # Индикатор статуса без деталей
97
+ status_indicator = gr.Textbox(
98
+ label="Service State", value="Checking...", interactive=False
99
+ )
100
 
101
+ btn = gr.Button("Refresh Status")
 
102
 
103
+ def check_safe():
104
+ if client:
105
+ return "✅ Online / Connected to Upstream"
106
+ else:
107
+ return "⚠️ Offline / Reconnecting..."
108
 
109
+ # При загрузке и нажатии проверяем статус
110
+ demo.load(check_safe, outputs=status_indicator)
111
+ btn.click(check_safe, outputs=status_indicator)
 
 
 
 
112
 
113
+ return demo
 
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
+ app = gr.mount_gradio_app(fastapi_app, create_secure_ui(), path="/ui")
117
 
 
118
  if __name__ == "__main__":
119
  import uvicorn
120
 
requirements.txt CHANGED
@@ -1,4 +1,5 @@
1
- gradio
2
  fastapi==0.104.1
3
  uvicorn[standard]==0.24.0
4
- requests==2.31.0
 
 
 
 
1
  fastapi==0.104.1
2
  uvicorn[standard]==0.24.0
3
+ python-multipart==0.0.6
4
+ gradio>=4.0.0
5
+ gradio_client>=0.8.0