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

更改文件调用

最后更新 查看 MarkdownAgent 设置

在 Sandbox SDK 0.12 中,容器中的 SDK 服务器处理文件调用。它会创建缺失的目录,将二进制文件作为 base64 返回,并隐藏点文件。在 1.0 中,Files 为每次调用在容器中运行一个小型 helper,并且只做该调用要求的事情。

开始之前

你的 1.0 类已替换 0.12 Sandbox 类,并具有 替换 Sandbox 类 中的 container getter 和 ensureRunning() 方法。Files 需要镜像中的 sandbox-shim helper,该页的 Dockerfile 会复制它。

导入 Files,并将其创建为类的字段。只要 Durable Object 在运行,this.ctx.container 就是同一个对象,因此一个 Files 对象可为 Durable Object 启动的每个容器服务。在每次文件调用之前调用 ensureRunning():

src/index.tsts
import { Files, SandboxFileError } from "@cloudflare/sandbox";

export class MySandbox extends DurableObject<Env> {
    private readonly files = new Files(this.container);
    // ...
}

本页替换 readFile()、readFileStream()、writeFile()、exists()、listFiles()、mkdir()、deleteFile()、renameFile()、moveFile()、watch() 和 checkChanges()。有关命令,请参阅 更改命令调用。

更改每个文件调用

这些调用保持含义,只是名称或结果不同:

0.12 1.0
readFile(path),然后取文本文件的 .content await (await this.files.readFile(path)).text()
先调用 readFileStream(path),然后使用 streamFile() 或 collectFile() (await this.files.readFile(path)).body,其中包含文件字节。0.12 返回服务器发送事件,而这些辅助方法会对其进行解码。
exists(path) this.files.stat(path),捕获代码为 ENOENT 的 SandboxFileError
mkdir(path, { recursive }) this.files.mkdir(path, { recursive })
deleteFile(path) this.files.remove(path)
renameFile(), moveFile() this.files.rename(source, destination)

0.12 会相对于会话的工作目录解析相对路径,默认是 /workspace。Files 会抛出 TypeError: cwd is required when path is relative,因此请在 cwd 选项中传入目录:

src/index.tsts
const text = await (
    await this.files.readFile("app/package.json", { cwd: "/workspace" })
).text();

写入新目录

0.12 会创建缺失的父目录。请先用 mkdir() 创建它们:

src/index.ts (0.12)ts
await sandbox.writeFile("/workspace/app/src/index.js", code);
src/index.ts (1.0)ts
await this.files.mkdir("/workspace/app/src", { recursive: true });
await this.files.writeFile("/workspace/app/src/index.js", code);

没有 mkdir() 时,writeFile() 会以 SandboxFileError 代码 ENOENT 拒绝。0.12 使用 encoding: "base64" 接受 base64 的地方,请传入字节。writeFile() 也接受字符串、Blob 或流。

0.12 将流写入临时文件,再将其重命名到位。writeFile() 直接写入文件,因此读取者可能看到部分写入的内容。要保持 0.12 行为,请写入临时路径并调用 rename(),如 writeFile 所示。

读取二进制文件

0.12 将二进制文件作为 base64 字符串返回,并带有 encoding: "base64"。readFile() 返回 Response,因此直接读取字节:

src/index.ts (0.12)ts
const file = await sandbox.readFile("/workspace/logo.png");
const bytes = Uint8Array.from(atob(file.content), (c) =>
    c.charCodeAt(0),
);
src/index.ts (1.0)ts
const response = await this.files.readFile("/workspace/logo.png");
const bytes = new Uint8Array(await response.arrayBuffer());

响应没有 Content-Type 标头,而 0.12 会返回 mimeType。要获取大小,请调用 stat()。

列出文件

除非传入 includeHidden: true,否则 0.12 会省略点文件,并返回每个文件的大小和修改时间。readDirectory() 返回每个条目,只有 name 和 type:

src/index.ts (0.12)ts
const result = await sandbox.listFiles("/workspace/app");

for (const file of result.files) {
    console.log(file.name, file.size);
}
src/index.ts (1.0)ts
const entries = await this.files.readDirectory("/workspace/app");

for (const entry of entries) {
    if (entry.name.startsWith(".")) {
        continue;
    }

    const stat = await this.files.stat(`/workspace/app/${entry.name}`);
    console.log(entry.name, Number(stat.size));
}

stat() 以 bigint 返回大小,JSON.stringify() 无法序列化它。请用 Number() 转换。每次 Files 调用都会在容器中启动一个 helper 进程。对于递归列表,请使用 readDirectory 中的遍历。

处理错误

失败的调用会抛出 SandboxFileError,其 code 是 Linux 错误名称。每个 0.12 错误类对应一个代码:

0.12 code
FileNotFoundError ENOENT
FileExistsError EEXIST
PermissionDeniedError EACCES
FileSystemError 失败的代码,例如 EISDIR 或 ENOTDIR

SandboxFileError 不是类,因此请用 SandboxFileError.is() 检查它:

src/index.tsts
try {
    await this.files.stat("/workspace/package.json");
} catch (cause) {
    if (SandboxFileError.is(cause) && cause.code === "ENOENT") {
        // The file does not exist.
    } else {
        throw cause;
    }
}

两个调用的行为与 0.12 不同:

  • deleteFile() 会拒绝目录。remove() 在带有 recursive: true 时会删除目录,没有该选项时会以 EISDIR 拒绝。

  • renameFile() 和 moveFile() 会运行 mv。如果目标是现有目录,mv 会将源文件移入其中。rename() 则遵循 Linux rename 规则:它会替换目标;如果目标是包含文件的目录,则会以 ENOTEMPTY 拒绝操作。请传入完整的新路径,例如 /workspace/archive/report.txt。

  • mv 还会跨文件系统复制。rename() 会以 EXDEV 拒绝操作,因此请先复制源文件、检查结果,然后再删除源文件:

    src/index.tsts
    const copy = await this.container.exec(["cp", "-a", "--", source, destination]);
    const { exitCode, stderr } = await copy.output();
    
    if (exitCode !== 0) {
        throw new Error(new TextDecoder().decode(stderr));
    }
    
    await this.files.remove(source, { recursive: true });

替换文件监视

要替换 watch(),请在容器中运行 inotifywait,并在请求打开期间流式传输其输出。将 inotify-tools 添加到 Dockerfile 中的 apt-get install 行,然后返回监视器的输出:

src/index.tsts
// Stop watching after 10 minutes.
const watcher = await this.container.exec([
    "timeout",
    "--kill-after=5",
    "600",
    "inotifywait",
    "-m",
    "-r",
    "-e",
    "create,modify,delete,move",
    "--format",
    "%e %w%f",
    "/workspace",
]);

return new Response(watcher.stdout, {
    headers: { "Content-Type": "text/event-stream" },
});

每行包含一个事件和路径,例如 MODIFY /workspace/src/index.ts。text/event-stream 类型会保持响应流式传输,如 流式输出 所述。这些行不是 server-sent events,因此请用 fetch() 和 stream reader 读取它们。

timeout 会在 10 分钟后停止监视器,流随之结束。如果客户端先断开连接,监视器会在下次写入事件时退出。

向 inotifywait 传入绝对路径,而 0.12 会相对于 /workspace 解析相对路径。0.12 的 include 和 exclude 选项接受 glob 模式列表。inotifywait 对每个选项接受一个正则表达式,例如 --exclude '(\.log|/node_modules/.*)$',并且只使用最后一个 --exclude。

checkChanges() 没有替换项。监视器只报告它运行期间发生的更改,因此客户端重连时请再次读取它需要的文件。

检查文件调用

在尚不存在的目录中写入文件,且不调用 mkdir()。该调用会以 SandboxFileError 代码 ENOENT 拒绝,消息为 writeFile '/workspace/new/deep/a.txt': No such file or directory (os error 2)。

在带有 recursive: true 的 mkdir() 之后,写入会成功。对包含点文件的目录调用 readDirectory()。结果会包含该点文件:

[
    { "name": ".hidden", "type": "file" },
    { "name": "shown", "type": "file" }
]

相关资源

这篇文档对您有帮助吗?