Fix WebUI startup blocking on slow gateway routes

This commit is contained in:
chengyongru
2026-06-13 21:59:36 +08:00
committed by Xubin Ren
parent 3a221d74cf
commit af0e3441d7
12 changed files with 280 additions and 17 deletions
+44 -7
View File
@@ -9,9 +9,11 @@ Also houses shared HTTP utility functions used by both this module and
from __future__ import annotations
import asyncio
import json
import mimetypes
import re
import time
from collections.abc import Callable
from pathlib import Path
from typing import TYPE_CHECKING, Any
@@ -76,6 +78,8 @@ from nanobot.webui.thread_disk import delete_webui_thread
from nanobot.webui.transcript import build_webui_thread_response
from nanobot.webui.workspaces import WebUIWorkspaceController
_SLOW_WEBUI_HTTP_LOG_MS = 1_000
if TYPE_CHECKING:
from nanobot.bus.queue import MessageBus
from nanobot.cron.service import CronService
@@ -192,7 +196,21 @@ class GatewayHTTPHandler:
async def dispatch(self, connection: Any, request: WsRequest) -> Any | None:
"""Route an HTTP request. Returns Response or None."""
got, _ = _parse_request_path(request.path)
started = time.perf_counter()
response: Any | None = None
try:
response = await self._dispatch_resolved(connection, request, got)
return response
finally:
self._log_slow_http(got, response, started)
async def _dispatch_resolved(
self,
connection: Any,
request: WsRequest,
got: str,
) -> Any | None:
# Token issue endpoint
if self.config.token_issue_path:
issue_expected = _normalize_config_path(self.config.token_issue_path)
@@ -209,7 +227,7 @@ class GatewayHTTPHandler:
return response
# Session routes
response = self._dispatch_session_routes(request, got)
response = await self._dispatch_session_routes(request, got)
if response is not None:
return response
@@ -219,7 +237,7 @@ class GatewayHTTPHandler:
return response
# Misc routes
response = self._dispatch_misc_routes(connection, request, got)
response = await self._dispatch_misc_routes(connection, request, got)
if response is not None:
return response
@@ -235,6 +253,20 @@ class GatewayHTTPHandler:
return connection.respond(404, "Not Found")
def _log_slow_http(self, path: str, response: Any | None, started: float) -> None:
elapsed_ms = int((time.perf_counter() - started) * 1000)
if elapsed_ms < _SLOW_WEBUI_HTTP_LOG_MS:
return
if not (path.startswith("/api/") or path == "/webui/bootstrap"):
return
status = getattr(response, "status_code", None)
self._log.warning(
"slow webui http route path={} status={} duration_ms={}",
path,
status if status is not None else "none",
elapsed_ms,
)
# -- Token issue --------------------------------------------------------
def _handle_token_issue(self, connection: Any, request: Any) -> Any:
@@ -302,7 +334,7 @@ class GatewayHTTPHandler:
# -- Session routes -----------------------------------------------------
def _dispatch_session_routes(self, request: WsRequest, got: str) -> Response | None:
async def _dispatch_session_routes(self, request: WsRequest, got: str) -> Response | None:
m = re.match(r"^/api/sessions/([^/]+)/messages$", got)
if m:
return self._handle_session_messages(request, m.group(1))
@@ -325,11 +357,16 @@ class GatewayHTTPHandler:
return None
def _handle_sessions_list(self, request: WsRequest) -> Response:
async def _handle_sessions_list(self, request: WsRequest) -> Response:
if not self.check_api_token(request):
return _http_error(401, "Unauthorized")
if self.session_manager is None:
return _http_error(503, "session manager unavailable")
payload = await asyncio.to_thread(self._sessions_list_payload)
return _http_json_response(payload)
def _sessions_list_payload(self) -> dict[str, Any]:
assert self.session_manager is not None
sessions = list_webui_sessions(self.session_manager)
from nanobot.session.webui_turns import websocket_turn_wall_started_at
@@ -346,7 +383,7 @@ class GatewayHTTPHandler:
scope = self.workspaces.scope_for_session_key(key)
row["workspace_scope"] = scope.payload()
cleaned.append(row)
return _http_json_response({"sessions": cleaned})
return {"sessions": cleaned}
def _handle_session_messages(self, request: WsRequest, key: str) -> Response:
if not self.check_api_token(request):
@@ -496,11 +533,11 @@ class GatewayHTTPHandler:
# -- Misc routes --------------------------------------------------------
def _dispatch_misc_routes(
async def _dispatch_misc_routes(
self, connection: Any, request: WsRequest, got: str
) -> Response | None:
if got == "/api/sessions":
return self._handle_sessions_list(request)
return await self._handle_sessions_list(request)
if got == "/api/commands":
return self._handle_commands(request)
if got == "/api/workspaces":