跳转到内容
本站为社区维护的非官方中文翻译,与 Cloudflare, Inc. 无关联。本站不提供账户登录,请勿在任何非官方站点输入您的 Cloudflare 账户信息。官方文档:developers.cloudflare.com

从 sandbox 调用经过认证的 API

最后更新 查看 MarkdownAgent 设置

让 sandbox 中的代码调用需要 token 的 API,同时将 token 留在 Worker 中。来自 sandbox 的请求会到达 Worker 中的 entrypoint,后者只允许你选择的请求并添加 token。本例中,代码会从 GitHub API 读取你的个人资料。

前提条件

  • 带有 Durable Object 的 Worker(该 Durable Object 使用 Durable Object 调度策略 启动 container),或使用名为 LOADER 的 Worker Loader 绑定加载 Dynamic Workers 的 Worker。要创建一个,请参阅 运行 Linux 命令 或 运行 JavaScript。
  • 可以读取你个人资料的 GitHub access token。

存储 token

  1. 在 wrangler.jsonc 中将 token 声明为 secret,然后生成类型:

    {
        "secrets": {
            "required": ["GITHUB_TOKEN"],
        },
    }
    [secrets]
    required = [ "GITHUB_TOKEN" ]
    npx wrangler types
  2. Wrangler 提示时存储 token:

    npx wrangler secret put GITHUB_TOKEN

从 Container 调用 API

container 会像往常一样向 api.github.com 发送请求。Durable Object 拦截发往该 hostname 的 HTTPS 请求,并将其交付给 Worker 中的 entrypoint,由后者添加 token。

  1. 向 Worker 添加 entrypoint,接受一个 GitHub API 请求并添加 token:

    src/index.jsjs
    import { WorkerEntrypoint } from "cloudflare:workers";
    
    export class GitHubGateway extends WorkerEntrypoint {
    	async fetch(request) {
    		const url = new URL(request.url);
    
    		if (
    			request.method !== "GET" ||
    			url.hostname !== "api.github.com" ||
    			url.pathname !== "/user" ||
    			url.search !== ""
    		) {
    			return new Response("Forbidden", { status: 403 });
    		}
    
    		return fetch("https://api.github.com/user", {
    			headers: {
    				Accept: "application/vnd.github+json",
    				Authorization: `Bearer ${this.env.GITHUB_TOKEN}`,
    				"User-Agent": "cloudflare-sandbox",
    				"X-GitHub-Api-Version": "2022-11-28",
    			},
    		});
    	}
    }
    src/index.tsts
    import { WorkerEntrypoint } from "cloudflare:workers";
    
    export class GitHubGateway extends WorkerEntrypoint<Env> {
        async fetch(request: Request): Promise<Response> {
            const url = new URL(request.url);
    
            if (
                request.method !== "GET" ||
                url.hostname !== "api.github.com" ||
                url.pathname !== "/user" ||
                url.search !== ""
            ) {
                return new Response("Forbidden", { status: 403 });
            }
    
            return fetch("https://api.github.com/user", {
                headers: {
                    Accept: "application/vnd.github+json",
                    Authorization: `Bearer ${this.env.GITHUB_TOKEN}`,
                    "User-Agent": "cloudflare-sandbox",
                    "X-GitHub-Api-Version": "2022-11-28",
                },
            });
        }
    }

    该 entrypoint 只允许 GET /user,因此 sandbox 中的代码无法将 token 用于其他用途。只允许代码需要的每个请求,不要更多。

  2. 向 Durable Object 添加方法,拦截 api.github.com 并运行调用它的代码:

    src/index.tsts
    export class MyContainer extends DurableObject<Env> {
        // ...
    
        async getUsername(): Promise<string> {
            const container = this.ctx.container;
    
            if (!container) {
                throw new Error("No container is configured");
            }
    
            await container.interceptOutboundHttps(
                "api.github.com",
                this.ctx.exports.GitHubGateway,
            );
    
            if (!container.running) {
                container.start({
                    image: "cloudflare/debian-trixie",
                    entrypoint: ["sleep", "infinity"],
                    // The container can reach only the hostnames you intercept
                    enableInternet: false,
                });
            }
    
            const process = await container.exec(
                [
                    "node",
                    "--input-type=module",
                    "--eval",
                    `const response = await fetch("https://api.github.com/user");
    if (!response.ok) throw new Error("GitHub request failed");
    console.log((await response.json()).login);`,
                ],
                {
                    env: {
                        // The intercept terminates TLS with a certificate that the container
                        // CA certificate signs. Node.js reads the CA certificate from this path
                        NODE_EXTRA_CA_CERTS: "/etc/cloudflare/certs/cloudflare-containers-ca.crt",
                    },
                },
            );
            const output = await process.output();
    
            if (output.exitCode !== 0) {
                throw new Error(new TextDecoder().decode(output.stderr));
            }
    
            return new TextDecoder().decode(output.stdout).trim();
        }
    }
  3. 向 Worker 添加返回用户名的路由:

    src/index.tsts
    if (url.pathname === "/username") {
        const sandbox = env.MY_CONTAINER.getByName("github");
        return Response.json({ username: await sandbox.getUsername() });
    }

    该路由使用独立的沙盒名称。已运行的实例会保留启动时的 Internet 访问设置,因此 enableInternet: false 只对该方法启动的实例生效。

  4. 部署 Worker,然后向 Wrangler 打印的 workers.dev URL 上的 /username 发送请求:

    npx wrangler deploy
    curl https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/username
    { "username": "octocat" }

从 Dynamic Worker 调用 API

Dynamic Worker 获得的是调用一个固定 GitHub 端点的方法,而不是 token。

  1. 向 Worker 添加带有使用 token 调用 GitHub 的方法的 entrypoint:

    src/index.jsjs
    import { WorkerEntrypoint } from "cloudflare:workers";
    
    export class GitHub extends WorkerEntrypoint {
    	async getUsername() {
    		const response = await fetch("https://api.github.com/user", {
    			headers: {
    				Accept: "application/vnd.github+json",
    				Authorization: `Bearer ${this.env.GITHUB_TOKEN}`,
    				"User-Agent": "cloudflare-sandbox",
    				"X-GitHub-Api-Version": "2022-11-28",
    			},
    		});
    
    		if (!response.ok) {
    			throw new Error(`GitHub returned ${response.status}`);
    		}
    
    		const user = await response.json();
    		return user.login;
    	}
    }
    src/index.tsts
    import { WorkerEntrypoint } from "cloudflare:workers";
    
    export class GitHub extends WorkerEntrypoint<Env> {
        async getUsername(): Promise<string> {
            const response = await fetch("https://api.github.com/user", {
                headers: {
                    Accept: "application/vnd.github+json",
                    Authorization: `Bearer ${this.env.GITHUB_TOKEN}`,
                    "User-Agent": "cloudflare-sandbox",
                    "X-GitHub-Api-Version": "2022-11-28",
                },
            });
    
            if (!response.ok) {
                throw new Error(`GitHub returned ${response.status}`);
            }
    
            const user = await response.json<{ login: string }>();
            return user.login;
        }
    }
  2. 在 Worker 的 fetch() handler 中,将 entrypoint 作为绑定传给 Dynamic Worker。handler 需要其 ctx 参数以便使用 ctx.exports:

    src/index.tsts
    const sandbox = env.LOADER.load({
        compatibilityDate: "2026-10-01",
        mainModule: "code.js",
        modules: {
            "code.js": `
                import { WorkerEntrypoint } from "cloudflare:workers";
    
                export class Code extends WorkerEntrypoint {
                    async run() {
                        return this.env.GITHUB.getUsername();
                    }
                }
            `,
        },
        env: {
            // Create a stub that the Dynamic Worker can receive
            GITHUB: ctx.exports.GitHub({ props: {} }),
        },
        // Block every other `fetch()` and `connect()` call from the Dynamic Worker
        globalOutbound: null,
    });
    
    const username = await sandbox
        .getEntrypoint<WorkerEntrypoint & { run(): Promise<string> }>("Code")
        .run();
    return Response.json({ username });

    代码调用 this.env.GITHUB.getUsername(),该方法在你的 Worker 中读取 token。

  3. 部署 Worker,然后向 Wrangler 打印的 workers.dev URL 发送请求:

    npx wrangler deploy
    curl https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev
    { "username": "octocat" }

请先认证对 Worker 的调用方,以免其他人使用你的 token 配额。更多信息请参阅 Sandbox 安全。

相关资源

这篇文档对您有帮助吗?