为您的应用打造高性能存储服务:Storage Foundation API

Thomas Steiner
Thomas Steiner

Web 平台越来越多地为开发者提供所需工具,帮助他们构建精细调整的高性能 Web 应用。最值得注意的是,WebAssembly (Wasm) 为开发快速且强大的 Web 应用打开了大门,而 Emscripten 等技术现在让开发者能够在 Web 上重复使用经过验证的代码。为了真正充分利用这一潜力,开发者在存储方面必须拥有同样的强大功能和灵活性。

这正是 Storage Foundation API 的用武之地。Storage Foundation API 是一个快速且不带偏见的新存储 API,可解锁 Web 上备受欢迎的新用例,例如实现高性能数据库和妥善管理大型临时文件。借助这个新接口,开发者可以将“自己的存储空间”引入到 Web 中,缩小 Web 代码与平台专用代码之间的功能差距。

Storage Foundation API 的设计类似于非常基本的文件系统,因此它提供了通用、简单且高性能的基本元素,开发者可以基于这些元素构建更高级别的组件,从而实现灵活性。应用可以根据自己的需求充分利用最合适的工具,在易用性、性能和可靠性之间取得适当的平衡。

为什么 Web 需要另一个存储空间 API?

Web 平台为开发者提供了多种存储选项,每种选项都是根据特定用例构建的。

  • 其中一些选项显然与此提案不重叠,因为它们只允许存储少量数据,例如 Cookie 或由 sessionStoragelocalStorage 机制组成的 Web Storage API
  • 其他选项(例如 File and Directory Entries APIWebSQL)已出于各种原因被废弃。
  • File System Access API 具有类似的 API Surface,但其用途是与客户端的文件系统交互,并提供对可能不在源代码或浏览器所有权范围内的数据的访问权限。这种不同的侧重点带来了更严格的安全注意事项和更高的性能开销。
  • IndexedDB API 可用作 Storage Foundation API 的某些用例的后端。例如,Emscripten 包含 IDBFS,这是一种基于 IndexedDB 的永久性文件系统。不过,由于 IndexedDB 本质上是一个键值对存储,因此存在明显的性能限制。此外,在 IndexedDB 下,直接访问文件的子部分更难且速度更慢。
  • 最后,CacheStorage 接口得到广泛支持,并经过优化,可用于存储大型数据(例如 Web 应用资源),但值不可变。

Storage Foundation API 旨在通过允许高效存储在应用源代码中定义的可变大型文件,弥补之前存储选项的所有缺点。

Storage Foundation API 的建议用例

可能使用此 API 的网站示例包括:

  • 处理大量视频、音频或图片数据的效率应用或创意应用。此类应用可以将分块卸载到磁盘,而不是将其保留在内存中。
  • 依赖于可从 Wasm 访问的永久性文件系统且需要的性能超出 IDBFS 可保证的应用。

什么是 Storage Foundation API?

该 API 主要分为两个部分:

  • 文件系统调用,提供与文件和文件路径交互的基本功能。
  • 文件句柄,用于提供对现有文件的读取和写入权限。

文件系统调用

Storage Foundation API 引入了一个新的对象 storageFoundation,该对象位于 window 对象中,并包含多个函数:

  • storageFoundation.open(name):打开给定名称的文件(如果存在),否则创建新文件。返回一个 promise,由打开的文件解析。
  • storageFoundation.delete(name):移除具有给定名称的文件。返回一个 Promise,在文件被删除时解析。
  • storageFoundation.rename(oldName, newName):将文件从旧名称重命名为新名称。返回一个 promise,在重命名文件时解析。
  • storageFoundation.getAll():返回一个 Promise,该 Promise 会解析为包含所有现有文件名的数组。
  • storageFoundation.requestCapacity(requestedCapacity):请求新的容量(以字节为单位),以供当前执行上下文使用。返回一个 promise,由可用剩余容量来解析。
  • storageFoundation.releaseCapacity(toBeReleasedCapacity):从当前执行上下文中释放指定数量的字节,并返回一个解析为剩余容量的 Promise。
  • storageFoundation.getRemainingCapacity():返回一个 Promise,该 Promise 会解析为当前执行上下文可用的容量。

文件句柄

您可以通过以下函数处理文件:

  • NativeIOFile.close():关闭文件,并返回一个在操作完成时解析的 Promise。
  • NativeIOFile.flush():将文件的内存状态与存储设备同步(即刷新),并返回一个在操作完成时解析的 promise。
  • NativeIOFile.getLength():返回一个 Promise,其解析结果为文件的长度(以字节为单位)。
  • NativeIOFile.setLength(length):设置文件的长度(以字节为单位),并返回一个在操作完成时解析的 Promise。如果新长度小于当前长度,则从文件末尾开始移除字节。否则,系统会使用值为零的字节扩展文件。
  • NativeIOFile.read(buffer, offset):通过传输给定缓冲区(然后将其保持分离)的结果缓冲区读取给定偏移处的文件内容。返回包含传输缓冲区和成功读取的字节数的 NativeIOReadResult

    NativeIOReadResult 是一个由以下两个条目组成的对象:

    • buffer:一个 ArrayBufferView,这是传递给 read() 的缓冲区传输的结果。其类型和长度与源缓冲区相同。
    • readBytes:成功读取到 buffer 中的字节数。如果发生错误或读取范围超出文件末尾,则此值可能会小于缓冲区大小。如果读取范围超出文件末尾,则将其设置为零。
  • NativeIOFile.write(buffer, offset):将指定缓冲区的内容写入文件中的指定偏移处。缓冲区会在写入任何数据之前传输,因此会保持分离状态。返回包含传输的缓冲区和成功写入的字节数的 NativeIOWriteResult。如果写入范围超出文件长度,则会延长文件。

    NativeIOWriteResult 是一个由以下两个条目组成的对象:

    • buffer:一个 ArrayBufferView,这是传递给 write() 的缓冲区传输的结果。其类型和长度与源缓冲区相同。
    • writtenBytes:成功写入 buffer 的字节数。如果发生错误,此值可能会小于缓冲区大小。

完整示例

为了更清楚地介绍上述概念,下面提供了两个完整示例,引导您了解 Storage Foundation 文件生命周期的不同阶段。

打开、写入、读取、关闭

// Open a file (creating it if needed).
const file = await storageFoundation.open('test_file');
try {
  // Request 100 bytes of capacity for this context.
  await storageFoundation.requestCapacity(100);

  const writeBuffer = new Uint8Array([64, 65, 66]);
  // Write the buffer at offset 0. After this operation, `result.buffer`
  // contains the transferred buffer and `result.writtenBytes` is 3,
  // the number of bytes written. `writeBuffer` is left detached.
  let result = await file.write(writeBuffer, 0);

  const readBuffer = new Uint8Array(3);
  // Read at offset 1. `result.buffer` contains the transferred buffer,
  // `result.readBytes` is 2, the number of bytes read. `readBuffer` is left
  // detached.
  result = await file.read(readBuffer, 1);
  // `Uint8Array(3) [65, 66, 0]`
  console.log(result.buffer);
} finally {
  file.close();
}

打开、列出、删除

// Open three different files (creating them if needed).
await storageFoundation.open('sunrise');
await storageFoundation.open('noon');
await storageFoundation.open('sunset');
// List all existing files.
// `["sunset", "sunrise", "noon"]`
await storageFoundation.getAll();
// Delete one of the three files.
await storageFoundation.delete('noon');
// List all remaining existing files.
// `["sunrise", "noon"]`
await storageFoundation.getAll();

演示

您可以使用下面嵌入的 Storage Foundation API 演示进行试玩。创建、重命名、写入和读取文件,并在您进行更改时查看您请求更新的可用容量。您可以在 Glitch 上找到此演示的源代码

安全与权限

Chromium 团队使用控制对强大 Web 平台功能的访问权限中定义的核心原则(包括用户控制、透明度和人体工学)设计和实现了 Storage Foundation API。

与 Web 上的其他新型存储 API 遵循相同模式一样,对 Storage Foundation API 的访问受源限制,这意味着来源只能访问自己创建的数据。它也仅限于安全上下文。

用户控制

存储配额将用于分配对磁盘空间的访问权限,并防止滥用。您需要先请求要占用的内存。与其他存储 API 一样,用户可以通过浏览器清除 Storage Foundation API 占用的空间。

实用链接

致谢

Storage Foundation API 由 Emanuel KrivoyRichard Stotz 指定和实现。本文由 Pete LePageJoe Medley 审核。

主打图片来自 Unsplash 上的 Markus Spiske