如何使用Fetch API获取/设置多个'Set-Cookie'头?

13

正如您所知,RFC 6265 指出可以使用多个名称为 Set-Cookie 的标头。

然而,Fetch API 不允许这样做,因为其 Headers 接口公开的所有方法(包括 get()set()append()entries() 以及其他所有方法)都已实现为将具有相同名称的所有标头的值合并为单个由逗号分隔的标头。

例如,如果我们执行以下操作:

var headers = new Headers();
headers.append('content-type', 'text/plain');
headers.append('set-cookie', 'test1=v; Max-Age=0');
headers.append('set-cookie', 'test2=v; Max-Age=0');
headers.append('set-cookie', 'test3=v; Max-Age=0');

然后我们尝试使用get('set-cookie')或通过对headers变量进行entries()迭代来读取set-cookie值,我们得到如下结果:

'set-cookie' : test1=v; Max-Age=0, test2=v; Max-Age=0, test3=v; Max-Age=0
请注意,如果我们尝试读取或操作具有相同名称的多个标头的现有响应对象(即由其他框架创建的支持此允许行为的框架),则会发生相同的错误行为:换句话说,似乎Fetch API完全无法正确处理这种情况。现在,虽然某些标头(如Accept)需要此行为,但大多数浏览器(包括Chrome和Firefox)无法正确解析Set-Cookie标头,因此无法正确设置Cookie。这是否是已知的错误?如果是这样,是否有可用的解决方法可以克服这个问题?

我相信这是唯一一个不允许像这样合并的HTTP头。所以我并不完全惊讶这种情况没有被考虑。当您使用.entries()迭代标头时,是否会获得单独的条目? - Evert
@Evert 不好意思,就像我之前解释的那样,所有这些方法都会将值合并成一个,不管响应对象实际包含什么。换句话说,您总是会得到一个单一(合并的)Set-Cookie条目。 - Darkseal
1
Set-Cookie 是由服务器发送的,而不是客户端。为什么在 Fetch API 中需要它? - Barmar
@Barmar 我开发了一个CORS代理,将请求转发到上游并将响应发送给客户端,因此我必须以各种方式处理HTTP响应。如果您有兴趣,请私信我,我会发送详细信息,但请相信我 - 我绝对需要这个答案。 - Darkseal
如果您想尝试获取赏金,这里是我需要修复/解决的代码(第240行及以下)。阅读注释应该足以理解我遇到的问题以及我想要获得的内容。 - Darkseal
不确定您是否仍然遇到此问题。但是,我能够使用headers.append在Cloudflare Worker内部设置多个Set-Cookie,从而在Chrome和Firefox中解决了此问题。似乎这个问题已经在某个地方得到了修复。 - Jspies
2个回答

11

这是标准中已知的一个“问题”。实际上,这是Fetch API标准Headers部分的第一个注释:

header列表不同,Headers对象无法表示超过一个Set-Cookie头。从某种意义上说,这是有问题的,因为Set-Cookie头与所有其他头不同,不能合并,但由于客户端JavaScript无法访问Set-Cookie头,因此被认为是可以接受的妥协。实现可以选择更高效的Headers对象表示方式,即使是对于头列表,只要它们还支持与Set-Cookie头相关联的数据结构。

您可以在规范存储库中阅读更多内容或提出自己的问题。
虽然已经有一些问题对Set-Cookie进行了详细讨论:

您提到使用解决方法,但这实际上取决于您的用例。
注释中提到使用辅助结构来处理这些问题。
如果您真的想将这些cookie存储在Headers对象中,则可以添加自定义标头进行存储:

new Headers([
  ['X-MyOwn-Set-Cookie-1', 'cookie1=value1'],
  ['X-MyOwn-Set-Cookie-2', 'cookie2=value2']
]);

显然,这不是标准的可接受解决方案,但也许你的实际考虑与这种妥协一致。


正如这个注释和@Barmar在评论中指出的那样,通常您使用服务器而不是前端来使用Set-Cookie
例如,使用express设置多个Set-Cookie没有问题:

test.js

const express = require('express');

const app = express();

const cookies = [
  { key: 'cookie1', value: 'value1' },
  { key: 'cookie2', value: 'value2' },
];

app.get('*', (req, res) => {
  console.log(req.url);
  for (const { key, value } of cookies) {
    res.cookie(key, value, { expires: new Date(Date.now() + 1000 * 60), httpOnly: true });
  }
  res.status(200).send('Success');
});

app.listen(3000, () => console.log(`Listening on http://localhost:3000/`));

一号航站楼

$ node test.js
Listening on http://localhost:3000/

2号航站楼

$ curl -v http://localhost:3000/
[...]
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Set-Cookie: cookie1=value1; Path=/; Expires=Tue, 04 Aug 2020 19:45:53 GMT; HttpOnly
< Set-Cookie: cookie2=value2; Path=/; Expires=Tue, 04 Aug 2020 19:45:53 GMT; HttpOnly
< Content-Type: text/html; charset=utf-8
[...]

2
谢谢您的解释,它确实为我从谷歌上找到的内容增添了一些东西。正如您通过阅读我对@Barman的回答所看到的那样,我需要找到一个适合大多数浏览器(Chrome、Firefox、IE、Edge、Safari)接受的合适解决方案,因为我正在开发一个“无服务器”CORS代理(托管在Cloudflare Worker中),旨在将HTTP请求转发到上游和HTTP响应转发给客户端 - 因此需要“重构”一个有效的头列表,其中包含多个Set-Cookie实例,以适当的方式提供给客户端。 - Darkseal
如果你想试着获取赏金,这里是我需要修复/解决的代码(第240行及其以下)。阅读注释应该足以理解我遇到的问题以及我想要得到的结果。 - Darkseal

5

为了解决这个问题,getSetCookie() 方法被添加到 Fetch API 接口中。此方法提供了获取所有 Set-Cookie header 值的能力。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接