异步访问 HTTP Cookie

Victor Costan

什么是 Cookie Store API?

Cookie Store API 会将 HTTP Cookie 公开给服务工件,并提供 document.cookie 的异步替代方案。借助此 API,您可以更轻松地:

  • 通过异步访问 Cookie,避免主线程卡顿。
  • 避免轮询 Cookie,因为可以观察 Cookie 的更改。
  • 从服务工件访问 Cookie。

阅读铺垫消息

当前状态

步骤 状态
1. 创建铺垫消息 完成
2. 创建规范的初始草稿 完成
**3. 收集反馈并迭代规范** **进行中**
4. 来源试用 已暂停
5. 发布 尚未开始

如何使用异步 Cookie 存储区?

启用来源试用

如需在本地试用,您可以在命令行上启用该 API:

chrome --enable-blink-features=CookieStore

在命令行中传递此标志时,系统会在 Chrome 中为当前会话全局启用该 API。

或者,您也可以在 chrome://flags 中启用 #enable-experimental-web-platform-features 标志。

您(可能)不需要 Cookie

在深入了解这个新 API 之前,我想先说明一下,Cookie 仍然是 Web 平台最糟糕的客户端存储基元,应作为最后的补救手段。这并非偶然 - Cookie 是 Web 的第一个客户端存储机制,我们从中学到了很多。

避免使用 Cookie 的主要原因如下:

  • Cookie 可将存储架构引入到后端 API。 每个 HTTP 请求都携带 Cookie Jar 的快照。这样,后端工程师就可以轻松地引入对当前 Cookie 格式的依赖性。一旦发生这种情况,您的前端将无法更改其存储架构,除非将匹配的更改部署到后端。

  • Cookie 具有复杂的安全模型。现代 Web 平台功能遵循相同的源政策,这意味着每个应用都会获得自己的沙盒,并且完全独立于用户可能运行的其他应用。Cookie 作用域会导致安全问题变得更加复杂,仅仅尝试对其进行总结,本文的篇幅就会翻倍。

  • Cookie 的性能开销很高。浏览器需要在每个 HTTP 请求中包含 Cookie 的快照,因此对 Cookie 的每项更改都必须传播到存储空间和网络堆栈。现代浏览器采用了高度优化的 Cookie 存储区实现,但我们永远无法使 Cookie 的效率与其他无需与网络堆栈通信的存储机制一样高。

出于上述所有原因,现代 Web 应用应避免使用 Cookie,而应将会话标识符存储到 IndexedDB,并通过 fetch API 将标识符明确添加到特定 HTTP 请求的标头或正文中。

尽管如此,您之所以还在阅读本文,是因为您有充分的理由使用 Cookie...

久经考验的 document.cookie API 是导致应用卡顿的相当可靠的来源。例如,每当您使用 document.cookie Getter 时,浏览器都必须停止执行 JavaScript,直到获得您请求的 Cookie 信息。这可能需要进行进程跳转或磁盘读取,并会导致界面卡顿。

要解决此问题,一个简单的方法是从 document.cookie Getter 切换到异步 Cookie 存储 API。

await cookieStore.get('session_id');

// {
//   domain: "example.com",
//   expires: 1593745721000,
//   name: "session_id",
//   path: "/",
//   sameSite: "unrestricted",
//   secure: true,
//   value: "yxlgco2xtqb.ly25tv3tkb8"
// }

可以采用类似的方式替换 document.cookie setter。请注意,只有在 cookieStore.set 返回的 Promise 解析后,更改才保证会应用。

await cookieStore.set({name: 'opt_out', value: '1'});

// undefined

观察,不要轮询

一种通过 JavaScript 访问 Cookie 的热门应用会检测用户何时退出账号并更新界面。目前,这通过轮询 document.cookie 来实现,这会导致卡顿并对电池续航时间产生负面影响。

Cookie Store API 提供了一种观察 Cookie 更改的替代方法,无需轮询。

cookieStore.addEventListener('change', event => {
  for (const cookie of event.changed) {
    if (cookie.name === 'session_id') sessionCookieChanged(cookie.value);
  }
  for (const cookie of event.deleted) {
    if (cookie.name === 'session_id') sessionCookieChanged(null);
  }
});

欢迎使用 Service Worker

由于采用同步设计,document.cookie API 尚未向服务工件提供。Cookie Store API 是异步的,因此可以在服务工作器中使用。

在文档上下文和 Service Worker 中,与 Cookie 的互动方式相同。

// Works in documents and service workers.
async function logOut() {
  await cookieStore.delete('session_id');
}

不过,在 Service Worker 中观察 Cookie 变化的做法略有不同。唤醒服务工作器的开销可能非常高,因此我们必须明确说明该工作器感兴趣的 Cookie 更改。

在以下示例中,使用 IndexedDB 缓存用户数据的应用会监控会话 Cookie 的更改,并在用户退出登录时舍弃缓存的数据。

// Specify the cookie changes we're interested in during the install event.
self.addEventListener('install', event => {
  event.waitUntil(cookieStore.subscribeToChanges([{name: 'session_id'}]));
});

// Delete cached data when the user logs out.
self.addEventListener('cookiechange', event => {
  for (const cookie of event.deleted) {
    if (cookie.name === 'session_id') {
      indexedDB.deleteDatabase('user_cache');
      break;
    }
  }
});

最佳做法

即将推出。

反馈

如果您试用此 API,请告诉我们您的想法!请将有关 API 形状的反馈发送到规范代码库,并将实现 bug 报告给 Blink>Storage>CookiesAPI Blink 组件。

除了说明中概述的有关性能测量和用例之外,我们还特别有兴趣了解这些用例。

其他资源