http2: convert Http2Settings to an AsyncWrap
PR-URL: https://github.com/nodejs/node/pull/17763 Refs: https://github.com/nodejs/node/issues/17746 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
parent
6100e12667
commit
bbaea1236f
@ -324,23 +324,14 @@ function onStreamRead(nread, buf, handle) {
|
||||
|
||||
// Called when the remote peer settings have been updated.
|
||||
// Resets the cached settings.
|
||||
function onSettings(ack) {
|
||||
function onSettings() {
|
||||
const session = this[kOwner];
|
||||
if (session.destroyed)
|
||||
return;
|
||||
session[kUpdateTimer]();
|
||||
let event = 'remoteSettings';
|
||||
if (ack) {
|
||||
debug(`Http2Session ${sessionName(session[kType])}: settings acknowledged`);
|
||||
if (session[kState].pendingAck > 0)
|
||||
session[kState].pendingAck--;
|
||||
session[kLocalSettings] = undefined;
|
||||
event = 'localSettings';
|
||||
} else {
|
||||
debug(`Http2Session ${sessionName(session[kType])}: new settings received`);
|
||||
session[kRemoteSettings] = undefined;
|
||||
}
|
||||
process.nextTick(emit, session, event, session[event]);
|
||||
debug(`Http2Session ${sessionName(session[kType])}: new settings received`);
|
||||
session[kRemoteSettings] = undefined;
|
||||
process.nextTick(emit, session, 'remoteSettings', session.remoteSettings);
|
||||
}
|
||||
|
||||
// If the stream exists, an attempt will be made to emit an event
|
||||
@ -540,15 +531,32 @@ function onSessionInternalError(code) {
|
||||
this[kOwner].destroy(new NghttpError(code));
|
||||
}
|
||||
|
||||
function settingsCallback(cb, ack, duration) {
|
||||
this[kState].pendingAck--;
|
||||
this[kLocalSettings] = undefined;
|
||||
if (ack) {
|
||||
debug(`Http2Session ${sessionName(this[kType])}: settings received`);
|
||||
const settings = this.localSettings;
|
||||
if (typeof cb === 'function')
|
||||
cb(null, settings, duration);
|
||||
this.emit('localSettings', settings);
|
||||
} else {
|
||||
debug(`Http2Session ${sessionName(this[kType])}: settings canceled`);
|
||||
if (typeof cb === 'function')
|
||||
cb(new errors.Error('ERR_HTTP2_SETTINGS_CANCEL'));
|
||||
}
|
||||
}
|
||||
|
||||
// Submits a SETTINGS frame to be sent to the remote peer.
|
||||
function submitSettings(settings) {
|
||||
function submitSettings(settings, callback) {
|
||||
if (this.destroyed)
|
||||
return;
|
||||
debug(`Http2Session ${sessionName(this[kType])}: submitting settings`);
|
||||
this[kUpdateTimer]();
|
||||
this[kLocalSettings] = undefined;
|
||||
updateSettingsBuffer(settings);
|
||||
this[kHandle].settings();
|
||||
if (!this[kHandle].settings(settingsCallback.bind(this, callback))) {
|
||||
this.destroy(new errors.Error('ERR_HTTP2_MAX_PENDING_SETTINGS_ACK'));
|
||||
}
|
||||
}
|
||||
|
||||
// Submits a PRIORITY frame to be sent to the remote peer
|
||||
@ -783,7 +791,6 @@ class Http2Session extends EventEmitter {
|
||||
streams: new Map(),
|
||||
pendingStreams: new Set(),
|
||||
pendingAck: 0,
|
||||
maxPendingAck: Math.max(1, (options.maxPendingAck | 0) || 10),
|
||||
writeQueueSize: 0
|
||||
};
|
||||
|
||||
@ -951,21 +958,19 @@ class Http2Session extends EventEmitter {
|
||||
}
|
||||
|
||||
// Submits a SETTINGS frame to be sent to the remote peer.
|
||||
settings(settings) {
|
||||
settings(settings, callback) {
|
||||
if (this.destroyed)
|
||||
throw new errors.Error('ERR_HTTP2_INVALID_SESSION');
|
||||
|
||||
assertIsObject(settings, 'settings');
|
||||
settings = validateSettings(settings);
|
||||
const state = this[kState];
|
||||
if (state.pendingAck === state.maxPendingAck) {
|
||||
throw new errors.Error('ERR_HTTP2_MAX_PENDING_SETTINGS_ACK',
|
||||
this[kState].pendingAck);
|
||||
}
|
||||
|
||||
if (callback && typeof callback !== 'function')
|
||||
throw new errors.TypeError('ERR_INVALID_CALLBACK');
|
||||
debug(`Http2Session ${sessionName(this[kType])}: sending settings`);
|
||||
|
||||
state.pendingAck++;
|
||||
const settingsFn = submitSettings.bind(this, settings);
|
||||
this[kState].pendingAck++;
|
||||
|
||||
const settingsFn = submitSettings.bind(this, settings, callback);
|
||||
if (this.connecting) {
|
||||
this.once('connect', settingsFn);
|
||||
return;
|
||||
|
@ -174,7 +174,8 @@ const IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS = 3;
|
||||
const IDX_OPTIONS_PADDING_STRATEGY = 4;
|
||||
const IDX_OPTIONS_MAX_HEADER_LIST_PAIRS = 5;
|
||||
const IDX_OPTIONS_MAX_OUTSTANDING_PINGS = 6;
|
||||
const IDX_OPTIONS_FLAGS = 7;
|
||||
const IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS = 7;
|
||||
const IDX_OPTIONS_FLAGS = 8;
|
||||
|
||||
function updateOptionsBuffer(options) {
|
||||
var flags = 0;
|
||||
@ -213,6 +214,11 @@ function updateOptionsBuffer(options) {
|
||||
optionsBuffer[IDX_OPTIONS_MAX_OUTSTANDING_PINGS] =
|
||||
options.maxOutstandingPings;
|
||||
}
|
||||
if (typeof options.maxOutstandingSettings === 'number') {
|
||||
flags |= (1 << IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS);
|
||||
optionsBuffer[IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS] =
|
||||
Math.max(1, options.maxOutstandingSettings);
|
||||
}
|
||||
optionsBuffer[IDX_OPTIONS_FLAGS] = flags;
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,7 @@ namespace node {
|
||||
V(HTTP2SESSION) \
|
||||
V(HTTP2STREAM) \
|
||||
V(HTTP2PING) \
|
||||
V(HTTP2SETTINGS) \
|
||||
V(HTTPPARSER) \
|
||||
V(JSSTREAM) \
|
||||
V(PIPECONNECTWRAP) \
|
||||
|
@ -288,6 +288,7 @@ class ModuleWrap;
|
||||
V(host_import_module_dynamically_callback, v8::Function) \
|
||||
V(http2ping_constructor_template, v8::ObjectTemplate) \
|
||||
V(http2stream_constructor_template, v8::ObjectTemplate) \
|
||||
V(http2settings_constructor_template, v8::ObjectTemplate) \
|
||||
V(inspector_console_api_object, v8::Object) \
|
||||
V(module_load_list_array, v8::Array) \
|
||||
V(pbkdf2_constructor_template, v8::ObjectTemplate) \
|
||||
|
@ -153,22 +153,28 @@ Http2Options::Http2Options(Environment* env) {
|
||||
if (flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_PINGS)) {
|
||||
SetMaxOutstandingPings(buffer[IDX_OPTIONS_MAX_OUTSTANDING_PINGS]);
|
||||
}
|
||||
|
||||
// The HTTP2 specification places no limits on the number of HTTP2
|
||||
// SETTINGS frames that can be sent. In order to prevent PINGS from being
|
||||
// abused as an attack vector, however, we place a strict upper limit
|
||||
// on the number of unacknowledged SETTINGS that can be sent at any given
|
||||
// time.
|
||||
if (flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS)) {
|
||||
SetMaxOutstandingSettings(buffer[IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS]);
|
||||
}
|
||||
}
|
||||
|
||||
// The Http2Settings class is used to configure a SETTINGS frame that is
|
||||
// to be sent to the connected peer. The settings are set using a TypedArray
|
||||
// that is shared with the JavaScript side.
|
||||
Http2Settings::Http2Settings(Environment* env) : env_(env) {
|
||||
void Http2Session::Http2Settings::Init() {
|
||||
entries_.AllocateSufficientStorage(IDX_SETTINGS_COUNT);
|
||||
AliasedBuffer<uint32_t, v8::Uint32Array>& buffer =
|
||||
env->http2_state()->settings_buffer;
|
||||
env()->http2_state()->settings_buffer;
|
||||
uint32_t flags = buffer[IDX_SETTINGS_COUNT];
|
||||
|
||||
size_t n = 0;
|
||||
|
||||
if (flags & (1 << IDX_SETTINGS_HEADER_TABLE_SIZE)) {
|
||||
uint32_t val = buffer[IDX_SETTINGS_HEADER_TABLE_SIZE];
|
||||
DEBUG_HTTP2("Http2Settings: setting header table size: %d\n", val);
|
||||
DEBUG_HTTP2SESSION2(session, "setting header table size: %d\n", val);
|
||||
entries_[n].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
|
||||
entries_[n].value = val;
|
||||
n++;
|
||||
@ -176,7 +182,7 @@ Http2Settings::Http2Settings(Environment* env) : env_(env) {
|
||||
|
||||
if (flags & (1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS)) {
|
||||
uint32_t val = buffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS];
|
||||
DEBUG_HTTP2("Http2Settings: setting max concurrent streams: %d\n", val);
|
||||
DEBUG_HTTP2SESSION2(session, "setting max concurrent streams: %d\n", val);
|
||||
entries_[n].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
|
||||
entries_[n].value = val;
|
||||
n++;
|
||||
@ -184,7 +190,7 @@ Http2Settings::Http2Settings(Environment* env) : env_(env) {
|
||||
|
||||
if (flags & (1 << IDX_SETTINGS_MAX_FRAME_SIZE)) {
|
||||
uint32_t val = buffer[IDX_SETTINGS_MAX_FRAME_SIZE];
|
||||
DEBUG_HTTP2("Http2Settings: setting max frame size: %d\n", val);
|
||||
DEBUG_HTTP2SESSION2(session, "setting max frame size: %d\n", val);
|
||||
entries_[n].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE;
|
||||
entries_[n].value = val;
|
||||
n++;
|
||||
@ -192,7 +198,7 @@ Http2Settings::Http2Settings(Environment* env) : env_(env) {
|
||||
|
||||
if (flags & (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE)) {
|
||||
uint32_t val = buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE];
|
||||
DEBUG_HTTP2("Http2Settings: setting initial window size: %d\n", val);
|
||||
DEBUG_HTTP2SESSION2(session, "setting initial window size: %d\n", val);
|
||||
entries_[n].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
|
||||
entries_[n].value = val;
|
||||
n++;
|
||||
@ -200,7 +206,7 @@ Http2Settings::Http2Settings(Environment* env) : env_(env) {
|
||||
|
||||
if (flags & (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE)) {
|
||||
uint32_t val = buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE];
|
||||
DEBUG_HTTP2("Http2Settings: setting max header list size: %d\n", val);
|
||||
DEBUG_HTTP2SESSION2(session, "setting max header list size: %d\n", val);
|
||||
entries_[n].settings_id = NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE;
|
||||
entries_[n].value = val;
|
||||
n++;
|
||||
@ -208,7 +214,7 @@ Http2Settings::Http2Settings(Environment* env) : env_(env) {
|
||||
|
||||
if (flags & (1 << IDX_SETTINGS_ENABLE_PUSH)) {
|
||||
uint32_t val = buffer[IDX_SETTINGS_ENABLE_PUSH];
|
||||
DEBUG_HTTP2("Http2Settings: setting enable push: %d\n", val);
|
||||
DEBUG_HTTP2SESSION2(session, "setting enable push: %d\n", val);
|
||||
entries_[n].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
|
||||
entries_[n].value = val;
|
||||
n++;
|
||||
@ -217,13 +223,46 @@ Http2Settings::Http2Settings(Environment* env) : env_(env) {
|
||||
count_ = n;
|
||||
}
|
||||
|
||||
Http2Session::Http2Settings::Http2Settings(
|
||||
Environment* env)
|
||||
: AsyncWrap(env,
|
||||
env->http2settings_constructor_template()
|
||||
->NewInstance(env->context())
|
||||
.ToLocalChecked(),
|
||||
AsyncWrap::PROVIDER_HTTP2SETTINGS),
|
||||
session_(nullptr),
|
||||
startTime_(0) {
|
||||
Init();
|
||||
}
|
||||
|
||||
// The Http2Settings class is used to configure a SETTINGS frame that is
|
||||
// to be sent to the connected peer. The settings are set using a TypedArray
|
||||
// that is shared with the JavaScript side.
|
||||
Http2Session::Http2Settings::Http2Settings(
|
||||
Http2Session* session)
|
||||
: AsyncWrap(session->env(),
|
||||
session->env()->http2settings_constructor_template()
|
||||
->NewInstance(session->env()->context())
|
||||
.ToLocalChecked(),
|
||||
AsyncWrap::PROVIDER_HTTP2SETTINGS),
|
||||
session_(session),
|
||||
startTime_(uv_hrtime()) {
|
||||
Init();
|
||||
}
|
||||
|
||||
Http2Session::Http2Settings::~Http2Settings() {
|
||||
if (!object().IsEmpty())
|
||||
ClearWrap(object());
|
||||
persistent().Reset();
|
||||
CHECK(persistent().IsEmpty());
|
||||
}
|
||||
|
||||
// Generates a Buffer that contains the serialized payload of a SETTINGS
|
||||
// frame. This can be used, for instance, to create the Base64-encoded
|
||||
// content of an Http2-Settings header field.
|
||||
inline Local<Value> Http2Settings::Pack() {
|
||||
inline Local<Value> Http2Session::Http2Settings::Pack() {
|
||||
const size_t len = count_ * 6;
|
||||
Local<Value> buf = Buffer::New(env_, len).ToLocalChecked();
|
||||
Local<Value> buf = Buffer::New(env(), len).ToLocalChecked();
|
||||
ssize_t ret =
|
||||
nghttp2_pack_settings_payload(
|
||||
reinterpret_cast<uint8_t*>(Buffer::Data(buf)), len,
|
||||
@ -231,14 +270,14 @@ inline Local<Value> Http2Settings::Pack() {
|
||||
if (ret >= 0)
|
||||
return buf;
|
||||
else
|
||||
return Undefined(env_->isolate());
|
||||
return Undefined(env()->isolate());
|
||||
}
|
||||
|
||||
// Updates the shared TypedArray with the current remote or local settings for
|
||||
// the session.
|
||||
inline void Http2Settings::Update(Environment* env,
|
||||
Http2Session* session,
|
||||
get_setting fn) {
|
||||
inline void Http2Session::Http2Settings::Update(Environment* env,
|
||||
Http2Session* session,
|
||||
get_setting fn) {
|
||||
AliasedBuffer<uint32_t, v8::Uint32Array>& buffer =
|
||||
env->http2_state()->settings_buffer;
|
||||
buffer[IDX_SETTINGS_HEADER_TABLE_SIZE] =
|
||||
@ -256,7 +295,7 @@ inline void Http2Settings::Update(Environment* env,
|
||||
}
|
||||
|
||||
// Initializes the shared TypedArray with the default settings values.
|
||||
inline void Http2Settings::RefreshDefaults(Environment* env) {
|
||||
inline void Http2Session::Http2Settings::RefreshDefaults(Environment* env) {
|
||||
AliasedBuffer<uint32_t, v8::Uint32Array>& buffer =
|
||||
env->http2_state()->settings_buffer;
|
||||
|
||||
@ -279,6 +318,24 @@ inline void Http2Settings::RefreshDefaults(Environment* env) {
|
||||
}
|
||||
|
||||
|
||||
void Http2Session::Http2Settings::Send() {
|
||||
Http2Scope h2scope(session_);
|
||||
CHECK_EQ(nghttp2_submit_settings(**session_, NGHTTP2_FLAG_NONE,
|
||||
*entries_, length()), 0);
|
||||
}
|
||||
|
||||
void Http2Session::Http2Settings::Done(bool ack) {
|
||||
uint64_t end = uv_hrtime();
|
||||
double duration = (end - startTime_) / 1e6;
|
||||
|
||||
Local<Value> argv[2] = {
|
||||
Boolean::New(env()->isolate(), ack),
|
||||
Number::New(env()->isolate(), duration)
|
||||
};
|
||||
MakeCallback(env()->ondone_string(), arraysize(argv), argv);
|
||||
delete this;
|
||||
}
|
||||
|
||||
// The Http2Priority class initializes an appropriate nghttp2_priority_spec
|
||||
// struct used when either creating a stream or updating its priority
|
||||
// settings.
|
||||
@ -417,6 +474,7 @@ Http2Session::Http2Session(Environment* env,
|
||||
: std::max(maxHeaderPairs, 1); // minimum # of response headers
|
||||
|
||||
max_outstanding_pings_ = opts.GetMaxOutstandingPings();
|
||||
max_outstanding_settings_ = opts.GetMaxOutstandingSettings();
|
||||
|
||||
padding_strategy_ = opts.GetPaddingStrategy();
|
||||
|
||||
@ -554,22 +612,6 @@ inline ssize_t Http2Session::OnCallbackPadding(size_t frameLen,
|
||||
}
|
||||
|
||||
|
||||
// Sends a SETTINGS frame to the connected peer. This has the side effect of
|
||||
// changing the settings state within the nghttp2_session, but those will
|
||||
// only be considered active once the connected peer acknowledges the SETTINGS
|
||||
// frame.
|
||||
// Note: This *must* send a SETTINGS frame even if niv == 0
|
||||
inline void Http2Session::Settings(const nghttp2_settings_entry iv[],
|
||||
size_t niv) {
|
||||
DEBUG_HTTP2SESSION2(this, "submitting %d settings", niv);
|
||||
Http2Scope h2scope(this);
|
||||
// This will fail either if the system is out of memory, or if the settings
|
||||
// values are not within the appropriate range. We should be catching the
|
||||
// latter before it gets this far so crash in either case.
|
||||
CHECK_EQ(nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, iv, niv), 0);
|
||||
}
|
||||
|
||||
|
||||
// Write data received from the i/o stream to the underlying nghttp2_session.
|
||||
// On each call to nghttp2_session_mem_recv, nghttp2 will begin calling the
|
||||
// various callback functions. Each of these will typically result in a call
|
||||
@ -1044,15 +1086,17 @@ inline void Http2Session::HandlePingFrame(const nghttp2_frame* frame) {
|
||||
|
||||
// Called by OnFrameReceived when a complete SETTINGS frame has been received.
|
||||
inline void Http2Session::HandleSettingsFrame(const nghttp2_frame* frame) {
|
||||
Isolate* isolate = env()->isolate();
|
||||
HandleScope scope(isolate);
|
||||
Local<Context> context = env()->context();
|
||||
Context::Scope context_scope(context);
|
||||
|
||||
bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
|
||||
|
||||
Local<Value> argv[1] = { Boolean::New(isolate, ack) };
|
||||
MakeCallback(env()->onsettings_string(), arraysize(argv), argv);
|
||||
if (ack) {
|
||||
// If this is an acknowledgement, we should have an Http2Settings
|
||||
// object for it.
|
||||
Http2Settings* settings = PopSettings();
|
||||
if (settings != nullptr)
|
||||
settings->Done(true);
|
||||
} else {
|
||||
// Otherwise, notify the session about a new settings
|
||||
MakeCallback(env()->onsettings_string(), 0, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Callback used when data has been written to the stream.
|
||||
@ -1954,7 +1998,7 @@ void HttpErrorString(const FunctionCallbackInfo<Value>& args) {
|
||||
// output for an HTTP2-Settings header field.
|
||||
void PackSettings(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
Http2Settings settings(env);
|
||||
Http2Session::Http2Settings settings(env);
|
||||
args.GetReturnValue().Set(settings.Pack());
|
||||
}
|
||||
|
||||
@ -1963,7 +2007,7 @@ void PackSettings(const FunctionCallbackInfo<Value>& args) {
|
||||
// default values.
|
||||
void RefreshDefaultSettings(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
Http2Settings::RefreshDefaults(env);
|
||||
Http2Session::Http2Settings::RefreshDefaults(env);
|
||||
}
|
||||
|
||||
// Sets the next stream ID the Http2Session. If successful, returns true.
|
||||
@ -2061,17 +2105,6 @@ void Http2Session::Destroy(const FunctionCallbackInfo<Value>& args) {
|
||||
session->Close(code, socketDestroyed);
|
||||
}
|
||||
|
||||
// Submits a SETTINGS frame for the Http2Session
|
||||
void Http2Session::Settings(const FunctionCallbackInfo<Value>& args) {
|
||||
Http2Session* session;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
|
||||
Environment* env = session->env();
|
||||
|
||||
Http2Settings settings(env);
|
||||
session->Http2Session::Settings(*settings, settings.length());
|
||||
DEBUG_HTTP2SESSION(session, "settings submitted");
|
||||
}
|
||||
|
||||
// Submits a new request on the Http2Session and returns either an error code
|
||||
// or the Http2Stream object.
|
||||
void Http2Session::Request(const FunctionCallbackInfo<Value>& args) {
|
||||
@ -2373,6 +2406,26 @@ void Http2Session::Ping(const FunctionCallbackInfo<Value>& args) {
|
||||
args.GetReturnValue().Set(true);
|
||||
}
|
||||
|
||||
// Submits a SETTINGS frame for the Http2Session
|
||||
void Http2Session::Settings(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
Http2Session* session;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
|
||||
|
||||
Http2Session::Http2Settings* settings = new Http2Settings(session);
|
||||
Local<Object> obj = settings->object();
|
||||
obj->Set(env->context(), env->ondone_string(), args[0]).FromJust();
|
||||
|
||||
if (!session->AddSettings(settings)) {
|
||||
settings->Done(false);
|
||||
return args.GetReturnValue().Set(false);
|
||||
}
|
||||
|
||||
settings->Send();
|
||||
args.GetReturnValue().Set(true);
|
||||
}
|
||||
|
||||
|
||||
Http2Session::Http2Ping* Http2Session::PopPing() {
|
||||
Http2Ping* ping = nullptr;
|
||||
if (!outstanding_pings_.empty()) {
|
||||
@ -2389,6 +2442,21 @@ bool Http2Session::AddPing(Http2Session::Http2Ping* ping) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Http2Session::Http2Settings* Http2Session::PopSettings() {
|
||||
Http2Settings* settings = nullptr;
|
||||
if (!outstanding_settings_.empty()) {
|
||||
settings = outstanding_settings_.front();
|
||||
outstanding_settings_.pop();
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
bool Http2Session::AddSettings(Http2Session::Http2Settings* settings) {
|
||||
if (outstanding_settings_.size() == max_outstanding_settings_)
|
||||
return false;
|
||||
outstanding_settings_.push(settings);
|
||||
return true;
|
||||
}
|
||||
|
||||
Http2Session::Http2Ping::Http2Ping(
|
||||
Http2Session* session)
|
||||
@ -2488,6 +2556,13 @@ void Initialize(Local<Object> target,
|
||||
pingt->SetInternalFieldCount(1);
|
||||
env->set_http2ping_constructor_template(pingt);
|
||||
|
||||
Local<FunctionTemplate> setting = FunctionTemplate::New(env->isolate());
|
||||
setting->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Setting"));
|
||||
AsyncWrap::AddWrapMethods(env, setting);
|
||||
Local<ObjectTemplate> settingt = setting->InstanceTemplate();
|
||||
settingt->SetInternalFieldCount(1);
|
||||
env->set_http2settings_constructor_template(settingt);
|
||||
|
||||
Local<FunctionTemplate> stream = FunctionTemplate::New(env->isolate());
|
||||
stream->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Stream"));
|
||||
env->SetProtoMethod(stream, "id", Http2Stream::GetID);
|
||||
|
@ -76,6 +76,9 @@ void inline debug_vfprintf(const char* format, ...) {
|
||||
// option.
|
||||
#define DEFAULT_MAX_PINGS 10
|
||||
|
||||
// Also strictly limit the number of outstanding SETTINGS frames a user sends
|
||||
#define DEFAULT_MAX_SETTINGS 10
|
||||
|
||||
// These are the standard HTTP/2 defaults as specified by the RFC
|
||||
#define DEFAULT_SETTINGS_HEADER_TABLE_SIZE 4096
|
||||
#define DEFAULT_SETTINGS_ENABLE_PUSH 1
|
||||
@ -484,41 +487,20 @@ class Http2Options {
|
||||
return max_outstanding_pings_;
|
||||
}
|
||||
|
||||
void SetMaxOutstandingSettings(size_t max) {
|
||||
max_outstanding_settings_ = max;
|
||||
}
|
||||
|
||||
size_t GetMaxOutstandingSettings() {
|
||||
return max_outstanding_settings_;
|
||||
}
|
||||
|
||||
private:
|
||||
nghttp2_option* options_;
|
||||
uint32_t max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS;
|
||||
padding_strategy_type padding_strategy_ = PADDING_STRATEGY_NONE;
|
||||
size_t max_outstanding_pings_ = DEFAULT_MAX_PINGS;
|
||||
};
|
||||
|
||||
// The Http2Settings class is used to parse the settings passed in for
|
||||
// an Http2Session, converting those into an array of nghttp2_settings_entry
|
||||
// structs.
|
||||
class Http2Settings {
|
||||
public:
|
||||
explicit Http2Settings(Environment* env);
|
||||
|
||||
size_t length() const { return count_; }
|
||||
|
||||
nghttp2_settings_entry* operator*() {
|
||||
return *entries_;
|
||||
}
|
||||
|
||||
// Returns a Buffer instance with the serialized SETTINGS payload
|
||||
inline Local<Value> Pack();
|
||||
|
||||
// Resets the default values in the settings buffer
|
||||
static inline void RefreshDefaults(Environment* env);
|
||||
|
||||
// Update the local or remote settings for the given session
|
||||
static inline void Update(Environment* env,
|
||||
Http2Session* session,
|
||||
get_setting fn);
|
||||
|
||||
private:
|
||||
Environment* env_;
|
||||
size_t count_ = 0;
|
||||
MaybeStackBuffer<nghttp2_settings_entry, IDX_SETTINGS_COUNT> entries_;
|
||||
size_t max_outstanding_settings_ = DEFAULT_MAX_SETTINGS;
|
||||
};
|
||||
|
||||
class Http2Priority {
|
||||
@ -792,6 +774,7 @@ class Http2Session : public AsyncWrap {
|
||||
~Http2Session() override;
|
||||
|
||||
class Http2Ping;
|
||||
class Http2Settings;
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
@ -841,9 +824,6 @@ class Http2Session : public AsyncWrap {
|
||||
// Removes a stream instance from this session
|
||||
inline void RemoveStream(int32_t id);
|
||||
|
||||
// Submits a SETTINGS frame to the connected peer.
|
||||
inline void Settings(const nghttp2_settings_entry iv[], size_t niv);
|
||||
|
||||
// Write data to the session
|
||||
inline ssize_t Write(const uv_buf_t* bufs, size_t nbufs);
|
||||
|
||||
@ -896,6 +876,9 @@ class Http2Session : public AsyncWrap {
|
||||
Http2Ping* PopPing();
|
||||
bool AddPing(Http2Ping* ping);
|
||||
|
||||
Http2Settings* PopSettings();
|
||||
bool AddSettings(Http2Settings* settings);
|
||||
|
||||
private:
|
||||
// Frame Padding Strategies
|
||||
inline ssize_t OnMaxFrameSizePadding(size_t frameLength,
|
||||
@ -1026,6 +1009,9 @@ class Http2Session : public AsyncWrap {
|
||||
size_t max_outstanding_pings_ = DEFAULT_MAX_PINGS;
|
||||
std::queue<Http2Ping*> outstanding_pings_;
|
||||
|
||||
size_t max_outstanding_settings_ = DEFAULT_MAX_SETTINGS;
|
||||
std::queue<Http2Settings*> outstanding_settings_;
|
||||
|
||||
std::vector<nghttp2_stream_write> outgoing_buffers_;
|
||||
std::vector<uint8_t> outgoing_storage_;
|
||||
|
||||
@ -1050,6 +1036,45 @@ class Http2Session::Http2Ping : public AsyncWrap {
|
||||
uint64_t startTime_;
|
||||
};
|
||||
|
||||
// The Http2Settings class is used to parse the settings passed in for
|
||||
// an Http2Session, converting those into an array of nghttp2_settings_entry
|
||||
// structs.
|
||||
class Http2Session::Http2Settings : public AsyncWrap {
|
||||
public:
|
||||
explicit Http2Settings(Environment* env);
|
||||
explicit Http2Settings(Http2Session* session);
|
||||
~Http2Settings();
|
||||
|
||||
size_t self_size() const override { return sizeof(*this); }
|
||||
|
||||
void Send();
|
||||
void Done(bool ack);
|
||||
|
||||
size_t length() const { return count_; }
|
||||
|
||||
nghttp2_settings_entry* operator*() {
|
||||
return *entries_;
|
||||
}
|
||||
|
||||
// Returns a Buffer instance with the serialized SETTINGS payload
|
||||
inline Local<Value> Pack();
|
||||
|
||||
// Resets the default values in the settings buffer
|
||||
static inline void RefreshDefaults(Environment* env);
|
||||
|
||||
// Update the local or remote settings for the given session
|
||||
static inline void Update(Environment* env,
|
||||
Http2Session* session,
|
||||
get_setting fn);
|
||||
|
||||
private:
|
||||
void Init();
|
||||
Http2Session* session_;
|
||||
uint64_t startTime_;
|
||||
size_t count_ = 0;
|
||||
MaybeStackBuffer<nghttp2_settings_entry, IDX_SETTINGS_COUNT> entries_;
|
||||
};
|
||||
|
||||
class ExternalHeader :
|
||||
public String::ExternalOneByteStringResource {
|
||||
public:
|
||||
|
@ -49,6 +49,7 @@ namespace http2 {
|
||||
IDX_OPTIONS_PADDING_STRATEGY,
|
||||
IDX_OPTIONS_MAX_HEADER_LIST_PAIRS,
|
||||
IDX_OPTIONS_MAX_OUTSTANDING_PINGS,
|
||||
IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS,
|
||||
IDX_OPTIONS_FLAGS
|
||||
};
|
||||
|
||||
|
@ -77,9 +77,7 @@ server.listen(
|
||||
// State will only be valid after connect event is emitted
|
||||
req.on('ready', common.mustCall(() => {
|
||||
assert.doesNotThrow(() => {
|
||||
client.settings({
|
||||
maxHeaderListSize: 1
|
||||
});
|
||||
client.settings({ maxHeaderListSize: 1 }, common.mustCall());
|
||||
});
|
||||
|
||||
// Verify valid error ranges
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
// Tests that attempting to send too many non-acknowledged
|
||||
// settings frames will result in a throw.
|
||||
// settings frames will result in an error
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
@ -9,53 +9,41 @@ if (!common.hasCrypto)
|
||||
const assert = require('assert');
|
||||
const h2 = require('http2');
|
||||
|
||||
const maxPendingAck = 2;
|
||||
const server = h2.createServer({ maxPendingAck: maxPendingAck + 1 });
|
||||
|
||||
let clients = 2;
|
||||
const maxOutstandingSettings = 2;
|
||||
|
||||
function doTest(session) {
|
||||
for (let n = 0; n < maxPendingAck; n++)
|
||||
assert.doesNotThrow(() => session.settings({ enablePush: false }));
|
||||
common.expectsError(() => session.settings({ enablePush: false }),
|
||||
{
|
||||
code: 'ERR_HTTP2_MAX_PENDING_SETTINGS_ACK',
|
||||
type: Error
|
||||
});
|
||||
session.on('error', common.expectsError({
|
||||
code: 'ERR_HTTP2_MAX_PENDING_SETTINGS_ACK',
|
||||
type: Error
|
||||
}));
|
||||
for (let n = 0; n < maxOutstandingSettings; n++) {
|
||||
session.settings({ enablePush: false });
|
||||
assert.strictEqual(session.pendingSettingsAck, true);
|
||||
}
|
||||
}
|
||||
|
||||
server.on('stream', common.mustNotCall());
|
||||
{
|
||||
const server = h2.createServer({ maxOutstandingSettings });
|
||||
server.on('stream', common.mustNotCall());
|
||||
server.once('session', common.mustCall((session) => doTest(session)));
|
||||
|
||||
server.once('session', common.mustCall((session) => doTest(session)));
|
||||
|
||||
server.listen(0);
|
||||
|
||||
const closeServer = common.mustCall(() => {
|
||||
if (--clients === 0)
|
||||
server.close();
|
||||
}, clients);
|
||||
|
||||
server.on('listening', common.mustCall(() => {
|
||||
const client = h2.connect(`http://localhost:${server.address().port}`,
|
||||
{ maxPendingAck: maxPendingAck + 1 });
|
||||
let remaining = maxPendingAck + 1;
|
||||
|
||||
client.on('close', closeServer);
|
||||
client.on('localSettings', common.mustCall(() => {
|
||||
if (--remaining <= 0) {
|
||||
client.close();
|
||||
}
|
||||
}, maxPendingAck + 1));
|
||||
client.on('connect', common.mustCall(() => doTest(client)));
|
||||
}));
|
||||
|
||||
// Setting maxPendingAck to 0, defaults it to 1
|
||||
server.on('listening', common.mustCall(() => {
|
||||
const client = h2.connect(`http://localhost:${server.address().port}`,
|
||||
{ maxPendingAck: 0 });
|
||||
|
||||
client.on('close', closeServer);
|
||||
client.on('localSettings', common.mustCall(() => {
|
||||
client.close();
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const client = h2.connect(`http://localhost:${server.address().port}`);
|
||||
// On some operating systems, an ECONNRESET error may be emitted.
|
||||
// On others it won't be. Do not make this a mustCall
|
||||
client.on('error', () => {});
|
||||
client.on('close', common.mustCall(() => server.close()));
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
const server = h2.createServer();
|
||||
server.on('stream', common.mustNotCall());
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const client = h2.connect(`http://localhost:${server.address().port}`,
|
||||
{ maxOutstandingSettings });
|
||||
client.on('connect', () => doTest(client));
|
||||
client.on('close', () => server.close());
|
||||
}));
|
||||
}
|
||||
|
@ -17,7 +17,8 @@ const IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS = 3;
|
||||
const IDX_OPTIONS_PADDING_STRATEGY = 4;
|
||||
const IDX_OPTIONS_MAX_HEADER_LIST_PAIRS = 5;
|
||||
const IDX_OPTIONS_MAX_OUTSTANDING_PINGS = 6;
|
||||
const IDX_OPTIONS_FLAGS = 7;
|
||||
const IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS = 7;
|
||||
const IDX_OPTIONS_FLAGS = 8;
|
||||
|
||||
{
|
||||
updateOptionsBuffer({
|
||||
@ -27,7 +28,8 @@ const IDX_OPTIONS_FLAGS = 7;
|
||||
peerMaxConcurrentStreams: 4,
|
||||
paddingStrategy: 5,
|
||||
maxHeaderListPairs: 6,
|
||||
maxOutstandingPings: 7
|
||||
maxOutstandingPings: 7,
|
||||
maxOutstandingSettings: 8
|
||||
});
|
||||
|
||||
strictEqual(optionsBuffer[IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE], 1);
|
||||
@ -37,6 +39,7 @@ const IDX_OPTIONS_FLAGS = 7;
|
||||
strictEqual(optionsBuffer[IDX_OPTIONS_PADDING_STRATEGY], 5);
|
||||
strictEqual(optionsBuffer[IDX_OPTIONS_MAX_HEADER_LIST_PAIRS], 6);
|
||||
strictEqual(optionsBuffer[IDX_OPTIONS_MAX_OUTSTANDING_PINGS], 7);
|
||||
strictEqual(optionsBuffer[IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS], 8);
|
||||
|
||||
const flags = optionsBuffer[IDX_OPTIONS_FLAGS];
|
||||
|
||||
@ -47,6 +50,7 @@ const IDX_OPTIONS_FLAGS = 7;
|
||||
ok(flags & (1 << IDX_OPTIONS_PADDING_STRATEGY));
|
||||
ok(flags & (1 << IDX_OPTIONS_MAX_HEADER_LIST_PAIRS));
|
||||
ok(flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_PINGS));
|
||||
ok(flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS));
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -25,6 +25,7 @@ const fixtures = require('../common/fixtures');
|
||||
delete providers.HTTP2SESSION;
|
||||
delete providers.HTTP2STREAM;
|
||||
delete providers.HTTP2PING;
|
||||
delete providers.HTTP2SETTINGS;
|
||||
|
||||
const obj_keys = Object.keys(providers);
|
||||
if (obj_keys.length > 0)
|
||||
|
Loading…
x
Reference in New Issue
Block a user