Files 会读取并更改正在运行的 container 中的文件和目录。它不会启动或停止 container。
每个方法都会通过 exec() 在容器中运行 sandbox-shim 辅助二进制文件。除 readFile() 外,每个方法都会等待辅助程序完成;readFile() 会在文件打开后立即返回,并流式传输文件内容。路径遵循 Linux 规则,Files 不会限制调用方可打开的路径。
import { Files } from "@cloudflare/sandbox";
import { DurableObject } from "cloudflare:workers";
export class MyContainer extends DurableObject {
files;
constructor(ctx, env) {
super(ctx, env);
if (!ctx.container) {
throw new Error("No container is configured");
}
// ctx.container stays the same object while the Durable Object runs.
this.files = new Files(ctx.container);
}
// The container must already be running.
async readInput() {
await this.files.mkdir("/workspace", { recursive: true });
await this.files.writeFile("/workspace/input.txt", "hello\n");
return this.files.readFile("/workspace/input.txt");
}
}import { Files } from "@cloudflare/sandbox";
import { DurableObject } from "cloudflare:workers";
export class MyContainer extends DurableObject<Env> {
private readonly files: Files;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
if (!ctx.container) {
throw new Error("No container is configured");
}
// ctx.container stays the same object while the Durable Object runs.
this.files = new Files(ctx.container);
}
// The container must already be running.
async readInput(): Promise<Response> {
await this.files.mkdir("/workspace", { recursive: true });
await this.files.writeFile("/workspace/input.txt", "hello\n");
return this.files.readFile("/workspace/input.txt");
}
}完整 Worker 请参阅 在 sandbox 内外移动文件。
使用 Files 前,请满足软件包要求:镜像中的 helper 二进制文件、nodejs_compat 标志,以及正在运行的 container。Files 没有其他要求。
new Files(container: Pick<Container, "exec">)container— 来自this.ctx.container的 container,或任何带有兼容exec()方法的对象。
- 路径是绝对路径,或相对于
cwd选项的相对路径。 - 相对路径需要
cwd。cwd必须是绝对路径。 - 路径不能为空,也不能包含空字符。
- 无效路径或
cwd会在 helper 进程启动前抛出TypeError。 - 相对路径会拼接到
cwd上,并由 Linux 解析拼接后的路径。绝对路径会忽略cwd。不存在的cwd会以SandboxFileErrorENOENT拒绝。路径中任何缺失的目录也是如此。 - 路径会像在 Linux 中一样通过符号链接解析,除非某个方法另有说明。
每个方法都接受 FileOperationOptions:
interface FileOperationOptions {
cwd?: string;
user?: string;
signal?: AbortSignal;
}| 选项 | 类型 | 说明 |
|---|---|---|
cwd |
string |
用于解析相对路径的绝对目录。对于 rename(),它会解析两条路径。 |
user |
string |
数字形式的 Linux 用户和组 ID,格式为 uid:gid,例如 "1000:1000"。会传给 exec()。省略时,helper 以镜像用户运行。仅用户 ID,或用户名/组名,会抛出 TypeError。 |
signal |
AbortSignal |
取消操作并终止其 helper 进程。操作完成后触发的信号没有效果,包括比调用更长寿的 AbortSignal.timeout()。调用会以中止原因拒绝:无原因的 abort() 产生 AbortError,AbortSignal.timeout() 产生 TimeoutError。取消可能留下每个方法列出的部分效果。 |
mkdir() 还接受 recursive。remove() 还接受 recursive 和 force。方法不接受的选项,或类型错误的选项,会在 helper 进程启动前抛出 TypeError。在这些选项中,只有 user 和 signal 会到达 exec()。
readFile(path: string, options?: FileOperationOptions): Promise<Response>以字节形式读取文件内容。
- 返回状态为
200且没有Content-Typeheader 的Response。正文会对 helper 进程施加背压。 - 无法打开文件时以
SandboxFileError拒绝,例如缺失文件的ENOENT或目录的EISDIR。 - 文件打开后的错误会使响应正文出错,而不是拒绝调用。
像任何 HTTP 响应一样读取 Response。text()、json()、arrayBuffer() 和 blob() 会将整个文件加载到内存中。对于大文件或大小未知的文件,请改为流式读取 body。
writeFile(
path: string,
content:
| string
| ArrayBuffer
| ArrayBufferView
| Blob
| ReadableStream<Uint8Array>,
options?: FileOperationOptions,
): Promise<void>创建文件,或截断现有文件,然后将 content 流式写入其中。
content是FileContent值,例如string、Uint8Array或ReadableStream<Uint8Array>。- 父目录必须存在。缺失的父目录会以
ENOENT拒绝。path处的目录会以EISDIR拒绝。 - 文件会在消费
ReadableStream之前打开。之后的失败可能留下已创建、已截断或部分写入的文件。 - 如果
content流出错,调用会以流的错误拒绝,而不是SandboxFileError。
rename() 会一步替换现有目标。为避免其他进程读取部分写入的文件,请将文件写入临时路径,然后重命名:
await files.writeFile(`${path}.partial`, content, { cwd });
await files.rename(`${path}.partial`, path, { cwd });stat(path: string, options?: FileOperationOptions): Promise<SandboxFileStat>返回路径的 SandboxFileStat 元数据。会跟随路径末尾的符号链接。
lstat(path: string, options?: FileOperationOptions): Promise<SandboxFileStat>返回路径的 SandboxFileStat 元数据,不跟随路径末尾的符号链接。对于符号链接,type 为 "symlink",size 为链接目标的长度。
readDirectory(
path: string,
options?: FileOperationOptions,
): Promise<SandboxDirectoryEntry[]>将目录的直接条目作为 SandboxDirectoryEntry 值返回。
- 列表未排序。文件系统决定顺序。列表不包含
.或..。 path可以是指向目录的符号链接。该方法不跟随其他符号链接。- 该方法不会递归,也不会为每个条目返回元数据。
path处的文件会以ENOTDIR拒绝。- 不是有效 UTF-8 的条目名称会以
EILSEQ拒绝整个调用。
要列出目录树,请使用 readDirectory() 遍历。此遍历不跟随指向目录的符号链接:
import type { Files } from "@cloudflare/sandbox";
async function* walk(files: Files, directory: string): AsyncGenerator<string> {
for (const entry of await files.readDirectory(directory)) {
const path = `${directory}/${entry.name}`;
yield path;
if (entry.type === "directory") {
yield* walk(files, path);
}
}
}每次调用 readDirectory() 都会在 container 中启动一个进程。对于大型树,一次 container.exec(["find", directory]) 调用更快。要取消它,请将 signal 传给 exec()。
mkdir(path: string, options?: MkdirOptions): Promise<void>
type MkdirOptions = FileOperationOptions & {
recursive?: boolean;
};创建目录。
- 使用
recursive: true时,该方法会创建缺失的父目录。没有它时,缺失的父目录会以ENOENT拒绝。recursive默认为false。 path处的现有文件会以EEXIST拒绝。path处的现有目录也会以EEXIST拒绝,除非recursive为true。- 父目录位置上的文件会以
ENOTDIR拒绝。 - 失败的递归调用可能留下它创建的父目录。
rename(
source: string,
destination: string,
options?: FileOperationOptions,
): Promise<void>使用 Linux rename 规则重命名文件、目录或符号链接。
destination处的现有文件会被替换。- 将目录重命名到非空目录上会以
ENOTEMPTY拒绝。 - 跨文件系统重命名会以
EXDEV拒绝。 - 该方法不会回退到复制再删除。
- 此方法的
SandboxFileError会同时设置path和destination。
remove(path: string, options?: RemoveOptions): Promise<void>
type RemoveOptions = FileOperationOptions & {
recursive?: boolean;
force?: boolean;
};删除文件或符号链接。会删除符号链接本身,而不是其目标。
- 目录需要
recursive: true。没有它时,目录会以EISDIR拒绝,即使force为true。 - 使用
recursive: true时,该方法会删除目录树,且不跟随其中的符号链接。 - 使用
force: true时,缺失目标会成功,而不是以ENOENT拒绝。 - 失败的递归调用可能留下树的一部分。
type FileContent =
| string
| ArrayBuffer
| ArrayBufferView
| Blob
| ReadableStream<Uint8Array>;字符串会以 UTF-8 写入。
| 字段 | 类型 | 说明 |
|---|---|---|
type |
SandboxFileType |
文件类型 |
size |
bigint |
大小(字节) |
mode |
number |
Linux 模式,包括文件类型位。权限为 644 的常规文件的 mode 为 0o100644。 |
uid |
number |
所有者用户 ID |
gid |
number |
所有者组 ID |
accessedAt |
Date |
最近访问时间 |
modifiedAt |
Date |
最近内容修改时间 |
changedAt |
Date |
最近元数据更改时间 |
对 bigint 使用 JSON.stringify() 和 Response.json() 会抛出 TypeError。在序列化 SandboxFileStat 之前,请用 Number() 或 String() 转换 size。bigint 和 Date 值会通过 Durable Object RPC 原样传递。
| 字段 | 类型 | 说明 |
|---|---|---|
name |
string |
条目名称 |
type |
SandboxFileType |
条目类型。符号链接的类型为 "symlink"。 |
type SandboxFileType =
| "file"
| "directory"
| "symlink"
| "blockDevice"
| "characterDevice"
| "fifo"
| "socket";Linux 拒绝了文件操作。
| 字段 | 类型 | 说明 |
|---|---|---|
name |
"SandboxFileError" |
错误名称 |
code |
string |
Linux 错误名称,例如 ENOENT。运行时没有该错误名称时为 UNKNOWN。 |
operation |
string |
失败的方法:readFile、writeFile、stat、lstat、readDirectory、mkdir、rename 或 remove |
path |
string |
传给方法的路径。对于 rename(),是源路径。 |
destination |
string |
目标路径。仅由 rename() 设置。其他方法为 undefined。 |
detail |
string |
来自 helper 的错误描述 |
message 的形式为 <operation> '<path>': <detail>,例如 readFile 'notes.txt': No such file or directory (os error 2)。
SandboxFileError 不是类。请使用 SandboxFileError.is(error) 而不是 instanceof。它可以识别同一 Worker 中抛出的错误以及通过 Durable Object RPC 返回的错误。
Files 无法完成与 helper 二进制文件的交换。与已安装软件包版本不匹配的 cloudflare/sandbox 镜像标签可能导致此错误。
| 字段 | 类型 | 说明 |
|---|---|---|
name |
"SandboxProtocolError" |
错误名称 |
code |
"SANDBOX_PROTOCOL_ERROR" |
错误代码 |
detail |
string |
错误描述 |
使用 SandboxProtocolError.is(error) 识别该错误。
Files 不会包装来自运行时或其输入的错误。
| 条件 | 错误 |
|---|---|
无效路径、cwd 或其他选项 |
TypeError |
| container 未运行 | 来自 exec() 的 Error |
| 镜像不包含 helper 二进制文件 | 来自 exec() 的 Error,会点名 /usr/local/bin/sandbox-shim |
signal 已中止 |
中止原因 |
writeFile() 的 content 流出错 |
来自流的错误 |