在 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():
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 选项中传入目录:
const text = await (
await this.files.readFile("app/package.json", { cwd: "/workspace" })
).text();0.12 会创建缺失的父目录。请先用 mkdir() 创建它们:
await sandbox.writeFile("/workspace/app/src/index.js", code);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,因此直接读取字节:
const file = await sandbox.readFile("/workspace/logo.png");
const bytes = Uint8Array.from(atob(file.content), (c) =>
c.charCodeAt(0),
);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:
const result = await sandbox.listFiles("/workspace/app");
for (const file of result.files) {
console.log(file.name, file.size);
}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() 检查它:
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()则遵循 Linuxrename规则:它会替换目标;如果目标是包含文件的目录,则会以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 行,然后返回监视器的输出:
// 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" }
]