Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion doc/api/http2.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -669,10 +669,16 @@ added:

* `windowSize` {number}

Sets the local endpoint's window size.
Sets the local endpoint's connection-level window size.
The `windowSize` is the total window size to set, not
the delta.

Increases take effect immediately, but decreases only apply as the window
already advertised to the peer is consumed, since a window that has been
advertised cannot be retracted. To use a window smaller than the default from
the start of the connection, set the `connectionWindowSize` option when
creating the server or client session instead.

```mjs
import { createServer } from 'node:http2';

Expand DownExpand Up@@ -2844,6 +2850,9 @@ Throws `ERR_INVALID_ARG_TYPE` for invalid `settings` argument.
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v25.7.0
- v24.15.0
Expand DownExpand Up@@ -2961,6 +2970,13 @@ changes:
error that should tell the peer to not open any more streams, continuing
to open streams is therefore regarded as a sign of a misbehaving peer.
**Default:** `100`.
* `connectionWindowSize` {number} Sets the initial flow control window for
each session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `streamResetBurst` {number} and `streamResetRate` {number} Sets the rate
Expand DownExpand Up@@ -3072,6 +3088,9 @@ server.listen(8000);
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v25.7.0
- v24.15.0
Expand DownExpand Up@@ -3180,6 +3199,14 @@ changes:
error that should tell the peer to not open any more streams, continuing
to open streams is therefore regarded as a sign of a misbehaving peer.
**Default:** `100`.
* `connectionWindowSize` {number} Sets the initial flow control window for
each session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
**Default:** `33554432`.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `streamResetBurst` {number} and `streamResetRate` {number} Sets the rate
Expand DownExpand Up@@ -3276,6 +3303,9 @@ server.listen(8443);
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v15.10.0
- v14.16.0
Expand DownExpand Up@@ -3363,6 +3393,14 @@ changes:
* `protocol` {string} The protocol to connect with, if not set in the
`authority`. Value may be either `'http:'` or `'https:'`. **Default:**
`'https:'`
* `connectionWindowSize` {number} Sets the initial flow control window for
this session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
**Default:** `33554432`.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `remoteCustomSettings` {Array} The array of integer values determines the
Expand DownExpand Up@@ -3795,6 +3833,9 @@ properties.
* `initialWindowSize` {number} Specifies the _sender's_ initial window size in
bytes for stream-level flow control. The minimum allowed value is 0. The
maximum allowed value is 2<sup>32</sup>-1. **Default:** `4194304`.
This is a per-stream limit; the window for the connection as a whole is
configured separately with the `connectionWindowSize` option of
[`http2.createServer()`][] or [`http2.connect()`][].
* `maxFrameSize` {number} Specifies the size in bytes of the largest frame
payload. The minimum allowed value is 16,384. The maximum allowed value is
2<sup>24</sup>-1. **Default:** `16384`.
Expand DownExpand Up@@ -5252,6 +5293,7 @@ you need to implement any fall-back behavior yourself.
[`http.createServer()`]: http.md#httpcreateserveroptions-requestlistener
[`http2.SecureServer`]: #class-http2secureserver
[`http2.Server`]: #class-http2server
[`http2.connect()`]: #http2connectauthority-options-listener
[`http2.createSecureServer()`]: #http2createsecureserveroptions-onrequesthandler
[`http2.createServer()`]: #http2createserveroptions-onrequesthandler
[`http2stream.pushStream()`]: #http2streampushstreamheaders-options-callback
Expand Down
59 changes: 28 additions & 31 deletions lib/internal/http2/core.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -241,6 +241,8 @@ function debugSessionObj(session, message, ...args) {
const kMaxFrameSize = (2 ** 24) - 1;
const kMaxInt = (2 ** 32) - 1;
const kMaxInitialWindowSize = (2 ** 31) - 1; // HTTP/2 spec maximum
// A connection window of 0 stalls the connection
const kMinConnectionWindowSize = 1;
const kMaxStreams = (2 ** 32) - 1;
const kMaxALTSVC = (2 ** 14) - 2;

Expand DownExpand Up@@ -3590,19 +3592,38 @@ function connectionListener(socket) {
this.emit('session', session);
}

function initializeOptions(options) {
assertIsObject(options, 'options');
options = { ...options };
assertIsObject(options.settings, 'options.settings');
options.settings = { ...options.settings };

// Validates the session options that apply to both client and server sessions
function validateSessionOptions(options) {
assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
if (options.remoteCustomSettings) {
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
}

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}

if (options.connectionWindowSize !== undefined) {
validateInt32(options.connectionWindowSize, 'options.connectionWindowSize',
kMinConnectionWindowSize, kMaxInitialWindowSize);
}
}

function initializeOptions(options) {
assertIsObject(options, 'options');
options = { ...options };
assertIsObject(options.settings, 'options.settings');
options.settings = { ...options.settings };

validateSessionOptions(options);

if (options.maxSessionInvalidFrames !== undefined)
validateUint32(options.maxSessionInvalidFrames, 'options.maxSessionInvalidFrames');

Expand All@@ -3619,16 +3640,6 @@ function initializeOptions(options) {
// TODO(danbev): is this a good default value?
options.unknownProtocolTimeout = 10000;

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}


// Initialize http1Options bag for HTTP/1 fallback when allowHTTP1 is true.
// This bag is passed to storeHTTPOptions() to configure HTTP/1 server
// behavior (timeouts, IncomingMessage/ServerResponse classes, etc.).
Expand DownExpand Up@@ -3840,21 +3851,7 @@ function connect(authority, options, listener) {
maxOriginSetSize = 128;
}

assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
if (options.remoteCustomSettings) {
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
}

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}
validateSessionOptions(options);

if (typeof authority === 'string')
authority = new URL(authority);
Expand Down
8 changes: 7 additions & 1 deletion lib/internal/http2/util.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -233,7 +233,8 @@ const IDX_OPTIONS_MAX_SETTINGS = 9;
const IDX_OPTIONS_STREAM_RESET_RATE = 10;
const IDX_OPTIONS_STREAM_RESET_BURST = 11;
const IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION = 12;
const IDX_OPTIONS_FLAGS = 13;
const IDX_OPTIONS_CONNECTION_WINDOW_SIZE = 13;
const IDX_OPTIONS_FLAGS = 14;

function updateOptionsBuffer(options) {
let flags = 0;
Expand DownExpand Up@@ -303,6 +304,11 @@ function updateOptionsBuffer(options) {
optionsBuffer[IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION] =
options.strictFieldWhitespaceValidation === true ? 0 : 1;
}
if (typeof options.connectionWindowSize === 'number') {
flags |= (1 << IDX_OPTIONS_CONNECTION_WINDOW_SIZE);
optionsBuffer[IDX_OPTIONS_CONNECTION_WINDOW_SIZE] =
options.connectionWindowSize;
}

optionsBuffer[IDX_OPTIONS_FLAGS] = flags;
}
Expand Down
19 changes: 11 additions & 8 deletions src/node_http2.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -217,6 +217,11 @@ Http2Options::Http2Options(Http2State* http2_state, SessionType type) {
static_cast<size_t>(buffer[IDX_OPTIONS_MAX_SETTINGS]));
}

if (flags & (1 << IDX_OPTIONS_CONNECTION_WINDOW_SIZE)) {
set_connection_window_size(
static_cast<int32_t>(buffer[IDX_OPTIONS_CONNECTION_WINDOW_SIZE]));
}

if ((flags & (1 << IDX_OPTIONS_STREAM_RESET_BURST)) &&
(flags & (1 << IDX_OPTIONS_STREAM_RESET_RATE))) {
nghttp2_option_set_stream_reset_rate_limit(
Expand DownExpand Up@@ -612,15 +617,13 @@ Http2Session::Http2Session(Http2State* http2_state,
&alloc_info), 0);
session_.reset(session);

// Increase the default local connection window to improve throughput
// on high-latency connections. The default 64KB window limits throughput
// to window_size / RTT. With a 32MB connection window, throughput is
// significantly improved. See https://github.com/nodejs/node/issues/38426
// The default connection window is larger than the 64KB required by the
// spec to improve throughput on high-latency connections, where throughput
// is limited to window_size / RTT. User settings have to be applied here
// initially rather than updating later as windows cannot be shrunk after
// they've been advertised.
CHECK_EQ(nghttp2_session_set_local_window_size(
session,
NGHTTP2_FLAG_NONE,
0,
DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE),
session, NGHTTP2_FLAG_NONE, 0, opts.connection_window_size()),
0);

outgoing_storage_.reserve(1024);
Expand Down
15 changes: 11 additions & 4 deletions src/node_http2.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,10 @@ constexpr size_t kDefaultMaxSettings = 10;
// Default maximum total memory cap for Http2Session.
constexpr uint64_t kDefaultMaxSessionMemory = 10000000;

// Default connection-level flow control window (32MB) to improve throughput
// on high-latency connections. See https://github.com/nodejs/node/issues/38426
constexpr int32_t kDefaultConnectionWindowSize = 33554432;

// These are the standard HTTP/2 defaults as specified by the RFC
constexpr uint32_t DEFAULT_SETTINGS_HEADER_TABLE_SIZE = 4096;
constexpr uint32_t DEFAULT_SETTINGS_ENABLE_PUSH = 1;
Expand All@@ -51,10 +55,6 @@ constexpr uint32_t MAX_MAX_FRAME_SIZE = 16777215;
constexpr uint32_t MIN_MAX_FRAME_SIZE = DEFAULT_SETTINGS_MAX_FRAME_SIZE;
constexpr uint32_t MAX_INITIAL_WINDOW_SIZE = 2147483647;

// Default local connection window size (32MB) to improve throughput
// on high-latency connections. See https://github.com/nodejs/node/issues/38426
constexpr uint32_t DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE = 33554432;

// Stream is not going to have any DATA frames
constexpr int STREAM_OPTION_EMPTY_PAYLOAD = 0x1;

Expand DownExpand Up@@ -245,9 +245,16 @@ class Http2Options {
return max_session_memory_;
}

void set_connection_window_size(int32_t size) {
connection_window_size_ = size;
}

int32_t connection_window_size() const { return connection_window_size_; }

private:
Nghttp2OptionPointer options_;
uint64_t max_session_memory_ = kDefaultMaxSessionMemory;
int32_t connection_window_size_ = kDefaultConnectionWindowSize;
uint32_t max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS;
PaddingStrategy padding_strategy_ = PADDING_STRATEGY_NONE;
size_t max_outstanding_pings_ = kDefaultMaxPings;
Expand Down
1 change: 1 addition & 0 deletions src/node_http2_state.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,7 @@ namespace http2 {
IDX_OPTIONS_STREAM_RESET_RATE,
IDX_OPTIONS_STREAM_RESET_BURST,
IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION,
IDX_OPTIONS_CONNECTION_WINDOW_SIZE,
IDX_OPTIONS_FLAGS
};

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion doc/api/http2.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -669,10 +669,16 @@ added:

* `windowSize` {number}

Sets the local endpoint's window size.
Sets the local endpoint's connection-level window size.
The `windowSize` is the total window size to set, not
the delta.

Increases take effect immediately, but decreases only apply as the window
already advertised to the peer is consumed, since a window that has been
advertised cannot be retracted. To use a window smaller than the default from
the start of the connection, set the `connectionWindowSize` option when
creating the server or client session instead.

```mjs
import { createServer } from 'node:http2';

Expand DownExpand Up@@ -2844,6 +2850,9 @@ Throws `ERR_INVALID_ARG_TYPE` for invalid `settings` argument.
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v25.7.0
- v24.15.0
Expand DownExpand Up@@ -2961,6 +2970,13 @@ changes:
error that should tell the peer to not open any more streams, continuing
to open streams is therefore regarded as a sign of a misbehaving peer.
**Default:** `100`.
* `connectionWindowSize` {number} Sets the initial flow control window for
each session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `streamResetBurst` {number} and `streamResetRate` {number} Sets the rate
Expand DownExpand Up@@ -3072,6 +3088,9 @@ server.listen(8000);
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v25.7.0
- v24.15.0
Expand DownExpand Up@@ -3180,6 +3199,14 @@ changes:
error that should tell the peer to not open any more streams, continuing
to open streams is therefore regarded as a sign of a misbehaving peer.
**Default:** `100`.
* `connectionWindowSize` {number} Sets the initial flow control window for
each session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
**Default:** `33554432`.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `streamResetBurst` {number} and `streamResetRate` {number} Sets the rate
Expand DownExpand Up@@ -3276,6 +3303,9 @@ server.listen(8443);
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v15.10.0
- v14.16.0
Expand DownExpand Up@@ -3363,6 +3393,14 @@ changes:
* `protocol` {string} The protocol to connect with, if not set in the
`authority`. Value may be either `'http:'` or `'https:'`. **Default:**
`'https:'`
* `connectionWindowSize` {number} Sets the initial flow control window for
this session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
**Default:** `33554432`.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `remoteCustomSettings` {Array} The array of integer values determines the
Expand DownExpand Up@@ -3795,6 +3833,9 @@ properties.
* `initialWindowSize` {number} Specifies the _sender's_ initial window size in
bytes for stream-level flow control. The minimum allowed value is 0. The
maximum allowed value is 2<sup>32</sup>-1. **Default:** `4194304`.
This is a per-stream limit; the window for the connection as a whole is
configured separately with the `connectionWindowSize` option of
[`http2.createServer()`][] or [`http2.connect()`][].
* `maxFrameSize` {number} Specifies the size in bytes of the largest frame
payload. The minimum allowed value is 16,384. The maximum allowed value is
2<sup>24</sup>-1. **Default:** `16384`.
Expand DownExpand Up@@ -5252,6 +5293,7 @@ you need to implement any fall-back behavior yourself.
[`http.createServer()`]: http.md#httpcreateserveroptions-requestlistener
[`http2.SecureServer`]: #class-http2secureserver
[`http2.Server`]: #class-http2server
[`http2.connect()`]: #http2connectauthority-options-listener
[`http2.createSecureServer()`]: #http2createsecureserveroptions-onrequesthandler
[`http2.createServer()`]: #http2createserveroptions-onrequesthandler
[`http2stream.pushStream()`]: #http2streampushstreamheaders-options-callback
Expand Down
59 changes: 28 additions & 31 deletions lib/internal/http2/core.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -241,6 +241,8 @@ function debugSessionObj(session, message, ...args) {
const kMaxFrameSize = (2 ** 24) - 1;
const kMaxInt = (2 ** 32) - 1;
const kMaxInitialWindowSize = (2 ** 31) - 1; // HTTP/2 spec maximum
// A connection window of 0 stalls the connection
const kMinConnectionWindowSize = 1;
const kMaxStreams = (2 ** 32) - 1;
const kMaxALTSVC = (2 ** 14) - 2;

Expand DownExpand Up@@ -3590,19 +3592,38 @@ function connectionListener(socket) {
this.emit('session', session);
}

function initializeOptions(options) {
assertIsObject(options, 'options');
options = { ...options };
assertIsObject(options.settings, 'options.settings');
options.settings = { ...options.settings };

// Validates the session options that apply to both client and server sessions
function validateSessionOptions(options) {
assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
if (options.remoteCustomSettings) {
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
}

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}

if (options.connectionWindowSize !== undefined) {
validateInt32(options.connectionWindowSize, 'options.connectionWindowSize',
kMinConnectionWindowSize, kMaxInitialWindowSize);
}
}

function initializeOptions(options) {
assertIsObject(options, 'options');
options = { ...options };
assertIsObject(options.settings, 'options.settings');
options.settings = { ...options.settings };

validateSessionOptions(options);

if (options.maxSessionInvalidFrames !== undefined)
validateUint32(options.maxSessionInvalidFrames, 'options.maxSessionInvalidFrames');

Expand All@@ -3619,16 +3640,6 @@ function initializeOptions(options) {
// TODO(danbev): is this a good default value?
options.unknownProtocolTimeout = 10000;

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}


// Initialize http1Options bag for HTTP/1 fallback when allowHTTP1 is true.
// This bag is passed to storeHTTPOptions() to configure HTTP/1 server
// behavior (timeouts, IncomingMessage/ServerResponse classes, etc.).
Expand DownExpand Up@@ -3840,21 +3851,7 @@ function connect(authority, options, listener) {
maxOriginSetSize = 128;
}

assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
if (options.remoteCustomSettings) {
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
}

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}
validateSessionOptions(options);

if (typeof authority === 'string')
authority = new URL(authority);
Expand Down
8 changes: 7 additions & 1 deletion lib/internal/http2/util.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -233,7 +233,8 @@ const IDX_OPTIONS_MAX_SETTINGS = 9;
const IDX_OPTIONS_STREAM_RESET_RATE = 10;
const IDX_OPTIONS_STREAM_RESET_BURST = 11;
const IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION = 12;
const IDX_OPTIONS_FLAGS = 13;
const IDX_OPTIONS_CONNECTION_WINDOW_SIZE = 13;
const IDX_OPTIONS_FLAGS = 14;

function updateOptionsBuffer(options) {
let flags = 0;
Expand DownExpand Up@@ -303,6 +304,11 @@ function updateOptionsBuffer(options) {
optionsBuffer[IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION] =
options.strictFieldWhitespaceValidation === true ? 0 : 1;
}
if (typeof options.connectionWindowSize === 'number') {
flags |= (1 << IDX_OPTIONS_CONNECTION_WINDOW_SIZE);
optionsBuffer[IDX_OPTIONS_CONNECTION_WINDOW_SIZE] =
options.connectionWindowSize;
}

optionsBuffer[IDX_OPTIONS_FLAGS] = flags;
}
Expand Down
19 changes: 11 additions & 8 deletions src/node_http2.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -217,6 +217,11 @@ Http2Options::Http2Options(Http2State* http2_state, SessionType type) {
static_cast<size_t>(buffer[IDX_OPTIONS_MAX_SETTINGS]));
}

if (flags & (1 << IDX_OPTIONS_CONNECTION_WINDOW_SIZE)) {
set_connection_window_size(
static_cast<int32_t>(buffer[IDX_OPTIONS_CONNECTION_WINDOW_SIZE]));
}

if ((flags & (1 << IDX_OPTIONS_STREAM_RESET_BURST)) &&
(flags & (1 << IDX_OPTIONS_STREAM_RESET_RATE))) {
nghttp2_option_set_stream_reset_rate_limit(
Expand DownExpand Up@@ -612,15 +617,13 @@ Http2Session::Http2Session(Http2State* http2_state,
&alloc_info), 0);
session_.reset(session);

// Increase the default local connection window to improve throughput
// on high-latency connections. The default 64KB window limits throughput
// to window_size / RTT. With a 32MB connection window, throughput is
// significantly improved. See https://github.com/nodejs/node/issues/38426
// The default connection window is larger than the 64KB required by the
// spec to improve throughput on high-latency connections, where throughput
// is limited to window_size / RTT. User settings have to be applied here
// initially rather than updating later as windows cannot be shrunk after
// they've been advertised.
CHECK_EQ(nghttp2_session_set_local_window_size(
session,
NGHTTP2_FLAG_NONE,
0,
DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE),
session, NGHTTP2_FLAG_NONE, 0, opts.connection_window_size()),
0);

outgoing_storage_.reserve(1024);
Expand Down
15 changes: 11 additions & 4 deletions src/node_http2.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,10 @@ constexpr size_t kDefaultMaxSettings = 10;
// Default maximum total memory cap for Http2Session.
constexpr uint64_t kDefaultMaxSessionMemory = 10000000;

// Default connection-level flow control window (32MB) to improve throughput
// on high-latency connections. See https://github.com/nodejs/node/issues/38426
constexpr int32_t kDefaultConnectionWindowSize = 33554432;

// These are the standard HTTP/2 defaults as specified by the RFC
constexpr uint32_t DEFAULT_SETTINGS_HEADER_TABLE_SIZE = 4096;
constexpr uint32_t DEFAULT_SETTINGS_ENABLE_PUSH = 1;
Expand All@@ -51,10 +55,6 @@ constexpr uint32_t MAX_MAX_FRAME_SIZE = 16777215;
constexpr uint32_t MIN_MAX_FRAME_SIZE = DEFAULT_SETTINGS_MAX_FRAME_SIZE;
constexpr uint32_t MAX_INITIAL_WINDOW_SIZE = 2147483647;

// Default local connection window size (32MB) to improve throughput
// on high-latency connections. See https://github.com/nodejs/node/issues/38426
constexpr uint32_t DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE = 33554432;

// Stream is not going to have any DATA frames
constexpr int STREAM_OPTION_EMPTY_PAYLOAD = 0x1;

Expand DownExpand Up@@ -245,9 +245,16 @@ class Http2Options {
return max_session_memory_;
}

void set_connection_window_size(int32_t size) {
connection_window_size_ = size;
}

int32_t connection_window_size() const { return connection_window_size_; }

private:
Nghttp2OptionPointer options_;
uint64_t max_session_memory_ = kDefaultMaxSessionMemory;
int32_t connection_window_size_ = kDefaultConnectionWindowSize;
uint32_t max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS;
PaddingStrategy padding_strategy_ = PADDING_STRATEGY_NONE;
size_t max_outstanding_pings_ = kDefaultMaxPings;
Expand Down
1 change: 1 addition & 0 deletions src/node_http2_state.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,7 @@ namespace http2 {
IDX_OPTIONS_STREAM_RESET_RATE,
IDX_OPTIONS_STREAM_RESET_BURST,
IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION,
IDX_OPTIONS_CONNECTION_WINDOW_SIZE,
IDX_OPTIONS_FLAGS
};

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion doc/api/http2.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -669,10 +669,16 @@ added:

* `windowSize` {number}

Sets the local endpoint's window size.
Sets the local endpoint's connection-level window size.
The `windowSize` is the total window size to set, not
the delta.

Increases take effect immediately, but decreases only apply as the window
already advertised to the peer is consumed, since a window that has been
advertised cannot be retracted. To use a window smaller than the default from
the start of the connection, set the `connectionWindowSize` option when
creating the server or client session instead.

```mjs
import { createServer } from 'node:http2';

Expand DownExpand Up@@ -2844,6 +2850,9 @@ Throws `ERR_INVALID_ARG_TYPE` for invalid `settings` argument.
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v25.7.0
- v24.15.0
Expand DownExpand Up@@ -2961,6 +2970,13 @@ changes:
error that should tell the peer to not open any more streams, continuing
to open streams is therefore regarded as a sign of a misbehaving peer.
**Default:** `100`.
* `connectionWindowSize` {number} Sets the initial flow control window for
each session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `streamResetBurst` {number} and `streamResetRate` {number} Sets the rate
Expand DownExpand Up@@ -3072,6 +3088,9 @@ server.listen(8000);
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v25.7.0
- v24.15.0
Expand DownExpand Up@@ -3180,6 +3199,14 @@ changes:
error that should tell the peer to not open any more streams, continuing
to open streams is therefore regarded as a sign of a misbehaving peer.
**Default:** `100`.
* `connectionWindowSize` {number} Sets the initial flow control window for
each session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
**Default:** `33554432`.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `streamResetBurst` {number} and `streamResetRate` {number} Sets the rate
Expand DownExpand Up@@ -3276,6 +3303,9 @@ server.listen(8443);
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v15.10.0
- v14.16.0
Expand DownExpand Up@@ -3363,6 +3393,14 @@ changes:
* `protocol` {string} The protocol to connect with, if not set in the
`authority`. Value may be either `'http:'` or `'https:'`. **Default:**
`'https:'`
* `connectionWindowSize` {number} Sets the initial flow control window for
this session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
**Default:** `33554432`.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `remoteCustomSettings` {Array} The array of integer values determines the
Expand DownExpand Up@@ -3795,6 +3833,9 @@ properties.
* `initialWindowSize` {number} Specifies the _sender's_ initial window size in
bytes for stream-level flow control. The minimum allowed value is 0. The
maximum allowed value is 2<sup>32</sup>-1. **Default:** `4194304`.
This is a per-stream limit; the window for the connection as a whole is
configured separately with the `connectionWindowSize` option of
[`http2.createServer()`][] or [`http2.connect()`][].
* `maxFrameSize` {number} Specifies the size in bytes of the largest frame
payload. The minimum allowed value is 16,384. The maximum allowed value is
2<sup>24</sup>-1. **Default:** `16384`.
Expand DownExpand Up@@ -5252,6 +5293,7 @@ you need to implement any fall-back behavior yourself.
[`http.createServer()`]: http.md#httpcreateserveroptions-requestlistener
[`http2.SecureServer`]: #class-http2secureserver
[`http2.Server`]: #class-http2server
[`http2.connect()`]: #http2connectauthority-options-listener
[`http2.createSecureServer()`]: #http2createsecureserveroptions-onrequesthandler
[`http2.createServer()`]: #http2createserveroptions-onrequesthandler
[`http2stream.pushStream()`]: #http2streampushstreamheaders-options-callback
Expand Down
59 changes: 28 additions & 31 deletions lib/internal/http2/core.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -241,6 +241,8 @@ function debugSessionObj(session, message, ...args) {
const kMaxFrameSize = (2 ** 24) - 1;
const kMaxInt = (2 ** 32) - 1;
const kMaxInitialWindowSize = (2 ** 31) - 1; // HTTP/2 spec maximum
// A connection window of 0 stalls the connection
const kMinConnectionWindowSize = 1;
const kMaxStreams = (2 ** 32) - 1;
const kMaxALTSVC = (2 ** 14) - 2;

Expand DownExpand Up@@ -3590,19 +3592,38 @@ function connectionListener(socket) {
this.emit('session', session);
}

function initializeOptions(options) {
assertIsObject(options, 'options');
options = { ...options };
assertIsObject(options.settings, 'options.settings');
options.settings = { ...options.settings };

// Validates the session options that apply to both client and server sessions
function validateSessionOptions(options) {
assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
if (options.remoteCustomSettings) {
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
}

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}

if (options.connectionWindowSize !== undefined) {
validateInt32(options.connectionWindowSize, 'options.connectionWindowSize',
kMinConnectionWindowSize, kMaxInitialWindowSize);
}
}

function initializeOptions(options) {
assertIsObject(options, 'options');
options = { ...options };
assertIsObject(options.settings, 'options.settings');
options.settings = { ...options.settings };

validateSessionOptions(options);

if (options.maxSessionInvalidFrames !== undefined)
validateUint32(options.maxSessionInvalidFrames, 'options.maxSessionInvalidFrames');

Expand All@@ -3619,16 +3640,6 @@ function initializeOptions(options) {
// TODO(danbev): is this a good default value?
options.unknownProtocolTimeout = 10000;

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}


// Initialize http1Options bag for HTTP/1 fallback when allowHTTP1 is true.
// This bag is passed to storeHTTPOptions() to configure HTTP/1 server
// behavior (timeouts, IncomingMessage/ServerResponse classes, etc.).
Expand DownExpand Up@@ -3840,21 +3851,7 @@ function connect(authority, options, listener) {
maxOriginSetSize = 128;
}

assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
if (options.remoteCustomSettings) {
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
}

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}
validateSessionOptions(options);

if (typeof authority === 'string')
authority = new URL(authority);
Expand Down
8 changes: 7 additions & 1 deletion lib/internal/http2/util.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -233,7 +233,8 @@ const IDX_OPTIONS_MAX_SETTINGS = 9;
const IDX_OPTIONS_STREAM_RESET_RATE = 10;
const IDX_OPTIONS_STREAM_RESET_BURST = 11;
const IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION = 12;
const IDX_OPTIONS_FLAGS = 13;
const IDX_OPTIONS_CONNECTION_WINDOW_SIZE = 13;
const IDX_OPTIONS_FLAGS = 14;

function updateOptionsBuffer(options) {
let flags = 0;
Expand DownExpand Up@@ -303,6 +304,11 @@ function updateOptionsBuffer(options) {
optionsBuffer[IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION] =
options.strictFieldWhitespaceValidation === true ? 0 : 1;
}
if (typeof options.connectionWindowSize === 'number') {
flags |= (1 << IDX_OPTIONS_CONNECTION_WINDOW_SIZE);
optionsBuffer[IDX_OPTIONS_CONNECTION_WINDOW_SIZE] =
options.connectionWindowSize;
}

optionsBuffer[IDX_OPTIONS_FLAGS] = flags;
}
Expand Down
19 changes: 11 additions & 8 deletions src/node_http2.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -217,6 +217,11 @@ Http2Options::Http2Options(Http2State* http2_state, SessionType type) {
static_cast<size_t>(buffer[IDX_OPTIONS_MAX_SETTINGS]));
}

if (flags & (1 << IDX_OPTIONS_CONNECTION_WINDOW_SIZE)) {
set_connection_window_size(
static_cast<int32_t>(buffer[IDX_OPTIONS_CONNECTION_WINDOW_SIZE]));
}

if ((flags & (1 << IDX_OPTIONS_STREAM_RESET_BURST)) &&
(flags & (1 << IDX_OPTIONS_STREAM_RESET_RATE))) {
nghttp2_option_set_stream_reset_rate_limit(
Expand DownExpand Up@@ -612,15 +617,13 @@ Http2Session::Http2Session(Http2State* http2_state,
&alloc_info), 0);
session_.reset(session);

// Increase the default local connection window to improve throughput
// on high-latency connections. The default 64KB window limits throughput
// to window_size / RTT. With a 32MB connection window, throughput is
// significantly improved. See https://github.com/nodejs/node/issues/38426
// The default connection window is larger than the 64KB required by the
// spec to improve throughput on high-latency connections, where throughput
// is limited to window_size / RTT. User settings have to be applied here
// initially rather than updating later as windows cannot be shrunk after
// they've been advertised.
CHECK_EQ(nghttp2_session_set_local_window_size(
session,
NGHTTP2_FLAG_NONE,
0,
DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE),
session, NGHTTP2_FLAG_NONE, 0, opts.connection_window_size()),
0);

outgoing_storage_.reserve(1024);
Expand Down
15 changes: 11 additions & 4 deletions src/node_http2.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,10 @@ constexpr size_t kDefaultMaxSettings = 10;
// Default maximum total memory cap for Http2Session.
constexpr uint64_t kDefaultMaxSessionMemory = 10000000;

// Default connection-level flow control window (32MB) to improve throughput
// on high-latency connections. See https://github.com/nodejs/node/issues/38426
constexpr int32_t kDefaultConnectionWindowSize = 33554432;

// These are the standard HTTP/2 defaults as specified by the RFC
constexpr uint32_t DEFAULT_SETTINGS_HEADER_TABLE_SIZE = 4096;
constexpr uint32_t DEFAULT_SETTINGS_ENABLE_PUSH = 1;
Expand All@@ -51,10 +55,6 @@ constexpr uint32_t MAX_MAX_FRAME_SIZE = 16777215;
constexpr uint32_t MIN_MAX_FRAME_SIZE = DEFAULT_SETTINGS_MAX_FRAME_SIZE;
constexpr uint32_t MAX_INITIAL_WINDOW_SIZE = 2147483647;

// Default local connection window size (32MB) to improve throughput
// on high-latency connections. See https://github.com/nodejs/node/issues/38426
constexpr uint32_t DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE = 33554432;

// Stream is not going to have any DATA frames
constexpr int STREAM_OPTION_EMPTY_PAYLOAD = 0x1;

Expand DownExpand Up@@ -245,9 +245,16 @@ class Http2Options {
return max_session_memory_;
}

void set_connection_window_size(int32_t size) {
connection_window_size_ = size;
}

int32_t connection_window_size() const { return connection_window_size_; }

private:
Nghttp2OptionPointer options_;
uint64_t max_session_memory_ = kDefaultMaxSessionMemory;
int32_t connection_window_size_ = kDefaultConnectionWindowSize;
uint32_t max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS;
PaddingStrategy padding_strategy_ = PADDING_STRATEGY_NONE;
size_t max_outstanding_pings_ = kDefaultMaxPings;
Expand Down
1 change: 1 addition & 0 deletions src/node_http2_state.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,7 @@ namespace http2 {
IDX_OPTIONS_STREAM_RESET_RATE,
IDX_OPTIONS_STREAM_RESET_BURST,
IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION,
IDX_OPTIONS_CONNECTION_WINDOW_SIZE,
IDX_OPTIONS_FLAGS
};

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion doc/api/http2.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -669,10 +669,16 @@ added:

* `windowSize` {number}

Sets the local endpoint's window size.
Sets the local endpoint's connection-level window size.
The `windowSize` is the total window size to set, not
the delta.

Increases take effect immediately, but decreases only apply as the window
already advertised to the peer is consumed, since a window that has been
advertised cannot be retracted. To use a window smaller than the default from
the start of the connection, set the `connectionWindowSize` option when
creating the server or client session instead.

```mjs
import { createServer } from 'node:http2';

Expand DownExpand Up@@ -2844,6 +2850,9 @@ Throws `ERR_INVALID_ARG_TYPE` for invalid `settings` argument.
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v25.7.0
- v24.15.0
Expand DownExpand Up@@ -2961,6 +2970,13 @@ changes:
error that should tell the peer to not open any more streams, continuing
to open streams is therefore regarded as a sign of a misbehaving peer.
**Default:** `100`.
* `connectionWindowSize` {number} Sets the initial flow control window for
each session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `streamResetBurst` {number} and `streamResetRate` {number} Sets the rate
Expand DownExpand Up@@ -3072,6 +3088,9 @@ server.listen(8000);
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v25.7.0
- v24.15.0
Expand DownExpand Up@@ -3180,6 +3199,14 @@ changes:
error that should tell the peer to not open any more streams, continuing
to open streams is therefore regarded as a sign of a misbehaving peer.
**Default:** `100`.
* `connectionWindowSize` {number} Sets the initial flow control window for
each session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
**Default:** `33554432`.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `streamResetBurst` {number} and `streamResetRate` {number} Sets the rate
Expand DownExpand Up@@ -3276,6 +3303,9 @@ server.listen(8443);
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v15.10.0
- v14.16.0
Expand DownExpand Up@@ -3363,6 +3393,14 @@ changes:
* `protocol` {string} The protocol to connect with, if not set in the
`authority`. Value may be either `'http:'` or `'https:'`. **Default:**
`'https:'`
* `connectionWindowSize` {number} Sets the initial flow control window for
this session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
**Default:** `33554432`.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `remoteCustomSettings` {Array} The array of integer values determines the
Expand DownExpand Up@@ -3795,6 +3833,9 @@ properties.
* `initialWindowSize` {number} Specifies the _sender's_ initial window size in
bytes for stream-level flow control. The minimum allowed value is 0. The
maximum allowed value is 2<sup>32</sup>-1. **Default:** `4194304`.
This is a per-stream limit; the window for the connection as a whole is
configured separately with the `connectionWindowSize` option of
[`http2.createServer()`][] or [`http2.connect()`][].
* `maxFrameSize` {number} Specifies the size in bytes of the largest frame
payload. The minimum allowed value is 16,384. The maximum allowed value is
2<sup>24</sup>-1. **Default:** `16384`.
Expand DownExpand Up@@ -5252,6 +5293,7 @@ you need to implement any fall-back behavior yourself.
[`http.createServer()`]: http.md#httpcreateserveroptions-requestlistener
[`http2.SecureServer`]: #class-http2secureserver
[`http2.Server`]: #class-http2server
[`http2.connect()`]: #http2connectauthority-options-listener
[`http2.createSecureServer()`]: #http2createsecureserveroptions-onrequesthandler
[`http2.createServer()`]: #http2createserveroptions-onrequesthandler
[`http2stream.pushStream()`]: #http2streampushstreamheaders-options-callback
Expand Down
59 changes: 28 additions & 31 deletions lib/internal/http2/core.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -241,6 +241,8 @@ function debugSessionObj(session, message, ...args) {
const kMaxFrameSize = (2 ** 24) - 1;
const kMaxInt = (2 ** 32) - 1;
const kMaxInitialWindowSize = (2 ** 31) - 1; // HTTP/2 spec maximum
// A connection window of 0 stalls the connection
const kMinConnectionWindowSize = 1;
const kMaxStreams = (2 ** 32) - 1;
const kMaxALTSVC = (2 ** 14) - 2;

Expand DownExpand Up@@ -3590,19 +3592,38 @@ function connectionListener(socket) {
this.emit('session', session);
}

function initializeOptions(options) {
assertIsObject(options, 'options');
options = { ...options };
assertIsObject(options.settings, 'options.settings');
options.settings = { ...options.settings };

// Validates the session options that apply to both client and server sessions
function validateSessionOptions(options) {
assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
if (options.remoteCustomSettings) {
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
}

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}

if (options.connectionWindowSize !== undefined) {
validateInt32(options.connectionWindowSize, 'options.connectionWindowSize',
kMinConnectionWindowSize, kMaxInitialWindowSize);
}
}

function initializeOptions(options) {
assertIsObject(options, 'options');
options = { ...options };
assertIsObject(options.settings, 'options.settings');
options.settings = { ...options.settings };

validateSessionOptions(options);

if (options.maxSessionInvalidFrames !== undefined)
validateUint32(options.maxSessionInvalidFrames, 'options.maxSessionInvalidFrames');

Expand All@@ -3619,16 +3640,6 @@ function initializeOptions(options) {
// TODO(danbev): is this a good default value?
options.unknownProtocolTimeout = 10000;

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}


// Initialize http1Options bag for HTTP/1 fallback when allowHTTP1 is true.
// This bag is passed to storeHTTPOptions() to configure HTTP/1 server
// behavior (timeouts, IncomingMessage/ServerResponse classes, etc.).
Expand DownExpand Up@@ -3840,21 +3851,7 @@ function connect(authority, options, listener) {
maxOriginSetSize = 128;
}

assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
if (options.remoteCustomSettings) {
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
}

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}
validateSessionOptions(options);

if (typeof authority === 'string')
authority = new URL(authority);
Expand Down
8 changes: 7 additions & 1 deletion lib/internal/http2/util.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -233,7 +233,8 @@ const IDX_OPTIONS_MAX_SETTINGS = 9;
const IDX_OPTIONS_STREAM_RESET_RATE = 10;
const IDX_OPTIONS_STREAM_RESET_BURST = 11;
const IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION = 12;
const IDX_OPTIONS_FLAGS = 13;
const IDX_OPTIONS_CONNECTION_WINDOW_SIZE = 13;
const IDX_OPTIONS_FLAGS = 14;

function updateOptionsBuffer(options) {
let flags = 0;
Expand DownExpand Up@@ -303,6 +304,11 @@ function updateOptionsBuffer(options) {
optionsBuffer[IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION] =
options.strictFieldWhitespaceValidation === true ? 0 : 1;
}
if (typeof options.connectionWindowSize === 'number') {
flags |= (1 << IDX_OPTIONS_CONNECTION_WINDOW_SIZE);
optionsBuffer[IDX_OPTIONS_CONNECTION_WINDOW_SIZE] =
options.connectionWindowSize;
}

optionsBuffer[IDX_OPTIONS_FLAGS] = flags;
}
Expand Down
19 changes: 11 additions & 8 deletions src/node_http2.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -217,6 +217,11 @@ Http2Options::Http2Options(Http2State* http2_state, SessionType type) {
static_cast<size_t>(buffer[IDX_OPTIONS_MAX_SETTINGS]));
}

if (flags & (1 << IDX_OPTIONS_CONNECTION_WINDOW_SIZE)) {
set_connection_window_size(
static_cast<int32_t>(buffer[IDX_OPTIONS_CONNECTION_WINDOW_SIZE]));
}

if ((flags & (1 << IDX_OPTIONS_STREAM_RESET_BURST)) &&
(flags & (1 << IDX_OPTIONS_STREAM_RESET_RATE))) {
nghttp2_option_set_stream_reset_rate_limit(
Expand DownExpand Up@@ -612,15 +617,13 @@ Http2Session::Http2Session(Http2State* http2_state,
&alloc_info), 0);
session_.reset(session);

// Increase the default local connection window to improve throughput
// on high-latency connections. The default 64KB window limits throughput
// to window_size / RTT. With a 32MB connection window, throughput is
// significantly improved. See https://github.com/nodejs/node/issues/38426
// The default connection window is larger than the 64KB required by the
// spec to improve throughput on high-latency connections, where throughput
// is limited to window_size / RTT. User settings have to be applied here
// initially rather than updating later as windows cannot be shrunk after
// they've been advertised.
CHECK_EQ(nghttp2_session_set_local_window_size(
session,
NGHTTP2_FLAG_NONE,
0,
DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE),
session, NGHTTP2_FLAG_NONE, 0, opts.connection_window_size()),
0);

outgoing_storage_.reserve(1024);
Expand Down
15 changes: 11 additions & 4 deletions src/node_http2.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,10 @@ constexpr size_t kDefaultMaxSettings = 10;
// Default maximum total memory cap for Http2Session.
constexpr uint64_t kDefaultMaxSessionMemory = 10000000;

// Default connection-level flow control window (32MB) to improve throughput
// on high-latency connections. See https://github.com/nodejs/node/issues/38426
constexpr int32_t kDefaultConnectionWindowSize = 33554432;

// These are the standard HTTP/2 defaults as specified by the RFC
constexpr uint32_t DEFAULT_SETTINGS_HEADER_TABLE_SIZE = 4096;
constexpr uint32_t DEFAULT_SETTINGS_ENABLE_PUSH = 1;
Expand All@@ -51,10 +55,6 @@ constexpr uint32_t MAX_MAX_FRAME_SIZE = 16777215;
constexpr uint32_t MIN_MAX_FRAME_SIZE = DEFAULT_SETTINGS_MAX_FRAME_SIZE;
constexpr uint32_t MAX_INITIAL_WINDOW_SIZE = 2147483647;

// Default local connection window size (32MB) to improve throughput
// on high-latency connections. See https://github.com/nodejs/node/issues/38426
constexpr uint32_t DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE = 33554432;

// Stream is not going to have any DATA frames
constexpr int STREAM_OPTION_EMPTY_PAYLOAD = 0x1;

Expand DownExpand Up@@ -245,9 +245,16 @@ class Http2Options {
return max_session_memory_;
}

void set_connection_window_size(int32_t size) {
connection_window_size_ = size;
}

int32_t connection_window_size() const { return connection_window_size_; }

private:
Nghttp2OptionPointer options_;
uint64_t max_session_memory_ = kDefaultMaxSessionMemory;
int32_t connection_window_size_ = kDefaultConnectionWindowSize;
uint32_t max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS;
PaddingStrategy padding_strategy_ = PADDING_STRATEGY_NONE;
size_t max_outstanding_pings_ = kDefaultMaxPings;
Expand Down
1 change: 1 addition & 0 deletions src/node_http2_state.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,7 @@ namespace http2 {
IDX_OPTIONS_STREAM_RESET_RATE,
IDX_OPTIONS_STREAM_RESET_BURST,
IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION,
IDX_OPTIONS_CONNECTION_WINDOW_SIZE,
IDX_OPTIONS_FLAGS
};

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion doc/api/http2.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -669,10 +669,16 @@ added:

* `windowSize` {number}

Sets the local endpoint's window size.
Sets the local endpoint's connection-level window size.
The `windowSize` is the total window size to set, not
the delta.

Increases take effect immediately, but decreases only apply as the window
already advertised to the peer is consumed, since a window that has been
advertised cannot be retracted. To use a window smaller than the default from
the start of the connection, set the `connectionWindowSize` option when
creating the server or client session instead.

```mjs
import { createServer } from 'node:http2';

Expand DownExpand Up@@ -2844,6 +2850,9 @@ Throws `ERR_INVALID_ARG_TYPE` for invalid `settings` argument.
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v25.7.0
- v24.15.0
Expand DownExpand Up@@ -2961,6 +2970,13 @@ changes:
error that should tell the peer to not open any more streams, continuing
to open streams is therefore regarded as a sign of a misbehaving peer.
**Default:** `100`.
* `connectionWindowSize` {number} Sets the initial flow control window for
each session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `streamResetBurst` {number} and `streamResetRate` {number} Sets the rate
Expand DownExpand Up@@ -3072,6 +3088,9 @@ server.listen(8000);
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v25.7.0
- v24.15.0
Expand DownExpand Up@@ -3180,6 +3199,14 @@ changes:
error that should tell the peer to not open any more streams, continuing
to open streams is therefore regarded as a sign of a misbehaving peer.
**Default:** `100`.
* `connectionWindowSize` {number} Sets the initial flow control window for
each session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
**Default:** `33554432`.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `streamResetBurst` {number} and `streamResetRate` {number} Sets the rate
Expand DownExpand Up@@ -3276,6 +3303,9 @@ server.listen(8443);
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v15.10.0
- v14.16.0
Expand DownExpand Up@@ -3363,6 +3393,14 @@ changes:
* `protocol` {string} The protocol to connect with, if not set in the
`authority`. Value may be either `'http:'` or `'https:'`. **Default:**
`'https:'`
* `connectionWindowSize` {number} Sets the initial flow control window for
this session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
**Default:** `33554432`.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `remoteCustomSettings` {Array} The array of integer values determines the
Expand DownExpand Up@@ -3795,6 +3833,9 @@ properties.
* `initialWindowSize` {number} Specifies the _sender's_ initial window size in
bytes for stream-level flow control. The minimum allowed value is 0. The
maximum allowed value is 2<sup>32</sup>-1. **Default:** `4194304`.
This is a per-stream limit; the window for the connection as a whole is
configured separately with the `connectionWindowSize` option of
[`http2.createServer()`][] or [`http2.connect()`][].
* `maxFrameSize` {number} Specifies the size in bytes of the largest frame
payload. The minimum allowed value is 16,384. The maximum allowed value is
2<sup>24</sup>-1. **Default:** `16384`.
Expand DownExpand Up@@ -5252,6 +5293,7 @@ you need to implement any fall-back behavior yourself.
[`http.createServer()`]: http.md#httpcreateserveroptions-requestlistener
[`http2.SecureServer`]: #class-http2secureserver
[`http2.Server`]: #class-http2server
[`http2.connect()`]: #http2connectauthority-options-listener
[`http2.createSecureServer()`]: #http2createsecureserveroptions-onrequesthandler
[`http2.createServer()`]: #http2createserveroptions-onrequesthandler
[`http2stream.pushStream()`]: #http2streampushstreamheaders-options-callback
Expand Down
59 changes: 28 additions & 31 deletions lib/internal/http2/core.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -241,6 +241,8 @@ function debugSessionObj(session, message, ...args) {
const kMaxFrameSize = (2 ** 24) - 1;
const kMaxInt = (2 ** 32) - 1;
const kMaxInitialWindowSize = (2 ** 31) - 1; // HTTP/2 spec maximum
// A connection window of 0 stalls the connection
const kMinConnectionWindowSize = 1;
const kMaxStreams = (2 ** 32) - 1;
const kMaxALTSVC = (2 ** 14) - 2;

Expand DownExpand Up@@ -3590,19 +3592,38 @@ function connectionListener(socket) {
this.emit('session', session);
}

function initializeOptions(options) {
assertIsObject(options, 'options');
options = { ...options };
assertIsObject(options.settings, 'options.settings');
options.settings = { ...options.settings };

// Validates the session options that apply to both client and server sessions
function validateSessionOptions(options) {
assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
if (options.remoteCustomSettings) {
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
}

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}

if (options.connectionWindowSize !== undefined) {
validateInt32(options.connectionWindowSize, 'options.connectionWindowSize',
kMinConnectionWindowSize, kMaxInitialWindowSize);
}
}

function initializeOptions(options) {
assertIsObject(options, 'options');
options = { ...options };
assertIsObject(options.settings, 'options.settings');
options.settings = { ...options.settings };

validateSessionOptions(options);

if (options.maxSessionInvalidFrames !== undefined)
validateUint32(options.maxSessionInvalidFrames, 'options.maxSessionInvalidFrames');

Expand All@@ -3619,16 +3640,6 @@ function initializeOptions(options) {
// TODO(danbev): is this a good default value?
options.unknownProtocolTimeout = 10000;

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}


// Initialize http1Options bag for HTTP/1 fallback when allowHTTP1 is true.
// This bag is passed to storeHTTPOptions() to configure HTTP/1 server
// behavior (timeouts, IncomingMessage/ServerResponse classes, etc.).
Expand DownExpand Up@@ -3840,21 +3851,7 @@ function connect(authority, options, listener) {
maxOriginSetSize = 128;
}

assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
if (options.remoteCustomSettings) {
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
}

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}
validateSessionOptions(options);

if (typeof authority === 'string')
authority = new URL(authority);
Expand Down
8 changes: 7 additions & 1 deletion lib/internal/http2/util.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -233,7 +233,8 @@ const IDX_OPTIONS_MAX_SETTINGS = 9;
const IDX_OPTIONS_STREAM_RESET_RATE = 10;
const IDX_OPTIONS_STREAM_RESET_BURST = 11;
const IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION = 12;
const IDX_OPTIONS_FLAGS = 13;
const IDX_OPTIONS_CONNECTION_WINDOW_SIZE = 13;
const IDX_OPTIONS_FLAGS = 14;

function updateOptionsBuffer(options) {
let flags = 0;
Expand DownExpand Up@@ -303,6 +304,11 @@ function updateOptionsBuffer(options) {
optionsBuffer[IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION] =
options.strictFieldWhitespaceValidation === true ? 0 : 1;
}
if (typeof options.connectionWindowSize === 'number') {
flags |= (1 << IDX_OPTIONS_CONNECTION_WINDOW_SIZE);
optionsBuffer[IDX_OPTIONS_CONNECTION_WINDOW_SIZE] =
options.connectionWindowSize;
}

optionsBuffer[IDX_OPTIONS_FLAGS] = flags;
}
Expand Down
19 changes: 11 additions & 8 deletions src/node_http2.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -217,6 +217,11 @@ Http2Options::Http2Options(Http2State* http2_state, SessionType type) {
static_cast<size_t>(buffer[IDX_OPTIONS_MAX_SETTINGS]));
}

if (flags & (1 << IDX_OPTIONS_CONNECTION_WINDOW_SIZE)) {
set_connection_window_size(
static_cast<int32_t>(buffer[IDX_OPTIONS_CONNECTION_WINDOW_SIZE]));
}

if ((flags & (1 << IDX_OPTIONS_STREAM_RESET_BURST)) &&
(flags & (1 << IDX_OPTIONS_STREAM_RESET_RATE))) {
nghttp2_option_set_stream_reset_rate_limit(
Expand DownExpand Up@@ -612,15 +617,13 @@ Http2Session::Http2Session(Http2State* http2_state,
&alloc_info), 0);
session_.reset(session);

// Increase the default local connection window to improve throughput
// on high-latency connections. The default 64KB window limits throughput
// to window_size / RTT. With a 32MB connection window, throughput is
// significantly improved. See https://github.com/nodejs/node/issues/38426
// The default connection window is larger than the 64KB required by the
// spec to improve throughput on high-latency connections, where throughput
// is limited to window_size / RTT. User settings have to be applied here
// initially rather than updating later as windows cannot be shrunk after
// they've been advertised.
CHECK_EQ(nghttp2_session_set_local_window_size(
session,
NGHTTP2_FLAG_NONE,
0,
DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE),
session, NGHTTP2_FLAG_NONE, 0, opts.connection_window_size()),
0);

outgoing_storage_.reserve(1024);
Expand Down
15 changes: 11 additions & 4 deletions src/node_http2.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,10 @@ constexpr size_t kDefaultMaxSettings = 10;
// Default maximum total memory cap for Http2Session.
constexpr uint64_t kDefaultMaxSessionMemory = 10000000;

// Default connection-level flow control window (32MB) to improve throughput
// on high-latency connections. See https://github.com/nodejs/node/issues/38426
constexpr int32_t kDefaultConnectionWindowSize = 33554432;

// These are the standard HTTP/2 defaults as specified by the RFC
constexpr uint32_t DEFAULT_SETTINGS_HEADER_TABLE_SIZE = 4096;
constexpr uint32_t DEFAULT_SETTINGS_ENABLE_PUSH = 1;
Expand All@@ -51,10 +55,6 @@ constexpr uint32_t MAX_MAX_FRAME_SIZE = 16777215;
constexpr uint32_t MIN_MAX_FRAME_SIZE = DEFAULT_SETTINGS_MAX_FRAME_SIZE;
constexpr uint32_t MAX_INITIAL_WINDOW_SIZE = 2147483647;

// Default local connection window size (32MB) to improve throughput
// on high-latency connections. See https://github.com/nodejs/node/issues/38426
constexpr uint32_t DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE = 33554432;

// Stream is not going to have any DATA frames
constexpr int STREAM_OPTION_EMPTY_PAYLOAD = 0x1;

Expand DownExpand Up@@ -245,9 +245,16 @@ class Http2Options {
return max_session_memory_;
}

void set_connection_window_size(int32_t size) {
connection_window_size_ = size;
}

int32_t connection_window_size() const { return connection_window_size_; }

private:
Nghttp2OptionPointer options_;
uint64_t max_session_memory_ = kDefaultMaxSessionMemory;
int32_t connection_window_size_ = kDefaultConnectionWindowSize;
uint32_t max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS;
PaddingStrategy padding_strategy_ = PADDING_STRATEGY_NONE;
size_t max_outstanding_pings_ = kDefaultMaxPings;
Expand Down
1 change: 1 addition & 0 deletions src/node_http2_state.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,7 @@ namespace http2 {
IDX_OPTIONS_STREAM_RESET_RATE,
IDX_OPTIONS_STREAM_RESET_BURST,
IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION,
IDX_OPTIONS_CONNECTION_WINDOW_SIZE,
IDX_OPTIONS_FLAGS
};

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion doc/api/http2.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -669,10 +669,16 @@ added:

* `windowSize` {number}

Sets the local endpoint's window size.
Sets the local endpoint's connection-level window size.
The `windowSize` is the total window size to set, not
the delta.

Increases take effect immediately, but decreases only apply as the window
already advertised to the peer is consumed, since a window that has been
advertised cannot be retracted. To use a window smaller than the default from
the start of the connection, set the `connectionWindowSize` option when
creating the server or client session instead.

```mjs
import { createServer } from 'node:http2';

Expand DownExpand Up@@ -2844,6 +2850,9 @@ Throws `ERR_INVALID_ARG_TYPE` for invalid `settings` argument.
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v25.7.0
- v24.15.0
Expand DownExpand Up@@ -2961,6 +2970,13 @@ changes:
error that should tell the peer to not open any more streams, continuing
to open streams is therefore regarded as a sign of a misbehaving peer.
**Default:** `100`.
* `connectionWindowSize` {number} Sets the initial flow control window for
each session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `streamResetBurst` {number} and `streamResetRate` {number} Sets the rate
Expand DownExpand Up@@ -3072,6 +3088,9 @@ server.listen(8000);
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v25.7.0
- v24.15.0
Expand DownExpand Up@@ -3180,6 +3199,14 @@ changes:
error that should tell the peer to not open any more streams, continuing
to open streams is therefore regarded as a sign of a misbehaving peer.
**Default:** `100`.
* `connectionWindowSize` {number} Sets the initial flow control window for
each session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
**Default:** `33554432`.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `streamResetBurst` {number} and `streamResetRate` {number} Sets the rate
Expand DownExpand Up@@ -3276,6 +3303,9 @@ server.listen(8443);
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v15.10.0
- v14.16.0
Expand DownExpand Up@@ -3363,6 +3393,14 @@ changes:
* `protocol` {string} The protocol to connect with, if not set in the
`authority`. Value may be either `'http:'` or `'https:'`. **Default:**
`'https:'`
* `connectionWindowSize` {number} Sets the initial flow control window for
this session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
**Default:** `33554432`.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `remoteCustomSettings` {Array} The array of integer values determines the
Expand DownExpand Up@@ -3795,6 +3833,9 @@ properties.
* `initialWindowSize` {number} Specifies the _sender's_ initial window size in
bytes for stream-level flow control. The minimum allowed value is 0. The
maximum allowed value is 2<sup>32</sup>-1. **Default:** `4194304`.
This is a per-stream limit; the window for the connection as a whole is
configured separately with the `connectionWindowSize` option of
[`http2.createServer()`][] or [`http2.connect()`][].
* `maxFrameSize` {number} Specifies the size in bytes of the largest frame
payload. The minimum allowed value is 16,384. The maximum allowed value is
2<sup>24</sup>-1. **Default:** `16384`.
Expand DownExpand Up@@ -5252,6 +5293,7 @@ you need to implement any fall-back behavior yourself.
[`http.createServer()`]: http.md#httpcreateserveroptions-requestlistener
[`http2.SecureServer`]: #class-http2secureserver
[`http2.Server`]: #class-http2server
[`http2.connect()`]: #http2connectauthority-options-listener
[`http2.createSecureServer()`]: #http2createsecureserveroptions-onrequesthandler
[`http2.createServer()`]: #http2createserveroptions-onrequesthandler
[`http2stream.pushStream()`]: #http2streampushstreamheaders-options-callback
Expand Down
59 changes: 28 additions & 31 deletions lib/internal/http2/core.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -241,6 +241,8 @@ function debugSessionObj(session, message, ...args) {
const kMaxFrameSize = (2 ** 24) - 1;
const kMaxInt = (2 ** 32) - 1;
const kMaxInitialWindowSize = (2 ** 31) - 1; // HTTP/2 spec maximum
// A connection window of 0 stalls the connection
const kMinConnectionWindowSize = 1;
const kMaxStreams = (2 ** 32) - 1;
const kMaxALTSVC = (2 ** 14) - 2;

Expand DownExpand Up@@ -3590,19 +3592,38 @@ function connectionListener(socket) {
this.emit('session', session);
}

function initializeOptions(options) {
assertIsObject(options, 'options');
options = { ...options };
assertIsObject(options.settings, 'options.settings');
options.settings = { ...options.settings };

// Validates the session options that apply to both client and server sessions
function validateSessionOptions(options) {
assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
if (options.remoteCustomSettings) {
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
}

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}

if (options.connectionWindowSize !== undefined) {
validateInt32(options.connectionWindowSize, 'options.connectionWindowSize',
kMinConnectionWindowSize, kMaxInitialWindowSize);
}
}

function initializeOptions(options) {
assertIsObject(options, 'options');
options = { ...options };
assertIsObject(options.settings, 'options.settings');
options.settings = { ...options.settings };

validateSessionOptions(options);

if (options.maxSessionInvalidFrames !== undefined)
validateUint32(options.maxSessionInvalidFrames, 'options.maxSessionInvalidFrames');

Expand All@@ -3619,16 +3640,6 @@ function initializeOptions(options) {
// TODO(danbev): is this a good default value?
options.unknownProtocolTimeout = 10000;

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}


// Initialize http1Options bag for HTTP/1 fallback when allowHTTP1 is true.
// This bag is passed to storeHTTPOptions() to configure HTTP/1 server
// behavior (timeouts, IncomingMessage/ServerResponse classes, etc.).
Expand DownExpand Up@@ -3840,21 +3851,7 @@ function connect(authority, options, listener) {
maxOriginSetSize = 128;
}

assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
if (options.remoteCustomSettings) {
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
}

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}
validateSessionOptions(options);

if (typeof authority === 'string')
authority = new URL(authority);
Expand Down
8 changes: 7 additions & 1 deletion lib/internal/http2/util.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -233,7 +233,8 @@ const IDX_OPTIONS_MAX_SETTINGS = 9;
const IDX_OPTIONS_STREAM_RESET_RATE = 10;
const IDX_OPTIONS_STREAM_RESET_BURST = 11;
const IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION = 12;
const IDX_OPTIONS_FLAGS = 13;
const IDX_OPTIONS_CONNECTION_WINDOW_SIZE = 13;
const IDX_OPTIONS_FLAGS = 14;

function updateOptionsBuffer(options) {
let flags = 0;
Expand DownExpand Up@@ -303,6 +304,11 @@ function updateOptionsBuffer(options) {
optionsBuffer[IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION] =
options.strictFieldWhitespaceValidation === true ? 0 : 1;
}
if (typeof options.connectionWindowSize === 'number') {
flags |= (1 << IDX_OPTIONS_CONNECTION_WINDOW_SIZE);
optionsBuffer[IDX_OPTIONS_CONNECTION_WINDOW_SIZE] =
options.connectionWindowSize;
}

optionsBuffer[IDX_OPTIONS_FLAGS] = flags;
}
Expand Down
19 changes: 11 additions & 8 deletions src/node_http2.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -217,6 +217,11 @@ Http2Options::Http2Options(Http2State* http2_state, SessionType type) {
static_cast<size_t>(buffer[IDX_OPTIONS_MAX_SETTINGS]));
}

if (flags & (1 << IDX_OPTIONS_CONNECTION_WINDOW_SIZE)) {
set_connection_window_size(
static_cast<int32_t>(buffer[IDX_OPTIONS_CONNECTION_WINDOW_SIZE]));
}

if ((flags & (1 << IDX_OPTIONS_STREAM_RESET_BURST)) &&
(flags & (1 << IDX_OPTIONS_STREAM_RESET_RATE))) {
nghttp2_option_set_stream_reset_rate_limit(
Expand DownExpand Up@@ -612,15 +617,13 @@ Http2Session::Http2Session(Http2State* http2_state,
&alloc_info), 0);
session_.reset(session);

// Increase the default local connection window to improve throughput
// on high-latency connections. The default 64KB window limits throughput
// to window_size / RTT. With a 32MB connection window, throughput is
// significantly improved. See https://github.com/nodejs/node/issues/38426
// The default connection window is larger than the 64KB required by the
// spec to improve throughput on high-latency connections, where throughput
// is limited to window_size / RTT. User settings have to be applied here
// initially rather than updating later as windows cannot be shrunk after
// they've been advertised.
CHECK_EQ(nghttp2_session_set_local_window_size(
session,
NGHTTP2_FLAG_NONE,
0,
DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE),
session, NGHTTP2_FLAG_NONE, 0, opts.connection_window_size()),
0);

outgoing_storage_.reserve(1024);
Expand Down
15 changes: 11 additions & 4 deletions src/node_http2.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,10 @@ constexpr size_t kDefaultMaxSettings = 10;
// Default maximum total memory cap for Http2Session.
constexpr uint64_t kDefaultMaxSessionMemory = 10000000;

// Default connection-level flow control window (32MB) to improve throughput
// on high-latency connections. See https://github.com/nodejs/node/issues/38426
constexpr int32_t kDefaultConnectionWindowSize = 33554432;

// These are the standard HTTP/2 defaults as specified by the RFC
constexpr uint32_t DEFAULT_SETTINGS_HEADER_TABLE_SIZE = 4096;
constexpr uint32_t DEFAULT_SETTINGS_ENABLE_PUSH = 1;
Expand All@@ -51,10 +55,6 @@ constexpr uint32_t MAX_MAX_FRAME_SIZE = 16777215;
constexpr uint32_t MIN_MAX_FRAME_SIZE = DEFAULT_SETTINGS_MAX_FRAME_SIZE;
constexpr uint32_t MAX_INITIAL_WINDOW_SIZE = 2147483647;

// Default local connection window size (32MB) to improve throughput
// on high-latency connections. See https://github.com/nodejs/node/issues/38426
constexpr uint32_t DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE = 33554432;

// Stream is not going to have any DATA frames
constexpr int STREAM_OPTION_EMPTY_PAYLOAD = 0x1;

Expand DownExpand Up@@ -245,9 +245,16 @@ class Http2Options {
return max_session_memory_;
}

void set_connection_window_size(int32_t size) {
connection_window_size_ = size;
}

int32_t connection_window_size() const { return connection_window_size_; }

private:
Nghttp2OptionPointer options_;
uint64_t max_session_memory_ = kDefaultMaxSessionMemory;
int32_t connection_window_size_ = kDefaultConnectionWindowSize;
uint32_t max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS;
PaddingStrategy padding_strategy_ = PADDING_STRATEGY_NONE;
size_t max_outstanding_pings_ = kDefaultMaxPings;
Expand Down
1 change: 1 addition & 0 deletions src/node_http2_state.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,7 @@ namespace http2 {
IDX_OPTIONS_STREAM_RESET_RATE,
IDX_OPTIONS_STREAM_RESET_BURST,
IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION,
IDX_OPTIONS_CONNECTION_WINDOW_SIZE,
IDX_OPTIONS_FLAGS
};

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion doc/api/http2.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -669,10 +669,16 @@ added:

* `windowSize` {number}

Sets the local endpoint's window size.
Sets the local endpoint's connection-level window size.
The `windowSize` is the total window size to set, not
the delta.

Increases take effect immediately, but decreases only apply as the window
already advertised to the peer is consumed, since a window that has been
advertised cannot be retracted. To use a window smaller than the default from
the start of the connection, set the `connectionWindowSize` option when
creating the server or client session instead.

```mjs
import { createServer } from 'node:http2';

Expand DownExpand Up@@ -2844,6 +2850,9 @@ Throws `ERR_INVALID_ARG_TYPE` for invalid `settings` argument.
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v25.7.0
- v24.15.0
Expand DownExpand Up@@ -2961,6 +2970,13 @@ changes:
error that should tell the peer to not open any more streams, continuing
to open streams is therefore regarded as a sign of a misbehaving peer.
**Default:** `100`.
* `connectionWindowSize` {number} Sets the initial flow control window for
each session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `streamResetBurst` {number} and `streamResetRate` {number} Sets the rate
Expand DownExpand Up@@ -3072,6 +3088,9 @@ server.listen(8000);
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v25.7.0
- v24.15.0
Expand DownExpand Up@@ -3180,6 +3199,14 @@ changes:
error that should tell the peer to not open any more streams, continuing
to open streams is therefore regarded as a sign of a misbehaving peer.
**Default:** `100`.
* `connectionWindowSize` {number} Sets the initial flow control window for
each session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
**Default:** `33554432`.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `streamResetBurst` {number} and `streamResetRate` {number} Sets the rate
Expand DownExpand Up@@ -3276,6 +3303,9 @@ server.listen(8443);
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v15.10.0
- v14.16.0
Expand DownExpand Up@@ -3363,6 +3393,14 @@ changes:
* `protocol` {string} The protocol to connect with, if not set in the
`authority`. Value may be either `'http:'` or `'https:'`. **Default:**
`'https:'`
* `connectionWindowSize` {number} Sets the initial flow control window for
this session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
**Default:** `33554432`.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `remoteCustomSettings` {Array} The array of integer values determines the
Expand DownExpand Up@@ -3795,6 +3833,9 @@ properties.
* `initialWindowSize` {number} Specifies the _sender's_ initial window size in
bytes for stream-level flow control. The minimum allowed value is 0. The
maximum allowed value is 2<sup>32</sup>-1. **Default:** `4194304`.
This is a per-stream limit; the window for the connection as a whole is
configured separately with the `connectionWindowSize` option of
[`http2.createServer()`][] or [`http2.connect()`][].
* `maxFrameSize` {number} Specifies the size in bytes of the largest frame
payload. The minimum allowed value is 16,384. The maximum allowed value is
2<sup>24</sup>-1. **Default:** `16384`.
Expand DownExpand Up@@ -5252,6 +5293,7 @@ you need to implement any fall-back behavior yourself.
[`http.createServer()`]: http.md#httpcreateserveroptions-requestlistener
[`http2.SecureServer`]: #class-http2secureserver
[`http2.Server`]: #class-http2server
[`http2.connect()`]: #http2connectauthority-options-listener
[`http2.createSecureServer()`]: #http2createsecureserveroptions-onrequesthandler
[`http2.createServer()`]: #http2createserveroptions-onrequesthandler
[`http2stream.pushStream()`]: #http2streampushstreamheaders-options-callback
Expand Down
59 changes: 28 additions & 31 deletions lib/internal/http2/core.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -241,6 +241,8 @@ function debugSessionObj(session, message, ...args) {
const kMaxFrameSize = (2 ** 24) - 1;
const kMaxInt = (2 ** 32) - 1;
const kMaxInitialWindowSize = (2 ** 31) - 1; // HTTP/2 spec maximum
// A connection window of 0 stalls the connection
const kMinConnectionWindowSize = 1;
const kMaxStreams = (2 ** 32) - 1;
const kMaxALTSVC = (2 ** 14) - 2;

Expand DownExpand Up@@ -3590,19 +3592,38 @@ function connectionListener(socket) {
this.emit('session', session);
}

function initializeOptions(options) {
assertIsObject(options, 'options');
options = { ...options };
assertIsObject(options.settings, 'options.settings');
options.settings = { ...options.settings };

// Validates the session options that apply to both client and server sessions
function validateSessionOptions(options) {
assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
if (options.remoteCustomSettings) {
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
}

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}

if (options.connectionWindowSize !== undefined) {
validateInt32(options.connectionWindowSize, 'options.connectionWindowSize',
kMinConnectionWindowSize, kMaxInitialWindowSize);
}
}

function initializeOptions(options) {
assertIsObject(options, 'options');
options = { ...options };
assertIsObject(options.settings, 'options.settings');
options.settings = { ...options.settings };

validateSessionOptions(options);

if (options.maxSessionInvalidFrames !== undefined)
validateUint32(options.maxSessionInvalidFrames, 'options.maxSessionInvalidFrames');

Expand All@@ -3619,16 +3640,6 @@ function initializeOptions(options) {
// TODO(danbev): is this a good default value?
options.unknownProtocolTimeout = 10000;

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}


// Initialize http1Options bag for HTTP/1 fallback when allowHTTP1 is true.
// This bag is passed to storeHTTPOptions() to configure HTTP/1 server
// behavior (timeouts, IncomingMessage/ServerResponse classes, etc.).
Expand DownExpand Up@@ -3840,21 +3851,7 @@ function connect(authority, options, listener) {
maxOriginSetSize = 128;
}

assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
if (options.remoteCustomSettings) {
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
}

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}
validateSessionOptions(options);

if (typeof authority === 'string')
authority = new URL(authority);
Expand Down
8 changes: 7 additions & 1 deletion lib/internal/http2/util.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -233,7 +233,8 @@ const IDX_OPTIONS_MAX_SETTINGS = 9;
const IDX_OPTIONS_STREAM_RESET_RATE = 10;
const IDX_OPTIONS_STREAM_RESET_BURST = 11;
const IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION = 12;
const IDX_OPTIONS_FLAGS = 13;
const IDX_OPTIONS_CONNECTION_WINDOW_SIZE = 13;
const IDX_OPTIONS_FLAGS = 14;

function updateOptionsBuffer(options) {
let flags = 0;
Expand DownExpand Up@@ -303,6 +304,11 @@ function updateOptionsBuffer(options) {
optionsBuffer[IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION] =
options.strictFieldWhitespaceValidation === true ? 0 : 1;
}
if (typeof options.connectionWindowSize === 'number') {
flags |= (1 << IDX_OPTIONS_CONNECTION_WINDOW_SIZE);
optionsBuffer[IDX_OPTIONS_CONNECTION_WINDOW_SIZE] =
options.connectionWindowSize;
}

optionsBuffer[IDX_OPTIONS_FLAGS] = flags;
}
Expand Down
19 changes: 11 additions & 8 deletions src/node_http2.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -217,6 +217,11 @@ Http2Options::Http2Options(Http2State* http2_state, SessionType type) {
static_cast<size_t>(buffer[IDX_OPTIONS_MAX_SETTINGS]));
}

if (flags & (1 << IDX_OPTIONS_CONNECTION_WINDOW_SIZE)) {
set_connection_window_size(
static_cast<int32_t>(buffer[IDX_OPTIONS_CONNECTION_WINDOW_SIZE]));
}

if ((flags & (1 << IDX_OPTIONS_STREAM_RESET_BURST)) &&
(flags & (1 << IDX_OPTIONS_STREAM_RESET_RATE))) {
nghttp2_option_set_stream_reset_rate_limit(
Expand DownExpand Up@@ -612,15 +617,13 @@ Http2Session::Http2Session(Http2State* http2_state,
&alloc_info), 0);
session_.reset(session);

// Increase the default local connection window to improve throughput
// on high-latency connections. The default 64KB window limits throughput
// to window_size / RTT. With a 32MB connection window, throughput is
// significantly improved. See https://github.com/nodejs/node/issues/38426
// The default connection window is larger than the 64KB required by the
// spec to improve throughput on high-latency connections, where throughput
// is limited to window_size / RTT. User settings have to be applied here
// initially rather than updating later as windows cannot be shrunk after
// they've been advertised.
CHECK_EQ(nghttp2_session_set_local_window_size(
session,
NGHTTP2_FLAG_NONE,
0,
DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE),
session, NGHTTP2_FLAG_NONE, 0, opts.connection_window_size()),
0);

outgoing_storage_.reserve(1024);
Expand Down
15 changes: 11 additions & 4 deletions src/node_http2.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,10 @@ constexpr size_t kDefaultMaxSettings = 10;
// Default maximum total memory cap for Http2Session.
constexpr uint64_t kDefaultMaxSessionMemory = 10000000;

// Default connection-level flow control window (32MB) to improve throughput
// on high-latency connections. See https://github.com/nodejs/node/issues/38426
constexpr int32_t kDefaultConnectionWindowSize = 33554432;

// These are the standard HTTP/2 defaults as specified by the RFC
constexpr uint32_t DEFAULT_SETTINGS_HEADER_TABLE_SIZE = 4096;
constexpr uint32_t DEFAULT_SETTINGS_ENABLE_PUSH = 1;
Expand All@@ -51,10 +55,6 @@ constexpr uint32_t MAX_MAX_FRAME_SIZE = 16777215;
constexpr uint32_t MIN_MAX_FRAME_SIZE = DEFAULT_SETTINGS_MAX_FRAME_SIZE;
constexpr uint32_t MAX_INITIAL_WINDOW_SIZE = 2147483647;

// Default local connection window size (32MB) to improve throughput
// on high-latency connections. See https://github.com/nodejs/node/issues/38426
constexpr uint32_t DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE = 33554432;

// Stream is not going to have any DATA frames
constexpr int STREAM_OPTION_EMPTY_PAYLOAD = 0x1;

Expand DownExpand Up@@ -245,9 +245,16 @@ class Http2Options {
return max_session_memory_;
}

void set_connection_window_size(int32_t size) {
connection_window_size_ = size;
}

int32_t connection_window_size() const { return connection_window_size_; }

private:
Nghttp2OptionPointer options_;
uint64_t max_session_memory_ = kDefaultMaxSessionMemory;
int32_t connection_window_size_ = kDefaultConnectionWindowSize;
uint32_t max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS;
PaddingStrategy padding_strategy_ = PADDING_STRATEGY_NONE;
size_t max_outstanding_pings_ = kDefaultMaxPings;
Expand Down
1 change: 1 addition & 0 deletions src/node_http2_state.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,7 @@ namespace http2 {
IDX_OPTIONS_STREAM_RESET_RATE,
IDX_OPTIONS_STREAM_RESET_BURST,
IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION,
IDX_OPTIONS_CONNECTION_WINDOW_SIZE,
IDX_OPTIONS_FLAGS
};

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion doc/api/http2.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -669,10 +669,16 @@ added:

* `windowSize` {number}

Sets the local endpoint's window size.
Sets the local endpoint's connection-level window size.
The `windowSize` is the total window size to set, not
the delta.

Increases take effect immediately, but decreases only apply as the window
already advertised to the peer is consumed, since a window that has been
advertised cannot be retracted. To use a window smaller than the default from
the start of the connection, set the `connectionWindowSize` option when
creating the server or client session instead.

```mjs
import { createServer } from 'node:http2';

Expand DownExpand Up@@ -2844,6 +2850,9 @@ Throws `ERR_INVALID_ARG_TYPE` for invalid `settings` argument.
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v25.7.0
- v24.15.0
Expand DownExpand Up@@ -2961,6 +2970,13 @@ changes:
error that should tell the peer to not open any more streams, continuing
to open streams is therefore regarded as a sign of a misbehaving peer.
**Default:** `100`.
* `connectionWindowSize` {number} Sets the initial flow control window for
each session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `streamResetBurst` {number} and `streamResetRate` {number} Sets the rate
Expand DownExpand Up@@ -3072,6 +3088,9 @@ server.listen(8000);
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v25.7.0
- v24.15.0
Expand DownExpand Up@@ -3180,6 +3199,14 @@ changes:
error that should tell the peer to not open any more streams, continuing
to open streams is therefore regarded as a sign of a misbehaving peer.
**Default:** `100`.
* `connectionWindowSize` {number} Sets the initial flow control window for
each session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
**Default:** `33554432`.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `streamResetBurst` {number} and `streamResetRate` {number} Sets the rate
Expand DownExpand Up@@ -3276,6 +3303,9 @@ server.listen(8443);
<!-- YAML
added: v8.4.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65619
description: Added the `connectionWindowSize` option.
- version:
- v15.10.0
- v14.16.0
Expand DownExpand Up@@ -3363,6 +3393,14 @@ changes:
* `protocol` {string} The protocol to connect with, if not set in the
`authority`. Value may be either `'http:'` or `'https:'`. **Default:**
`'https:'`
* `connectionWindowSize` {number} Sets the initial flow control window for
this session, in bytes. This is the total amount of data the remote peer
may send across all streams before it has to wait for a `WINDOW_UPDATE`.
The equivalent per-stream limit is `settings.initialWindowSize`. The
minimum allowed value is `1` and the maximum is 2<sup>31</sup>-1. Values
below 65535 will not take effect until the initial protocol-default
window of 65535 has been used.
**Default:** `33554432`.
* `settings` {HTTP/2 Settings Object} The initial settings to send to the
remote peer upon connection.
* `remoteCustomSettings` {Array} The array of integer values determines the
Expand DownExpand Up@@ -3795,6 +3833,9 @@ properties.
* `initialWindowSize` {number} Specifies the _sender's_ initial window size in
bytes for stream-level flow control. The minimum allowed value is 0. The
maximum allowed value is 2<sup>32</sup>-1. **Default:** `4194304`.
This is a per-stream limit; the window for the connection as a whole is
configured separately with the `connectionWindowSize` option of
[`http2.createServer()`][] or [`http2.connect()`][].
* `maxFrameSize` {number} Specifies the size in bytes of the largest frame
payload. The minimum allowed value is 16,384. The maximum allowed value is
2<sup>24</sup>-1. **Default:** `16384`.
Expand DownExpand Up@@ -5252,6 +5293,7 @@ you need to implement any fall-back behavior yourself.
[`http.createServer()`]: http.md#httpcreateserveroptions-requestlistener
[`http2.SecureServer`]: #class-http2secureserver
[`http2.Server`]: #class-http2server
[`http2.connect()`]: #http2connectauthority-options-listener
[`http2.createSecureServer()`]: #http2createsecureserveroptions-onrequesthandler
[`http2.createServer()`]: #http2createserveroptions-onrequesthandler
[`http2stream.pushStream()`]: #http2streampushstreamheaders-options-callback
Expand Down
59 changes: 28 additions & 31 deletions lib/internal/http2/core.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -241,6 +241,8 @@ function debugSessionObj(session, message, ...args) {
const kMaxFrameSize = (2 ** 24) - 1;
const kMaxInt = (2 ** 32) - 1;
const kMaxInitialWindowSize = (2 ** 31) - 1; // HTTP/2 spec maximum
// A connection window of 0 stalls the connection
const kMinConnectionWindowSize = 1;
const kMaxStreams = (2 ** 32) - 1;
const kMaxALTSVC = (2 ** 14) - 2;

Expand DownExpand Up@@ -3590,19 +3592,38 @@ function connectionListener(socket) {
this.emit('session', session);
}

function initializeOptions(options) {
assertIsObject(options, 'options');
options = { ...options };
assertIsObject(options.settings, 'options.settings');
options.settings = { ...options.settings };

// Validates the session options that apply to both client and server sessions
function validateSessionOptions(options) {
assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
if (options.remoteCustomSettings) {
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
}

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}

if (options.connectionWindowSize !== undefined) {
validateInt32(options.connectionWindowSize, 'options.connectionWindowSize',
kMinConnectionWindowSize, kMaxInitialWindowSize);
}
}

function initializeOptions(options) {
assertIsObject(options, 'options');
options = { ...options };
assertIsObject(options.settings, 'options.settings');
options.settings = { ...options.settings };

validateSessionOptions(options);

if (options.maxSessionInvalidFrames !== undefined)
validateUint32(options.maxSessionInvalidFrames, 'options.maxSessionInvalidFrames');

Expand All@@ -3619,16 +3640,6 @@ function initializeOptions(options) {
// TODO(danbev): is this a good default value?
options.unknownProtocolTimeout = 10000;

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}


// Initialize http1Options bag for HTTP/1 fallback when allowHTTP1 is true.
// This bag is passed to storeHTTPOptions() to configure HTTP/1 server
// behavior (timeouts, IncomingMessage/ServerResponse classes, etc.).
Expand DownExpand Up@@ -3840,21 +3851,7 @@ function connect(authority, options, listener) {
maxOriginSetSize = 128;
}

assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
if (options.remoteCustomSettings) {
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
}

if (options.strictSingleValueFields !== undefined) {
validateBoolean(
options.strictSingleValueFields,
'options.strictSingleValueFields',
);
} else {
options.strictSingleValueFields = true;
}
validateSessionOptions(options);

if (typeof authority === 'string')
authority = new URL(authority);
Expand Down
8 changes: 7 additions & 1 deletion lib/internal/http2/util.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -233,7 +233,8 @@ const IDX_OPTIONS_MAX_SETTINGS = 9;
const IDX_OPTIONS_STREAM_RESET_RATE = 10;
const IDX_OPTIONS_STREAM_RESET_BURST = 11;
const IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION = 12;
const IDX_OPTIONS_FLAGS = 13;
const IDX_OPTIONS_CONNECTION_WINDOW_SIZE = 13;
const IDX_OPTIONS_FLAGS = 14;

function updateOptionsBuffer(options) {
let flags = 0;
Expand DownExpand Up@@ -303,6 +304,11 @@ function updateOptionsBuffer(options) {
optionsBuffer[IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION] =
options.strictFieldWhitespaceValidation === true ? 0 : 1;
}
if (typeof options.connectionWindowSize === 'number') {
flags |= (1 << IDX_OPTIONS_CONNECTION_WINDOW_SIZE);
optionsBuffer[IDX_OPTIONS_CONNECTION_WINDOW_SIZE] =
options.connectionWindowSize;
}

optionsBuffer[IDX_OPTIONS_FLAGS] = flags;
}
Expand Down
19 changes: 11 additions & 8 deletions src/node_http2.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -217,6 +217,11 @@ Http2Options::Http2Options(Http2State* http2_state, SessionType type) {
static_cast<size_t>(buffer[IDX_OPTIONS_MAX_SETTINGS]));
}

if (flags & (1 << IDX_OPTIONS_CONNECTION_WINDOW_SIZE)) {
set_connection_window_size(
static_cast<int32_t>(buffer[IDX_OPTIONS_CONNECTION_WINDOW_SIZE]));
}

if ((flags & (1 << IDX_OPTIONS_STREAM_RESET_BURST)) &&
(flags & (1 << IDX_OPTIONS_STREAM_RESET_RATE))) {
nghttp2_option_set_stream_reset_rate_limit(
Expand DownExpand Up@@ -612,15 +617,13 @@ Http2Session::Http2Session(Http2State* http2_state,
&alloc_info), 0);
session_.reset(session);

// Increase the default local connection window to improve throughput
// on high-latency connections. The default 64KB window limits throughput
// to window_size / RTT. With a 32MB connection window, throughput is
// significantly improved. See https://github.com/nodejs/node/issues/38426
// The default connection window is larger than the 64KB required by the
// spec to improve throughput on high-latency connections, where throughput
// is limited to window_size / RTT. User settings have to be applied here
// initially rather than updating later as windows cannot be shrunk after
// they've been advertised.
CHECK_EQ(nghttp2_session_set_local_window_size(
session,
NGHTTP2_FLAG_NONE,
0,
DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE),
session, NGHTTP2_FLAG_NONE, 0, opts.connection_window_size()),
0);

outgoing_storage_.reserve(1024);
Expand Down
15 changes: 11 additions & 4 deletions src/node_http2.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,10 @@ constexpr size_t kDefaultMaxSettings = 10;
// Default maximum total memory cap for Http2Session.
constexpr uint64_t kDefaultMaxSessionMemory = 10000000;

// Default connection-level flow control window (32MB) to improve throughput
// on high-latency connections. See https://github.com/nodejs/node/issues/38426
constexpr int32_t kDefaultConnectionWindowSize = 33554432;

// These are the standard HTTP/2 defaults as specified by the RFC
constexpr uint32_t DEFAULT_SETTINGS_HEADER_TABLE_SIZE = 4096;
constexpr uint32_t DEFAULT_SETTINGS_ENABLE_PUSH = 1;
Expand All@@ -51,10 +55,6 @@ constexpr uint32_t MAX_MAX_FRAME_SIZE = 16777215;
constexpr uint32_t MIN_MAX_FRAME_SIZE = DEFAULT_SETTINGS_MAX_FRAME_SIZE;
constexpr uint32_t MAX_INITIAL_WINDOW_SIZE = 2147483647;

// Default local connection window size (32MB) to improve throughput
// on high-latency connections. See https://github.com/nodejs/node/issues/38426
constexpr uint32_t DEFAULT_SETTINGS_LOCAL_CONNECTION_WINDOW_SIZE = 33554432;

// Stream is not going to have any DATA frames
constexpr int STREAM_OPTION_EMPTY_PAYLOAD = 0x1;

Expand DownExpand Up@@ -245,9 +245,16 @@ class Http2Options {
return max_session_memory_;
}

void set_connection_window_size(int32_t size) {
connection_window_size_ = size;
}

int32_t connection_window_size() const { return connection_window_size_; }

private:
Nghttp2OptionPointer options_;
uint64_t max_session_memory_ = kDefaultMaxSessionMemory;
int32_t connection_window_size_ = kDefaultConnectionWindowSize;
uint32_t max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS;
PaddingStrategy padding_strategy_ = PADDING_STRATEGY_NONE;
size_t max_outstanding_pings_ = kDefaultMaxPings;
Expand Down
1 change: 1 addition & 0 deletions src/node_http2_state.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,7 @@ namespace http2 {
IDX_OPTIONS_STREAM_RESET_RATE,
IDX_OPTIONS_STREAM_RESET_BURST,
IDX_OPTIONS_STRICT_HTTP_FIELD_WHITESPACE_VALIDATION,
IDX_OPTIONS_CONNECTION_WINDOW_SIZE,
IDX_OPTIONS_FLAGS
};

Expand Down
Loading
Loading