| | |
| | 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" |
| |
|
| | |
| | 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" |
| | } |
| | ] |
| |
|
| | |
| | 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}") |
| | |
| | |
| | if self.path == "/graphs": |
| | self._handle_graphs_request() |
| | |
| | elif self.path.startswith("/api/designer/") or self.path.startswith("/api/dev/"): |
| | self._handle_designer_request() |
| | |
| | else: |
| | self._proxy_request("GET") |
| | |
| | def do_POST(self): |
| | """Обработка POST запросов""" |
| | logger.info(f"PROXY: POST запрос: {self.path}") |
| | |
| | |
| | if self.path.startswith("/api/designer/") or self.path.startswith("/api/dev/"): |
| | self._handle_designer_request() |
| | |
| | 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: |
| | |
| | url = f"{API_SERVER}{self.path}" |
| | try: |
| | with urllib.request.urlopen(url) as response: |
| | data = response.read().decode('utf-8') |
| | |
| | |
| | try: |
| | json_data = json.loads(data) |
| | |
| | |
| | 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}" |
| | |
| | |
| | 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) |
| | |
| | |
| | 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) |