Skip to content

CubeAPI 是怎么接住第一个请求的

CubeAPI 是 CubeSandbox 的对外入口,用 Rust + axum 实现。它兼容 E2B REST API,同时提供 /cubeapi/v1/ 前缀的内部管理接口。

这篇文章从 main() 函数开始,拆解一个 HTTP 请求从到达 CubeAPI 到被转发给 CubeMaster 的完整路径。


1. 先看全貌:请求穿过 CubeAPI 的哪些层


2. 进程启动:main() 做了什么

CubeAPI/src/main.rs 是入口。启动过程分 4 步:

关键代码

rust
// CubeAPI/src/main.rs
let config = ServerConfig::from_cli_and_env(&cli);  // CLI flags + 环境变量
let logger = ArcLogger::new(&config).await;
let state = AppState::new(config, logger);           // 共享状态
let router = build_router(state);                    // 路由 + 中间件
let listener = TcpListener::bind(&bind).await?;
axum::serve(listener, router).await?;

配置优先级

CubeAPI 的配置分 3 层,优先级从高到低:

  1. CLI flags--bind, --cubemaster-url, --auth-callback-url
  2. 环境变量CUBE_API_BIND, CUBE_MASTER_ADDR, AUTH_CALLBACK_URL
  3. 内置默认值0.0.0.0:3000, http://127.0.0.1:8089

3. 路由注册:两套路由,一个 server

CubeAPI/src/routes.rsbuild_router() 注册了两套路由:

3.1 E2B 兼容路由(根路径)

rust
// routes.rs → build_e2b_router()
Router::new()
    .route("/health", get(health::health))
    // sandbox 路由
    .route("/sandboxes", post(sandboxes::create_sandbox))
    .route("/sandboxes", get(sandboxes::list_sandboxes))
    .route("/sandboxes/:sandboxID", get(sandboxes::get_sandbox))
    .route("/sandboxes/:sandboxID", delete(sandboxes::kill_sandbox))
    .route("/sandboxes/:sandboxID/pause", post(sandboxes::pause_sandbox))
    .route("/sandboxes/:sandboxID/resume", post(sandboxes::resume_sandbox))
    .route("/sandboxes/:sandboxID/connect", post(sandboxes::connect_sandbox))
    // template 路由
    .route("/templates", get(templates::list_templates))
    .route("/templates", post(templates::create_template))
    // ...

3.2 内部管理路由(/cubeapi/v1/ 前缀)

rust
// routes.rs → build_cubeapi_router()
Router::new()
    .route("/health", get(health::health))
    // 同上 sandbox + template 路由
    // 额外增加 cluster 路由
    .route("/cluster/overview", get(cluster::cluster_overview))
    .route("/nodes", get(cluster::list_nodes))
    .route("/nodes/:nodeID", get(cluster::get_node))
    .route("/config", get(config::get_config))

3.3 路由挂载

rust
Router::new()
    .merge(e2b_router)                    // 根路径
    .nest("/cubeapi/v1", cubeapi_router)  // 内部管理前缀

这意味着:

  • POST /sandboxesPOST /cubeapi/v1/sandboxes 都能创建沙箱
  • GET /cluster/overview 只在 /cubeapi/v1/ 下可用,根路径返回 404

4. 全局中间件栈

所有请求都经过以下中间件,顺序不可变:

rust
// routes.rs
ServiceBuilder::new()
    .layer(SetRequestIdLayer::x_request_id(MakeRequestUuid))
    .layer(TraceLayer::new_for_http())
    .layer(TimeoutLayer::new(Duration::from_secs(30)))
    .layer(CompressionLayer::new())
    .layer(CorsLayer::permissive())

5. 路由级中间件:鉴权与限流

sandbox 路由比 template / cluster 路由多一层 rate_limit:

rust
// routes.rs
fn with_auth_and_rate_limit(routes, state, auth_configured) {
    routes
        .layer(middleware::from_fn_with_state(state.clone(), rate_limit))
        .layer(middleware::from_fn_with_state(state.clone(), unified_auth))
}

详见中间件、鉴权与错误处理


6. AppState:共享状态长什么样

CubeAPI/src/state.rs 定义了 AppState,axum 在每个请求中 clone 它(O(1),内部是 Arc):

rust
pub struct AppState {
    pub rate_limiter: Arc<DefaultKeyedRateLimiter<String>>,  // per-key 限流
    pub http_client: reqwest::Client,                         // 连接池
    pub services: AppServices,                                // SandboxService + TemplateService
    pub logger: ArcLogger,                                    // 结构化日志
    pub config: Arc<ServerConfig>,                            // 配置快照
}

AppServices 内部持有 CubeMasterClient,所有对 CubeMaster 的调用都通过它:

rust
let cubemaster = CubeMasterClient::new(config.cubemaster_url.clone(), http_client.clone());
let services = AppServices::new(&config, cubemaster.clone());

7. 一个请求的完整旅程

POST /sandboxes 为例:


延伸阅读