Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f5c0e28

Browse filesBrowse files
committed
http2: allow Host in HTTP/2 requests
The HTTP/2 spec allows Host to be used instead of :authority in requests, and this is in fact *preferred* when converting from HTTP/1. We erroneously treated Host as a connection header, thus disallowing it in requests. The patch corrects this, aligning Node.js behaviour with the HTTP/2 spec and with nghttp2: - Treat Host as a single-value header instead of a connection header. - Don't autofill :authority if Host is present. - The compatibility API (request.authority) falls back to using Host if :authority is not present. This is semver-major because requests are no longer guaranteed to have :authority set. An explanatory note was added to the docs. Fixes: #29858 PR-URL: #34664 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Gerhard Stöbich <deb2001-github@yahoo.de> Reviewed-By: Yongsheng Zhang <zyszys98@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Ricky Zhou <0x19951125@gmail.com>
1 parent 42a3a7f commit f5c0e28
Copy full SHA for f5c0e28

File tree

Expand file treeCollapse file tree

6 files changed

+125
-14
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

6 files changed

+125
-14
lines changed
Open diff view settings
Collapse file

‎doc/api/http2.md‎

Copy file name to clipboardExpand all lines: doc/api/http2.md
+22-3Lines changed: 22 additions & 3 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
<!-- YAML
33
added: v8.4.0
44
changes:
5+
- version: REPLACEME
6+
pr-url: https://github.com/nodejs/node/pull/34664
7+
description: Requests with the `host` header (with or without
8+
`:authority`) can now be sent/received.
59
- version: v10.10.0
610
pr-url: https://github.com/nodejs/node/pull/22466
711
description: HTTP/2 is now Stable. Previously, it had been Experimental.
@@ -2530,7 +2534,7 @@ For incoming headers:
25302534
`access-control-max-age`, `access-control-request-method`, `content-encoding`,
25312535
`content-language`, `content-length`, `content-location`, `content-md5`,
25322536
`content-range`, `content-type`, `date`, `dnt`, `etag`, `expires`, `from`,
2533-
`if-match`, `if-modified-since`, `if-none-match`, `if-range`,
2537+
`host`, `if-match`, `if-modified-since`, `if-none-match`, `if-range`,
25342538
`if-unmodified-since`, `last-modified`, `location`, `max-forwards`,
25352539
`proxy-authorization`, `range`, `referer`,`retry-after`, `tk`,
25362540
`upgrade-insecure-requests`, `user-agent` or `x-content-type-options` are
@@ -2909,8 +2913,10 @@ added: v8.4.0
29092913

29102914
* {string}
29112915

2912-
The request authority pseudo header field. It can also be accessed via
2913-
`req.headers[':authority']`.
2916+
The request authority pseudo header field. Because HTTP/2 allows requests
2917+
to set either `:authority` or `host`, this value is derived from
2918+
`req.headers[':authority']` if present. Otherwise, it is derived from
2919+
`req.headers['host']`.
29142920

29152921
#### `request.complete`
29162922
<!-- YAML
@@ -3709,6 +3715,18 @@ following additional properties:
37093715
* `type` {string} Either `'server'` or `'client'` to identify the type of
37103716
`Http2Session`.
37113717

3718+
## Note on `:authority` and `host`
3719+
3720+
HTTP/2 requires requests to have either the `:authority` pseudo-header
3721+
or the `host` header. Prefer `:authority` when constructing an HTTP/2
3722+
request directly, and `host` when converting from HTTP/1 (in proxies,
3723+
for instance).
3724+
3725+
The compatibility API falls back to `host` if `:authority` is not
3726+
present. See [`request.authority`][] for more information. However,
3727+
if you don't use the compatibility API (or use `req.headers` directly),
3728+
you need to implement any fall-back behaviour yourself.
3729+
37123730
[ALPN Protocol ID]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
37133731
[ALPN negotiation]: #http2_alpn_negotiation
37143732
[Compatibility API]: #http2_compatibility_api
@@ -3749,6 +3767,7 @@ following additional properties:
37493767
[`net.Socket.prototype.unref()`]: net.html#net_socket_unref
37503768
[`net.Socket`]: net.html#net_class_net_socket
37513769
[`net.connect()`]: net.html#net_net_connect
3770+
[`request.authority`]: #http2_request_authority
37523771
[`request.socket`]: #http2_request_socket
37533772
[`request.socket.getPeerCertificate()`]: tls.html#tls_tlssocket_getpeercertificate_detailed
37543773
[`response.end()`]: #http2_response_end_data_encoding_callback
Collapse file

‎lib/internal/http2/compat.js‎

Copy file name to clipboardExpand all lines: lib/internal/http2/compat.js
+3-2Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ const {
5050
kSocket,
5151
kRequest,
5252
kProxySocket,
53-
assertValidPseudoHeader
53+
assertValidPseudoHeader,
54+
getAuthority
5455
} = require('internal/http2/util');
5556
const { _checkIsHttpToken: checkIsHttpToken } = require('_http_common');
5657

@@ -395,7 +396,7 @@ class Http2ServerRequest extends Readable {
395396
}
396397

397398
get authority() {
398-
return this[kHeaders][HTTP2_HEADER_AUTHORITY];
399+
return getAuthority(this[kHeaders]);
399400
}
400401

401402
get scheme() {
Collapse file

‎lib/internal/http2/core.js‎

Copy file name to clipboardExpand all lines: lib/internal/http2/core.js
+5-4Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ const {
118118
assertValidPseudoHeaderResponse,
119119
assertValidPseudoHeaderTrailer,
120120
assertWithinRange,
121+
getAuthority,
121122
getDefaultSettings,
122123
getSessionState,
123124
getSettings,
@@ -1636,7 +1637,7 @@ class ClientHttp2Session extends Http2Session {
16361637
const connect = headers[HTTP2_HEADER_METHOD] === HTTP2_METHOD_CONNECT;
16371638

16381639
if (!connect || headers[HTTP2_HEADER_PROTOCOL] !== undefined) {
1639-
if (headers[HTTP2_HEADER_AUTHORITY] === undefined)
1640+
if (getAuthority(headers) === undefined)
16401641
headers[HTTP2_HEADER_AUTHORITY] = this[kAuthority];
16411642
if (headers[HTTP2_HEADER_SCHEME] === undefined)
16421643
headers[HTTP2_HEADER_SCHEME] = this[kProtocol].slice(0, -1);
@@ -1667,7 +1668,7 @@ class ClientHttp2Session extends Http2Session {
16671668
const stream = new ClientHttp2Stream(this, undefined, undefined, {});
16681669
stream[kSentHeaders] = headers;
16691670
stream[kOrigin] = `${headers[HTTP2_HEADER_SCHEME]}://` +
1670-
`${headers[HTTP2_HEADER_AUTHORITY]}`;
1671+
`${getAuthority(headers)}`;
16711672

16721673
// Close the writable side of the stream if options.endStream is set.
16731674
if (options.endStream)
@@ -2459,7 +2460,7 @@ class ServerHttp2Stream extends Http2Stream {
24592460
handle.owner = this;
24602461
this[kInit](id, handle);
24612462
this[kProtocol] = headers[HTTP2_HEADER_SCHEME];
2462-
this[kAuthority] = headers[HTTP2_HEADER_AUTHORITY];
2463+
this[kAuthority] = getAuthority(headers);
24632464
}
24642465

24652466
// True if the remote peer accepts push streams
@@ -2502,7 +2503,7 @@ class ServerHttp2Stream extends Http2Stream {
25022503

25032504
if (headers[HTTP2_HEADER_METHOD] === undefined)
25042505
headers[HTTP2_HEADER_METHOD] = HTTP2_METHOD_GET;
2505-
if (headers[HTTP2_HEADER_AUTHORITY] === undefined)
2506+
if (getAuthority(headers) === undefined)
25062507
headers[HTTP2_HEADER_AUTHORITY] = this[kAuthority];
25072508
if (headers[HTTP2_HEADER_SCHEME] === undefined)
25082509
headers[HTTP2_HEADER_SCHEME] = this[kProtocol];
Collapse file

‎lib/internal/http2/util.js‎

Copy file name to clipboardExpand all lines: lib/internal/http2/util.js
+14-2Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const {
6161
HTTP2_HEADER_ETAG,
6262
HTTP2_HEADER_EXPIRES,
6363
HTTP2_HEADER_FROM,
64+
HTTP2_HEADER_HOST,
6465
HTTP2_HEADER_IF_MATCH,
6566
HTTP2_HEADER_IF_NONE_MATCH,
6667
HTTP2_HEADER_IF_MODIFIED_SINCE,
@@ -84,7 +85,6 @@ const {
8485
HTTP2_HEADER_HTTP2_SETTINGS,
8586
HTTP2_HEADER_TE,
8687
HTTP2_HEADER_TRANSFER_ENCODING,
87-
HTTP2_HEADER_HOST,
8888
HTTP2_HEADER_KEEP_ALIVE,
8989
HTTP2_HEADER_PROXY_CONNECTION,
9090

@@ -131,6 +131,7 @@ const kSingleValueHeaders = new Set([
131131
HTTP2_HEADER_ETAG,
132132
HTTP2_HEADER_EXPIRES,
133133
HTTP2_HEADER_FROM,
134+
HTTP2_HEADER_HOST,
134135
HTTP2_HEADER_IF_MATCH,
135136
HTTP2_HEADER_IF_MODIFIED_SINCE,
136137
HTTP2_HEADER_IF_NONE_MATCH,
@@ -429,7 +430,6 @@ function isIllegalConnectionSpecificHeader(name, value) {
429430
switch (name) {
430431
case HTTP2_HEADER_CONNECTION:
431432
case HTTP2_HEADER_UPGRADE:
432-
case HTTP2_HEADER_HOST:
433433
case HTTP2_HEADER_HTTP2_SETTINGS:
434434
case HTTP2_HEADER_KEEP_ALIVE:
435435
case HTTP2_HEADER_PROXY_CONNECTION:
@@ -625,12 +625,24 @@ function sessionName(type) {
625625
}
626626
}
627627

628+
function getAuthority(headers) {
629+
// For non-CONNECT requests, HTTP/2 allows either :authority
630+
// or Host to be used equivalently. The first is preferred
631+
// when making HTTP/2 requests, and the latter is preferred
632+
// when converting from an HTTP/1 message.
633+
if (headers[HTTP2_HEADER_AUTHORITY] !== undefined)
634+
return headers[HTTP2_HEADER_AUTHORITY];
635+
if (headers[HTTP2_HEADER_HOST] !== undefined)
636+
return headers[HTTP2_HEADER_HOST];
637+
}
638+
628639
module.exports = {
629640
assertIsObject,
630641
assertValidPseudoHeader,
631642
assertValidPseudoHeaderResponse,
632643
assertValidPseudoHeaderTrailer,
633644
assertWithinRange,
645+
getAuthority,
634646
getDefaultSettings,
635647
getSessionState,
636648
getSettings,
Collapse file
+63Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
if (!common.hasCrypto)
5+
common.skip('missing crypto');
6+
const assert = require('assert');
7+
const h2 = require('http2');
8+
9+
// Requests using host instead of :authority should be allowed
10+
// and Http2ServerRequest.authority should fall back to host
11+
12+
// :authority should NOT be auto-filled if host is present
13+
14+
const server = h2.createServer();
15+
server.listen(0, common.mustCall(function() {
16+
const port = server.address().port;
17+
server.once('request', common.mustCall(function(request, response) {
18+
const expected = {
19+
':path': '/foobar',
20+
':method': 'GET',
21+
':scheme': 'http',
22+
'host': `localhost:${port}`
23+
};
24+
25+
assert.strictEqual(request.authority, expected.host);
26+
27+
const headers = request.headers;
28+
for (const [name, value] of Object.entries(expected)) {
29+
assert.strictEqual(headers[name], value);
30+
}
31+
32+
const rawHeaders = request.rawHeaders;
33+
for (const [name, value] of Object.entries(expected)) {
34+
const position = rawHeaders.indexOf(name);
35+
assert.notStrictEqual(position, -1);
36+
assert.strictEqual(rawHeaders[position + 1], value);
37+
}
38+
39+
assert(!Object.hasOwnProperty.call(headers, ':authority'));
40+
assert(!Object.hasOwnProperty.call(rawHeaders, ':authority'));
41+
42+
response.on('finish', common.mustCall(function() {
43+
server.close();
44+
}));
45+
response.end();
46+
}));
47+
48+
const url = `http://localhost:${port}`;
49+
const client = h2.connect(url, common.mustCall(function() {
50+
const headers = {
51+
':path': '/foobar',
52+
':method': 'GET',
53+
':scheme': 'http',
54+
'host': `localhost:${port}`
55+
};
56+
const request = client.request(headers);
57+
request.on('end', common.mustCall(function() {
58+
client.close();
59+
}));
60+
request.end();
61+
request.resume();
62+
}));
63+
}));
Collapse file

‎test/parallel/test-http2-util-headers-list.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-http2-util-headers-list.js
+18-3Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ const common = require('../common');
88
if (!common.hasCrypto)
99
common.skip('missing crypto');
1010
const assert = require('assert');
11-
const { mapToHeaders, toHeaderObject } = require('internal/http2/util');
11+
const {
12+
getAuthority,
13+
mapToHeaders,
14+
toHeaderObject
15+
} = require('internal/http2/util');
1216
const { sensitiveHeaders } = require('http2');
1317
const { internalBinding } = require('internal/test/binding');
1418
const {
@@ -34,6 +38,7 @@ const {
3438
HTTP2_HEADER_ETAG,
3539
HTTP2_HEADER_EXPIRES,
3640
HTTP2_HEADER_FROM,
41+
HTTP2_HEADER_HOST,
3742
HTTP2_HEADER_IF_MATCH,
3843
HTTP2_HEADER_IF_MODIFIED_SINCE,
3944
HTTP2_HEADER_IF_NONE_MATCH,
@@ -86,7 +91,6 @@ const {
8691
HTTP2_HEADER_HTTP2_SETTINGS,
8792
HTTP2_HEADER_TE,
8893
HTTP2_HEADER_TRANSFER_ENCODING,
89-
HTTP2_HEADER_HOST,
9094
HTTP2_HEADER_KEEP_ALIVE,
9195
HTTP2_HEADER_PROXY_CONNECTION
9296
} = internalBinding('http2').constants;
@@ -225,6 +229,7 @@ const {
225229
HTTP2_HEADER_ETAG,
226230
HTTP2_HEADER_EXPIRES,
227231
HTTP2_HEADER_FROM,
232+
HTTP2_HEADER_HOST,
228233
HTTP2_HEADER_IF_MATCH,
229234
HTTP2_HEADER_IF_MODIFIED_SINCE,
230235
HTTP2_HEADER_IF_NONE_MATCH,
@@ -289,7 +294,6 @@ const {
289294
HTTP2_HEADER_HTTP2_SETTINGS,
290295
HTTP2_HEADER_TE,
291296
HTTP2_HEADER_TRANSFER_ENCODING,
292-
HTTP2_HEADER_HOST,
293297
HTTP2_HEADER_PROXY_CONNECTION,
294298
HTTP2_HEADER_KEEP_ALIVE,
295299
'Connection',
@@ -327,6 +331,17 @@ assert.throws(
327331
mapToHeaders({ te: 'trailers' });
328332
mapToHeaders({ te: ['trailers'] });
329333

334+
// HTTP/2 encourages use of Host instead of :authority when converting
335+
// from HTTP/1 to HTTP/2, so we no longer disallow it.
336+
// Refs: https://github.com/nodejs/node/issues/29858
337+
mapToHeaders({ [HTTP2_HEADER_HOST]: 'abc' });
338+
339+
// If both are present, the latter has priority
340+
assert.strictEqual(getAuthority({
341+
[HTTP2_HEADER_AUTHORITY]: 'abc',
342+
[HTTP2_HEADER_HOST]: 'def'
343+
}), 'abc');
344+
330345

331346
{
332347
const rawHeaders = [

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.