Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

http: response.setHeaders() #46109

Merged
merged 12 commits into from
Jan 9, 2023
70 changes: 69 additions & 1 deletion doc/api/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -1953,6 +1953,8 @@ non-string values. However, the non-string values will be converted to strings
for network transmission. The same response object is returned to the caller,
to enable call chaining.

> To set multiple header values at once see [`response.setHeaders()`][].

```js
response.setHeader('Content-Type', 'text/html');
```
Expand Down Expand Up @@ -1987,6 +1989,57 @@ header will not yield the expected result. If progressive population of headers
is desired with potential future retrieval and modification, use
[`response.setHeader()`][] instead of [`response.writeHead()`][].

### `response.setHeaders(headers)`

<!-- YAML
added: REPLACEME
-->

* `headers` {Headers|Object|Array}
* Returns: {http.ServerResponse}

Returns the response object.

Sets multiple header values for implicit headers.
`headers` may be an instance of [`Headers`][] or `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`.

```js
const headers = new Headers({ foo: 'bar' });
response.setHeaders(headers);
```

or

```js
response.setHeaders(['foo', 'bar', 'fizz', 'buzz']);
```

or

```js
response.setHeaders({
foo: 'bar',
fizz: 'buzz',
});
```

When headers have been set with [`response.setHeaders()`][], they will be merged
with any headers passed to [`response.writeHead()`][], with the headers passed
to [`response.writeHead()`][] given precedence.

```js
// Returns content-type = text/plain
const server = http.createServer((req, res) => {
res.setHeaders(['Content-Type', 'text/html', 'X-Foo', 'bar']);
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('ok');
});
```

### `response.setTimeout(msecs[, callback])`

<!-- YAML
Expand Down Expand Up @@ -2203,6 +2256,10 @@ response.writeEarlyHints({
<!-- YAML
added: v0.1.30
changes:
- version:
- REPLACEME
pr-url: https://github.com/nodejs/node/pull/46109
description: Allow passing headers as Headers object.
- version: v14.14.0
pr-url: https://github.com/nodejs/node/pull/35274
description: Allow passing headers as an array.
Expand All @@ -2222,7 +2279,7 @@ changes:

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

Sends a response header to the request. The status code is a 3-digit HTTP
Expand All @@ -2247,6 +2304,15 @@ response
.end(body);
```

`headers` may also be an instance of the [`Headers`][].

```js
const body = 'hello world';
response
.writeHead(200, new Headers({ foo: 'bar' }))
marco-ippolito marked this conversation as resolved.
Show resolved Hide resolved
.end(body);
```

This method must only be called once on a message and it must
be called before [`response.end()`][] is called.

Expand Down Expand Up @@ -3760,6 +3826,7 @@ Set the maximum number of idle HTTP parsers. **Default:** `1000`.
[`Buffer.byteLength()`]: buffer.md#static-method-bufferbytelengthstring-encoding
[`Duplex`]: stream.md#class-streamduplex
[`HPE_HEADER_OVERFLOW`]: errors.md#hpe_header_overflow
[`Headers`]: globals.md#class-headers
[`TypeError`]: errors.md#class-typeerror
[`URL`]: url.md#the-whatwg-url-api
[`agent.createConnection()`]: #agentcreateconnectionoptions-callback
Expand Down Expand Up @@ -3803,6 +3870,7 @@ Set the maximum number of idle HTTP parsers. **Default:** `1000`.
[`response.end()`]: #responseenddata-encoding-callback
[`response.getHeader()`]: #responsegetheadername
[`response.setHeader()`]: #responsesetheadername-value
[`response.setHeaders()`]: #responsesetheadersheaders
[`response.socket`]: #responsesocket
[`response.writableEnded`]: #responsewritableended
[`response.writableFinished`]: #responsewritablefinished
Expand Down
36 changes: 36 additions & 0 deletions lib/_http_outgoing.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
debug = fn;
});

const { Headers } = require('internal/deps/undici/undici');

marco-ippolito marked this conversation as resolved.
Show resolved Hide resolved
const HIGH_WATER_MARK = getDefaultHighWaterMark();

const kCorked = Symbol('corked');
Expand Down Expand Up @@ -673,6 +675,40 @@ OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
return this;
};

OutgoingMessage.prototype.setHeaders = function setHeaders(headers) {
if (this._header) {
throw new ERR_HTTP_HEADERS_SENT('set');
}
let k;

if (headers instanceof Headers) {
const keys = [...headers.keys()];
marco-ippolito marked this conversation as resolved.
Show resolved Hide resolved
for (let i = 0; i < keys.length; i++) {
k = keys[i];
if (k) this.setHeader(k, headers.get(k));
}
} else if (ArrayIsArray(headers)) {
if (headers.length % 2 !== 0) {
throw new ERR_INVALID_ARG_VALUE('headers', headers);
}

for (let n = 0; n < headers.length; n += 2) {
k = headers[n + 0];
if (k) this.setHeader(k, headers[n + 1]);
}
} else if (headers) {
const keys = ObjectKeys(headers);
// Retain for(;;) loop for performance reasons
// Refs: https://github.com/nodejs/node/pull/30958
for (let i = 0; i < keys.length; i++) {
k = keys[i];
if (k) this.setHeader(k, headers[k]);
}
}

return this;
};

OutgoingMessage.prototype.appendHeader = function appendHeader(name, value) {
if (this._header) {
throw new ERR_HTTP_HEADERS_SENT('append');
Expand Down
29 changes: 5 additions & 24 deletions lib/_http_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
'use strict';

const {
ArrayIsArray,
Error,
MathMin,
ObjectKeys,
ObjectFromEntries,
ObjectSetPrototypeOf,
RegExpPrototypeExec,
ReflectApply,
Expand Down Expand Up @@ -76,7 +76,6 @@ const {
ERR_HTTP_INVALID_STATUS_CODE,
ERR_HTTP_SOCKET_ENCODING,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_CHAR
} = codes;
const {
Expand All @@ -90,6 +89,7 @@ const { setInterval, clearInterval } = require('timers');
let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
debug = fn;
});
const { Headers } = require('internal/deps/undici/undici');

const dc = require('diagnostics_channel');
const onRequestStartChannel = dc.channel('http.server.request.start');
Expand Down Expand Up @@ -359,30 +359,11 @@ function writeHead(statusCode, reason, obj) {
let headers;
if (this[kOutHeaders]) {
// Slow-case: when progressive API and header fields are passed.
let k;
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
for (let i = 0; i < keys.length; i++) {
k = keys[i];
if (k) this.setHeader(k, obj[k]);
}
}
if (k === undefined && this._header) {
ShogunPanda marked this conversation as resolved.
Show resolved Hide resolved
throw new ERR_HTTP_HEADERS_SENT('render');
}
this.setHeaders(obj);
marco-ippolito marked this conversation as resolved.
Show resolved Hide resolved
// Only progressive api is used
headers = this[kOutHeaders];
} else if (obj instanceof Headers) {
headers = ObjectFromEntries(obj.entries());
} else {
// Only writeHead() called
headers = obj;
Expand Down
89 changes: 89 additions & 0 deletions test/parallel/test-http-response-setheaders.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
'use strict';
const common = require('../common');
const http = require('http');
const assert = require('assert');


{
const server = http.createServer({ requireHostHeader: false }, common.mustCall((req, res) => {
res.setHeaders(['foo', '1', 'bar', '2' ]);
res.writeHead(200);
res.end();
}));

server.listen(0, common.mustCall(() => {
http.get({ port: server.address().port, headers: [] }, (res) => {
assert.strictEqual(res.statusCode, 200);
assert.strictEqual(res.headers.foo, '1');
assert.strictEqual(res.headers.bar, '2');
res.resume().on('end', common.mustCall(() => {
server.close();
}));
});
}));
}

{
const server = http.createServer({ requireHostHeader: false }, common.mustCall((req, res) => {
res.setHeaders({
foo: '1',
bar: '2'
});
res.writeHead(200);
res.end();
}));

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

{
const server = http.createServer({ requireHostHeader: false }, common.mustCall((req, res) => {
res.writeHead(200); // headers already sent
assert.throws(() => {
res.setHeaders({
foo: 'bar',
});
}, {
code: 'ERR_HTTP_HEADERS_SENT'
});
res.end();
}));

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

{
const server = http.createServer({ requireHostHeader: false }, common.mustCall((req, res) => {
const headers = new globalThis.Headers({ foo: '1', bar: '2' });
res.setHeaders(headers);
res.writeHead(200);
res.end();
}));

server.listen(0, common.mustCall(() => {
http.get({ port: server.address().port }, (res) => {
assert.strictEqual(res.statusCode, 200);
assert.strictEqual(res.headers.foo, '1');
assert.strictEqual(res.headers.bar, '2');
res.resume().on('end', common.mustCall(() => {
server.close();
}));
});
}));
}
35 changes: 35 additions & 0 deletions test/parallel/test-http-write-head-2.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,38 @@ const http = require('http');
}));
}));
}

{
const server = http.createServer(common.mustCall((req, res) => {
res.writeHead(200, new globalThis.Headers({ foo: 'bar' }));
res.end();
}));

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

{
const server = http.createServer(common.mustCall((req, res) => {
res.setHeader('test', '1');
res.writeHead(200, new globalThis.Headers({ 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();
}));
}));
}));
}