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
38 changes: 38 additions & 0 deletions 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,40 @@ 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}
* Returns: {http.ServerResponse}

Returns the response object.

Sets multiple header values for implicit headers.
`headers` must be an instance of [`Headers`][], if header already exists
in the to-be-sent headers, its value will be replaced.

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

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) => {
const headers = new Headers({ 'Content-Type': 'text/html' });
res.setHeaders(headers);
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('ok');
});
```

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

<!-- YAML
Expand Down Expand Up @@ -3760,6 +3796,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 +3840,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
18 changes: 18 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,22 @@ 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');
}

if (headers instanceof Headers === false) {
marco-ippolito marked this conversation as resolved.
Show resolved Hide resolved
throw new ERR_INVALID_ARG_TYPE('headers', 'Headers', headers);
}

for (const key of headers.keys()) {
ShogunPanda marked this conversation as resolved.
Show resolved Hide resolved
this.setHeader(key, headers.get(key));
}

return this;
};

OutgoingMessage.prototype.appendHeader = function appendHeader(name, value) {
if (this._header) {
throw new ERR_HTTP_HEADERS_SENT('append');
Expand Down
3 changes: 0 additions & 3 deletions lib/_http_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,9 +378,6 @@ function writeHead(statusCode, reason, obj) {
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');
}
// Only progressive api is used
headers = this[kOutHeaders];
} else {
Expand Down
111 changes: 111 additions & 0 deletions test/parallel/test-http-response-setheaders.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
'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.writeHead(200); // Headers already sent
const headers = new globalThis.Headers({ foo: '1' });
assert.throws(() => {
res.setHeaders(headers);
}, {
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) => {
assert.throws(() => {
res.setHeaders(['foo', '1']);
}, {
code: 'ERR_INVALID_ARG_TYPE'
});
assert.throws(() => {
res.setHeaders({ foo: '1' });
}, {
code: 'ERR_INVALID_ARG_TYPE'
});
assert.throws(() => {
res.setHeaders(null);
}, {
code: 'ERR_INVALID_ARG_TYPE'
});
assert.throws(() => {
res.setHeaders(undefined);
}, {
code: 'ERR_INVALID_ARG_TYPE'
});
assert.throws(() => {
res.setHeaders('test');
}, {
code: 'ERR_INVALID_ARG_TYPE'
});
assert.throws(() => {
res.setHeaders(1);
}, {
code: 'ERR_INVALID_ARG_TYPE'
});
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();
}));
});
}));
}

{
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, ['foo', '3']);
res.end();
}));

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