From dfe56847ac9d04c0ad3f2846cd39b39414079de7 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Wed, 1 Nov 2017 10:02:46 -0700 Subject: [PATCH] http2: refactor settings handling Add `Http2Seettings` utility class for handling settings logic and reducing code duplication. PR-URL: https://github.com/nodejs/node/pull/16668 Reviewed-By: Sebastiaan Deckers Reviewed-By: Anatoli Papirovski Reviewed-By: Khaidi Chu Reviewed-By: Matteo Collina --- lib/internal/http2/util.js | 4 +- src/node_http2.cc | 290 ++++++++++++++++--------------------- src/node_http2.h | 43 +++++- 3 files changed, 167 insertions(+), 170 deletions(-) diff --git a/lib/internal/http2/util.js b/lib/internal/http2/util.js index 4b6f8cc5c687a3..03d0639624083d 100644 --- a/lib/internal/http2/util.js +++ b/lib/internal/http2/util.js @@ -255,9 +255,9 @@ function getDefaultSettings() { function getSettings(session, remote) { const holder = Object.create(null); if (remote) - binding.refreshRemoteSettings(session); + session.refreshRemoteSettings(); else - binding.refreshLocalSettings(session); + session.refreshLocalSettings(); holder.headerTableSize = settingsBuffer[IDX_SETTINGS_HEADER_TABLE_SIZE]; diff --git a/src/node_http2.cc b/src/node_http2.cc index 744d55078501a8..cc8afec207ffba 100644 --- a/src/node_http2.cc +++ b/src/node_http2.cc @@ -69,6 +69,117 @@ Http2Options::Http2Options(Environment* env) { } } +Http2Settings::Http2Settings(Environment* env) : env_(env) { + entries_.AllocateSufficientStorage(IDX_SETTINGS_COUNT); + AliasedBuffer& 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("Setting header table size: %d\n", val); + entries_[n].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + entries_[n].value = val; + n++; + } + + if (flags & (1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS)) { + uint32_t val = buffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS]; + DEBUG_HTTP2("Setting max concurrent streams: %d\n", val); + entries_[n].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + entries_[n].value = val; + n++; + } + + if (flags & (1 << IDX_SETTINGS_MAX_FRAME_SIZE)) { + uint32_t val = buffer[IDX_SETTINGS_MAX_FRAME_SIZE]; + DEBUG_HTTP2("Setting max frame size: %d\n", val); + entries_[n].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + entries_[n].value = val; + n++; + } + + if (flags & (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE)) { + uint32_t val = buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE]; + DEBUG_HTTP2("Setting initial window size: %d\n", val); + entries_[n].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + entries_[n].value = val; + n++; + } + + if (flags & (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE)) { + uint32_t val = buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE]; + DEBUG_HTTP2("Setting max header list size: %d\n", val); + entries_[n].settings_id = NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE; + entries_[n].value = val; + n++; + } + + if (flags & (1 << IDX_SETTINGS_ENABLE_PUSH)) { + uint32_t val = buffer[IDX_SETTINGS_ENABLE_PUSH]; + DEBUG_HTTP2("Setting enable push: %d\n", val); + entries_[n].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; + entries_[n].value = val; + n++; + } + + count_ = n; +} + +inline Local Http2Settings::Pack() { + const size_t len = count_ * 6; + Local buf = Buffer::New(env_, len).ToLocalChecked(); + ssize_t ret = + nghttp2_pack_settings_payload( + reinterpret_cast(Buffer::Data(buf)), len, + *entries_, count_); + if (ret >= 0) + return buf; + else + return Undefined(env_->isolate()); +} + +inline void Http2Settings::Update(Environment* env, + Http2Session* session, + get_setting fn) { + AliasedBuffer& buffer = + env->http2_state()->settings_buffer; + buffer[IDX_SETTINGS_HEADER_TABLE_SIZE] = + fn(session->session(), NGHTTP2_SETTINGS_HEADER_TABLE_SIZE); + buffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS] = + fn(session->session(), NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); + buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] = + fn(session->session(), NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE); + buffer[IDX_SETTINGS_MAX_FRAME_SIZE] = + fn(session->session(), NGHTTP2_SETTINGS_MAX_FRAME_SIZE); + buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE] = + fn(session->session(), NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE); + buffer[IDX_SETTINGS_ENABLE_PUSH] = + fn(session->session(), NGHTTP2_SETTINGS_ENABLE_PUSH); +} + + +inline void Http2Settings::RefreshDefaults(Environment* env) { + AliasedBuffer& buffer = + env->http2_state()->settings_buffer; + + buffer[IDX_SETTINGS_HEADER_TABLE_SIZE] = + DEFAULT_SETTINGS_HEADER_TABLE_SIZE; + buffer[IDX_SETTINGS_ENABLE_PUSH] = + DEFAULT_SETTINGS_ENABLE_PUSH; + buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] = + DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE; + buffer[IDX_SETTINGS_MAX_FRAME_SIZE] = + DEFAULT_SETTINGS_MAX_FRAME_SIZE; + buffer[IDX_SETTINGS_COUNT] = + (1 << IDX_SETTINGS_HEADER_TABLE_SIZE) | + (1 << IDX_SETTINGS_ENABLE_PUSH) | + (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE) | + (1 << IDX_SETTINGS_MAX_FRAME_SIZE); +} + Http2Session::Http2Session(Environment* env, Local wrap, @@ -178,119 +289,24 @@ void HttpErrorString(const FunctionCallbackInfo& args) { // output for an HTTP2-Settings header field. void PackSettings(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - HandleScope scope(env->isolate()); - - std::vector entries; - entries.reserve(6); - - AliasedBuffer& buffer = - env->http2_state()->settings_buffer; - uint32_t flags = buffer[IDX_SETTINGS_COUNT]; - - if (flags & (1 << IDX_SETTINGS_HEADER_TABLE_SIZE)) { - DEBUG_HTTP2("Setting header table size: %d\n", - static_cast(buffer[IDX_SETTINGS_HEADER_TABLE_SIZE])); - entries.push_back({NGHTTP2_SETTINGS_HEADER_TABLE_SIZE, - buffer[IDX_SETTINGS_HEADER_TABLE_SIZE]}); - } - - if (flags & (1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS)) { - DEBUG_HTTP2("Setting max concurrent streams: %d\n", - static_cast( - buffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS])); - entries.push_back({NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, - buffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS]}); - } - - if (flags & (1 << IDX_SETTINGS_MAX_FRAME_SIZE)) { - DEBUG_HTTP2("Setting max frame size: %d\n", - static_cast(buffer[IDX_SETTINGS_MAX_FRAME_SIZE])); - entries.push_back({NGHTTP2_SETTINGS_MAX_FRAME_SIZE, - buffer[IDX_SETTINGS_MAX_FRAME_SIZE]}); - } - - if (flags & (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE)) { - DEBUG_HTTP2("Setting initial window size: %d\n", - static_cast( - buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE])); - entries.push_back({NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, - buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE]}); - } - - if (flags & (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE)) { - DEBUG_HTTP2("Setting max header list size: %d\n", - static_cast( - buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE])); - entries.push_back({NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, - buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE]}); - } - - if (flags & (1 << IDX_SETTINGS_ENABLE_PUSH)) { - DEBUG_HTTP2("Setting enable push: %d\n", - static_cast(buffer[IDX_SETTINGS_ENABLE_PUSH])); - entries.push_back({NGHTTP2_SETTINGS_ENABLE_PUSH, - buffer[IDX_SETTINGS_ENABLE_PUSH]}); - } - - const size_t len = entries.size() * 6; - MaybeStackBuffer buf(len); - ssize_t ret = - nghttp2_pack_settings_payload( - reinterpret_cast(*buf), len, &entries[0], entries.size()); - if (ret >= 0) { - args.GetReturnValue().Set( - Buffer::Copy(env, *buf, len).ToLocalChecked()); - } + Http2Settings settings(env); + args.GetReturnValue().Set(settings.Pack()); } // Used to fill in the spec defined initial values for each setting. void RefreshDefaultSettings(const FunctionCallbackInfo& args) { DEBUG_HTTP2("Http2Session: refreshing default settings\n"); Environment* env = Environment::GetCurrent(args); - AliasedBuffer& buffer = - env->http2_state()->settings_buffer; - - buffer[IDX_SETTINGS_HEADER_TABLE_SIZE] = - DEFAULT_SETTINGS_HEADER_TABLE_SIZE; - buffer[IDX_SETTINGS_ENABLE_PUSH] = - DEFAULT_SETTINGS_ENABLE_PUSH; - buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] = - DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE; - buffer[IDX_SETTINGS_MAX_FRAME_SIZE] = - DEFAULT_SETTINGS_MAX_FRAME_SIZE; - buffer[IDX_SETTINGS_COUNT] = - (1 << IDX_SETTINGS_HEADER_TABLE_SIZE) | - (1 << IDX_SETTINGS_ENABLE_PUSH) | - (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE) | - (1 << IDX_SETTINGS_MAX_FRAME_SIZE); + Http2Settings::RefreshDefaults(env); } template -void RefreshSettings(const FunctionCallbackInfo& args) { +void Http2Session::RefreshSettings(const FunctionCallbackInfo& args) { DEBUG_HTTP2("Http2Session: refreshing settings for session\n"); Environment* env = Environment::GetCurrent(args); -#if defined(DEBUG) && DEBUG - CHECK_EQ(args.Length(), 1); - CHECK(args[0]->IsObject()); -#endif Http2Session* session; - ASSIGN_OR_RETURN_UNWRAP(&session, args[0].As()); - nghttp2_session* s = session->session(); - - AliasedBuffer& buffer = - env->http2_state()->settings_buffer; - buffer[IDX_SETTINGS_HEADER_TABLE_SIZE] = - fn(s, NGHTTP2_SETTINGS_HEADER_TABLE_SIZE); - buffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS] = - fn(s, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); - buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] = - fn(s, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE); - buffer[IDX_SETTINGS_MAX_FRAME_SIZE] = - fn(s, NGHTTP2_SETTINGS_MAX_FRAME_SIZE); - buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE] = - fn(s, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE); - buffer[IDX_SETTINGS_ENABLE_PUSH] = - fn(s, NGHTTP2_SETTINGS_ENABLE_PUSH); + ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); + Http2Settings::Update(env, session, fn); } // Used to fill in the spec defined initial values for each setting. @@ -460,65 +476,9 @@ void Http2Session::SubmitSettings(const FunctionCallbackInfo& args) { ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); Environment* env = session->env(); - AliasedBuffer& buffer = - env->http2_state()->settings_buffer; - uint32_t flags = buffer[IDX_SETTINGS_COUNT]; - - std::vector entries; - entries.reserve(6); - - if (flags & (1 << IDX_SETTINGS_HEADER_TABLE_SIZE)) { - DEBUG_HTTP2("Setting header table size: %d\n", - static_cast(buffer[IDX_SETTINGS_HEADER_TABLE_SIZE])); - entries.push_back({NGHTTP2_SETTINGS_HEADER_TABLE_SIZE, - buffer[IDX_SETTINGS_HEADER_TABLE_SIZE]}); - } - - if (flags & (1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS)) { - DEBUG_HTTP2("Setting max concurrent streams: %d\n", - static_cast( - buffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS])); - entries.push_back({NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, - buffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS]}); - } - - if (flags & (1 << IDX_SETTINGS_MAX_FRAME_SIZE)) { - DEBUG_HTTP2("Setting max frame size: %d\n", - static_cast(buffer[IDX_SETTINGS_MAX_FRAME_SIZE])); - entries.push_back({NGHTTP2_SETTINGS_MAX_FRAME_SIZE, - buffer[IDX_SETTINGS_MAX_FRAME_SIZE]}); - } - - if (flags & (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE)) { - DEBUG_HTTP2("Setting initial window size: %d\n", - static_cast( - buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE])); - entries.push_back({NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, - buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE]}); - } - - if (flags & (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE)) { - DEBUG_HTTP2("Setting max header list size: %d\n", - static_cast( - buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE])); - entries.push_back({NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, - buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE]}); - } - - if (flags & (1 << IDX_SETTINGS_ENABLE_PUSH)) { - DEBUG_HTTP2("Setting enable push: %d\n", - static_cast(buffer[IDX_SETTINGS_ENABLE_PUSH])); - entries.push_back({NGHTTP2_SETTINGS_ENABLE_PUSH, - buffer[IDX_SETTINGS_ENABLE_PUSH]}); - } - - if (entries.size() > 0) { - args.GetReturnValue().Set( - session->Nghttp2Session::SubmitSettings(&entries[0], entries.size())); - } else { - args.GetReturnValue().Set( - session->Nghttp2Session::SubmitSettings(nullptr, 0)); - } + Http2Settings settings(env); + args.GetReturnValue().Set( + session->Nghttp2Session::SubmitSettings(*settings, settings.length())); } void Http2Session::SubmitRstStream(const FunctionCallbackInfo& args) { @@ -1327,6 +1287,12 @@ void Initialize(Local target, Http2Session::FlushData); env->SetProtoMethod(session, "updateChunksSent", Http2Session::UpdateChunksSent); + env->SetProtoMethod( + session, "refreshLocalSettings", + Http2Session::RefreshSettings); + env->SetProtoMethod( + session, "refreshRemoteSettings", + Http2Session::RefreshSettings); StreamBase::AddMethods(env, session, StreamBase::kFlagHasWritev | StreamBase::kFlagNoShutdown); @@ -1416,10 +1382,6 @@ HTTP_KNOWN_METHODS(STRING_CONSTANT) HTTP_STATUS_CODES(V) #undef V - env->SetMethod(target, "refreshLocalSettings", - RefreshSettings); - env->SetMethod(target, "refreshRemoteSettings", - RefreshSettings); env->SetMethod(target, "refreshDefaultSettings", RefreshDefaultSettings); env->SetMethod(target, "refreshSessionState", RefreshSessionState); env->SetMethod(target, "refreshStreamState", RefreshStreamState); diff --git a/src/node_http2.h b/src/node_http2.h index 1006dff3ced70b..84cffc613eff64 100644 --- a/src/node_http2.h +++ b/src/node_http2.h @@ -299,6 +299,14 @@ const char* nghttp2_errname(int rv) { #define MIN_MAX_FRAME_SIZE DEFAULT_SETTINGS_MAX_FRAME_SIZE #define MAX_INITIAL_WINDOW_SIZE 2147483647 +// This allows for 4 default-sized frames with their frame headers +static const size_t kAllocBufferSize = 4 * (16384 + 9); + +typedef uint32_t(*get_setting)(nghttp2_session* session, + nghttp2_settings_id id); + +class Http2Session; + // The Http2Options class is used to parse the options object passed in to // a Http2Session object and convert those into an appropriate nghttp2_option // struct. This is the primary mechanism by which the Http2Session object is @@ -331,11 +339,35 @@ class Http2Options { padding_strategy_type padding_strategy_ = PADDING_STRATEGY_NONE; }; -// This allows for 4 default-sized frames with their frame headers -static const size_t kAllocBufferSize = 4 * (16384 + 9); +// 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); -typedef uint32_t(*get_setting)(nghttp2_session* session, - nghttp2_settings_id id); + size_t length() const { return count_; } + + nghttp2_settings_entry* operator*() { + return *entries_; + } + + // Returns a Buffer instance with the serialized SETTINGS payload + inline Local 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 entries_; +}; class Http2Session : public AsyncWrap, public StreamBase, @@ -463,6 +495,9 @@ class Http2Session : public AsyncWrap, static void FlushData(const FunctionCallbackInfo& args); static void UpdateChunksSent(const FunctionCallbackInfo& args); + template + static void RefreshSettings(const FunctionCallbackInfo& args); + template static void GetSettings(const FunctionCallbackInfo& args);