diff --git a/gennghttpxfun.py b/gennghttpxfun.py index fc161ad3..d2f04143 100755 --- a/gennghttpxfun.py +++ b/gennghttpxfun.py @@ -142,6 +142,10 @@ OPTIONS = [ "frontend-http2-connection-window-size", "backend-http2-window-size", "backend-http2-connection-window-size", + "frontend-http2-encoder-dynamic-table-size", + "frontend-http2-decoder-dynamic-table-size", + "backend-http2-encoder-dynamic-table-size", + "backend-http2-decoder-dynamic-table-size", ] LOGVARS = [ diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 59615fb4..c7a75d57 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -2532,6 +2532,19 @@ NGHTTP2_EXTERN void nghttp2_option_set_max_send_header_block_length(nghttp2_option *option, size_t val); +/** + * @function + * + * This option sets the maximum dynamic table size for deflating + * header fields. The default value is 4KiB. In HTTP/2, receiver of + * deflated header block can specify maximum dynamic table size. The + * actual maximum size is the minimum of the size receiver specified + * and this option value. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_max_deflate_dynamic_table_size(nghttp2_option *option, + size_t val); + /** * @function * @@ -4583,7 +4596,7 @@ typedef struct nghttp2_hd_deflater nghttp2_hd_deflater; * * Initializes |*deflater_ptr| for deflating name/values pairs. * - * The |deflate_hd_table_bufsize_max| is the upper bound of header + * The |max_deflate_dynamic_table_size| is the upper bound of header * table size the deflater will use. * * If this function fails, |*deflater_ptr| is left untouched. @@ -4594,8 +4607,9 @@ typedef struct nghttp2_hd_deflater nghttp2_hd_deflater; * :enum:`NGHTTP2_ERR_NOMEM` * Out of memory. */ -NGHTTP2_EXTERN int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr, - size_t deflate_hd_table_bufsize_max); +NGHTTP2_EXTERN int +nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr, + size_t max_deflate_dynamic_table_size); /** * @function @@ -4612,9 +4626,10 @@ NGHTTP2_EXTERN int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr, * The library code does not refer to |mem| pointer after this * function returns, so the application can safely free it. */ -NGHTTP2_EXTERN int nghttp2_hd_deflate_new2(nghttp2_hd_deflater **deflater_ptr, - size_t deflate_hd_table_bufsize_max, - nghttp2_mem *mem); +NGHTTP2_EXTERN int +nghttp2_hd_deflate_new2(nghttp2_hd_deflater **deflater_ptr, + size_t max_deflate_dynamic_table_size, + nghttp2_mem *mem); /** * @function @@ -4627,18 +4642,18 @@ NGHTTP2_EXTERN void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater); * @function * * Changes header table size of the |deflater| to - * |settings_hd_table_bufsize_max| bytes. This may trigger eviction + * |settings_max_dynamic_table_size| bytes. This may trigger eviction * in the dynamic table. * - * The |settings_hd_table_bufsize_max| should be the value received in - * SETTINGS_HEADER_TABLE_SIZE. + * The |settings_max_dynamic_table_size| should be the value received + * in SETTINGS_HEADER_TABLE_SIZE. * * The deflater never uses more memory than - * ``deflate_hd_table_bufsize_max`` bytes specified in + * ``max_deflate_dynamic_table_size`` bytes specified in * `nghttp2_hd_deflate_new()`. Therefore, if - * |settings_hd_table_bufsize_max| > ``deflate_hd_table_bufsize_max``, - * resulting maximum table size becomes - * ``deflate_hd_table_bufsize_max``. + * |settings_max_dynamic_table_size| > + * ``max_deflate_dynamic_table_size``, resulting maximum table size + * becomes ``max_deflate_dynamic_table_size``. * * This function returns 0 if it succeeds, or one of the following * negative error codes: @@ -4648,7 +4663,7 @@ NGHTTP2_EXTERN void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater); */ NGHTTP2_EXTERN int nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater, - size_t settings_hd_table_bufsize_max); + size_t settings_max_dynamic_table_size); /** * @function @@ -4819,8 +4834,8 @@ NGHTTP2_EXTERN void nghttp2_hd_inflate_del(nghttp2_hd_inflater *inflater); * Changes header table size in the |inflater|. This may trigger * eviction in the dynamic table. * - * The |settings_hd_table_bufsize_max| should be the value transmitted - * in SETTINGS_HEADER_TABLE_SIZE. + * The |settings_max_dynamic_table_size| should be the value + * transmitted in SETTINGS_HEADER_TABLE_SIZE. * * This function must not be called while header block is being * inflated. In other words, this function must be called after @@ -4841,7 +4856,7 @@ NGHTTP2_EXTERN void nghttp2_hd_inflate_del(nghttp2_hd_inflater *inflater); */ NGHTTP2_EXTERN int nghttp2_hd_inflate_change_table_size(nghttp2_hd_inflater *inflater, - size_t settings_hd_table_bufsize_max); + size_t settings_max_dynamic_table_size); /** * @enum diff --git a/lib/nghttp2_hd.c b/lib/nghttp2_hd.c index 275c966c..87abb3c4 100644 --- a/lib/nghttp2_hd.c +++ b/lib/nghttp2_hd.c @@ -684,7 +684,7 @@ int nghttp2_hd_deflate_init(nghttp2_hd_deflater *deflater, nghttp2_mem *mem) { } int nghttp2_hd_deflate_init2(nghttp2_hd_deflater *deflater, - size_t deflate_hd_table_bufsize_max, + size_t max_deflate_dynamic_table_size, nghttp2_mem *mem) { int rv; rv = hd_context_init(&deflater->ctx, mem); @@ -694,14 +694,14 @@ int nghttp2_hd_deflate_init2(nghttp2_hd_deflater *deflater, hd_map_init(&deflater->map); - if (deflate_hd_table_bufsize_max < NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE) { + if (max_deflate_dynamic_table_size < NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE) { deflater->notify_table_size_change = 1; - deflater->ctx.hd_table_bufsize_max = deflate_hd_table_bufsize_max; + deflater->ctx.hd_table_bufsize_max = max_deflate_dynamic_table_size; } else { deflater->notify_table_size_change = 0; } - deflater->deflate_hd_table_bufsize_max = deflate_hd_table_bufsize_max; + deflater->deflate_hd_table_bufsize_max = max_deflate_dynamic_table_size; deflater->min_hd_table_bufsize_max = UINT32_MAX; return 0; @@ -1225,9 +1225,9 @@ static void hd_context_shrink_table_size(nghttp2_hd_context *context, } } -int nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater, - size_t settings_hd_table_bufsize_max) { - size_t next_bufsize = nghttp2_min(settings_hd_table_bufsize_max, +int nghttp2_hd_deflate_change_table_size( + nghttp2_hd_deflater *deflater, size_t settings_max_dynamic_table_size) { + size_t next_bufsize = nghttp2_min(settings_max_dynamic_table_size, deflater->deflate_hd_table_bufsize_max); deflater->ctx.hd_table_bufsize_max = next_bufsize; @@ -1241,8 +1241,8 @@ int nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater, return 0; } -int nghttp2_hd_inflate_change_table_size(nghttp2_hd_inflater *inflater, - size_t settings_hd_table_bufsize_max) { +int nghttp2_hd_inflate_change_table_size( + nghttp2_hd_inflater *inflater, size_t settings_max_dynamic_table_size) { switch (inflater->state) { case NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE: case NGHTTP2_HD_STATE_INFLATE_START: @@ -1258,16 +1258,16 @@ int nghttp2_hd_inflate_change_table_size(nghttp2_hd_inflater *inflater, strictly smaller than the current negotiated maximum size, encoder must send dynamic table size update. In other cases, we cannot expect it to do so. */ - if (inflater->ctx.hd_table_bufsize_max > settings_hd_table_bufsize_max) { + if (inflater->ctx.hd_table_bufsize_max > settings_max_dynamic_table_size) { inflater->state = NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE; /* Remember minimum value, and validate that encoder sends the value less than or equal to this. */ - inflater->min_hd_table_bufsize_max = settings_hd_table_bufsize_max; + inflater->min_hd_table_bufsize_max = settings_max_dynamic_table_size; } - inflater->settings_hd_table_bufsize_max = settings_hd_table_bufsize_max; + inflater->settings_hd_table_bufsize_max = settings_max_dynamic_table_size; - inflater->ctx.hd_table_bufsize_max = settings_hd_table_bufsize_max; + inflater->ctx.hd_table_bufsize_max = settings_max_dynamic_table_size; hd_context_shrink_table_size(&inflater->ctx, NULL); return 0; diff --git a/lib/nghttp2_hd.h b/lib/nghttp2_hd.h index 1da9eb58..42b07c68 100644 --- a/lib/nghttp2_hd.h +++ b/lib/nghttp2_hd.h @@ -288,7 +288,7 @@ int nghttp2_hd_deflate_init(nghttp2_hd_deflater *deflater, nghttp2_mem *mem); /* * Initializes |deflater| for deflating name/values pairs. * - * The encoder only uses up to |deflate_hd_table_bufsize_max| bytes + * The encoder only uses up to |max_deflate_dynamic_table_size| bytes * for header table even if the larger value is specified later in * nghttp2_hd_change_table_size(). * @@ -299,7 +299,7 @@ int nghttp2_hd_deflate_init(nghttp2_hd_deflater *deflater, nghttp2_mem *mem); * Out of memory. */ int nghttp2_hd_deflate_init2(nghttp2_hd_deflater *deflater, - size_t deflate_hd_table_bufsize_max, + size_t max_deflate_dynamic_table_size, nghttp2_mem *mem); /* diff --git a/lib/nghttp2_option.c b/lib/nghttp2_option.c index 860c9f17..77e7db54 100644 --- a/lib/nghttp2_option.c +++ b/lib/nghttp2_option.c @@ -101,3 +101,9 @@ void nghttp2_option_set_max_send_header_block_length(nghttp2_option *option, option->opt_set_mask |= NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH; option->max_send_header_block_length = val; } + +void nghttp2_option_set_max_deflate_dynamic_table_size(nghttp2_option *option, + size_t val) { + option->opt_set_mask |= NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE; + option->max_deflate_dynamic_table_size = val; +} diff --git a/lib/nghttp2_option.h b/lib/nghttp2_option.h index fb79b920..e750d1be 100644 --- a/lib/nghttp2_option.h +++ b/lib/nghttp2_option.h @@ -64,6 +64,7 @@ typedef enum { NGHTTP2_OPT_NO_AUTO_PING_ACK = 1 << 6, NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES = 1 << 7, NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH = 1 << 8, + NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE = 1 << 9, } nghttp2_option_flag; /** @@ -74,6 +75,10 @@ struct nghttp2_option { * NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH */ size_t max_send_header_block_length; + /** + * NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE + */ + size_t max_deflate_dynamic_table_size; /** * Bitwise OR of nghttp2_option_flag to determine that which fields * are specified. diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index da5a6e55..a4123b48 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -390,6 +390,8 @@ static int session_new(nghttp2_session **session_ptr, const nghttp2_option *option, nghttp2_mem *mem) { int rv; size_t nbuffer; + size_t max_deflate_dynamic_table_size = + NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE; if (mem == NULL) { mem = nghttp2_mem_default(); @@ -407,19 +409,6 @@ static int session_new(nghttp2_session **session_ptr, /* next_stream_id is initialized in either nghttp2_session_client_new2 or nghttp2_session_server_new2 */ - rv = nghttp2_hd_deflate_init(&(*session_ptr)->hd_deflater, mem); - if (rv != 0) { - goto fail_hd_deflater; - } - rv = nghttp2_hd_inflate_init(&(*session_ptr)->hd_inflater, mem); - if (rv != 0) { - goto fail_hd_inflater; - } - rv = nghttp2_map_init(&(*session_ptr)->streams, mem); - if (rv != 0) { - goto fail_map; - } - nghttp2_stream_init(&(*session_ptr)->root, 0, NGHTTP2_STREAM_FLAG_NONE, NGHTTP2_STREAM_IDLE, NGHTTP2_DEFAULT_WEIGHT, 0, 0, NULL, mem); @@ -502,6 +491,24 @@ static int session_new(nghttp2_session **session_ptr, (*session_ptr)->max_send_header_block_length = option->max_send_header_block_length; } + + if (option->opt_set_mask & NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE) { + max_deflate_dynamic_table_size = option->max_deflate_dynamic_table_size; + } + } + + rv = nghttp2_hd_deflate_init2(&(*session_ptr)->hd_deflater, + max_deflate_dynamic_table_size, mem); + if (rv != 0) { + goto fail_hd_deflater; + } + rv = nghttp2_hd_inflate_init(&(*session_ptr)->hd_inflater, mem); + if (rv != 0) { + goto fail_hd_inflater; + } + rv = nghttp2_map_init(&(*session_ptr)->streams, mem); + if (rv != 0) { + goto fail_map; } nbuffer = ((*session_ptr)->max_send_header_block_length + diff --git a/src/HttpServer.cc b/src/HttpServer.cc index 5865a204..4fe74fde 100644 --- a/src/HttpServer.cc +++ b/src/HttpServer.cc @@ -96,6 +96,7 @@ Config::Config() num_worker(1), max_concurrent_streams(100), header_table_size(-1), + encoder_header_table_size(-1), window_bits(-1), connection_window_bits(-1), port(0), @@ -231,6 +232,7 @@ public: config_(config), ssl_ctx_(ssl_ctx), callbacks_(nullptr), + option_(nullptr), next_session_id_(1), tstamp_cached_(ev_now(loop)), cached_date_(util::http_date(tstamp_cached_)) { @@ -238,6 +240,13 @@ public: fill_callback(callbacks_, config_); + nghttp2_option_new(&option_); + + if (config_->encoder_header_table_size != -1) { + nghttp2_option_set_max_deflate_dynamic_table_size( + option_, config_->encoder_header_table_size); + } + ev_timer_init(&release_fd_timer_, release_fd_cb, 0., RELEASE_FD_TIMEOUT); release_fd_timer_.data = this; } @@ -246,6 +255,7 @@ public: for (auto handler : handlers_) { delete handler; } + nghttp2_option_del(option_); nghttp2_session_callbacks_del(callbacks_); } void add_handler(Http2Handler *handler) { handlers_.insert(handler); } @@ -283,6 +293,7 @@ public: return session_id; } const nghttp2_session_callbacks *get_callbacks() const { return callbacks_; } + const nghttp2_option *get_option() const { return option_; } void accept_connection(int fd) { util::make_socket_nodelay(fd); SSL *ssl = nullptr; @@ -408,6 +419,7 @@ private: const Config *config_; SSL_CTX *ssl_ctx_; nghttp2_session_callbacks *callbacks_; + nghttp2_option *option_; ev_timer release_fd_timer_; int64_t next_session_id_; ev_tstamp tstamp_cached_; @@ -825,7 +837,8 @@ int Http2Handler::on_write() { return write_(*this); } int Http2Handler::connection_made() { int r; - r = nghttp2_session_server_new(&session_, sessions_->get_callbacks(), this); + r = nghttp2_session_server_new2(&session_, sessions_->get_callbacks(), this, + sessions_->get_option()); if (r != 0) { return r; diff --git a/src/HttpServer.h b/src/HttpServer.h index 7e4819d9..db2137b3 100644 --- a/src/HttpServer.h +++ b/src/HttpServer.h @@ -69,6 +69,7 @@ struct Config { size_t num_worker; size_t max_concurrent_streams; ssize_t header_table_size; + ssize_t encoder_header_table_size; int window_bits; int connection_window_bits; uint16_t port; diff --git a/src/nghttp.cc b/src/nghttp.cc index 988d1b48..2db91a51 100644 --- a/src/nghttp.cc +++ b/src/nghttp.cc @@ -95,6 +95,7 @@ constexpr auto anchors = std::array{{ Config::Config() : header_table_size(-1), min_header_table_size(std::numeric_limits::max()), + encoder_header_table_size(-1), padding(0), max_concurrent_streams(100), peer_max_concurrent_streams(100), @@ -2619,6 +2620,11 @@ Options: the last value, that minimum value is set in SETTINGS frame payload before the last value, to simulate multiple header table size change. + --encoder-header-table-size= + Specify encoder header table size. The decoder (server) + specifies the maximum dynamic table size it accepts. + Then the negotiated dynamic table size is the minimum of + this option value and the value which server specified. -b, --padding= Add at most bytes to a frame payload as padding. Specify 0 to disable padding. @@ -2695,6 +2701,7 @@ int main(int argc, char **argv) { {"no-push", no_argument, &flag, 11}, {"max-concurrent-streams", required_argument, &flag, 12}, {"expect-continue", no_argument, &flag, 13}, + {"encoder-header-table-size", required_argument, &flag, 14}, {nullptr, 0, nullptr, 0}}; int option_index = 0; int c = getopt_long(argc, argv, "M:Oab:c:d:gm:np:r:hH:vst:uw:W:", @@ -2813,16 +2820,21 @@ int main(int argc, char **argv) { case 'm': config.multiply = strtoul(optarg, nullptr, 10); break; - case 'c': - errno = 0; - config.header_table_size = util::parse_uint_with_unit(optarg); - if (config.header_table_size == -1) { + case 'c': { + auto n = util::parse_uint_with_unit(optarg); + if (n == -1) { std::cerr << "-c: Bad option value: " << optarg << std::endl; exit(EXIT_FAILURE); } - config.min_header_table_size = - std::min(config.min_header_table_size, config.header_table_size); + if (n > std::numeric_limits::max()) { + std::cerr << "-c: Value too large. It should be less than or equal to " + << std::numeric_limits::max() << std::endl; + exit(EXIT_FAILURE); + } + config.header_table_size = n; + config.min_header_table_size = std::min(config.min_header_table_size, n); break; + } case '?': util::show_candidates(argv[optind - 1], long_options); exit(EXIT_FAILURE); @@ -2896,6 +2908,23 @@ int main(int argc, char **argv) { // expect-continue option config.expect_continue = true; break; + case 14: { + // encoder-header-table-size option + auto n = util::parse_uint_with_unit(optarg); + if (n == -1) { + std::cerr << "--encoder-header-table-size: Bad option value: " + << optarg << std::endl; + exit(EXIT_FAILURE); + } + if (n > std::numeric_limits::max()) { + std::cerr << "--encoder-header-table-size: Value too large. It " + "should be less than or equal to " + << std::numeric_limits::max() << std::endl; + exit(EXIT_FAILURE); + } + config.encoder_header_table_size = n; + break; + } } break; default: @@ -2916,6 +2945,11 @@ int main(int argc, char **argv) { nghttp2_option_set_peer_max_concurrent_streams( config.http2_option, config.peer_max_concurrent_streams); + if (config.encoder_header_table_size != -1) { + nghttp2_option_set_max_deflate_dynamic_table_size( + config.http2_option, config.encoder_header_table_size); + } + struct sigaction act {}; act.sa_handler = SIG_IGN; sigaction(SIGPIPE, &act, nullptr); diff --git a/src/nghttp.h b/src/nghttp.h index b45cf614..eb36a4ce 100644 --- a/src/nghttp.h +++ b/src/nghttp.h @@ -72,6 +72,7 @@ struct Config { nghttp2_option *http2_option; int64_t header_table_size; int64_t min_header_table_size; + int64_t encoder_header_table_size; size_t padding; size_t max_concurrent_streams; ssize_t peer_max_concurrent_streams; diff --git a/src/nghttpd.cc b/src/nghttpd.cc index 518308b8..9378b283 100644 --- a/src/nghttpd.cc +++ b/src/nghttpd.cc @@ -124,6 +124,11 @@ Options: --no-tls Disable SSL/TLS. -c, --header-table-size= Specify decoder header table size. + --encoder-header-table-size= + Specify encoder header table size. The decoder (client) + specifies the maximum dynamic table size it accepts. + Then the negotiated dynamic table size is the minimum of + this option value and the value which client specified. --color Force colored log output. -p, --push== Push resources s when is requested. @@ -219,6 +224,7 @@ int main(int argc, char **argv) { {"echo-upload", no_argument, &flag, 8}, {"mime-types-file", required_argument, &flag, 9}, {"no-content-length", no_argument, &flag, 10}, + {"encoder-header-table-size", required_argument, &flag, 11}, {nullptr, 0, nullptr, 0}}; int option_index = 0; int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:w:W:", long_options, @@ -276,14 +282,20 @@ int main(int argc, char **argv) { case 'v': config.verbose = true; break; - case 'c': - errno = 0; - config.header_table_size = util::parse_uint_with_unit(optarg); - if (config.header_table_size == -1) { + case 'c': { + auto n = util::parse_uint_with_unit(optarg); + if (n == -1) { std::cerr << "-c: Bad option value: " << optarg << std::endl; exit(EXIT_FAILURE); } + if (n > std::numeric_limits::max()) { + std::cerr << "-c: Value too large. It should be less than or equal to " + << std::numeric_limits::max() << std::endl; + exit(EXIT_FAILURE); + } + config.header_table_size = n; break; + } case 'p': if (parse_push_config(config, optarg) != 0) { std::cerr << "-p: Bad option value: " << optarg << std::endl; @@ -375,6 +387,23 @@ int main(int argc, char **argv) { // no-content-length option config.no_content_length = true; break; + case 11: { + // encoder-header-table-size option + auto n = util::parse_uint_with_unit(optarg); + if (n == -1) { + std::cerr << "--encoder-header-table-size: Bad option value: " + << optarg << std::endl; + exit(EXIT_FAILURE); + } + if (n > std::numeric_limits::max()) { + std::cerr << "--encoder-header-table-size: Value too large. It " + "should be less than or equal to " + << std::numeric_limits::max() << std::endl; + exit(EXIT_FAILURE); + } + config.encoder_header_table_size = n; + break; + } } break; default: diff --git a/src/shrpx.cc b/src/shrpx.cc index 8d70f630..9d601807 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -1345,14 +1345,21 @@ void fill_default_config(Config *config) { upstreamconf.connection_window_size = 64_k - 1; upstreamconf.max_concurrent_streams = 100; + upstreamconf.encoder_dynamic_table_size = 4_k; + upstreamconf.decoder_dynamic_table_size = 4_k; + nghttp2_option_new(&upstreamconf.option); nghttp2_option_set_no_auto_window_update(upstreamconf.option, 1); nghttp2_option_set_no_recv_client_magic(upstreamconf.option, 1); + nghttp2_option_set_max_deflate_dynamic_table_size( + upstreamconf.option, upstreamconf.encoder_dynamic_table_size); // For API endpoint, we enable automatic window update. This is // because we are a sink. nghttp2_option_new(&upstreamconf.alt_mode_option); nghttp2_option_set_no_recv_client_magic(upstreamconf.alt_mode_option, 1); + nghttp2_option_set_max_deflate_dynamic_table_size( + upstreamconf.alt_mode_option, upstreamconf.encoder_dynamic_table_size); } { @@ -1367,9 +1374,14 @@ void fill_default_config(Config *config) { downstreamconf.connection_window_size = (1u << 31) - 1; downstreamconf.max_concurrent_streams = 100; + downstreamconf.encoder_dynamic_table_size = 4_k; + downstreamconf.decoder_dynamic_table_size = 4_k; + nghttp2_option_new(&downstreamconf.option); nghttp2_option_set_no_auto_window_update(downstreamconf.option, 1); nghttp2_option_set_peer_max_concurrent_streams(downstreamconf.option, 100); + nghttp2_option_set_max_deflate_dynamic_table_size( + downstreamconf.option, downstreamconf.encoder_dynamic_table_size); } auto &loggingconf = config->logging; @@ -2053,6 +2065,36 @@ HTTP/2 and SPDY: be adjusted using --frontend-http2-window-size option as well. This option is only effective on recent Linux platform. + --frontend-http2-encoder-dynamic-table-size= + Specify the maximum dynamic table size of HPACK encoder + in the frontend HTTP/2 connection. The decoder (client) + specifies the maximum dynamic table size it accepts. + Then the negotiated dynamic table size is the minimum of + this option value and the value which client specified. + Default: )" + << util::utos_unit( + get_config()->http2.upstream.encoder_dynamic_table_size) << R"( + --frontend-http2-decoder-dynamic-table-size= + Specify the maximum dynamic table size of HPACK decoder + in the frontend HTTP/2 connection. + Default: )" + << util::utos_unit( + get_config()->http2.upstream.decoder_dynamic_table_size) << R"( + --backend-http2-encoder-dynamic-table-size= + Specify the maximum dynamic table size of HPACK encoder + in the backend HTTP/2 connection. The decoder (backend) + specifies the maximum dynamic table size it accepts. + Then the negotiated dynamic table size is the minimum of + this option value and the value which backend specified. + Default: )" + << util::utos_unit( + get_config()->http2.downstream.encoder_dynamic_table_size) << R"( + --backend-http2-decoder-dynamic-table-size= + Specify the maximum dynamic table size of HPACK decoder + in the backend HTTP/2 connection. + Default: )" + << util::utos_unit( + get_config()->http2.downstream.decoder_dynamic_table_size) << R"( Mode: (default mode) @@ -2875,6 +2917,14 @@ int main(int argc, char **argv) { 134}, {SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE.c_str(), required_argument, &flag, 135}, + {SHRPX_OPT_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE.c_str(), + required_argument, &flag, 136}, + {SHRPX_OPT_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE.c_str(), + required_argument, &flag, 137}, + {SHRPX_OPT_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE.c_str(), + required_argument, &flag, 138}, + {SHRPX_OPT_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE.c_str(), + required_argument, &flag, 139}, {nullptr, 0, nullptr, 0}}; int option_index = 0; @@ -3512,6 +3562,28 @@ int main(int argc, char **argv) { cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE, StringRef{optarg}); break; + case 136: + // --frontend-http2-encoder-dynamic-table-size + cmdcfgs.emplace_back( + SHRPX_OPT_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE, + StringRef{optarg}); + break; + case 137: + // --frontend-http2-decoder-dynamic-table-size + cmdcfgs.emplace_back( + SHRPX_OPT_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE, + StringRef{optarg}); + break; + case 138: + // --backend-http2-encoder-dynamic-table-size + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE, + StringRef{optarg}); + break; + case 139: + // --backend-http2-decoder-dynamic-table-size + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE, + StringRef{optarg}); + break; default: break; } diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 98c81d21..6196e6bf 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -1733,9 +1733,29 @@ int option_lookup_token(const char *name, size_t namelen) { break; } break; + case 40: + switch (name[39]) { + case 'e': + if (util::strieq_l("backend-http2-decoder-dynamic-table-siz", name, 39)) { + return SHRPX_OPTID_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE; + } + if (util::strieq_l("backend-http2-encoder-dynamic-table-siz", name, 39)) { + return SHRPX_OPTID_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE; + } + break; + } + break; case 41: switch (name[40]) { case 'e': + if (util::strieq_l("frontend-http2-decoder-dynamic-table-siz", name, + 40)) { + return SHRPX_OPTID_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE; + } + if (util::strieq_l("frontend-http2-encoder-dynamic-table-siz", name, + 40)) { + return SHRPX_OPTID_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE; + } if (util::strieq_l("frontend-http2-optimize-write-buffer-siz", name, 40)) { return SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE; @@ -2773,6 +2793,38 @@ int parse_config(Config *config, int optid, const StringRef &opt, } return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE: + if (parse_uint_with_unit(&config->http2.upstream.encoder_dynamic_table_size, + opt, optarg) != 0) { + return -1; + } + + nghttp2_option_set_max_deflate_dynamic_table_size( + config->http2.upstream.option, + config->http2.upstream.encoder_dynamic_table_size); + nghttp2_option_set_max_deflate_dynamic_table_size( + config->http2.upstream.alt_mode_option, + config->http2.upstream.encoder_dynamic_table_size); + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE: + return parse_uint_with_unit( + &config->http2.upstream.decoder_dynamic_table_size, opt, optarg); + case SHRPX_OPTID_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE: + if (parse_uint_with_unit( + &config->http2.downstream.encoder_dynamic_table_size, opt, + optarg) != 0) { + return -1; + } + + nghttp2_option_set_max_deflate_dynamic_table_size( + config->http2.downstream.option, + config->http2.downstream.encoder_dynamic_table_size); + + return 0; + case SHRPX_OPTID_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE: + return parse_uint_with_unit( + &config->http2.downstream.decoder_dynamic_table_size, opt, optarg); case SHRPX_OPTID_CONF: LOG(WARN) << "conf: ignored"; diff --git a/src/shrpx_config.h b/src/shrpx_config.h index 1eeb9a36..a36210c0 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -299,6 +299,14 @@ constexpr auto SHRPX_OPT_BACKEND_HTTP2_WINDOW_SIZE = StringRef::from_lit("backend-http2-window-size"); constexpr auto SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE = StringRef::from_lit("backend-http2-connection-window-size"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE = + StringRef::from_lit("frontend-http2-encoder-dynamic-table-size"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE = + StringRef::from_lit("frontend-http2-decoder-dynamic-table-size"); +constexpr auto SHRPX_OPT_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE = + StringRef::from_lit("backend-http2-encoder-dynamic-table-size"); +constexpr auto SHRPX_OPT_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE = + StringRef::from_lit("backend-http2-decoder-dynamic-table-size"); constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8; @@ -595,9 +603,11 @@ struct Http2Config { nghttp2_option *option; nghttp2_option *alt_mode_option; nghttp2_session_callbacks *callbacks; + size_t max_concurrent_streams; + size_t encoder_dynamic_table_size; + size_t decoder_dynamic_table_size; int32_t window_size; int32_t connection_window_size; - size_t max_concurrent_streams; bool optimize_write_buffer_size; bool optimize_window_size; } upstream; @@ -607,6 +617,8 @@ struct Http2Config { } timeout; nghttp2_option *option; nghttp2_session_callbacks *callbacks; + size_t encoder_dynamic_table_size; + size_t decoder_dynamic_table_size; int32_t window_size; int32_t connection_window_size; size_t max_concurrent_streams; @@ -795,6 +807,8 @@ enum { SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS, SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE, SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER, + SHRPX_OPTID_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE, + SHRPX_OPTID_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE, SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS, SHRPX_OPTID_BACKEND_HTTP2_SETTINGS_TIMEOUT, SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS, @@ -832,8 +846,10 @@ enum { SHRPX_OPTID_FRONTEND_FRAME_DEBUG, SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS, SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE, + SHRPX_OPTID_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE, SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER, SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER, + SHRPX_OPTID_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE, SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS, SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE, SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE, diff --git a/src/shrpx_http2_session.cc b/src/shrpx_http2_session.cc index 71bdbfa3..721fd5b7 100644 --- a/src/shrpx_http2_session.cc +++ b/src/shrpx_http2_session.cc @@ -1569,7 +1569,7 @@ int Http2Session::connection_made() { return -1; } - std::array entry; + std::array entry; size_t nentry = 2; entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; entry[0].value = http2conf.downstream.max_concurrent_streams; @@ -1583,6 +1583,13 @@ int Http2Session::connection_made() { ++nentry; } + if (http2conf.downstream.decoder_dynamic_table_size != + NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) { + entry[nentry].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + entry[nentry].value = http2conf.downstream.decoder_dynamic_table_size; + ++nentry; + } + rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(), nentry); if (rv != 0) { diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index 112f41ce..2d7e2876 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -939,7 +939,9 @@ Http2Upstream::Http2Upstream(ClientHandler *handler) flow_control_ = true; // TODO Maybe call from outside? - std::array entry; + std::array entry; + size_t nentry = 2; + entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; entry[0].value = http2conf.upstream.max_concurrent_streams; @@ -950,8 +952,15 @@ Http2Upstream::Http2Upstream(ClientHandler *handler) entry[1].value = http2conf.upstream.window_size; } + if (http2conf.upstream.decoder_dynamic_table_size != + NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) { + entry[nentry].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + entry[nentry].value = http2conf.upstream.decoder_dynamic_table_size; + ++nentry; + } + rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(), - entry.size()); + nentry); if (rv != 0) { ULOG(ERROR, this) << "nghttp2_submit_settings() returned error: " << nghttp2_strerror(rv); diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index de6c557a..5187b9af 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -6666,33 +6666,45 @@ void test_nghttp2_session_set_option(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; nghttp2_option *option; - - nghttp2_option_new(&option); - - nghttp2_option_set_no_auto_window_update(option, 1); + nghttp2_hd_deflater *deflater; + int rv; memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + /* Test for nghttp2_option_set_no_auto_window_update */ + nghttp2_option_new(&option); + nghttp2_option_set_no_auto_window_update(option, 1); + nghttp2_session_client_new2(&session, &callbacks, NULL, option); CU_ASSERT(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE); nghttp2_session_del(session); + nghttp2_option_del(option); + /* Test for nghttp2_option_set_peer_max_concurrent_streams */ + nghttp2_option_new(&option); nghttp2_option_set_peer_max_concurrent_streams(option, 100); nghttp2_session_client_new2(&session, &callbacks, NULL, option); CU_ASSERT(100 == session->remote_settings.max_concurrent_streams); nghttp2_session_del(session); + nghttp2_option_del(option); + /* Test for nghttp2_option_set_max_reserved_remote_streams */ + nghttp2_option_new(&option); nghttp2_option_set_max_reserved_remote_streams(option, 99); nghttp2_session_client_new2(&session, &callbacks, NULL, option); CU_ASSERT(99 == session->max_incoming_reserved_streams); nghttp2_session_del(session); + nghttp2_option_del(option); /* Test for nghttp2_option_set_no_auto_ping_ack */ + nghttp2_option_new(&option); nghttp2_option_set_no_auto_ping_ack(option, 1); nghttp2_session_client_new2(&session, &callbacks, NULL, option); @@ -6700,7 +6712,27 @@ void test_nghttp2_session_set_option(void) { CU_ASSERT(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_PING_ACK); nghttp2_session_del(session); + nghttp2_option_del(option); + /* Test for nghttp2_option_set_max_deflate_dynamic_table_size */ + nghttp2_option_new(&option); + nghttp2_option_set_max_deflate_dynamic_table_size(option, 0); + + nghttp2_session_client_new2(&session, &callbacks, NULL, option); + + deflater = &session->hd_deflater; + + rv = nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + CU_ASSERT(1 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == deflater->deflate_hd_table_bufsize_max); + CU_ASSERT(0 == deflater->ctx.hd_table_bufsize); + + nghttp2_session_del(session); nghttp2_option_del(option); }