异步访问 HTTP Cookie

Victor Costan

什么是 Cookie Store API?

Cookie Store API 会向 Service Worker 公开 HTTP Cookie,并为 document.cookie 提供异步替代方案。该 API 可让您更轻松地执行以下操作:

  • 通过异步访问 Cookie,避免主线程上出现卡顿。
  • 避免轮询 Cookie,因为可以观察到 Cookie 的变化。
  • 访问 Service Worker 的 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 Store 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 尚未提供给Service Worker。Cookie Store API 是异步的,因此可以在 Service Worker 中使用。

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

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

不过,在 Service Worker 中观察 Cookie 更改略有不同。唤醒 Service Worker 的成本可能很高,因此我们必须明确描述 Service Worker 感兴趣的 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 形状的反馈引导至规范代码库,并向 Blink>Storage>CookiesAPI Blink 组件报告实现 bug。

我们特别想了解性能测量值和说明中未概述的用例。

其他资源