Добавление прокси-сервера для отладки и перехвата запросов UI-API
Browse files- Dockerfile +4 -1
- app.py +141 -3
Dockerfile
CHANGED
|
@@ -22,6 +22,9 @@ RUN apt-get clean && apt-get update && apt-get install -y --no-install-recommend
|
|
| 22 |
ca-certificates \
|
| 23 |
&& apt-get clean && rm -rf /var/lib/apt/lists/* && rm -rf /tmp/*
|
| 24 |
|
|
|
|
|
|
|
|
|
|
| 25 |
# Установка Go 1.21
|
| 26 |
RUN wget https://golang.org/dl/go1.21.0.linux-amd64.tar.gz && \
|
| 27 |
tar -C /usr/local -xzf go1.21.0.linux-amd64.tar.gz && \
|
|
@@ -189,7 +192,7 @@ RUN chmod +x /app/app.py && \
|
|
| 189 |
USER tenuser
|
| 190 |
|
| 191 |
# Открываем порты
|
| 192 |
-
EXPOSE 7860 8080 3000
|
| 193 |
|
| 194 |
# Запускаем API сервер и Playground в dev-режиме
|
| 195 |
ENTRYPOINT ["python3", "/app/app.py"]
|
|
|
|
| 22 |
ca-certificates \
|
| 23 |
&& apt-get clean && rm -rf /var/lib/apt/lists/* && rm -rf /tmp/*
|
| 24 |
|
| 25 |
+
# Установка Python-пакетов
|
| 26 |
+
RUN pip3 install requests
|
| 27 |
+
|
| 28 |
# Установка Go 1.21
|
| 29 |
RUN wget https://golang.org/dl/go1.21.0.linux-amd64.tar.gz && \
|
| 30 |
tar -C /usr/local -xzf go1.21.0.linux-amd64.tar.gz && \
|
|
|
|
| 192 |
USER tenuser
|
| 193 |
|
| 194 |
# Открываем порты
|
| 195 |
+
EXPOSE 7860 8080 3000 9090
|
| 196 |
|
| 197 |
# Запускаем API сервер и Playground в dev-режиме
|
| 198 |
ENTRYPOINT ["python3", "/app/app.py"]
|
app.py
CHANGED
|
@@ -12,6 +12,11 @@ import logging
|
|
| 12 |
import urllib.request
|
| 13 |
import urllib.error
|
| 14 |
import tempfile
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
# Настройка логирования
|
| 17 |
logging.basicConfig(level=logging.INFO,
|
|
@@ -30,6 +35,133 @@ API_BINARY = Path("/app/server/bin/api")
|
|
| 30 |
PLAYGROUND_DIR = Path("/app/playground")
|
| 31 |
BACKUP_DIR = Path("/app/backup")
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
def ensure_directory_permissions(directory_path):
|
| 34 |
"""Обеспечиваем правильные разрешения для директории"""
|
| 35 |
directory = Path(directory_path)
|
|
@@ -264,17 +396,23 @@ def main():
|
|
| 264 |
test_thread.daemon = True
|
| 265 |
test_thread.start()
|
| 266 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
# Запускаем Playground UI в режиме dev на порту 7860 (порт Hugging Face)
|
| 268 |
logger.info("Запуск Playground UI в режиме разработки на порту 7860...")
|
| 269 |
os.environ["PORT"] = "7860"
|
| 270 |
-
os.environ["AGENT_SERVER_URL"] = "http://localhost:
|
| 271 |
os.environ["NEXT_PUBLIC_EDIT_GRAPH_MODE"] = "true" # Включаем расширенный режим редактирования
|
| 272 |
os.environ["NEXT_PUBLIC_DISABLE_CAMERA"] = "true" # Отключаем запрос на использование камеры
|
| 273 |
|
| 274 |
# Важные переменные для отключения запросов к дизайнеру
|
| 275 |
os.environ["NEXT_PUBLIC_DEV_MODE"] = "false"
|
| 276 |
-
os.environ["NEXT_PUBLIC_API_BASE_URL"] = "/api/agents"
|
| 277 |
-
os.environ["NEXT_PUBLIC_DESIGNER_API_URL"] = "http://localhost:
|
| 278 |
|
| 279 |
# Запускаем Playground UI
|
| 280 |
playground_process = subprocess.Popen(
|
|
|
|
| 12 |
import urllib.request
|
| 13 |
import urllib.error
|
| 14 |
import tempfile
|
| 15 |
+
import http.server
|
| 16 |
+
import socketserver
|
| 17 |
+
import requests
|
| 18 |
+
from threading import Thread
|
| 19 |
+
from http import HTTPStatus
|
| 20 |
|
| 21 |
# Настройка логирования
|
| 22 |
logging.basicConfig(level=logging.INFO,
|
|
|
|
| 35 |
PLAYGROUND_DIR = Path("/app/playground")
|
| 36 |
BACKUP_DIR = Path("/app/backup")
|
| 37 |
|
| 38 |
+
class ProxyHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
|
| 39 |
+
"""HTTP-прокси для отладки и исправления запросов между UI и API"""
|
| 40 |
+
|
| 41 |
+
def __init__(self, *args, **kwargs):
|
| 42 |
+
# Целевой API сервер
|
| 43 |
+
self.api_host = "localhost"
|
| 44 |
+
self.api_port = 8080
|
| 45 |
+
super().__init__(*args, **kwargs)
|
| 46 |
+
|
| 47 |
+
def do_GET(self):
|
| 48 |
+
"""Обработка GET запросов"""
|
| 49 |
+
logger.info(f"PROXY: GET запрос: {self.path}")
|
| 50 |
+
|
| 51 |
+
# Специальная обработка для запроса графов
|
| 52 |
+
if self.path == "/graphs" or self.path == "/api/agents/graphs":
|
| 53 |
+
self._handle_graphs_request()
|
| 54 |
+
return
|
| 55 |
+
|
| 56 |
+
# Проксирование запроса к API
|
| 57 |
+
target_url = f"http://{self.api_host}:{self.api_port}{self.path}"
|
| 58 |
+
try:
|
| 59 |
+
response = requests.get(target_url, headers=self._get_headers())
|
| 60 |
+
self._send_response(response)
|
| 61 |
+
except Exception as e:
|
| 62 |
+
logger.error(f"PROXY: Ошибка при проксировании GET-запроса: {e}")
|
| 63 |
+
self.send_error(HTTPStatus.INTERNAL_SERVER_ERROR, str(e))
|
| 64 |
+
|
| 65 |
+
def do_POST(self):
|
| 66 |
+
"""Обработка POST запросов"""
|
| 67 |
+
content_length = int(self.headers.get('Content-Length', 0))
|
| 68 |
+
post_data = self.rfile.read(content_length) if content_length > 0 else b''
|
| 69 |
+
|
| 70 |
+
logger.info(f"PROXY: POST запрос: {self.path}")
|
| 71 |
+
|
| 72 |
+
# Специальные запросы для дизайнера
|
| 73 |
+
if "/api/designer/v1/packages/reload" in self.path or "/api/dev/v1/packages/reload" in self.path:
|
| 74 |
+
self._handle_designer_reload()
|
| 75 |
+
return
|
| 76 |
+
|
| 77 |
+
# Проксирование запроса к API
|
| 78 |
+
target_url = f"http://{self.api_host}:{self.api_port}{self.path}"
|
| 79 |
+
try:
|
| 80 |
+
response = requests.post(target_url, data=post_data, headers=self._get_headers())
|
| 81 |
+
self._send_response(response)
|
| 82 |
+
except Exception as e:
|
| 83 |
+
logger.error(f"PROXY: Ошибка при проксировании POST-запроса: {e}")
|
| 84 |
+
self.send_error(HTTPStatus.INTERNAL_SERVER_ERROR, str(e))
|
| 85 |
+
|
| 86 |
+
def _handle_graphs_request(self):
|
| 87 |
+
"""Специальная обработка для запроса графов"""
|
| 88 |
+
try:
|
| 89 |
+
# Читаем графы из property.json
|
| 90 |
+
with open(PROPERTY_JSON, 'r') as f:
|
| 91 |
+
property_data = json.load(f)
|
| 92 |
+
|
| 93 |
+
graphs = property_data.get('graphs', [])
|
| 94 |
+
logger.info(f"PROXY: Возвращаем графы напрямую: {json.dumps(graphs)}")
|
| 95 |
+
|
| 96 |
+
self.send_response(HTTPStatus.OK)
|
| 97 |
+
self.send_header('Content-Type', 'application/json')
|
| 98 |
+
self.end_headers()
|
| 99 |
+
self.wfile.write(json.dumps(graphs).encode('utf-8'))
|
| 100 |
+
except Exception as e:
|
| 101 |
+
logger.error(f"PROXY: Ошибка при обработке запроса графов: {e}")
|
| 102 |
+
self.send_error(HTTPStatus.INTERNAL_SERVER_ERROR, str(e))
|
| 103 |
+
|
| 104 |
+
def _handle_designer_reload(self):
|
| 105 |
+
"""Обработка запросов дизайнера"""
|
| 106 |
+
response_data = {
|
| 107 |
+
"success": True,
|
| 108 |
+
"packages": [
|
| 109 |
+
{
|
| 110 |
+
"name": "default",
|
| 111 |
+
"description": "Default package",
|
| 112 |
+
"graphs": []
|
| 113 |
+
}
|
| 114 |
+
]
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
# Читаем графы из property.json
|
| 118 |
+
try:
|
| 119 |
+
with open(PROPERTY_JSON, 'r') as f:
|
| 120 |
+
property_data = json.load(f)
|
| 121 |
+
|
| 122 |
+
graphs = property_data.get('graphs', [])
|
| 123 |
+
response_data["packages"][0]["graphs"] = graphs
|
| 124 |
+
logger.info(f"PROXY: Возвращаем данные для дизайнера с графами: {json.dumps(graphs)}")
|
| 125 |
+
except Exception as e:
|
| 126 |
+
logger.error(f"PROXY: Ошибка при чтении графов для дизайнера: {e}")
|
| 127 |
+
|
| 128 |
+
self.send_response(HTTPStatus.OK)
|
| 129 |
+
self.send_header('Content-Type', 'application/json')
|
| 130 |
+
self.end_headers()
|
| 131 |
+
self.wfile.write(json.dumps(response_data).encode('utf-8'))
|
| 132 |
+
|
| 133 |
+
def _get_headers(self):
|
| 134 |
+
"""Получение заголовков для проксирования"""
|
| 135 |
+
headers = {}
|
| 136 |
+
for header in self.headers:
|
| 137 |
+
headers[header] = self.headers[header]
|
| 138 |
+
return headers
|
| 139 |
+
|
| 140 |
+
def _send_response(self, response):
|
| 141 |
+
"""Отправка ответа клиенту"""
|
| 142 |
+
self.send_response(response.status_code)
|
| 143 |
+
|
| 144 |
+
# Копируем заголовки
|
| 145 |
+
for header, value in response.headers.items():
|
| 146 |
+
self.send_header(header, value)
|
| 147 |
+
self.end_headers()
|
| 148 |
+
|
| 149 |
+
# Отправляем тело ответа
|
| 150 |
+
self.wfile.write(response.content)
|
| 151 |
+
|
| 152 |
+
def log_message(self, format, *args):
|
| 153 |
+
"""Переопределение логирования"""
|
| 154 |
+
logger.debug(f"PROXY: {self.address_string()} - {format % args}")
|
| 155 |
+
|
| 156 |
+
def run_proxy_server(port=9090):
|
| 157 |
+
"""Запуск прокси-сервера"""
|
| 158 |
+
try:
|
| 159 |
+
with socketserver.TCPServer(("", port), ProxyHTTPRequestHandler) as httpd:
|
| 160 |
+
logger.info(f"Запуск прокси-сервера на порту {port}")
|
| 161 |
+
httpd.serve_forever()
|
| 162 |
+
except Exception as e:
|
| 163 |
+
logger.error(f"Ошибка при запуске прокси-сервера: {e}")
|
| 164 |
+
|
| 165 |
def ensure_directory_permissions(directory_path):
|
| 166 |
"""Обеспечиваем правильные разрешения для директории"""
|
| 167 |
directory = Path(directory_path)
|
|
|
|
| 396 |
test_thread.daemon = True
|
| 397 |
test_thread.start()
|
| 398 |
|
| 399 |
+
# Запускаем прокси-сервер для перехвата и модификации запросов
|
| 400 |
+
proxy_port = 9090
|
| 401 |
+
proxy_thread = Thread(target=run_proxy_server, args=(proxy_port,))
|
| 402 |
+
proxy_thread.daemon = True
|
| 403 |
+
proxy_thread.start()
|
| 404 |
+
|
| 405 |
# Запускаем Playground UI в режиме dev на порту 7860 (порт Hugging Face)
|
| 406 |
logger.info("Запуск Playground UI в режиме разработки на порту 7860...")
|
| 407 |
os.environ["PORT"] = "7860"
|
| 408 |
+
os.environ["AGENT_SERVER_URL"] = f"http://localhost:{proxy_port}" # Указываем прокси вместо прямого API
|
| 409 |
os.environ["NEXT_PUBLIC_EDIT_GRAPH_MODE"] = "true" # Включаем расширенный режим редактирования
|
| 410 |
os.environ["NEXT_PUBLIC_DISABLE_CAMERA"] = "true" # Отключаем запрос на использование камеры
|
| 411 |
|
| 412 |
# Важные переменные для отключения запросов к дизайнеру
|
| 413 |
os.environ["NEXT_PUBLIC_DEV_MODE"] = "false"
|
| 414 |
+
os.environ["NEXT_PUBLIC_API_BASE_URL"] = f"/api/agents"
|
| 415 |
+
os.environ["NEXT_PUBLIC_DESIGNER_API_URL"] = f"http://localhost:{proxy_port}"
|
| 416 |
|
| 417 |
# Запускаем Playground UI
|
| 418 |
playground_process = subprocess.Popen(
|