This commit is contained in:
tao.chen
2026-07-27 15:04:31 +08:00
parent 4f9bb9fd66
commit e56bf0e56b
17 changed files with 528 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv
+1
View File
@@ -0,0 +1 @@
3.12
View File
+26
View File
@@ -0,0 +1,26 @@
[project]
name = "backend"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
authors = [
{ name = "tao.chen", email = "93983997+taochen-ct@users.noreply.github.com" }
]
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.140.0",
"loguru>=0.7.3",
"pydantic>=2.13.4",
"uvicorn>=0.51.0",
]
[project.scripts]
backend = "backend:main"
[[tool.uv.index]]
url = "https://pypi.tuna.tsinghua.edu.cn/simple/"
default = true
[build-system]
requires = ["uv_build>=0.11.31,<0.12.0"]
build-backend = "uv_build"
+2
View File
@@ -0,0 +1,2 @@
def main() -> None:
print("Hello from backend!")
+112
View File
@@ -0,0 +1,112 @@
# coding=utf-8
"""
@Time :2026/7/27
@Author :tao.chen
"""
import httpx
from fastapi import FastAPI, Request, Response, HTTPException, Depends, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from typing import Optional
app = FastAPI(title="Jupyter Auth & Router Backend")
RUNTIME_BASE_URL = "http://127.0.0.1:8001"
security = HTTPBearer(auto_error=False)
class RuntimeClient:
"""与 Runtime 管理服务交互"""
@staticmethod
async def get_workspace(workspace_id: str) -> Optional[dict]:
"""通过 action: get 查询单个 workspace 进程信息"""
async with httpx.AsyncClient(base_url=RUNTIME_BASE_URL) as client:
try:
resp = await client.post(
"/api/v1/jupyter",
json={"action": "get", "workspace_id": workspace_id},
timeout=3.0
)
if resp.status_code == 200:
return resp.json()
return None
except httpx.RequestError:
return None
@staticmethod
async def start_workspace(workspace_id: str) -> dict:
"""通过 action: start 启动 workspace 子进程"""
async with httpx.AsyncClient(base_url=RUNTIME_BASE_URL) as client:
resp = await client.post(
"/api/v1/jupyter",
json={"action": "start", "workspace_id": workspace_id},
timeout=10.0
)
if resp.status_code == 200:
return resp.json()
raise HTTPException(status_code=500, detail="Failed to start Jupyter process")
async def get_workspace_from_db(workspace_id: str):
"""查 MySQL 数据库校验 workspace 状态与权限(模拟)"""
mock_db = {
"test1234": {"workspace_id": "test1234", "is_locked": False, "owner_id": "user_001"}
}
return mock_db.get(workspace_id)
def verify_jwt_token(token: str) -> str:
"""校验用户 JWT"""
if token == "invalid-token":
raise HTTPException(status_code=401, detail="Invalid JWT")
return "user_001"
# ------------------------------------------------------------------
# Auth & Dynamic Route 核心接口
# ------------------------------------------------------------------
@app.get("/api/v1/auth/jupyter")
async def verify_jupyter_access(
request: Request,
response: Response,
auth: Optional[HTTPAuthorizationCredentials] = Depends(security)
):
workspace_id = request.headers.get("X-Original-Workspace-Id")
cookie_token = request.cookies.get("access_token")
bearer_token = auth.credentials if auth else None
token = bearer_token or cookie_token
if not token:
raise HTTPException(status_code=401, detail="Missing Authentication Token")
if not workspace_id:
raise HTTPException(status_code=400, detail="Missing Workspace ID")
# 1. 校验用户身份
current_user_id = verify_jwt_token(token)
# 2. 查 MySQL:校验权限与锁定状态
ws = await get_workspace_from_db(workspace_id)
if not ws:
raise HTTPException(status_code=404, detail="Workspace not found")
if ws["is_locked"]:
raise HTTPException(status_code=403, detail="Workspace is locked")
# 3. 使用 action: get 查询子进程
ws_info = await RuntimeClient.get_workspace(workspace_id)
# 4. 若未运行/不存在,主动触发 start
if not ws_info or ws_info.get("status") != "running":
ws_info = await RuntimeClient.start_workspace(workspace_id)
target_port = ws_info.get("port")
jupyter_token = ws_info.get("token")
if not target_port:
raise HTTPException(status_code=500, detail="Jupyter instance missing port configuration")
# 5. 向 Nginx 返回 Upstream 地址与 Token
response.headers["X-Upstream-Addr"] = f"http://127.0.0.1:{target_port}"
response.headers["X-Jupyter-Internal-Token"] = jupyter_token or ""
return {"status": "ok"}