From b3507134fb79979b0b9fb346410a3124c68b1dce Mon Sep 17 00:00:00 2001 From: Shogun Date: Mon, 10 Jan 2022 21:50:58 +0100 Subject: [PATCH 1/6] http: add uniqueHeaders option to request and createServer --- doc/api/http.md | 72 +++++++- lib/_http_client.js | 8 +- lib/_http_incoming.js | 50 ++++++ lib/_http_outgoing.js | 87 +++++++++- lib/_http_server.js | 8 +- test/parallel/test-http-multiple-headers.js | 173 ++++++++++++++++++++ 6 files changed, 385 insertions(+), 13 deletions(-) create mode 100644 test/parallel/test-http-multiple-headers.js diff --git a/doc/api/http.md b/doc/api/http.md index ce00906e6a4b5c..e87d4c3dd6604a 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -2366,8 +2366,28 @@ header name: `last-modified`, `location`, `max-forwards`, `proxy-authorization`, `referer`, `retry-after`, `server`, or `user-agent` are discarded. * `set-cookie` is always an array. Duplicates are added to the array. -* For duplicate `cookie` headers, the values are joined together with '; '. -* For all other headers, the values are joined together with ', '. +* For duplicate `cookie` headers, the values are joined together with `; `. +* For all other headers, the values are joined together with `, `. + +### `message.headersDistinct` + + + +* {Object} + +Similar to [`message.headers`][], but there is no join logic and the values are +always arrays of strings, even for headers received just once. + +```js +// Prints something like: +// +// { 'user-agent': ['curl/7.22.0'], +// host: ['127.0.0.1:8000'], +// accept: ['*/*'] } +console.log(request.headersDistinct); +``` ### `message.httpVersion` @@ -2501,6 +2521,18 @@ added: v0.3.0 The request/response trailers object. Only populated at the `'end'` event. +### `message.trailersDistinct` + + + +* {Object} + +Similar to [`message.trailers`][], but there is no join logic and the values are +always arrays of strings, even for headers received just once. +Only populated at the `'end'` event. + ### `message.url` + +* `name` {string} Header name +* `value` {string|string} Header value +* Returns: {this} + +Append a single header value for the header object. + +If the value is an array, this is equivalent of calling this method multiple +times. + +If there were no previous value for the header, this is equivalent of calling +[`outgoingMessage.setHeader(name, value)`][]. + +Depending of the value of `options.uniqueHeaders` when the client request or the +server were created, this will end up in the header being sent multiple times or +a single time with values joined using `, `. + ### `outgoingMessage.connection` * `name` {string} Header name -* `value` {string|string} Header value +* `value` {string|string[]} Header value * Returns: {this} Append a single header value for the header object. diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index 54daa7032495e4..9560d68d614f89 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -35,6 +35,7 @@ const { ObjectPrototypeHasOwnProperty, ObjectSetPrototypeOf, RegExpPrototypeTest, + SafeSet, StringPrototypeToLowerCase, Symbol, } = primordials; @@ -506,9 +507,7 @@ function processHeader(self, state, key, value, validate) { if (ArrayIsArray(value)) { if ( (value.length < 2 || !isCookieField(key)) && - !ArrayPrototypeIncludes( - self[kUniqueHeaders], StringPrototypeToLowerCase(key) - ) + (!self[kUniqueHeaders] || !self[kUniqueHeaders].has(StringPrototypeToLowerCase(key))) ) { // Retain for(;;) loop for performance reasons // Refs: https://github.com/nodejs/node/pull/30958 @@ -580,13 +579,13 @@ const validateHeaderValue = hideStackFrames((name, value) => { function parseUniqueHeadersOption(headers) { if (!ArrayIsArray(headers)) { - return []; + return null; } + const unique = new SafeSet(); const l = headers.length; - const unique = Array(l); for (let i = 0; i < l; i++) { - unique[i] = StringPrototypeToLowerCase(headers[i]); + unique.add(StringPrototypeToLowerCase(headers[i])); } return unique; @@ -848,7 +847,6 @@ function connectionCorkNT(conn) { conn.uncork(); } - OutgoingMessage.prototype.addTrailers = function addTrailers(headers) { this._trailer = ''; const keys = ObjectKeys(headers); @@ -873,9 +871,7 @@ OutgoingMessage.prototype.addTrailers = function addTrailers(headers) { const isArrayValue = ArrayIsArray(value); if ( isArrayValue && value.length > 1 && - !ArrayPrototypeIncludes( - this[kUniqueHeaders], StringPrototypeToLowerCase(field) - ) + (!this[kUniqueHeaders] || !this[kUniqueHeaders].has(StringPrototypeToLowerCase(field))) ) { for (let j = 0, l = value.length; j < l; j++) { if (checkInvalidHeaderChar(value[j])) { From f67bfb5722e00804e587a530e18dd2cac78c5155 Mon Sep 17 00:00:00 2001 From: Shogun Date: Wed, 11 May 2022 12:07:59 +0200 Subject: [PATCH 5/6] http: remove useless include --- lib/_http_outgoing.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index 9560d68d614f89..8b2d24bbf694e6 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -24,7 +24,6 @@ const { Array, ArrayIsArray, - ArrayPrototypeIncludes, ArrayPrototypeJoin, MathFloor, NumberPrototypeToString, From 12b3b22c9e06607fa7cdb7c74547b01bf36604da Mon Sep 17 00:00:00 2001 From: Shogun Date: Wed, 11 May 2022 12:09:29 +0200 Subject: [PATCH 6/6] http: linted code --- doc/api/http.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/http.md b/doc/api/http.md index 5ec745dbad1b2e..938514b871c570 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -2651,7 +2651,7 @@ added: REPLACEME --> * `name` {string} Header name -* `value` {string|string[]} Header value +* `value` {string|string\[]} Header value * Returns: {this} Append a single header value for the header object.