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 number Diff line number Diff 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 number Diff line number Diff 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 Down Expand 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 Down Expand 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 number Diff line number Diff 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 Down Expand 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 number Diff line number Diff 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 Down Expand 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 number Diff line number Diff 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 Down Expand 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 number Diff line number Diff 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