Добавление прокси-сервера для отладки и перехвата запросов 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(
|