Skip to content

Commit

Permalink
http2: Initial proposal for addtl http/2 settings
Browse files Browse the repository at this point in the history
Currently, node.js http/2 is limited in sending SETTINGs,
that are currently implemented by nghttp2.
However, nghttp2 has the ability to send arbitary SETTINGs,
that are not known beforehand.
This patch adds this feature including a fall back mechanism,
if a SETTING is implemented in a later nghttp2 or node version.

Fixes: nodejs#1337
  • Loading branch information
martenrichter committed Aug 19, 2023
1 parent 78842cf commit c8b3a5b
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 4 deletions.
3 changes: 3 additions & 0 deletions lib/internal/http2/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,9 @@ function pingCallback(cb) {
// All settings are optional and may be left undefined
const validateSettings = hideStackFrames((settings) => {
if (settings === undefined) return;

assertIsObject('customSettings', settings.customSettings)

assertWithinRange('headerTableSize',
settings.headerTableSize,
0, kMaxInt);
Expand Down
62 changes: 62 additions & 0 deletions lib/internal/http2/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ const IDX_SETTINGS_MAX_HEADER_LIST_SIZE = 5;
const IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL = 6;
const IDX_SETTINGS_FLAGS = 7;

// Maximum number of allowed additional settings
const MAX_ADDITIONAL_SETTINGS = 10;

const IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE = 0;
const IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH = 1;
const IDX_SESSION_STATE_NEXT_STREAM_ID = 2;
Expand Down Expand Up @@ -350,6 +353,65 @@ function getSettings(session, remote) {

function updateSettingsBuffer(settings) {
let flags = 0;

if (typeof settings.customSettings === 'object') {
let customSettings = settings.customSettings;
let numCustomSettings = 0;
for (const setting in customSettings) {
const val = customSettings[setting];
if (typeof setting === 'number' && typeof val === 'number') {
let set = false
if (setting < IDX_SETTINGS_FLAGS) {
set = true
switch (setting) {
case IDX_SETTINGS_HEADER_TABLE_SIZE:
flags |= (1 << IDX_SETTINGS_HEADER_TABLE_SIZE);
settingsBuffer[IDX_SETTINGS_HEADER_TABLE_SIZE] =
val;
break;
case IDX_SETTINGS_ENABLE_PUSH:
flags |= (1 << IDX_SETTINGS_ENABLE_PUSH);
settingsBuffer[IDX_SETTINGS_ENABLE_PUSH] = val;
break;
case IDX_SETTINGS_INITIAL_WINDOW_SIZE:
flags |= (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE);
settingsBuffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] =
val;
break;
case IDX_SETTINGS_MAX_FRAME_SIZE:
flags |= (1 << IDX_SETTINGS_MAX_FRAME_SIZE);
settingsBuffer[IDX_SETTINGS_MAX_FRAME_SIZE] =
val;
break;
case IDX_SETTINGS_MAX_CONCURRENT_STREAMS:
flags |= (1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS);
settingsBuffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS] = val;
break;
case IDX_SETTINGS_MAX_HEADER_LIST_SIZE:
flags |= (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE);
settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE] =
val;
break;
case IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL:
flags |= (1 << IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL);
settingsBuffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL] = val;
break;
default:
set = false;
break;
}
}
if (!set) { // not supported
if (numCustomSettings === MAX_ADDITIONAL_SETTINGS) throw new Error('Number of custom settings exceeds MAX_ADDITIONAL_SETTINGS');
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings] = setting;
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 1] = val;
numCustomSettings++;
}
}
settingsBuffer[IDX_SETTINGS_FLAGS + 1] = numCustomSettings;
}
}

if (typeof settings.headerTableSize === 'number') {
flags |= (1 << IDX_SETTINGS_HEADER_TABLE_SIZE);
settingsBuffer[IDX_SETTINGS_HEADER_TABLE_SIZE] =
Expand Down
15 changes: 14 additions & 1 deletion src/node_http2.cc
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,16 @@ size_t Http2Settings::Init(
HTTP2_SETTINGS(V)
#undef V

uint32_t numAddSettings = buffer[IDX_SETTINGS_COUNT + 1];
if (numAddSettings > 0) {
uint32_t offset = IDX_SETTINGS_COUNT + 1 + 1;
for (uint32_t i = 0; i < numAddSettings; i++) {
uint32_t key = buffer[offset + i * 2 + 0];
uint32_t val = buffer[offset + i * 2 + 1];
entries[count++] = nghttp2_settings_entry{(int32_t)key, val};
}
}

return count;
}
#undef GRABSETTING
Expand Down Expand Up @@ -262,7 +272,7 @@ Local<Value> Http2Settings::Pack() {
}

Local<Value> Http2Settings::Pack(Http2State* state) {
nghttp2_settings_entry entries[IDX_SETTINGS_COUNT];
nghttp2_settings_entry entries[IDX_SETTINGS_COUNT + MAX_ADDITIONAL_SETTINGS];
size_t count = Init(state, entries);
return Pack(state->env(), count, entries);
}
Expand Down Expand Up @@ -298,6 +308,8 @@ void Http2Settings::Update(Http2Session* session, get_setting fn) {
fn(session->session(), NGHTTP2_SETTINGS_ ## name);
HTTP2_SETTINGS(V)
#undef V
buffer[IDX_SETTINGS_COUNT + 1] =
0; // no additional settings are coming, clear them
}

// Initializes the shared TypedArray with the default settings values.
Expand All @@ -314,6 +326,7 @@ void Http2Settings::RefreshDefaults(Http2State* http2_state) {
#undef V

buffer[IDX_SETTINGS_COUNT] = flags;
buffer[IDX_SETTINGS_COUNT + 1] = 0; // no additional settings
}


Expand Down
2 changes: 1 addition & 1 deletion src/node_http2.h
Original file line number Diff line number Diff line change
Expand Up @@ -1035,7 +1035,7 @@ class Http2Settings : public AsyncWrap {
v8::Global<v8::Function> callback_;
uint64_t startTime_;
size_t count_ = 0;
nghttp2_settings_entry entries_[IDX_SETTINGS_COUNT];
nghttp2_settings_entry entries_[IDX_SETTINGS_COUNT + MAX_ADDITIONAL_SETTINGS];
};

class Origins {
Expand Down
9 changes: 7 additions & 2 deletions src/node_http2_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ namespace http2 {
IDX_SETTINGS_COUNT
};

// number of max additional settings, thus settings not implemente by nghttp2
const size_t MAX_ADDITIONAL_SETTINGS = 10;

enum Http2SessionStateIndex {
IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE,
IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH,
Expand Down Expand Up @@ -110,7 +113,8 @@ class Http2State : public BaseObject {
root_buffer),
settings_buffer(realm->isolate(),
offsetof(http2_state_internal, settings_buffer),
IDX_SETTINGS_COUNT + 1,
IDX_SETTINGS_COUNT + 1 + 1 +
2 * MAX_ADDITIONAL_SETTINGS,
root_buffer) {}

AliasedUint8Array root_buffer;
Expand All @@ -135,7 +139,8 @@ class Http2State : public BaseObject {
double stream_stats_buffer[IDX_STREAM_STATS_COUNT];
double session_stats_buffer[IDX_SESSION_STATS_COUNT];
uint32_t options_buffer[IDX_OPTIONS_FLAGS + 1];
uint32_t settings_buffer[IDX_SETTINGS_COUNT + 1];
uint32_t settings_buffer[IDX_SETTINGS_COUNT + 1 + 1 +
2 * MAX_ADDITIONAL_SETTINGS];
};
};

Expand Down

0 comments on commit c8b3a5b

Please sign in to comment.