Добавлен прокси для статического Playground UI, работающий с HuggingFace Space
Browse files- fallback.py +210 -5
fallback.py
CHANGED
@@ -558,6 +558,196 @@ def run_proxy_server():
|
|
558 |
|
559 |
return httpd
|
560 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
561 |
def run_playground():
|
562 |
"""Запуск Playground UI"""
|
563 |
logger.info("Starting Playground UI in development mode")
|
@@ -659,18 +849,33 @@ def run_playground():
|
|
659 |
time.sleep(3)
|
660 |
if playground_process.poll() is not None:
|
661 |
logger.error(f"All Playground UI launch methods failed")
|
662 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
663 |
else:
|
664 |
-
logger.info("
|
665 |
-
|
|
|
|
|
|
|
|
|
666 |
# Создаем заглушку для процесса
|
667 |
playground_process = subprocess.Popen(
|
668 |
-
["tail", "-f", "/dev/null"],
|
669 |
stdout=subprocess.PIPE,
|
670 |
stderr=subprocess.PIPE,
|
671 |
)
|
672 |
|
673 |
-
logger.info("Playground UI started successfully or
|
674 |
return playground_process
|
675 |
|
676 |
def main():
|
|
|
558 |
|
559 |
return httpd
|
560 |
|
561 |
+
def run_playground_proxy():
|
562 |
+
"""Запускает прокси-сервер для обслуживания Playground UI и проксирования запросов к API"""
|
563 |
+
import http.server
|
564 |
+
import socketserver
|
565 |
+
from urllib.parse import urljoin, urlparse
|
566 |
+
import urllib.request
|
567 |
+
import urllib.error
|
568 |
+
|
569 |
+
class PlaygroundProxyHandler(http.server.SimpleHTTPRequestHandler):
|
570 |
+
def log_message(self, format, *args):
|
571 |
+
logger.info(f"PLAYGROUND-PROXY: {format % args}")
|
572 |
+
|
573 |
+
def do_GET(self):
|
574 |
+
# Проксирование запросов к API
|
575 |
+
if self.path.startswith('/api/'):
|
576 |
+
api_path = self.path[5:] # Удаляем '/api/' из пути
|
577 |
+
api_url = f"http://localhost:8080/{api_path}"
|
578 |
+
|
579 |
+
try:
|
580 |
+
logger.info(f"Proxying GET request to API: {api_url}")
|
581 |
+
with urllib.request.urlopen(api_url) as response:
|
582 |
+
data = response.read()
|
583 |
+
self.send_response(response.status)
|
584 |
+
|
585 |
+
# Копируем все заголовки из ответа API
|
586 |
+
for header, value in response.getheaders():
|
587 |
+
if header.lower() not in ('transfer-encoding', 'connection'):
|
588 |
+
self.send_header(header, value)
|
589 |
+
|
590 |
+
# Устанавливаем CORS заголовки
|
591 |
+
self.send_header('Access-Control-Allow-Origin', '*')
|
592 |
+
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
593 |
+
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
594 |
+
|
595 |
+
self.end_headers()
|
596 |
+
self.wfile.write(data)
|
597 |
+
except urllib.error.URLError as e:
|
598 |
+
logger.error(f"Error proxying GET request to API: {e}")
|
599 |
+
self.send_error(502, f"Error proxying to API: {e}")
|
600 |
+
return
|
601 |
+
|
602 |
+
# Проксирование запросов к серверу графического редактора
|
603 |
+
elif self.path.startswith('/designer/'):
|
604 |
+
designer_path = self.path[10:] # Удаляем '/designer/' из пути
|
605 |
+
designer_url = f"http://localhost:49483/{designer_path}"
|
606 |
+
|
607 |
+
try:
|
608 |
+
logger.info(f"Proxying GET request to designer: {designer_url}")
|
609 |
+
with urllib.request.urlopen(designer_url) as response:
|
610 |
+
data = response.read()
|
611 |
+
self.send_response(response.status)
|
612 |
+
|
613 |
+
# Копируем все заголовки из ответа дизайнера
|
614 |
+
for header, value in response.getheaders():
|
615 |
+
if header.lower() not in ('transfer-encoding', 'connection'):
|
616 |
+
self.send_header(header, value)
|
617 |
+
|
618 |
+
# Устанавливаем CORS заголовки
|
619 |
+
self.send_header('Access-Control-Allow-Origin', '*')
|
620 |
+
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
621 |
+
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
622 |
+
|
623 |
+
self.end_headers()
|
624 |
+
self.wfile.write(data)
|
625 |
+
except urllib.error.URLError as e:
|
626 |
+
logger.error(f"Error proxying GET request to designer: {e}")
|
627 |
+
self.send_error(502, f"Error proxying to designer: {e}")
|
628 |
+
return
|
629 |
+
|
630 |
+
# Перенаправление корневого пути на Playground UI
|
631 |
+
elif self.path == '/' or self.path == '':
|
632 |
+
self.send_response(302)
|
633 |
+
self.send_header('Location', 'https://ten-framework.github.io/TEN-Playground-Static/')
|
634 |
+
self.end_headers()
|
635 |
+
return
|
636 |
+
|
637 |
+
# Для всех остальных запросов пытаемся обслужить статический файл
|
638 |
+
else:
|
639 |
+
self.send_error(404, "File Not Found")
|
640 |
+
|
641 |
+
def do_POST(self):
|
642 |
+
# Проксирование POST запросов к API
|
643 |
+
if self.path.startswith('/api/'):
|
644 |
+
api_path = self.path[5:] # Удаляем '/api/' из пути
|
645 |
+
api_url = f"http://localhost:8080/{api_path}"
|
646 |
+
|
647 |
+
content_length = int(self.headers.get('Content-Length', 0))
|
648 |
+
post_data = self.rfile.read(content_length) if content_length > 0 else b''
|
649 |
+
|
650 |
+
try:
|
651 |
+
logger.info(f"Proxying POST request to API: {api_url}")
|
652 |
+
request = urllib.request.Request(
|
653 |
+
api_url,
|
654 |
+
data=post_data,
|
655 |
+
headers={k: v for k, v in self.headers.items() if k.lower() not in ('host', 'content-length')},
|
656 |
+
method='POST'
|
657 |
+
)
|
658 |
+
|
659 |
+
with urllib.request.urlopen(request) as response:
|
660 |
+
data = response.read()
|
661 |
+
self.send_response(response.status)
|
662 |
+
|
663 |
+
# Копируем все заголовки из ответа API
|
664 |
+
for header, value in response.getheaders():
|
665 |
+
if header.lower() not in ('transfer-encoding', 'connection'):
|
666 |
+
self.send_header(header, value)
|
667 |
+
|
668 |
+
# Устанавливаем CORS заголовки
|
669 |
+
self.send_header('Access-Control-Allow-Origin', '*')
|
670 |
+
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
671 |
+
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
672 |
+
|
673 |
+
self.end_headers()
|
674 |
+
self.wfile.write(data)
|
675 |
+
except urllib.error.URLError as e:
|
676 |
+
logger.error(f"Error proxying POST request to API: {e}")
|
677 |
+
self.send_error(502, f"Error proxying to API: {e}")
|
678 |
+
|
679 |
+
# Проксирование запросов к серверу графического редактора
|
680 |
+
elif self.path.startswith('/designer/'):
|
681 |
+
designer_path = self.path[10:] # Удаляем '/designer/' из пути
|
682 |
+
designer_url = f"http://localhost:49483/{designer_path}"
|
683 |
+
|
684 |
+
content_length = int(self.headers.get('Content-Length', 0))
|
685 |
+
post_data = self.rfile.read(content_length) if content_length > 0 else b''
|
686 |
+
|
687 |
+
try:
|
688 |
+
logger.info(f"Proxying POST request to designer: {designer_url}")
|
689 |
+
request = urllib.request.Request(
|
690 |
+
designer_url,
|
691 |
+
data=post_data,
|
692 |
+
headers={k: v for k, v in self.headers.items() if k.lower() not in ('host', 'content-length')},
|
693 |
+
method='POST'
|
694 |
+
)
|
695 |
+
|
696 |
+
with urllib.request.urlopen(request) as response:
|
697 |
+
data = response.read()
|
698 |
+
self.send_response(response.status)
|
699 |
+
|
700 |
+
# Копируем все заголовки из ответа дизайнера
|
701 |
+
for header, value in response.getheaders():
|
702 |
+
if header.lower() not in ('transfer-encoding', 'connection'):
|
703 |
+
self.send_header(header, value)
|
704 |
+
|
705 |
+
# Устанавливаем CORS заголовки
|
706 |
+
self.send_header('Access-Control-Allow-Origin', '*')
|
707 |
+
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
708 |
+
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
709 |
+
|
710 |
+
self.end_headers()
|
711 |
+
self.wfile.write(data)
|
712 |
+
except urllib.error.URLError as e:
|
713 |
+
logger.error(f"Error proxying POST request to designer: {e}")
|
714 |
+
self.send_error(502, f"Error proxying to designer: {e}")
|
715 |
+
else:
|
716 |
+
self.send_error(404, "Not Found")
|
717 |
+
|
718 |
+
def do_OPTIONS(self):
|
719 |
+
self.send_response(200)
|
720 |
+
self.send_header('Access-Control-Allow-Origin', '*')
|
721 |
+
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
722 |
+
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
723 |
+
self.end_headers()
|
724 |
+
|
725 |
+
# Запуск прокси-сервера на порту 7860 (стандартный порт HF Space)
|
726 |
+
port = 7860
|
727 |
+
httpd = socketserver.ThreadingTCPServer(("", port), PlaygroundProxyHandler)
|
728 |
+
logger.info(f"Playground proxy server started on port {port}")
|
729 |
+
logger.info(f"Access Playground at: https://nitrox-ten.hf.space/")
|
730 |
+
|
731 |
+
# В отдельном потоке запускаем проверку доступности API сервера
|
732 |
+
def check_api_availability():
|
733 |
+
while True:
|
734 |
+
try:
|
735 |
+
# Проверка /graphs API
|
736 |
+
with urllib.request.urlopen("http://localhost:8080/graphs") as response:
|
737 |
+
if response.status == 200:
|
738 |
+
data = response.read().decode('utf-8')
|
739 |
+
logger.info(f"API /graphs endpoint is available: {data}")
|
740 |
+
except Exception as e:
|
741 |
+
logger.warning(f"API check failed: {e}")
|
742 |
+
|
743 |
+
time.sleep(30) # Проверяем каждые 30 секунд
|
744 |
+
|
745 |
+
api_check_thread = threading.Thread(target=check_api_availability, daemon=True)
|
746 |
+
api_check_thread.start()
|
747 |
+
|
748 |
+
# Запускаем сервер в основном потоке
|
749 |
+
httpd.serve_forever()
|
750 |
+
|
751 |
def run_playground():
|
752 |
"""Запуск Playground UI"""
|
753 |
logger.info("Starting Playground UI in development mode")
|
|
|
849 |
time.sleep(3)
|
850 |
if playground_process.poll() is not None:
|
851 |
logger.error(f"All Playground UI launch methods failed")
|
852 |
+
logger.info("Starting playground proxy as fallback")
|
853 |
+
|
854 |
+
# Запускаем прокси-сервер для Playground
|
855 |
+
playground_thread = threading.Thread(target=run_playground_proxy, daemon=True)
|
856 |
+
playground_thread.start()
|
857 |
+
|
858 |
+
# Создаем заглушку для процесса
|
859 |
+
playground_process = subprocess.Popen(
|
860 |
+
["tail", "-f", "/dev/null"],
|
861 |
+
stdout=subprocess.PIPE,
|
862 |
+
stderr=subprocess.PIPE,
|
863 |
+
)
|
864 |
else:
|
865 |
+
logger.info("Starting playground proxy as fallback")
|
866 |
+
|
867 |
+
# Запускаем прокси-сервер для Playground
|
868 |
+
playground_thread = threading.Thread(target=run_playground_proxy, daemon=True)
|
869 |
+
playground_thread.start()
|
870 |
+
|
871 |
# Создаем заглушку для процесса
|
872 |
playground_process = subprocess.Popen(
|
873 |
+
["tail", "-f", "/dev/null"],
|
874 |
stdout=subprocess.PIPE,
|
875 |
stderr=subprocess.PIPE,
|
876 |
)
|
877 |
|
878 |
+
logger.info("Playground UI started successfully or proxy is running")
|
879 |
return playground_process
|
880 |
|
881 |
def main():
|