http2: receive customsettings

This commit gives node.js the ability to also receive custom settings,
in addition to sending, them which was implemented before.
The custom settings received are limited to setting ids,
that were specified before, when creating the session eithers through
the server or the client.

PR-URL: https://github.com/nodejs/node/pull/51323
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
This commit is contained in:
Marten Richter 2024-01-07 18:33:52 +01:00 committed by GitHub
parent b25f2cd45e
commit bf37d4be69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 383 additions and 31 deletions

View File

@ -2498,6 +2498,11 @@ changes:
**Default:** `100`.
* `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
settings types, which are included in the `CustomSettings`-property of
the received remoteSettings. Please see the `CustomSettings`-property of
the `Http2Settings` object for more information,
on the allowed setting types.
* `Http1IncomingMessage` {http.IncomingMessage} Specifies the
`IncomingMessage` class to used for HTTP/1 fallback. Useful for extending
the original `http.IncomingMessage`. **Default:** `http.IncomingMessage`.
@ -2652,6 +2657,10 @@ changes:
**Default:** `100`.
* `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
settings types, which are included in the `customSettings`-property of the
received remoteSettings. Please see the `customSettings`-property of the
`Http2Settings` object for more information, on the allowed setting types.
* ...: Any [`tls.createServer()`][] options can be provided. For
servers, the identity options (`pfx` or `key`/`cert`) are usually required.
* `origins` {string\[]} An array of origin strings to send within an `ORIGIN`
@ -2780,6 +2789,10 @@ changes:
`'https:'`
* `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
settings types, which are included in the `CustomSettings`-property of the
received remoteSettings. Please see the `CustomSettings`-property of the
`Http2Settings` object for more information, on the allowed setting types.
* `createConnection` {Function} An optional callback that receives the `URL`
instance passed to `connect` and the `options` object, and returns any
[`Duplex`][] stream that is to be used as the connection for this session.
@ -3022,9 +3035,11 @@ properties.
it should be greater than 6, although it is not an error.
The values need to be unsigned integers in the range from 0 to 2^32-1.
Currently, a maximum of up 10 custom settings is supported.
It is only supported for sending SETTINGS.
Custom settings are not supported for the functions retrieving remote and
local settings as nghttp2 does not pass unknown HTTP/2 settings to Node.js.
It is only supported for sending SETTINGS, or for receiving settings values
specified in the `remoteCustomSettings` options of the server or client
object. Do not mix the `customSettings`-mechanism for a settings id with
interfaces for the natively handled settings, in case a setting becomes
natively supported in a future node version.
All additional properties on the settings object are ignored.

View File

@ -9,9 +9,11 @@ const {
FunctionPrototypeBind,
FunctionPrototypeCall,
MathMin,
Number,
ObjectAssign,
ObjectKeys,
ObjectDefineProperty,
ObjectEntries,
ObjectPrototypeHasOwnProperty,
Promise,
PromisePrototypeThen,
@ -105,6 +107,7 @@ const {
ERR_HTTP2_STREAM_CANCEL,
ERR_HTTP2_STREAM_ERROR,
ERR_HTTP2_STREAM_SELF_DEPENDENCY,
ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS,
ERR_HTTP2_TRAILERS_ALREADY_SENT,
ERR_HTTP2_TRAILERS_NOT_READY,
ERR_HTTP2_UNSUPPORTED_PROTOCOL,
@ -140,6 +143,7 @@ const {
const {
assertIsObject,
assertIsArray,
assertValidPseudoHeader,
assertValidPseudoHeaderResponse,
assertValidPseudoHeaderTrailer,
@ -155,7 +159,9 @@ const {
kRequest,
kProxySocket,
mapToHeaders,
MAX_ADDITIONAL_SETTINGS,
NghttpError,
remoteCustomSettingsToBuffer,
sessionName,
toHeaderObject,
updateOptionsBuffer,
@ -947,6 +953,15 @@ function pingCallback(cb) {
const validateSettings = hideStackFrames((settings) => {
if (settings === undefined) return;
assertIsObject.withoutStackTrace(settings.customSettings, 'customSettings', 'Number');
if (settings.customSettings) {
const entries = ObjectEntries(settings.customSettings);
if (entries.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
for (const { 0: key, 1: value } of entries) {
assertWithinRange.withoutStackTrace('customSettings:id', Number(key), 0, 0xffff);
assertWithinRange.withoutStackTrace('customSettings:value', Number(value), 0, kMaxInt);
}
}
assertWithinRange.withoutStackTrace('headerTableSize',
settings.headerTableSize,
@ -1031,6 +1046,9 @@ function setupHandle(socket, type, options) {
this[kState].flags |= SESSION_FLAGS_READY;
updateOptionsBuffer(options);
if (options.remoteCustomSettings) {
remoteCustomSettingsToBuffer(options.remoteCustomSettings);
}
const handle = new binding.Http2Session(type);
handle[kOwner] = this;
@ -3103,6 +3121,13 @@ function initializeOptions(options) {
assertIsObject(options.settings, 'options.settings');
options.settings = { ...options.settings };
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.maxSessionInvalidFrames !== undefined)
validateUint32(options.maxSessionInvalidFrames, 'maxSessionInvalidFrames');
@ -3277,6 +3302,13 @@ function connect(authority, options, listener) {
assertIsObject(options, 'options');
options = { ...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 (typeof authority === 'string')
authority = new URL(authority);

View File

@ -278,8 +278,19 @@ function updateOptionsBuffer(options) {
optionsBuffer[IDX_OPTIONS_FLAGS] = flags;
}
function addCustomSettingsToObj() {
const toRet = {};
const num = settingsBuffer[IDX_SETTINGS_FLAGS + 1];
for (let i = 0; i < num; i++) {
toRet[settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * i + 1].toString()] =
Number(settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * i + 2]);
}
return toRet;
}
function getDefaultSettings() {
settingsBuffer[IDX_SETTINGS_FLAGS] = 0;
settingsBuffer[IDX_SETTINGS_FLAGS + 1] = 0; // Length of custom settings
binding.refreshDefaultSettings();
const holder = { __proto__: null };
@ -327,6 +338,8 @@ function getDefaultSettings() {
settingsBuffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL] === 1;
}
if (settingsBuffer[IDX_SETTINGS_FLAGS + 1]) holder.customSettings = addCustomSettingsToObj();
return holder;
}
@ -338,7 +351,7 @@ function getSettings(session, remote) {
else
session.localSettings();
return {
const toRet = {
headerTableSize: settingsBuffer[IDX_SETTINGS_HEADER_TABLE_SIZE],
enablePush: !!settingsBuffer[IDX_SETTINGS_ENABLE_PUSH],
initialWindowSize: settingsBuffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE],
@ -349,6 +362,8 @@ function getSettings(session, remote) {
enableConnectProtocol:
!!settingsBuffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL],
};
if (settingsBuffer[IDX_SETTINGS_FLAGS + 1]) toRet.customSettings = addCustomSettingsToObj();
return toRet;
}
function updateSettingsBuffer(settings) {
@ -415,12 +430,22 @@ function updateSettingsBuffer(settings) {
}
}
if (!set) { // not supported
if (numCustomSettings === MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
let i = 0;
while (i < numCustomSettings) {
if (settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * i + 1] === nsetting) {
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * i + 2] = val;
break;
}
i++;
}
if (i === numCustomSettings) {
if (numCustomSettings === MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 1] = nsetting;
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 2] = val;
numCustomSettings++;
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 1] = nsetting;
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 2] = val;
numCustomSettings++;
}
}
}
}
@ -475,6 +500,24 @@ function updateSettingsBuffer(settings) {
settingsBuffer[IDX_SETTINGS_FLAGS] = flags;
}
function remoteCustomSettingsToBuffer(remoteCustomSettings) {
if (remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
let numCustomSettings = 0;
for (let i = 0; i < remoteCustomSettings.length; i++) {
const nsetting = remoteCustomSettings[i];
if (typeof nsetting === 'number' && nsetting <= 0xffff &&
nsetting >= 0) {
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 1] = nsetting;
numCustomSettings++;
} else
throw new ERR_HTTP2_INVALID_SETTING_VALUE.RangeError(
'Range Error', nsetting, 0, 0xffff);
}
settingsBuffer[IDX_SETTINGS_FLAGS + 1] = numCustomSettings;
}
function getSessionState(session) {
session.refreshState();
return {
@ -649,6 +692,14 @@ const assertIsObject = hideStackFrames((value, name, types) => {
}
});
const assertIsArray = hideStackFrames((value, name, types) => {
if (value !== undefined &&
(value === null ||
!ArrayIsArray(value))) {
throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(name, types || 'Array', value);
}
});
const assertWithinRange = hideStackFrames(
(name, value, min = 0, max = Infinity) => {
if (value !== undefined &&
@ -732,6 +783,7 @@ function getAuthority(headers) {
module.exports = {
assertIsObject,
assertIsArray,
assertValidPseudoHeader,
assertValidPseudoHeaderResponse,
assertValidPseudoHeaderTrailer,
@ -747,7 +799,9 @@ module.exports = {
kProxySocket,
kRequest,
mapToHeaders,
MAX_ADDITIONAL_SETTINGS,
NghttpError,
remoteCustomSettingsToBuffer,
sessionName,
toHeaderObject,
updateOptionsBuffer,

View File

@ -227,7 +227,6 @@ size_t Http2Settings::Init(
#define V(name) GRABSETTING(entries, count, name);
HTTP2_SETTINGS(V)
#undef V
uint32_t numAddSettings = buffer[IDX_SETTINGS_COUNT + 1];
if (numAddSettings > 0) {
uint32_t offset = IDX_SETTINGS_COUNT + 1 + 1;
@ -300,7 +299,7 @@ Local<Value> Http2Settings::Pack(
// Updates the shared TypedArray with the current remote or local settings for
// the session.
void Http2Settings::Update(Http2Session* session, get_setting fn) {
void Http2Settings::Update(Http2Session* session, get_setting fn, bool local) {
AliasedUint32Array& buffer = session->http2_state()->settings_buffer;
#define V(name) \
@ -308,8 +307,37 @@ 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
struct Http2Session::custom_settings_state& custom_settings =
session->custom_settings(local);
uint32_t count = 0;
size_t imax = std::min(custom_settings.number, MAX_ADDITIONAL_SETTINGS);
for (size_t i = 0; i < imax; i++) {
// We flag unset the settings with a bit above the allowed range
if (!(custom_settings.entries[i].settings_id & (~0xffff))) {
uint32_t settings_id =
(uint32_t)(custom_settings.entries[i].settings_id & 0xffff);
size_t j = 0;
while (j < count) {
if ((buffer[IDX_SETTINGS_COUNT + 1 + j * 2 + 1] & 0xffff) ==
settings_id) {
buffer[IDX_SETTINGS_COUNT + 1 + j * 2 + 1] = settings_id;
buffer[IDX_SETTINGS_COUNT + 1 + j * 2 + 2] =
custom_settings.entries[i].value;
break;
}
j++;
}
if (j == count && count < MAX_ADDITIONAL_SETTINGS) {
buffer[IDX_SETTINGS_COUNT + 1 + count * 2 + 1] = settings_id;
buffer[IDX_SETTINGS_COUNT + 1 + count * 2 + 2] =
custom_settings.entries[i].value;
count++;
}
}
// Comment for code review,
// one might also set the javascript object with an undefined value
}
buffer[IDX_SETTINGS_COUNT + 1] = count;
}
// Initializes the shared TypedArray with the default settings values.
@ -332,6 +360,9 @@ void Http2Settings::RefreshDefaults(Http2State* http2_state) {
void Http2Settings::Send() {
Http2Scope h2scope(session_.get());
// We have to update the local custom settings
session_->UpdateLocalCustomSettings(count_, &entries_[0]);
CHECK_EQ(nghttp2_submit_settings(
session_->session(),
NGHTTP2_FLAG_NONE,
@ -339,6 +370,34 @@ void Http2Settings::Send() {
count_), 0);
}
void Http2Session::UpdateLocalCustomSettings(size_t count,
nghttp2_settings_entry* entries) {
size_t number = local_custom_settings_.number;
for (size_t i = 0; i < count; ++i) {
nghttp2_settings_entry& s_entry = entries[i];
if (s_entry.settings_id >= IDX_SETTINGS_COUNT) {
// look if already included
size_t j = 0;
while (j < number) {
nghttp2_settings_entry& d_entry = local_custom_settings_.entries[j];
if (d_entry.settings_id == s_entry.settings_id) {
d_entry.value = s_entry.value;
break;
}
j++;
}
if (j == number && number < MAX_ADDITIONAL_SETTINGS) {
nghttp2_settings_entry& d_entry =
local_custom_settings_.entries[number];
d_entry.settings_id = s_entry.settings_id;
d_entry.value = s_entry.value;
number++;
}
}
}
local_custom_settings_.number = number;
}
void Http2Settings::Done(bool ack) {
uint64_t end = uv_hrtime();
double duration = (end - startTime_) / 1e6;
@ -505,6 +564,11 @@ Http2Session::Http2Session(Http2State* http2_state,
max_outstanding_pings_ = opts.max_outstanding_pings();
max_outstanding_settings_ = opts.max_outstanding_settings();
local_custom_settings_.number = 0;
remote_custom_settings_.number = 0;
// now, import possible custom_settings
FetchAllowedRemoteCustomSettings();
padding_strategy_ = opts.padding_strategy();
bool hasGetPaddingCallback =
@ -547,6 +611,24 @@ Http2Session::~Http2Session() {
CHECK_EQ(current_nghttp2_memory_, 0);
}
void Http2Session::FetchAllowedRemoteCustomSettings() {
AliasedUint32Array& buffer = http2_state_->settings_buffer;
uint32_t numAddSettings = buffer[IDX_SETTINGS_COUNT + 1];
if (numAddSettings > 0) {
nghttp2_settings_entry* entries = remote_custom_settings_.entries;
uint32_t offset = IDX_SETTINGS_COUNT + 1 + 1;
size_t count = 0;
for (uint32_t i = 0; i < numAddSettings; i++) {
uint32_t key =
(buffer[offset + i * 2 + 0] & 0xffff) |
(1
<< 16); // setting the bit 16 indicates, that no values has been set
entries[count++] = nghttp2_settings_entry{(int32_t)key, 0};
}
remote_custom_settings_.number = count;
}
}
void Http2Session::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("streams", streams_);
tracker->TrackField("outstanding_pings", outstanding_pings_);
@ -561,12 +643,11 @@ void Http2Session::MemoryInfo(MemoryTracker* tracker) const {
std::string Http2Session::diagnostic_name() const {
return std::string("Http2Session ") + TypeName() + " (" +
std::to_string(static_cast<int64_t>(get_async_id())) + ")";
std::to_string(static_cast<int64_t>(get_async_id())) + ")";
}
MaybeLocal<Object> Http2StreamPerformanceEntryTraits::GetDetails(
Environment* env,
const Http2StreamPerformanceEntry& entry) {
Environment* env, const Http2StreamPerformanceEntry& entry) {
Local<Object> obj = Object::New(env->isolate());
#define SET(name, val) \
@ -1554,6 +1635,26 @@ void Http2Session::HandleSettingsFrame(const nghttp2_frame* frame) {
bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
if (!ack) {
js_fields_->bitfield &= ~(1 << kSessionRemoteSettingsIsUpToDate);
// update additional settings
if (remote_custom_settings_.number > 0) {
nghttp2_settings_entry* iv = frame->settings.iv;
size_t niv = frame->settings.niv;
size_t numsettings = remote_custom_settings_.number;
for (size_t i = 0; i < niv; ++i) {
int32_t settings_id = iv[i].settings_id;
if (settings_id >=
IDX_SETTINGS_COUNT) { // unsupported, additional settings
for (size_t j = 0; j < numsettings; ++j) {
if ((remote_custom_settings_.entries[j].settings_id & 0xFFFF) ==
settings_id) {
remote_custom_settings_.entries[j].settings_id = settings_id;
remote_custom_settings_.entries[j].value = iv[i].value;
break;
}
}
}
}
}
if (!(js_fields_->bitfield & (1 << kSessionHasRemoteSettingsListeners)))
return;
// This is not a SETTINGS acknowledgement, notify and return
@ -2620,11 +2721,11 @@ void Http2Session::SetLocalWindowSize(
// A TypedArray instance is shared between C++ and JS land to contain the
// SETTINGS (either remote or local). RefreshSettings updates the current
// values established for each of the settings so those can be read in JS land.
template <get_setting fn>
template <get_setting fn, bool local>
void Http2Session::RefreshSettings(const FunctionCallbackInfo<Value>& args) {
Http2Session* session;
ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
Http2Settings::Update(session, fn);
Http2Settings::Update(session, fn, local);
Debug(session, "settings refreshed for session");
}
@ -3290,12 +3391,13 @@ void Initialize(Local<Object> target,
isolate,
session,
"localSettings",
Http2Session::RefreshSettings<nghttp2_session_get_local_settings>);
Http2Session::RefreshSettings<nghttp2_session_get_local_settings, true>);
SetProtoMethod(
isolate,
session,
"remoteSettings",
Http2Session::RefreshSettings<nghttp2_session_get_remote_settings>);
Http2Session::RefreshSettings<nghttp2_session_get_remote_settings,
false>);
SetConstructorFunction(context, target, "Http2Session", session);
Local<Object> constants = Object::New(isolate);

View File

@ -627,6 +627,15 @@ class Http2Session : public AsyncWrap,
flags_ |= kSessionStateClosed;
}
struct custom_settings_state {
size_t number;
nghttp2_settings_entry entries[MAX_ADDITIONAL_SETTINGS];
};
custom_settings_state& custom_settings(bool local) {
return local ? local_custom_settings_ : remote_custom_settings_;
}
#define IS_FLAG(name, flag) \
bool is_##name() const { return flags_ & flag; } \
void set_##name(bool on = true) { \
@ -715,7 +724,7 @@ class Http2Session : public AsyncWrap,
static void AltSvc(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Origin(const v8::FunctionCallbackInfo<v8::Value>& args);
template <get_setting fn>
template <get_setting fn, bool local>
static void RefreshSettings(const v8::FunctionCallbackInfo<v8::Value>& args);
uv_loop_t* event_loop() const {
@ -739,6 +748,9 @@ class Http2Session : public AsyncWrap,
current_session_memory_ -= amount;
}
void UpdateLocalCustomSettings(size_t count_,
nghttp2_settings_entry* entries_);
// Tell our custom memory allocator that this rcbuf is independent of
// this session now, and may outlive it.
void StopTrackingRcbuf(nghttp2_rcbuf* buf);
@ -776,6 +788,8 @@ class Http2Session : public AsyncWrap,
private:
void EmitStatistics();
void FetchAllowedRemoteCustomSettings();
// Frame Padding Strategies
ssize_t OnDWordAlignedPadding(size_t frameLength,
size_t maxPayloadLen);
@ -915,6 +929,9 @@ class Http2Session : public AsyncWrap,
size_t max_outstanding_settings_ = kDefaultMaxSettings;
std::queue<BaseObjectPtr<Http2Settings>> outstanding_settings_;
struct custom_settings_state local_custom_settings_;
struct custom_settings_state remote_custom_settings_;
std::vector<NgHttp2StreamWrite> outgoing_buffers_;
std::vector<uint8_t> outgoing_storage_;
size_t outgoing_length_ = 0;
@ -1018,8 +1035,7 @@ class Http2Settings : public AsyncWrap {
static void RefreshDefaults(Http2State* http2_state);
// Update the local or remote settings for the given session
static void Update(Http2Session* session,
get_setting fn);
static void Update(Http2Session* session, get_setting fn, bool local);
private:
static size_t Init(

View File

@ -140,7 +140,7 @@ class Http2State : public BaseObject {
double session_stats_buffer[IDX_SESSION_STATS_COUNT];
uint32_t options_buffer[IDX_OPTIONS_FLAGS + 1];
// first + 1: number of actual nghttp2 supported settings
// second + 1: number of additional settings not suppoted by nghttp2
// second + 1: number of additional settings not supported by nghttp2
// 2 * MAX_ADDITIONAL_SETTINGS: settings id and value for each
// additional setting
uint32_t settings_buffer[IDX_SETTINGS_COUNT + 1 + 1 +

View File

@ -48,6 +48,56 @@ server.listen(0, common.mustCall(() => {
)
);
assert.throws(
() => client.settings({ customSettings: {
0x11: 5,
0x12: 5,
0x13: 5,
0x14: 5,
0x15: 5,
0x16: 5,
0x17: 5,
0x18: 5,
0x19: 5,
0x1A: 5, // more than 10
0x1B: 5
} }),
{
code: 'ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS',
name: 'Error'
}
);
assert.throws(
() => client.settings({ customSettings: {
0x10000: 5,
} }),
{
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
name: 'RangeError'
}
);
assert.throws(
() => client.settings({ customSettings: {
0x55: 0x100000000,
} }),
{
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
name: 'RangeError'
}
);
assert.throws(
() => client.settings({ customSettings: {
0x55: -1,
} }),
{
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
name: 'RangeError'
}
);
[1, true, {}, []].forEach((invalidCallback) =>
assert.throws(
() => client.settings({}, invalidCallback),
@ -58,7 +108,7 @@ server.listen(0, common.mustCall(() => {
)
);
client.settings({ maxFrameSize: 1234567 });
client.settings({ maxFrameSize: 1234567, customSettings: { 0xbf: 12 } });
const req = client.request();
req.on('response', common.mustCall());

View File

@ -15,7 +15,7 @@ const server = http2.createServer({ maxSettings: 1 });
server.on('session', common.mustCall((session) => {
session.on('stream', common.mustNotCall());
session.on('remoteSettings', common.mustNotCall());
}));
}, 2));
server.on('stream', common.mustNotCall());
server.listen(0, common.mustCall(() => {
@ -30,7 +30,23 @@ server.listen(0, common.mustCall(() => {
},
});
client.on('error', common.mustCall(() => {
server.close();
client.on('error', common.mustCall((err) => {
// The same but with custom settings
const client2 = http2.connect(
`http://localhost:${server.address().port}`, {
settings: {
// The actual settings values do not matter.
headerTableSize: 1000,
customSettings: {
0x14: 45
}
},
});
client2.on('error', common.mustCall(() => {
server.close();
}));
}));
}));

View File

@ -6,7 +6,17 @@ if (!common.hasCrypto)
const assert = require('assert');
const h2 = require('http2');
const server = h2.createServer();
const server = h2.createServer({
remoteCustomSettings: [
55,
],
settings: {
customSettings: {
1244: 456
}
}
}
);
server.on(
'stream',
@ -20,6 +30,24 @@ server.on(
assert.strictEqual(typeof settings.maxConcurrentStreams, 'number');
assert.strictEqual(typeof settings.maxHeaderListSize, 'number');
assert.strictEqual(typeof settings.maxHeaderSize, 'number');
assert.strictEqual(typeof settings.customSettings, 'object');
let countCustom = 0;
if (settings.customSettings[55]) {
assert.strictEqual(typeof settings.customSettings[55], 'number');
assert.strictEqual(settings.customSettings[55], 12);
countCustom++;
}
if (settings.customSettings[155]) {
// Should not happen actually
assert.strictEqual(typeof settings.customSettings[155], 'number');
countCustom++;
}
if (settings.customSettings[1244]) {
assert.strictEqual(typeof settings.customSettings[1244], 'number');
assert.strictEqual(settings.customSettings[1244], 456);
countCustom++;
}
assert.strictEqual(countCustom, 1);
};
const localSettings = stream.session.localSettings;
@ -51,8 +79,15 @@ server.listen(
const client = h2.connect(`http://localhost:${server.address().port}`, {
settings: {
enablePush: false,
initialWindowSize: 123456
}
initialWindowSize: 123456,
customSettings: {
55: 12,
155: 144 // should not arrive
},
},
remoteCustomSettings: [
1244,
]
});
client.on(
@ -62,6 +97,7 @@ server.listen(
assert.strictEqual(settings.enablePush, false);
assert.strictEqual(settings.initialWindowSize, 123456);
assert.strictEqual(settings.maxFrameSize, 16384);
assert.strictEqual(settings.customSettings[55], 12);
}, 2)
);
@ -117,6 +153,37 @@ server.listen(
);
});
// Same tests as for the client on customSettings
assert.throws(
() => client.settings({ customSettings: {
0x10000: 5,
} }),
{
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
name: 'RangeError'
}
);
assert.throws(
() => client.settings({ customSettings: {
55: 0x100000000,
} }),
{
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
name: 'RangeError'
}
);
assert.throws(
() => client.settings({ customSettings: {
55: -1,
} }),
{
code: 'ERR_HTTP2_INVALID_SETTING_VALUE',
name: 'RangeError'
}
);
// Error checks for enablePush
[1, {}, 'test', [], null, Infinity, NaN].forEach((i) => {
assert.throws(