Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,16 @@ var http = require('http')

// older node versions don't have appendHeader
var isAppendHeaderSupported = typeof http.ServerResponse.prototype.appendHeader === 'function'

// node v22 (https://github.com/nodejs/node/pull/50394) made writeHead preserve
// duplicate headers passed as a raw array even when setHeader was called before;
// earlier versions collapse them to the last value. since this monkey-patch
// always applies headers via setHeader/appendHeader, only preserve duplicates on
// versions where node itself does, so behavior stays consistent with the runtime
var preservesDuplicateHeaders = isAppendHeaderSupported && parseInt(process.versions.node, 10) >= 22

var set1dArray = isAppendHeaderSupported ? set1dArrayWithAppend : set1dArrayWithSet
var set2dArray = preservesDuplicateHeaders ? set2dArrayWithAppend : set2dArrayWithSet

/**
* Create a replacement writeHead method.
Expand Down Expand Up @@ -145,7 +154,24 @@ function setWriteHeadHeaders (statusCode) {
return args
}

function set2dArray (res, headers) {
function set2dArrayWithAppend (res, headers) {
var key
for (var i = 0; i < headers.length; i++) {
key = headers[i][0]
if (key) {
res.removeHeader(key)
}
}

for (var j = 0; j < headers.length; j++) {
key = headers[j][0]
if (key) {
res.appendHeader(key, headers[j][1])
}
}
}

function set2dArrayWithSet (res, headers) {
var key
for (var i = 0; i < headers.length; i++) {
key = headers[i][0]
Expand Down
64 changes: 64 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ var request = require('supertest')
// older node versions don't have appendHeader
var isAppendHeaderSupported = typeof http.ServerResponse.prototype.appendHeader === 'function'

// node v22 (https://github.com/nodejs/node/pull/50394) preserves duplicate
// headers from a raw array passed to writeHead; earlier versions collapse them
var preservesDuplicateHeaders = isAppendHeaderSupported && parseInt(process.versions.node, 10) >= 22

describe('onHeaders(res, listener)', function () {
it('should fire after setHeader', function (done) {
var server = createServer(echoListener)
Expand Down Expand Up @@ -429,6 +433,66 @@ describe('onHeaders(res, listener)', function () {
})
})
})

describe('writeHead(status, 2d duplicate headers)', function () {
it('should be respected', function (done) {
var server = createServer(listener, handler)

function handler (req, res) {
res.writeHead(201, [['express', 'is good'], ['express', 'is great']])
}

function listener (req, res) {
// no need to duplicate existing listener tests further... right?
}

var response = request(server).get('/')

if (preservesDuplicateHeaders) {
response
.expect('express', 'is good, is great')
} else {
response
.expect('express', 'is great')
}

response
.expect(201)
.end(function (err, res) {
if (err) throw err

var expressIsGood = false
var expressIsGreat = false

// very old node versions do not have the `rawHeaders` prop
var headers = res.res.rawHeaders || res.res.headers

if (headers.length) {
for (var i = 0; i < headers.length; i++) {
const header = headers[i]

if (header === 'express') {
if (headers[i + 1] === 'is good') {
expressIsGood = true
} else if (headers[i + 1] === 'is great') {
expressIsGreat = true
}
}
}
} else {
expressIsGreat = headers.express === 'is great'
}

if (preservesDuplicateHeaders) {
assert.ok(expressIsGood)
}

assert.ok(expressIsGreat)

done()
})
})
})
})

function createServer (listener, handler) {
Expand Down
Loading