Skip to content

Commit 5ba4d96

Browse files
pimterryrichardlau
authored andcommitted
http2: add h2 compat support for appendHeader
PR-URL: #51412 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Marco Ippolito <[email protected]>
1 parent 0861498 commit 5ba4d96

File tree

5 files changed

+114
-8
lines changed

5 files changed

+114
-8
lines changed

doc/api/http.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -2991,12 +2991,12 @@ added:
29912991
* `value` {string|string\[]} Header value
29922992
* Returns: {this}
29932993

2994-
Append a single header value for the header object.
2994+
Append a single header value to the header object.
29952995

2996-
If the value is an array, this is equivalent of calling this method multiple
2996+
If the value is an array, this is equivalent to calling this method multiple
29972997
times.
29982998

2999-
If there were no previous value for the header, this is equivalent of calling
2999+
If there were no previous values for the header, this is equivalent to calling
30003000
[`outgoingMessage.setHeader(name, value)`][].
30013001

30023002
Depending of the value of `options.uniqueHeaders` when the client request or the

doc/api/http2.md

+30
Original file line numberDiff line numberDiff line change
@@ -3659,6 +3659,36 @@ message) to the response.
36593659
Attempting to set a header field name or value that contains invalid characters
36603660
will result in a [`TypeError`][] being thrown.
36613661

3662+
#### `response.appendHeader(name, value)`
3663+
3664+
<!-- YAML
3665+
added: REPLACEME
3666+
-->
3667+
3668+
* `name` {string}
3669+
* `value` {string|string\[]}
3670+
3671+
Append a single header value to the header object.
3672+
3673+
If the value is an array, this is equivalent to calling this method multiple
3674+
times.
3675+
3676+
If there were no previous values for the header, this is equivalent to calling
3677+
[`response.setHeader()`][].
3678+
3679+
Attempting to set a header field name or value that contains invalid characters
3680+
will result in a [`TypeError`][] being thrown.
3681+
3682+
```js
3683+
// Returns headers including "set-cookie: a" and "set-cookie: b"
3684+
const server = http2.createServer((req, res) => {
3685+
res.setHeader('set-cookie', 'a');
3686+
res.appendHeader('set-cookie', 'b');
3687+
res.writeHead(200);
3688+
res.end('ok');
3689+
});
3690+
```
3691+
36623692
#### `response.connection`
36633693

36643694
<!-- YAML

lib/internal/http2/compat.js

+67-2
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ const kRawHeaders = Symbol('rawHeaders');
7676
const kTrailers = Symbol('trailers');
7777
const kRawTrailers = Symbol('rawTrailers');
7878
const kSetHeader = Symbol('setHeader');
79+
const kAppendHeader = Symbol('appendHeader');
7980
const kAborted = Symbol('aborted');
8081

8182
let statusMessageWarned = false;
@@ -652,6 +653,47 @@ class Http2ServerResponse extends Stream {
652653
this[kHeaders][name] = value;
653654
}
654655

656+
appendHeader(name, value) {
657+
validateString(name, 'name');
658+
if (this[kStream].headersSent)
659+
throw new ERR_HTTP2_HEADERS_SENT();
660+
661+
this[kAppendHeader](name, value);
662+
}
663+
664+
[kAppendHeader](name, value) {
665+
name = StringPrototypeToLowerCase(StringPrototypeTrim(name));
666+
assertValidHeader(name, value);
667+
668+
if (!isConnectionHeaderAllowed(name, value)) {
669+
return;
670+
}
671+
672+
if (name[0] === ':')
673+
assertValidPseudoHeader(name);
674+
else if (!checkIsHttpToken(name))
675+
this.destroy(new ERR_INVALID_HTTP_TOKEN('Header name', name));
676+
677+
// Handle various possible cases the same as OutgoingMessage.appendHeader:
678+
const headers = this[kHeaders];
679+
if (headers === null || !headers[name]) {
680+
return this.setHeader(name, value);
681+
}
682+
683+
if (!ArrayIsArray(headers[name])) {
684+
headers[name] = [headers[name]];
685+
}
686+
687+
const existingValues = headers[name];
688+
if (ArrayIsArray(value)) {
689+
for (let i = 0, length = value.length; i < length; i++) {
690+
existingValues.push(value[i]);
691+
}
692+
} else {
693+
existingValues.push(value);
694+
}
695+
}
696+
655697
get statusMessage() {
656698
statusMessageWarn();
657699

@@ -684,18 +726,41 @@ class Http2ServerResponse extends Stream {
684726

685727
let i;
686728
if (ArrayIsArray(headers)) {
729+
if (this[kHeaders]) {
730+
// Headers in obj should override previous headers but still
731+
// allow explicit duplicates. To do so, we first remove any
732+
// existing conflicts, then use appendHeader. This is the
733+
// slow path, which only applies when you use setHeader and
734+
// then pass headers in writeHead too.
735+
736+
// We need to handle both the tuple and flat array formats, just
737+
// like the logic further below.
738+
if (headers.length && ArrayIsArray(headers[0])) {
739+
for (let n = 0; n < headers.length; n += 1) {
740+
const key = headers[n + 0][0];
741+
this.removeHeader(key);
742+
}
743+
} else {
744+
for (let n = 0; n < headers.length; n += 2) {
745+
const key = headers[n + 0];
746+
this.removeHeader(key);
747+
}
748+
}
749+
}
750+
751+
// Append all the headers provided in the array:
687752
if (headers.length && ArrayIsArray(headers[0])) {
688753
for (i = 0; i < headers.length; i++) {
689754
const header = headers[i];
690-
this[kSetHeader](header[0], header[1]);
755+
this[kAppendHeader](header[0], header[1]);
691756
}
692757
} else {
693758
if (headers.length % 2 !== 0) {
694759
throw new ERR_INVALID_ARG_VALUE('headers', headers);
695760
}
696761

697762
for (i = 0; i < headers.length; i += 2) {
698-
this[kSetHeader](headers[i], headers[i + 1]);
763+
this[kAppendHeader](headers[i], headers[i + 1]);
699764
}
700765
}
701766
} else if (typeof headers === 'object') {

test/parallel/test-http2-compat-serverresponse-headers.js

+10
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,18 @@ server.listen(0, common.mustCall(function() {
3838
response.setHeader(denormalised, expectedValue);
3939
assert.strictEqual(response.getHeader(denormalised), expectedValue);
4040
assert.strictEqual(response.hasHeader(denormalised), true);
41+
assert.strictEqual(response.hasHeader(real), true);
42+
43+
response.appendHeader(real, expectedValue);
44+
assert.deepStrictEqual(response.getHeader(real), [
45+
expectedValue,
46+
expectedValue,
47+
]);
48+
assert.strictEqual(response.hasHeader(real), true);
49+
4150
response.removeHeader(denormalised);
4251
assert.strictEqual(response.hasHeader(denormalised), false);
52+
assert.strictEqual(response.hasHeader(real), false);
4353

4454
['hasHeader', 'getHeader', 'removeHeader'].forEach((fnName) => {
4555
assert.throws(

test/parallel/test-http2-compat-serverresponse-writehead-array.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const http2 = require('http2');
1616
server.once('request', common.mustCall((request, response) => {
1717
const returnVal = response.writeHead(200, [
1818
['foo', 'bar'],
19+
['foo', 'baz'],
1920
['ABC', 123],
2021
]);
2122
assert.strictEqual(returnVal, response);
@@ -26,7 +27,7 @@ const http2 = require('http2');
2627
const request = client.request();
2728

2829
request.on('response', common.mustCall((headers) => {
29-
assert.strictEqual(headers.foo, 'bar');
30+
assert.strictEqual(headers.foo, 'bar, baz');
3031
assert.strictEqual(headers.abc, '123');
3132
assert.strictEqual(headers[':status'], 200);
3233
}, 1));
@@ -45,7 +46,7 @@ const http2 = require('http2');
4546
const port = server.address().port;
4647

4748
server.once('request', common.mustCall((request, response) => {
48-
const returnVal = response.writeHead(200, ['foo', 'bar', 'ABC', 123]);
49+
const returnVal = response.writeHead(200, ['foo', 'bar', 'foo', 'baz', 'ABC', 123]);
4950
assert.strictEqual(returnVal, response);
5051
response.end(common.mustCall(() => { server.close(); }));
5152
}));
@@ -54,7 +55,7 @@ const http2 = require('http2');
5455
const request = client.request();
5556

5657
request.on('response', common.mustCall((headers) => {
57-
assert.strictEqual(headers.foo, 'bar');
58+
assert.strictEqual(headers.foo, 'bar, baz');
5859
assert.strictEqual(headers.abc, '123');
5960
assert.strictEqual(headers[':status'], 200);
6061
}, 1));

0 commit comments

Comments
 (0)