fix: SPA middleware instead of catch-all, preserves WS
This commit is contained in:
parent
54c1e9d92c
commit
44a8ddf458
1 changed files with 33 additions and 14 deletions
|
|
@ -1003,17 +1003,36 @@ async def serve_index():
|
||||||
return {"message": "Claude Persistent Agent API v3.0", "docs": "/docs"}
|
return {"message": "Claude Persistent Agent API v3.0", "docs": "/docs"}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/{full_path:path}")
|
@app.get("/vite.svg")
|
||||||
async def serve_spa(full_path: str):
|
@app.get("/favicon.ico")
|
||||||
"""Serve React SPA for all non-API routes. API and WS are handled above."""
|
async def serve_static_root_files():
|
||||||
if full_path.startswith("api"):
|
"""Serve known static root files."""
|
||||||
raise HTTPException(404)
|
from starlette.requests import Request
|
||||||
# Try serving the file directly (e.g. vite.svg, favicon)
|
return FileResponse(str(STATIC_DIR / "vite.svg"))
|
||||||
file_path = STATIC_DIR / full_path
|
|
||||||
if file_path.is_file():
|
|
||||||
return FileResponse(str(file_path))
|
# SPA fallback via middleware — this avoids the catch-all route problem
|
||||||
# Fall back to index.html for SPA routing
|
# that breaks WebSocket routing in some Starlette versions
|
||||||
index = STATIC_DIR / "index.html"
|
from starlette.middleware.base import BaseHTTPMiddleware
|
||||||
if index.exists():
|
from starlette.requests import Request as StarletteRequest
|
||||||
return FileResponse(str(index))
|
from starlette.responses import Response
|
||||||
raise HTTPException(404)
|
|
||||||
|
|
||||||
|
class SPAFallbackMiddleware(BaseHTTPMiddleware):
|
||||||
|
async def dispatch(self, request: StarletteRequest, call_next):
|
||||||
|
response = await call_next(request)
|
||||||
|
# If a non-API GET request returned 404, serve index.html for SPA
|
||||||
|
if (response.status_code == 404
|
||||||
|
and request.method == "GET"
|
||||||
|
and not request.url.path.startswith("/api")
|
||||||
|
and not request.url.path.startswith("/docs")
|
||||||
|
and not request.url.path.startswith("/openapi")
|
||||||
|
and not request.url.path.startswith("/assets")
|
||||||
|
and "websocket" not in request.headers.get("upgrade", "").lower()):
|
||||||
|
index = STATIC_DIR / "index.html"
|
||||||
|
if index.exists():
|
||||||
|
return FileResponse(str(index))
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
app.add_middleware(SPAFallbackMiddleware)
|
||||||
|
|
|
||||||
Reference in a new issue