Исправление работы прокси-сервера в HF Space: всегда возвращаем заготовленные данные о графах, улучшена обработка ошибок и автоматический выбор порта
Browse files
app.py
CHANGED
@@ -453,7 +453,7 @@ def run_simple_proxy():
|
|
453 |
import socketserver
|
454 |
import json
|
455 |
|
456 |
-
# Предварительно подготовленные данные для графов
|
457 |
GRAPHS_DATA = [
|
458 |
{
|
459 |
"name": "Voice Agent",
|
@@ -502,29 +502,42 @@ def run_simple_proxy():
|
|
502 |
def do_GET(self):
|
503 |
logger.info(f"PROXY: GET запрос: {self.path}")
|
504 |
|
505 |
-
# Для запросов к /graphs возвращаем заранее подготовленный ответ
|
506 |
if self.path == "/graphs":
|
507 |
-
|
|
|
508 |
return
|
509 |
|
510 |
-
# Для запросов к Designer API возвращаем заранее подготовленный ответ
|
511 |
if self.path.startswith("/api/designer/") or self.path.startswith("/api/dev/"):
|
512 |
-
self.
|
|
|
513 |
return
|
514 |
|
515 |
# Для других запросов пробуем проксировать на API сервер
|
516 |
-
|
|
|
|
|
|
|
|
|
|
|
517 |
|
518 |
def do_POST(self):
|
519 |
logger.info(f"PROXY: POST запрос: {self.path}")
|
520 |
|
521 |
-
# Для запросов к Designer API возвращаем заранее подготовленный ответ
|
522 |
if self.path.startswith("/api/designer/") or self.path.startswith("/api/dev/"):
|
523 |
-
self.
|
|
|
524 |
return
|
525 |
|
526 |
# Для других запросов пробуем проксировать на API сервер
|
527 |
-
|
|
|
|
|
|
|
|
|
|
|
528 |
|
529 |
def do_OPTIONS(self):
|
530 |
logger.info(f"PROXY: OPTIONS запрос: {self.path}")
|
@@ -534,76 +547,40 @@ def run_simple_proxy():
|
|
534 |
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
535 |
self.end_headers()
|
536 |
|
537 |
-
def _handle_graphs_request(self):
|
538 |
-
"""Обрабатывает запросы к /graphs"""
|
539 |
-
logger.info("PROXY: Обработка запроса к /graphs")
|
540 |
-
|
541 |
-
# Сначала пробуем получить данные от API сервера
|
542 |
-
try:
|
543 |
-
with urllib.request.urlopen(f"http://localhost:{API_PORT}/graphs") as response:
|
544 |
-
data = response.read().decode('utf-8')
|
545 |
-
try:
|
546 |
-
json_data = json.loads(data)
|
547 |
-
# Если API вернул непустой список, используем его
|
548 |
-
if isinstance(json_data, list) and len(json_data) > 0:
|
549 |
-
logger.info("PROXY: API вернул непустой список графов, используем его")
|
550 |
-
self._send_response(200, data)
|
551 |
-
return
|
552 |
-
except Exception:
|
553 |
-
# Если ошибка парсинга JSON, используем заготовленные данные
|
554 |
-
pass
|
555 |
-
except Exception:
|
556 |
-
# Ес��и ошибка подключения к API, используем заготовленные данные
|
557 |
-
pass
|
558 |
-
|
559 |
-
# Если API недоступен или вернул некорректные данные, используем заготовленные данные
|
560 |
-
logger.info("PROXY: Возвращаем заготовленные данные о графах")
|
561 |
-
self._send_response(200, json.dumps(GRAPHS_DATA))
|
562 |
-
|
563 |
-
def _handle_designer_request(self):
|
564 |
-
"""Обрабатывает запросы к Designer API"""
|
565 |
-
logger.info(f"PROXY: Обработка запроса к Designer API: {self.path}")
|
566 |
-
self._send_response(200, json.dumps(DESIGNER_DATA))
|
567 |
-
|
568 |
def _proxy_to_api(self, method):
|
569 |
"""Проксирует запрос к API серверу"""
|
570 |
-
|
571 |
-
|
572 |
-
|
573 |
-
|
574 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
575 |
|
576 |
-
#
|
577 |
-
for header, value in
|
578 |
-
if header.lower()
|
579 |
-
|
580 |
|
581 |
-
#
|
582 |
-
|
583 |
-
|
584 |
-
body = self.rfile.read(content_length)
|
585 |
-
req.data = body
|
586 |
|
587 |
-
#
|
588 |
-
|
589 |
-
# Отправляем ответ клиенту
|
590 |
-
self.send_response(response.status)
|
591 |
-
|
592 |
-
# Копируем заголовки ответа
|
593 |
-
for header, value in response.getheaders():
|
594 |
-
if header.lower() != "transfer-encoding":
|
595 |
-
self.send_header(header, value)
|
596 |
-
|
597 |
-
# Добавляем CORS заголовки
|
598 |
-
self.send_header('Access-Control-Allow-Origin', '*')
|
599 |
-
self.end_headers()
|
600 |
-
|
601 |
-
# Копируем тело ответа
|
602 |
-
self.wfile.write(response.read())
|
603 |
-
except Exception as e:
|
604 |
-
logger.error(f"PROXY: Ошибка при проксировании запроса: {e}")
|
605 |
-
# В случае ошибки возвращаем пустой успешный ответ
|
606 |
-
self._send_response(200, json.dumps({"success": True}))
|
607 |
|
608 |
def _send_response(self, status_code, data):
|
609 |
"""Отправляет ответ с указанным статусом и данными"""
|
@@ -625,24 +602,39 @@ def run_simple_proxy():
|
|
625 |
|
626 |
# Запускаем прокси-сервер
|
627 |
try:
|
628 |
-
|
629 |
-
|
630 |
-
|
631 |
-
|
632 |
-
|
|
|
|
|
|
|
|
|
633 |
try:
|
634 |
-
|
635 |
-
|
636 |
-
|
637 |
-
|
638 |
-
|
639 |
-
|
640 |
-
|
641 |
-
|
642 |
-
|
643 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
644 |
except Exception as e:
|
645 |
-
logger.error(f"
|
646 |
|
647 |
def main():
|
648 |
processes = []
|
@@ -712,11 +704,15 @@ def main():
|
|
712 |
# Вместо вложенного try-except используем новый блок
|
713 |
logger.info("Создание встроенного прокси-сервера...")
|
714 |
|
715 |
-
|
716 |
-
|
717 |
-
|
718 |
-
|
719 |
-
|
|
|
|
|
|
|
|
|
720 |
|
721 |
# Настраиваем переменные окружения для Playground UI
|
722 |
os.environ["PORT"] = "7860"
|
|
|
453 |
import socketserver
|
454 |
import json
|
455 |
|
456 |
+
# Предварительно подготовленные данные для графов - формат правильный
|
457 |
GRAPHS_DATA = [
|
458 |
{
|
459 |
"name": "Voice Agent",
|
|
|
502 |
def do_GET(self):
|
503 |
logger.info(f"PROXY: GET запрос: {self.path}")
|
504 |
|
505 |
+
# Для запросов к /graphs ВСЕГДА возвращаем заранее подготовленный ответ
|
506 |
if self.path == "/graphs":
|
507 |
+
logger.info("PROXY: Возвращаем заготовленные данные о графах")
|
508 |
+
self._send_response(200, json.dumps(GRAPHS_DATA))
|
509 |
return
|
510 |
|
511 |
+
# Для запросов к Designer API ВСЕГДА возвращаем заранее подготовленный ответ
|
512 |
if self.path.startswith("/api/designer/") or self.path.startswith("/api/dev/"):
|
513 |
+
logger.info(f"PROXY: Обработка запроса к Designer API: {self.path}")
|
514 |
+
self._send_response(200, json.dumps(DESIGNER_DATA))
|
515 |
return
|
516 |
|
517 |
# Для других запросов пробуем проксировать на API сервер
|
518 |
+
try:
|
519 |
+
self._proxy_to_api("GET")
|
520 |
+
except Exception as e:
|
521 |
+
logger.error(f"PROXY: Ошибка при проксировании GET запроса: {e}")
|
522 |
+
# В случае ошибки возвращаем базовый успешный ответ
|
523 |
+
self._send_response(200, json.dumps({"success": True}))
|
524 |
|
525 |
def do_POST(self):
|
526 |
logger.info(f"PROXY: POST запрос: {self.path}")
|
527 |
|
528 |
+
# Для запросов к Designer API ВСЕГДА возвращаем заранее подготовленный ответ
|
529 |
if self.path.startswith("/api/designer/") or self.path.startswith("/api/dev/"):
|
530 |
+
logger.info(f"PROXY: Обработка POST запроса к Designer API: {self.path}")
|
531 |
+
self._send_response(200, json.dumps({"success": True}))
|
532 |
return
|
533 |
|
534 |
# Для других запросов пробуем проксировать на API сервер
|
535 |
+
try:
|
536 |
+
self._proxy_to_api("POST")
|
537 |
+
except Exception as e:
|
538 |
+
logger.error(f"PROXY: Ошибка при проксировании POST запроса: {e}")
|
539 |
+
# В случае ошибки возвращаем базовый успешный ответ
|
540 |
+
self._send_response(200, json.dumps({"success": True}))
|
541 |
|
542 |
def do_OPTIONS(self):
|
543 |
logger.info(f"PROXY: OPTIONS запрос: {self.path}")
|
|
|
547 |
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
548 |
self.end_headers()
|
549 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
550 |
def _proxy_to_api(self, method):
|
551 |
"""Проксирует запрос к API серверу"""
|
552 |
+
url = f"http://localhost:{API_PORT}{self.path}"
|
553 |
+
logger.info(f"PROXY: Проксирование запроса к API: {url}")
|
554 |
+
|
555 |
+
req = urllib.request.Request(url, method=method)
|
556 |
+
|
557 |
+
# Копирование заголовков
|
558 |
+
for header, value in self.headers.items():
|
559 |
+
if header.lower() not in ["host", "content-length"]:
|
560 |
+
req.add_header(header, value)
|
561 |
+
|
562 |
+
# Для POST-запросов копируем тело
|
563 |
+
if method == "POST":
|
564 |
+
content_length = int(self.headers.get('Content-Length', 0))
|
565 |
+
body = self.rfile.read(content_length)
|
566 |
+
req.data = body
|
567 |
+
|
568 |
+
# Выполняем запрос к API серверу
|
569 |
+
with urllib.request.urlopen(req) as response:
|
570 |
+
# Отправляем ответ клиенту
|
571 |
+
self.send_response(response.status)
|
572 |
|
573 |
+
# Копируем заголовки ответа
|
574 |
+
for header, value in response.getheaders():
|
575 |
+
if header.lower() != "transfer-encoding":
|
576 |
+
self.send_header(header, value)
|
577 |
|
578 |
+
# Добавляем CORS заголовки
|
579 |
+
self.send_header('Access-Control-Allow-Origin', '*')
|
580 |
+
self.end_headers()
|
|
|
|
|
581 |
|
582 |
+
# Копируем тело ответа
|
583 |
+
self.wfile.write(response.read())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
584 |
|
585 |
def _send_response(self, status_code, data):
|
586 |
"""Отправляет ответ с указанным статусом и данными"""
|
|
|
602 |
|
603 |
# Запускаем прокси-сервер
|
604 |
try:
|
605 |
+
# Пытаемся создать HTTP сервер на стандартном порту PROXY_PORT
|
606 |
+
try:
|
607 |
+
server = socketserver.TCPServer(("", PROXY_PORT), SimpleProxyHandler)
|
608 |
+
logger.info(f"Встроенный прокси-сервер успешно запущен на порту {PROXY_PORT}")
|
609 |
+
server.serve_forever()
|
610 |
+
except OSError as e:
|
611 |
+
# Если порт занят, пробуем порт PROXY_PORT+1
|
612 |
+
alt_port = PROXY_PORT + 1
|
613 |
+
logger.warning(f"Не удалось запустить прокси на порту {PROXY_PORT}, пробуем порт {alt_port}")
|
614 |
try:
|
615 |
+
server = socketserver.TCPServer(("", alt_port), SimpleProxyHandler)
|
616 |
+
logger.info(f"Встроенный прокси-сервер успешно запущен на порту {alt_port}")
|
617 |
+
# Обновляем переменную окружения для UI
|
618 |
+
os.environ["NEXT_PUBLIC_DESIGNER_API_URL"] = f"http://localhost:{alt_port}"
|
619 |
+
os.environ["AGENT_SERVER_URL"] = f"http://localhost:{alt_port}"
|
620 |
+
server.serve_forever()
|
621 |
+
except Exception as inner_e:
|
622 |
+
logger.error(f"Не удалось запустить прокси-сервер даже на альтернативном порту: {inner_e}")
|
623 |
+
# Если и это не получилось, пробуем на порту 0 (любой свободный)
|
624 |
+
try:
|
625 |
+
server = socketserver.TCPServer(("", 0), SimpleProxyHandler)
|
626 |
+
_, dynamic_port = server.server_address
|
627 |
+
logger.info(f"Встроенный прокси-сервер успешно запущен на динамическом порту {dynamic_port}")
|
628 |
+
# Обновляем переменную окружения для UI
|
629 |
+
os.environ["NEXT_PUBLIC_DESIGNER_API_URL"] = f"http://localhost:{dynamic_port}"
|
630 |
+
os.environ["AGENT_SERVER_URL"] = f"http://localhost:{dynamic_port}"
|
631 |
+
server.serve_forever()
|
632 |
+
except Exception as last_e:
|
633 |
+
logger.error(f"Все попытки запустить прокси-сервер не удались: {last_e}")
|
634 |
+
# Сдаемся, нет смысла продолжать
|
635 |
+
return
|
636 |
except Exception as e:
|
637 |
+
logger.error(f"Критическая ошибка в прокси-сервере: {e}")
|
638 |
|
639 |
def main():
|
640 |
processes = []
|
|
|
704 |
# Вместо вложенного try-except используем новый блок
|
705 |
logger.info("Создание встроенного прокси-сервера...")
|
706 |
|
707 |
+
try:
|
708 |
+
# Запускаем прокси-сервер в отдельном потоке - обязательный запуск
|
709 |
+
proxy_thread = threading.Thread(target=run_simple_proxy)
|
710 |
+
proxy_thread.daemon = True
|
711 |
+
proxy_thread.start()
|
712 |
+
logger.info(f"Встроенный прокси-сервер запущен на порту {PROXY_PORT}")
|
713 |
+
except Exception as e:
|
714 |
+
logger.error(f"Серьезная ошибка при запуске встроенного прокси-сервера: {e}")
|
715 |
+
# Даже если встроенный прокси не запустился, не сдаемся - используем директные URL
|
716 |
|
717 |
# Настраиваем переменные окружения для Playground UI
|
718 |
os.environ["PORT"] = "7860"
|