diff --git a/doc/api/http2.md b/doc/api/http2.md
index 698f1bf2d87e58..e1e82c9dce3456 100644
--- a/doc/api/http2.md
+++ b/doc/api/http2.md
@@ -2278,7 +2278,7 @@ not work.
For incoming headers:
* The `:status` header is converted to `number`.
* Duplicates of `:status`, `:method`, `:authority`, `:scheme`, `:path`,
-`age`, `authorization`, `access-control-allow-credentials`,
+`:protocol`, `age`, `authorization`, `access-control-allow-credentials`,
`access-control-max-age`, `access-control-request-method`, `content-encoding`,
`content-language`, `content-length`, `content-location`, `content-md5`,
`content-range`, `content-type`, `date`, `dnt`, `etag`, `expires`, `from`,
@@ -2335,6 +2335,10 @@ properties.
* `maxHeaderListSize` {number} Specifies the maximum size (uncompressed octets)
of header list that will be accepted. The minimum allowed value is 0. The
maximum allowed value is 232-1. **Default:** `65535`.
+* `enableConnectProtocol`{boolean} Specifies `true` if the "Extended Connect
+ Protocol" defined by [RFC 8441][] is to be enabled. This setting is only
+ meaningful if sent by the server. Once the `enableConnectProtocol` setting
+ has been enabled for a given `Http2Session`, it cannot be disabled.
All additional properties on the settings object are ignored.
@@ -2501,6 +2505,36 @@ req.on('end', () => {
req.end('Jane');
```
+### The Extended CONNECT Protocol
+
+[RFC 8441][] defines an "Extended CONNECT Protocol" extension to HTTP/2 that
+may be used to bootstrap the use of an `Http2Stream` using the `CONNECT`
+method as a tunnel for other communication protocols (such as WebSockets).
+
+The use of the Extended CONNECT Protocol is enabled by HTTP/2 servers by using
+the `enableConnectProtocol` setting:
+
+```js
+const http2 = require('http2');
+const settings = { enableConnectProtocol: true };
+const server = http2.createServer({ settings });
+```
+
+Once the client receives the `SETTINGS` frame from the server indicating that
+the extended CONNECT may be used, it may send `CONNECT` requests that use the
+`':protocol'` HTTP/2 pseudo-header:
+
+```js
+const http2 = require('http2');
+const client = http2.connect('http://localhost:8080');
+client.on('remoteSettings', (settings) => {
+ if (settings.enableConnectProtocol) {
+ const req = client.request({ ':method': 'CONNECT', ':protocol': 'foo' });
+ // ...
+ }
+});
+```
+
## Compatibility API
The Compatibility API has the goal of providing a similar developer experience
@@ -3361,6 +3395,7 @@ following additional properties:
[Readable Stream]: stream.html#stream_class_stream_readable
[RFC 7838]: https://tools.ietf.org/html/rfc7838
[RFC 8336]: https://tools.ietf.org/html/rfc8336
+[RFC 8441]: https://tools.ietf.org/html/rfc8441
[Using `options.selectPadding()`]: #http2_using_options_selectpadding
[`'checkContinue'`]: #http2_event_checkcontinue
[`'request'`]: #http2_event_request
diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js
index b1ed1eee8ff448..b08008857ec062 100644
--- a/lib/internal/http2/core.js
+++ b/lib/internal/http2/core.js
@@ -190,6 +190,7 @@ const {
HTTP2_HEADER_DATE,
HTTP2_HEADER_METHOD,
HTTP2_HEADER_PATH,
+ HTTP2_HEADER_PROTOCOL,
HTTP2_HEADER_SCHEME,
HTTP2_HEADER_STATUS,
HTTP2_HEADER_CONTENT_LENGTH,
@@ -1450,7 +1451,7 @@ class ClientHttp2Session extends Http2Session {
const connect = headers[HTTP2_HEADER_METHOD] === HTTP2_METHOD_CONNECT;
- if (!connect) {
+ if (!connect || headers[HTTP2_HEADER_PROTOCOL] !== undefined) {
if (headers[HTTP2_HEADER_AUTHORITY] === undefined)
headers[HTTP2_HEADER_AUTHORITY] = this[kAuthority];
if (headers[HTTP2_HEADER_SCHEME] === undefined)
diff --git a/lib/internal/http2/util.js b/lib/internal/http2/util.js
index 99466b36d3b321..94dc1198ea1060 100644
--- a/lib/internal/http2/util.js
+++ b/lib/internal/http2/util.js
@@ -20,6 +20,7 @@ const {
HTTP2_HEADER_AUTHORITY,
HTTP2_HEADER_SCHEME,
HTTP2_HEADER_PATH,
+ HTTP2_HEADER_PROTOCOL,
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,
HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE,
HTTP2_HEADER_ACCESS_CONTROL_REQUEST_METHOD,
@@ -78,7 +79,8 @@ const kValidPseudoHeaders = new Set([
HTTP2_HEADER_METHOD,
HTTP2_HEADER_AUTHORITY,
HTTP2_HEADER_SCHEME,
- HTTP2_HEADER_PATH
+ HTTP2_HEADER_PATH,
+ HTTP2_HEADER_PROTOCOL
]);
// This set contains headers that are permitted to have only a single
@@ -89,6 +91,7 @@ const kSingleValueHeaders = new Set([
HTTP2_HEADER_AUTHORITY,
HTTP2_HEADER_SCHEME,
HTTP2_HEADER_PATH,
+ HTTP2_HEADER_PROTOCOL,
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,
HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE,
HTTP2_HEADER_ACCESS_CONTROL_REQUEST_METHOD,
@@ -155,7 +158,8 @@ const IDX_SETTINGS_INITIAL_WINDOW_SIZE = 2;
const IDX_SETTINGS_MAX_FRAME_SIZE = 3;
const IDX_SETTINGS_MAX_CONCURRENT_STREAMS = 4;
const IDX_SETTINGS_MAX_HEADER_LIST_SIZE = 5;
-const IDX_SETTINGS_FLAGS = 6;
+const IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL = 6;
+const IDX_SETTINGS_FLAGS = 7;
const IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE = 0;
const IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH = 1;
@@ -277,6 +281,12 @@ function getDefaultSettings() {
settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE];
}
+ if ((flags & (1 << IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL)) ===
+ (1 << IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL)) {
+ holder.enableConnectProtocol =
+ settingsBuffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL];
+ }
+
return holder;
}
@@ -294,7 +304,8 @@ function getSettings(session, remote) {
initialWindowSize: settingsBuffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE],
maxFrameSize: settingsBuffer[IDX_SETTINGS_MAX_FRAME_SIZE],
maxConcurrentStreams: settingsBuffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS],
- maxHeaderListSize: settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE]
+ maxHeaderListSize: settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE],
+ enableConnectProtocol: settingsBuffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL]
};
}
@@ -329,6 +340,11 @@ function updateSettingsBuffer(settings) {
flags |= (1 << IDX_SETTINGS_ENABLE_PUSH);
settingsBuffer[IDX_SETTINGS_ENABLE_PUSH] = Number(settings.enablePush);
}
+ if (typeof settings.enableConnectProtocol === 'boolean') {
+ flags |= (1 << IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL);
+ settingsBuffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL] =
+ Number(settings.enableConnectProtocol);
+ }
settingsBuffer[IDX_SETTINGS_FLAGS] = flags;
}
diff --git a/src/node_http2.cc b/src/node_http2.cc
index 44353e2d57414c..2c339d7249562e 100644
--- a/src/node_http2.cc
+++ b/src/node_http2.cc
@@ -219,6 +219,7 @@ void Http2Session::Http2Settings::Init() {
GRABSETTING(INITIAL_WINDOW_SIZE, "initial window size");
GRABSETTING(MAX_HEADER_LIST_SIZE, "max header list size");
GRABSETTING(ENABLE_PUSH, "enable push");
+ GRABSETTING(ENABLE_CONNECT_PROTOCOL, "enable connect protocol");
#undef GRABSETTING
@@ -287,6 +288,8 @@ void Http2Session::Http2Settings::Update(Environment* env,
fn(**session, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE);
buffer[IDX_SETTINGS_ENABLE_PUSH] =
fn(**session, NGHTTP2_SETTINGS_ENABLE_PUSH);
+ buffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL] =
+ fn(**session, NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL);
}
// Initializes the shared TypedArray with the default settings values.
@@ -3091,6 +3094,7 @@ void Initialize(Local