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