Skip to content

Commit

Permalink
http: allow passing array of key/val into writeHead
Browse files Browse the repository at this point in the history
Enables an optimization when the user already has the headers
in an array form, e.g. when proxying.

PR-URL: #35274
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
  • Loading branch information
ronag authored and MylesBorins committed Oct 14, 2020
1 parent 7d1cdd4 commit 6ff152c
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 4 deletions.
10 changes: 9 additions & 1 deletion doc/api/http.md
Expand Up @@ -1786,6 +1786,9 @@ the request body should be sent. See the [`'checkContinue'`][] event on
<!-- YAML
added: v0.1.30
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/35274
description: Allow passing headers as an array.
- version:
- v11.10.0
- v10.17.0
Expand All @@ -1802,14 +1805,19 @@ changes:

* `statusCode` {number}
* `statusMessage` {string}
* `headers` {Object}
* `headers` {Object|Array}
* Returns: {http.ServerResponse}

Sends a response header to the request. The status code is a 3-digit HTTP
status code, like `404`. The last argument, `headers`, are the response headers.
Optionally one can give a human-readable `statusMessage` as the second
argument.

`headers` may be an `Array` where the keys and values are in the same list.
It is *not* a list of tuples. So, the even-numbered offsets are key values,
and the odd-numbered offsets are the associated values. The array is in the same
format as `request.rawHeaders`.

Returns a reference to the `ServerResponse`, so that calls can be chained.

```js
Expand Down
15 changes: 13 additions & 2 deletions lib/_http_outgoing.js
Expand Up @@ -53,6 +53,7 @@ const {
ERR_HTTP_TRAILER_INVALID,
ERR_INVALID_HTTP_TOKEN,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_CHAR,
ERR_METHOD_NOT_IMPLEMENTED,
ERR_STREAM_CANNOT_PIPE,
Expand Down Expand Up @@ -376,8 +377,18 @@ function _storeHeader(firstLine, headers) {
processHeader(this, state, entry[0], entry[1], false);
}
} else if (ArrayIsArray(headers)) {
for (const entry of headers) {
processHeader(this, state, entry[0], entry[1], true);
if (headers.length && ArrayIsArray(headers[0])) {
for (const entry of headers) {
processHeader(this, state, entry[0], entry[1], true);
}
} else {
if (headers.length % 2 !== 0) {
throw new ERR_INVALID_ARG_VALUE('headers', headers);
}

for (let n = 0; n < headers.length; n += 2) {
processHeader(this, state, headers[n + 0], headers[n + 1], true);
}
}
} else {
for (const key in headers) {
Expand Down
13 changes: 12 additions & 1 deletion lib/_http_server.js
Expand Up @@ -22,6 +22,7 @@
'use strict';

const {
ArrayIsArray,
Error,
ObjectKeys,
ObjectSetPrototypeOf,
Expand Down Expand Up @@ -62,6 +63,7 @@ const {
ERR_HTTP_HEADERS_SENT,
ERR_HTTP_INVALID_STATUS_CODE,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_CHAR
} = require('internal/errors').codes;
const {
Expand Down Expand Up @@ -269,7 +271,16 @@ function writeHead(statusCode, reason, obj) {
if (this[kOutHeaders]) {
// Slow-case: when progressive API and header fields are passed.
let k;
if (obj) {
if (ArrayIsArray(obj)) {
if (obj.length % 2 !== 0) {
throw new ERR_INVALID_ARG_VALUE('headers', obj);
}

for (let n = 0; n < obj.length; n += 2) {
k = obj[n + 0];
if (k) this.setHeader(k, obj[n + 1]);
}
} else if (obj) {
const keys = ObjectKeys(obj);
// Retain for(;;) loop for performance reasons
// Refs: https://github.com/nodejs/node/pull/30958
Expand Down
61 changes: 61 additions & 0 deletions test/parallel/test-http-write-head-2.js
@@ -0,0 +1,61 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const http = require('http');

// Verify that ServerResponse.writeHead() works with arrays.

{
const server = http.createServer(common.mustCall((req, res) => {
res.setHeader('test', '1');
res.writeHead(200, [ 'test', '2', 'test2', '2' ]);
res.end();
}));

server.listen(0, common.mustCall(() => {
http.get({ port: server.address().port }, common.mustCall((res) => {
assert.strictEqual(res.headers.test, '2');
assert.strictEqual(res.headers.test2, '2');
res.resume().on('end', common.mustCall(() => {
server.close();
}));
}));
}));
}

{
const server = http.createServer(common.mustCall((req, res) => {
res.writeHead(200, [ 'test', '1', 'test2', '2' ]);
res.end();
}));

server.listen(0, common.mustCall(function() {
http.get({ port: server.address().port }, common.mustCall((res) => {
assert.strictEqual(res.headers.test, '1');
assert.strictEqual(res.headers.test2, '2');
res.resume().on('end', common.mustCall(() => {
server.close();
}));
}));
}));
}


{
const server = http.createServer(common.mustCall((req, res) => {
try {
res.writeHead(200, [ 'test', '1', 'test2', '2', 'asd' ]);
} catch (err) {
assert.strictEqual(err.code, 'ERR_INVALID_ARG_VALUE');
}
res.end();
}));

server.listen(0, common.mustCall(function() {
http.get({ port: server.address().port }, common.mustCall((res) => {
res.resume().on('end', common.mustCall(() => {
server.close();
}));
}));
}));
}

0 comments on commit 6ff152c

Please sign in to comment.