#!/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)