Set-Cookie header gets concatenated in fetch if an interceptor is in place
Bug Description
If the dispatcher contains an interceptor, even a dummy one, the Set-Cookie headers will be concatenated together.
Reproducible By
const http = require('node:http')
const {Agent} = require('undici');
const dispatcher = new Agent().compose((dispatch) => dispatch)
const server = http.createServer((req, res) => {
res.writeHead(200, [['set-cookie', 'a=1'], ['set-cookie', 'b=2'], ['set-cookie', 'c=3']])
res.end("ok")
})
server.listen({port: 3002}, () => {
Promise.all([fetch('http://localhost:3002'), fetch('http://localhost:3002', {dispatcher})])
.then(([r1, r2]) => {
console.log(r1.headers.getSetCookie())
console.log(r2.headers.getSetCookie())
})
.then(() => server.close())
});
The output will be:
[ 'a=1', 'b=2', 'c=3' ]
[ 'a=1,b=2,c=3' ]
Expected Behavior
The two outputs should be identical.
Environment
Nodejs 24.4.1 Undici 7.13.0
I took a stab at this by changing WrapHandler. Another possible fix is to modify httpNetworkFetch->dispatch->onHeaders instead. Please let me know if that is preferred.
The concatenation is something that only seems to happen on fetch, attempting the same with undici#request does lead to the expected result of an array instead of a concatenated string.
Don't believe we should change something but open to chat about it.
cc: @KhafraDev
Note, there is a slight variance between using a composed dispatcher and a non-composed one in the resulted string, but they are essentially the same
If receiving an array is expected, #4390 is fine. If it isn't, I think it'd be more beneficial to align the dispatcher header behavior (in general, since it seems like a recurring issue).
I'd say we could align to always expect an array when it comes to multiple-value headers, pairing a bit with what's the expectation of node:http#request.
Arrays are fine for "parsed" headers of type Record<string, string | string[]>, but maybe not for "raw" header. This issue happens because an array appeared in a "raw" header, e.g.:
[key: Buffer, value: Buffer, ...] -> [key: Buffer, value: Buffer[], ...]
This is also not valid according to the type of onHeaders, which expects a Array<Buffer>, not Array<Buffer | Buffer[]>.