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

Web 平台不断为开发者提供构建经微调的高性能 Web 应用所需的工具。最值得注意的是,WebAssembly (Wasm) 为快速且强大的 Web 应用打开了一扇大门,而 Emscripten 等技术现在允许开发者在网络上重复使用经过尝试和测试的代码。为了真正发挥这种潜力,开发者在存储方面必须具有相同的功能和灵活性。

这正是 Storage Foundation API 的用武之地。Storage Foundation API 是一种快速且不预设立场的全新存储 API,可解锁呼声非常高的新 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。
  • storageFoundation.requestCapacity(requestedCapacity):请求供当前执行上下文使用的新容量(以字节为单位)。返回一个使用剩余可用容量进行解析的 promise。
  • storageFoundation.releaseCapacity(toBeReleasedCapacity):从当前执行上下文中释放指定数量的字节,并返回使用剩余容量进行解析的 promise。
  • storageFoundation.getRemainingCapacity():返回一个使用当前执行上下文可用的容量进行解析的 promise。

文件句柄

通过以下函数处理文件:

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

    NativeIOReadResult 是一个包含两个条目的对象:

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

    NativeIOWriteResult 是一个包含两个条目的对象:

    • bufferArrayBufferView,这是传输传递给 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 审核。

主打图片来自 UnsplashMarkus Spiske