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
43 changes: 43 additions & 0 deletions doc/api/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -2969,6 +2969,47 @@ Sets a single header value. If the header already exists in the to-be-sent
headers, its value will be replaced. Use an array of strings to send multiple
headers with the same name.

### `outgoingMessage.setHeaders(headers)`

<!-- YAML
added: REPLACEME
-->

* `headers` {Headers|Map}
* 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
marco-ippolito marked this conversation as resolved.
Show resolved Hide resolved
in the to-be-sent headers, its value will be replaced.

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

or

```js
const headers = new Map([['foo', 'bar']]);
res.setHeaders(headers);
```

When headers have been set with [`outgoingMessage.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');
});
```

### `outgoingMessage.setTimeout(msesc[, callback])`

<!-- YAML
Expand Down Expand Up @@ -3760,6 +3801,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 All @@ -3786,6 +3828,7 @@ Set the maximum number of idle HTTP parsers. **Default:** `1000`.
[`net.createConnection()`]: net.md#netcreateconnectionoptions-connectlistener
[`new URL()`]: url.md#new-urlinput-base
[`outgoingMessage.setHeader(name, value)`]: #outgoingmessagesetheadername-value
[`outgoingMessage.setHeaders()`]: #outgoingmessagesetheadersheaders
[`outgoingMessage.socket`]: #outgoingmessagesocket
[`removeHeader(name)`]: #requestremoveheadername
[`request.destroy()`]: #requestdestroyerror
Expand Down
22 changes: 22 additions & 0 deletions lib/_http_outgoing.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
debug = fn;
});


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 +674,27 @@ 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 ||
!typeof headers.keys === 'function' ||
!typeof headers.get === 'function'
marco-ippolito marked this conversation as resolved.
Show resolved Hide resolved
) {
throw new ERR_INVALID_ARG_TYPE('headers', ['Headers', 'Map'], 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
131 changes: 131 additions & 0 deletions test/parallel/test-http-response-setheaders.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
'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();
}));
});
}));
}

{
const server = http.createServer({ requireHostHeader: false }, common.mustCall((req, res) => {
const headers = new Map([['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();
}));
});
}));
}