mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
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:
parent
b25f2cd45e
commit
bf37d4be69
@ -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.
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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(
|
||||
|
@ -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 +
|
||||
|
@ -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());
|
||||
|
@ -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();
|
||||
}));
|
||||
}));
|
||||
|
||||
|
||||
}));
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user