ten / proxy_server.py
3v324v23's picture
Исправление проблемы отображения графов: добавление поля predefined_graphs и прокси-сервер для модификации ответов API
9c56f14
#!/usr/bin/env python3
import http.server
import socketserver
import urllib.request
import urllib.error
import json
import logging
import sys
import os
from pathlib import Path
# Настройка логирования
logging.basicConfig(level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
logger = logging.getLogger('ten-proxy')
# Настройки прокси
PROXY_PORT = 9090
API_SERVER = "http://localhost:8080"
# Мок-данные для /graphs
MOCK_GRAPHS_RESPONSE = [
{
"name": "Voice Agent",
"description": "Voice Agent with OpenAI",
"file": "voice_agent.json",
"id": "voice_agent",
"package": "default"
},
{
"name": "Chat Agent",
"description": "Chat Agent",
"file": "chat_agent.json",
"id": "chat_agent",
"package": "default"
}
]
# Мок-данные для designer API
MOCK_DESIGNER_RESPONSE = {
"success": True,
"packages": [
{
"name": "default",
"description": "Default package",
"graphs": [
{
"name": "Voice Agent",
"description": "Voice Agent with OpenAI",
"file": "voice_agent.json",
"id": "voice_agent",
"package": "default"
},
{
"name": "Chat Agent",
"description": "Chat Agent",
"file": "chat_agent.json",
"id": "chat_agent",
"package": "default"
}
]
}
]
}
class ProxyHandler(http.server.BaseHTTPRequestHandler):
"""Обработчик запросов для прокси-сервера"""
def do_GET(self):
"""Обработка GET запросов"""
logger.info(f"PROXY: GET запрос: {self.path}")
# Перехватываем запросы к /graphs
if self.path == "/graphs":
self._handle_graphs_request()
# Перехватываем запросы к designer API
elif self.path.startswith("/api/designer/") or self.path.startswith("/api/dev/"):
self._handle_designer_request()
# Все остальные запросы перенаправляем на API сервер
else:
self._proxy_request("GET")
def do_POST(self):
"""Обработка POST запросов"""
logger.info(f"PROXY: POST запрос: {self.path}")
# Перехватываем запросы к designer API
if self.path.startswith("/api/designer/") or self.path.startswith("/api/dev/"):
self._handle_designer_request()
# Все остальные запросы перенаправляем на API сервер
else:
self._proxy_request("POST")
def do_OPTIONS(self):
"""Обработка OPTIONS запросов для CORS"""
self.send_response(200)
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
self.end_headers()
def _handle_graphs_request(self):
"""Обрабатывает запросы к /graphs с возможностью модификации ответа"""
try:
# Сначала пробуем получить данные от реального API
url = f"{API_SERVER}{self.path}"
try:
with urllib.request.urlopen(url) as response:
data = response.read().decode('utf-8')
# Проверяем, является ли ответ валидным JSON
try:
json_data = json.loads(data)
# Если API вернул пустой список или ошибку, заменяем на мок-данные
if (isinstance(json_data, list) and len(json_data) == 0) or (isinstance(json_data, dict) and "code" in json_data):
logger.warning(f"API вернул пустой список или ошибку, используем мок-данные")
self._send_success_response(MOCK_GRAPHS_RESPONSE)
return
# Если всё нормально, возвращаем оригинальный ответ
logger.info(f"API вернул непустой список графов, используем его")
self._send_response_with_data(200, data)
return
except json.JSONDecodeError:
logger.error(f"Ответ API не является валидным JSON: {data}")
self._send_success_response(MOCK_GRAPHS_RESPONSE)
return
except urllib.error.URLError as e:
logger.error(f"Не удалось подключиться к API: {e}")
# В случае ошибки используем мок-данные
self._send_success_response(MOCK_GRAPHS_RESPONSE)
return
except Exception as e:
logger.error(f"Ошибка при обработке запроса /graphs: {e}")
self._send_success_response(MOCK_GRAPHS_RESPONSE)
def _handle_designer_request(self):
"""Обрабатывает запросы к designer API, всегда возвращая мок-данные"""
logger.info(f"Перехват запроса к Designer API: {self.path}")
self._send_success_response(MOCK_DESIGNER_RESPONSE)
def _proxy_request(self, method):
"""Перенаправляет запрос на API сервер"""
try:
url = f"{API_SERVER}{self.path}"
# Получение данных запроса для POST
data = None
if method == "POST":
content_length = int(self.headers.get('Content-Length', 0))
data = self.rfile.read(content_length)
# Создание запроса
req = urllib.request.Request(url, data=data, method=method)
# Копирование заголовков
for header, value in self.headers.items():
if header.lower() not in ["host", "content-length"]:
req.add_header(header, value)
# Выполнение запроса
try:
with urllib.request.urlopen(req) as response:
# Чтение данных ответа
response_data = response.read()
# Отправка ответа клиенту
self.send_response(response.status)
# Копирование заголовков ответа
for header, value in response.getheaders():
if header.lower() != "transfer-encoding":
self.send_header(header, value)
# Добавление CORS заголовков
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
self.end_headers()
self.wfile.write(response_data)
except urllib.error.URLError as e:
logger.error(f"Ошибка при проксировании запроса: {e}")
self.send_error(502, f"Bad Gateway: {e}")
except Exception as e:
logger.error(f"Ошибка при обработке запроса: {e}")
self.send_error(500, f"Internal Server Error: {e}")
def _send_success_response(self, data):
"""Отправляет успешный ответ с данными"""
response_data = json.dumps(data)
self._send_response_with_data(200, response_data)
def _send_response_with_data(self, status_code, data):
"""Отправляет ответ с указанным статусом и данными"""
self.send_response(status_code)
self.send_header('Content-Type', 'application/json')
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
self.end_headers()
if isinstance(data, str):
self.wfile.write(data.encode('utf-8'))
else:
self.wfile.write(data)
def log_message(self, format, *args):
"""Настраиваем логирование для сервера"""
logger.debug(f"PROXY: {self.address_string()} - {format % args}")
def run_proxy_server(port=PROXY_PORT):
"""Запускает прокси-сервер"""
try:
with socketserver.TCPServer(("", port), ProxyHandler) as httpd:
logger.info(f"Запуск прокси-сервера на порту {port}")
httpd.serve_forever()
except KeyboardInterrupt:
logger.info("Прокси-сервер остановлен")
except Exception as e:
logger.error(f"Ошибка при запуске прокси-сервера: {e}")
if __name__ == "__main__":
# Запуск прокси-сервера
proxy_port = int(os.environ.get("PROXY_PORT", PROXY_PORT))
run_proxy_server(proxy_port)