#!/usr/bin/env python3 import http.server import socketserver import json import logging import sys import os import urllib.request # Настройка логирования 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 = int(os.environ.get('PROXY_PORT', '9090')) API_PORT = 8080 # Предопределенные данные для графов GRAPHS_DATA = [ { "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" } ] # Данные для API дизайнера DESIGNER_DATA = { "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 SimpleProxyHandler(http.server.BaseHTTPRequestHandler): def do_GET(self): logger.info(f"PROXY: GET запрос: {self.path}") # Для запросов к /graphs ВСЕГДА возвращаем заранее подготовленный ответ if self.path == "/graphs": logger.info("PROXY: Возвращаем заготовленные данные о графах") self._send_response(200, json.dumps(GRAPHS_DATA)) return # Для запросов к Designer API ВСЕГДА возвращаем заранее подготовленный ответ if self.path.startswith("/api/designer/") or self.path.startswith("/api/dev/"): logger.info(f"PROXY: Обработка запроса к Designer API: {self.path}") self._send_response(200, json.dumps(DESIGNER_DATA)) return # Для других запросов пробуем проксировать на API сервер try: self._proxy_to_api("GET") except Exception as e: logger.error(f"PROXY: Ошибка при проксировании GET запроса: {e}") self._send_response(200, json.dumps({"success": True})) def do_POST(self): logger.info(f"PROXY: POST запрос: {self.path}") # Для запросов к Designer API ВСЕГДА возвращаем заранее подготовленный ответ if self.path.startswith("/api/designer/") or self.path.startswith("/api/dev/"): logger.info(f"PROXY: Обработка POST запроса к Designer API: {self.path}") self._send_response(200, json.dumps({"success": True})) return # Для других запросов пробуем проксировать на API сервер try: self._proxy_to_api("POST") except Exception as e: logger.error(f"PROXY: Ошибка при проксировании POST запроса: {e}") self._send_response(200, json.dumps({"success": True})) def do_OPTIONS(self): logger.info(f"PROXY: OPTIONS запрос: {self.path}") 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 _proxy_to_api(self, method): """Проксирует запрос к API серверу""" url = f"http://localhost:{API_PORT}{self.path}" logger.info(f"PROXY: Проксирование запроса к API: {url}") req = urllib.request.Request(url, method=method) # Копирование заголовков for header, value in self.headers.items(): if header.lower() not in ["host", "content-length"]: req.add_header(header, value) # Для POST-запросов копируем тело if method == "POST": content_length = int(self.headers.get('Content-Length', 0)) body = self.rfile.read(content_length) req.data = body # Выполняем запрос к API серверу with urllib.request.urlopen(req) as response: # Отправляем ответ клиенту 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.end_headers() # Копируем тело ответа self.wfile.write(response.read()) def _send_response(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 main(): port = PROXY_PORT logger.info(f"Запуск автономного прокси-сервера на порту {port}") # Пробуем разные порты, если основной занят for attempt in range(3): try: httpd = socketserver.TCPServer(("", port), SimpleProxyHandler) logger.info(f"Автономный прокси-сервер успешно запущен на порту {port}") httpd.serve_forever() break except OSError as e: if e.errno == 98: # Address already in use logger.warning(f"Порт {port} уже занят, пробуем порт {port+1}") port += 1 else: logger.error(f"Ошибка при запуске сервера: {e}") raise if __name__ == "__main__": main()