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

文件 API

最后更新 查看 MarkdownAgent 设置

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 没有其他要求。

Files

new Files(container: Pick<Container, "exec">)
  • container — 来自 this.ctx.container 的 container,或任何带有兼容 exec() 方法的对象。

路径

  • 路径是绝对路径,或相对于 cwd 选项的相对路径。
  • 相对路径需要 cwd。cwd 必须是绝对路径。
  • 路径不能为空,也不能包含空字符。
  • 无效路径或 cwd 会在 helper 进程启动前抛出 TypeError。
  • 相对路径会拼接到 cwd 上,并由 Linux 解析拼接后的路径。绝对路径会忽略 cwd。不存在的 cwd 会以 SandboxFileError ENOENT 拒绝。路径中任何缺失的目录也是如此。
  • 路径会像在 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

readFile(path: string, options?: FileOperationOptions): Promise<Response>

以字节形式读取文件内容。

  • 返回状态为 200 且没有 Content-Type header 的 Response。正文会对 helper 进程施加背压。
  • 无法打开文件时以 SandboxFileError 拒绝,例如缺失文件的 ENOENT 或目录的 EISDIR。
  • 文件打开后的错误会使响应正文出错,而不是拒绝调用。

像任何 HTTP 响应一样读取 Response。text()、json()、arrayBuffer() 和 blob() 会将整个文件加载到内存中。对于大文件或大小未知的文件,请改为流式读取 body。

writeFile

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

stat(path: string, options?: FileOperationOptions): Promise<SandboxFileStat>

返回路径的 SandboxFileStat 元数据。会跟随路径末尾的符号链接。

lstat

lstat(path: string, options?: FileOperationOptions): Promise<SandboxFileStat>

返回路径的 SandboxFileStat 元数据,不跟随路径末尾的符号链接。对于符号链接,type 为 "symlink",size 为链接目标的长度。

readDirectory

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

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

rename(
    source: string,
    destination: string,
    options?: FileOperationOptions,
): Promise<void>

使用 Linux rename 规则重命名文件、目录或符号链接。

  • destination 处的现有文件会被替换。
  • 将目录重命名到非空目录上会以 ENOTEMPTY 拒绝。
  • 跨文件系统重命名会以 EXDEV 拒绝。
  • 该方法不会回退到复制再删除。
  • 此方法的 SandboxFileError 会同时设置 path 和 destination。

remove

remove(path: string, options?: RemoveOptions): Promise<void>

type RemoveOptions = FileOperationOptions & {
    recursive?: boolean;
    force?: boolean;
};

删除文件或符号链接。会删除符号链接本身,而不是其目标。

  • 目录需要 recursive: true。没有它时,目录会以 EISDIR 拒绝,即使 force 为 true。
  • 使用 recursive: true 时,该方法会删除目录树,且不跟随其中的符号链接。
  • 使用 force: true 时,缺失目标会成功,而不是以 ENOENT 拒绝。
  • 失败的递归调用可能留下树的一部分。

类型

FileContent

type FileContent =
    | string
    | ArrayBuffer
    | ArrayBufferView
    | Blob
    | ReadableStream<Uint8Array>;

字符串会以 UTF-8 写入。

SandboxFileStat

字段 类型 说明
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 原样传递。

SandboxDirectoryEntry

字段 类型 说明
name string 条目名称
type SandboxFileType 条目类型。符号链接的类型为 "symlink"。

SandboxFileType

type SandboxFileType =
    | "file"
    | "directory"
    | "symlink"
    | "blockDevice"
    | "characterDevice"
    | "fifo"
    | "socket";

错误

SandboxFileError

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 返回的错误。

SandboxProtocolError

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 流出错 来自流的错误

相关资源

这篇文档对您有帮助吗?