Commit c7b0e044 authored by Tatsuhiro Tsujikawa's avatar Tatsuhiro Tsujikawa

Add nghttp2_option_set_max_send_header_block_length API function

This function sets the maximum length of header block (a set of header
fields per HEADERS frame) to send.  The length of given set of header
fields is calculated using nghttp2_hd_deflate_bound().  Previously,
this is hard-coded, and is 64KiB.
parent 47fa56fd
...@@ -57,6 +57,7 @@ APIDOCS= \ ...@@ -57,6 +57,7 @@ APIDOCS= \
nghttp2_option_new.rst \ nghttp2_option_new.rst \
nghttp2_option_set_builtin_recv_extension_type.rst \ nghttp2_option_set_builtin_recv_extension_type.rst \
nghttp2_option_set_max_reserved_remote_streams.rst \ nghttp2_option_set_max_reserved_remote_streams.rst \
nghttp2_option_set_max_send_header_block_length.rst \
nghttp2_option_set_no_auto_ping_ack.rst \ nghttp2_option_set_no_auto_ping_ack.rst \
nghttp2_option_set_no_auto_window_update.rst \ nghttp2_option_set_no_auto_window_update.rst \
nghttp2_option_set_no_http_messaging.rst \ nghttp2_option_set_no_http_messaging.rst \
......
...@@ -2412,6 +2412,21 @@ nghttp2_option_set_builtin_recv_extension_type(nghttp2_option *option, ...@@ -2412,6 +2412,21 @@ nghttp2_option_set_builtin_recv_extension_type(nghttp2_option *option,
NGHTTP2_EXTERN void nghttp2_option_set_no_auto_ping_ack(nghttp2_option *option, NGHTTP2_EXTERN void nghttp2_option_set_no_auto_ping_ack(nghttp2_option *option,
int val); int val);
/**
* @function
*
* This option sets the maximum length of header block (a set of
* header fields per one HEADERS frame) to send. The length of a
* given set of header fields is calculated using
* `nghttp2_hd_deflate_bound()`. The default value is 64KiB. If
* application attempts to send header fields larger than this limit,
* the transmission of the frame fails with error code
* :enum:`NGHTTP2_ERR_FRAME_SIZE_ERROR`.
*/
NGHTTP2_EXTERN void
nghttp2_option_set_max_send_header_block_length(nghttp2_option *option,
size_t val);
/** /**
* @function * @function
* *
......
...@@ -52,14 +52,12 @@ ...@@ -52,14 +52,12 @@
#define NGHTTP2_FRAMEBUF_CHUNKLEN \ #define NGHTTP2_FRAMEBUF_CHUNKLEN \
(NGHTTP2_FRAME_HDLEN + 1 + NGHTTP2_MAX_PAYLOADLEN) (NGHTTP2_FRAME_HDLEN + 1 + NGHTTP2_MAX_PAYLOADLEN)
/* Number of inbound buffer */
#define NGHTTP2_FRAMEBUF_MAX_NUM 5
/* The default length of DATA frame payload. */ /* The default length of DATA frame payload. */
#define NGHTTP2_DATA_PAYLOADLEN NGHTTP2_MAX_FRAME_SIZE_MIN #define NGHTTP2_DATA_PAYLOADLEN NGHTTP2_MAX_FRAME_SIZE_MIN
/* Maximum headers payload length, calculated in compressed form. /* Maximum headers block size to send, calculated using
This applies to transmission only. */ nghttp2_hd_deflate_bound(). This is the default value, and can be
overridden by nghttp2_option_set_max_send_header_block_size(). */
#define NGHTTP2_MAX_HEADERSLEN 65536 #define NGHTTP2_MAX_HEADERSLEN 65536
/* The number of bytes for each SETTINGS entry */ /* The number of bytes for each SETTINGS entry */
......
...@@ -95,3 +95,9 @@ void nghttp2_option_set_no_auto_ping_ack(nghttp2_option *option, int val) { ...@@ -95,3 +95,9 @@ void nghttp2_option_set_no_auto_ping_ack(nghttp2_option *option, int val) {
option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_PING_ACK; option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_PING_ACK;
option->no_auto_ping_ack = val; option->no_auto_ping_ack = val;
} }
void nghttp2_option_set_max_send_header_block_length(nghttp2_option *option,
size_t val) {
option->opt_set_mask |= NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH;
option->max_send_header_block_length = val;
}
...@@ -62,13 +62,18 @@ typedef enum { ...@@ -62,13 +62,18 @@ typedef enum {
NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS = 1 << 4, NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS = 1 << 4,
NGHTTP2_OPT_USER_RECV_EXT_TYPES = 1 << 5, NGHTTP2_OPT_USER_RECV_EXT_TYPES = 1 << 5,
NGHTTP2_OPT_NO_AUTO_PING_ACK = 1 << 6, NGHTTP2_OPT_NO_AUTO_PING_ACK = 1 << 6,
NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES = 1 << 7 NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES = 1 << 7,
NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH = 1 << 8,
} nghttp2_option_flag; } nghttp2_option_flag;
/** /**
* Struct to store option values for nghttp2_session. * Struct to store option values for nghttp2_session.
*/ */
struct nghttp2_option { struct nghttp2_option {
/**
* NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH
*/
size_t max_send_header_block_length;
/** /**
* Bitwise OR of nghttp2_option_flag to determine that which fields * Bitwise OR of nghttp2_option_flag to determine that which fields
* are specified. * are specified.
......
...@@ -389,6 +389,7 @@ static int session_new(nghttp2_session **session_ptr, ...@@ -389,6 +389,7 @@ static int session_new(nghttp2_session **session_ptr,
void *user_data, int server, void *user_data, int server,
const nghttp2_option *option, nghttp2_mem *mem) { const nghttp2_option *option, nghttp2_mem *mem) {
int rv; int rv;
size_t nbuffer;
if (mem == NULL) { if (mem == NULL) {
mem = nghttp2_mem_default(); mem = nghttp2_mem_default();
...@@ -441,16 +442,6 @@ static int session_new(nghttp2_session **session_ptr, ...@@ -441,16 +442,6 @@ static int session_new(nghttp2_session **session_ptr,
(*session_ptr)->server = 1; (*session_ptr)->server = 1;
} }
/* 1 for Pad Field. */
rv = nghttp2_bufs_init3(&(*session_ptr)->aob.framebufs,
NGHTTP2_FRAMEBUF_CHUNKLEN, NGHTTP2_FRAMEBUF_MAX_NUM,
1, NGHTTP2_FRAME_HDLEN + 1, mem);
if (rv != 0) {
goto fail_aob_framebuf;
}
active_outbound_item_reset(&(*session_ptr)->aob, mem);
init_settings(&(*session_ptr)->remote_settings); init_settings(&(*session_ptr)->remote_settings);
init_settings(&(*session_ptr)->local_settings); init_settings(&(*session_ptr)->local_settings);
...@@ -460,6 +451,8 @@ static int session_new(nghttp2_session **session_ptr, ...@@ -460,6 +451,8 @@ static int session_new(nghttp2_session **session_ptr,
/* Limit max outgoing concurrent streams to sensible value */ /* Limit max outgoing concurrent streams to sensible value */
(*session_ptr)->remote_settings.max_concurrent_streams = 100; (*session_ptr)->remote_settings.max_concurrent_streams = 100;
(*session_ptr)->max_send_header_block_length = NGHTTP2_MAX_HEADERSLEN;
if (option) { if (option) {
if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) && if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) &&
option->no_auto_window_update) { option->no_auto_window_update) {
...@@ -504,8 +497,31 @@ static int session_new(nghttp2_session **session_ptr, ...@@ -504,8 +497,31 @@ static int session_new(nghttp2_session **session_ptr,
option->no_auto_ping_ack) { option->no_auto_ping_ack) {
(*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_AUTO_PING_ACK; (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_AUTO_PING_ACK;
} }
if (option->opt_set_mask & NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH) {
(*session_ptr)->max_send_header_block_length =
option->max_send_header_block_length;
}
} }
nbuffer = ((*session_ptr)->max_send_header_block_length +
NGHTTP2_FRAMEBUF_CHUNKLEN - 1) /
NGHTTP2_FRAMEBUF_CHUNKLEN;
if (nbuffer == 0) {
nbuffer = 1;
}
/* 1 for Pad Field. */
rv = nghttp2_bufs_init3(&(*session_ptr)->aob.framebufs,
NGHTTP2_FRAMEBUF_CHUNKLEN, nbuffer, 1,
NGHTTP2_FRAME_HDLEN + 1, mem);
if (rv != 0) {
goto fail_aob_framebuf;
}
active_outbound_item_reset(&(*session_ptr)->aob, mem);
(*session_ptr)->callbacks = *callbacks; (*session_ptr)->callbacks = *callbacks;
(*session_ptr)->user_data = user_data; (*session_ptr)->user_data = user_data;
...@@ -1951,7 +1967,7 @@ static int session_prep_frame(nghttp2_session *session, ...@@ -1951,7 +1967,7 @@ static int session_prep_frame(nghttp2_session *session,
session, frame->headers.nva, frame->headers.nvlen, session, frame->headers.nva, frame->headers.nvlen,
NGHTTP2_PRIORITY_SPECLEN); NGHTTP2_PRIORITY_SPECLEN);
if (estimated_payloadlen > NGHTTP2_MAX_HEADERSLEN) { if (estimated_payloadlen > session->max_send_header_block_length) {
return NGHTTP2_ERR_FRAME_SIZE_ERROR; return NGHTTP2_ERR_FRAME_SIZE_ERROR;
} }
...@@ -1970,7 +1986,7 @@ static int session_prep_frame(nghttp2_session *session, ...@@ -1970,7 +1986,7 @@ static int session_prep_frame(nghttp2_session *session,
session, frame->headers.nva, frame->headers.nvlen, session, frame->headers.nva, frame->headers.nvlen,
NGHTTP2_PRIORITY_SPECLEN); NGHTTP2_PRIORITY_SPECLEN);
if (estimated_payloadlen > NGHTTP2_MAX_HEADERSLEN) { if (estimated_payloadlen > session->max_send_header_block_length) {
return NGHTTP2_ERR_FRAME_SIZE_ERROR; return NGHTTP2_ERR_FRAME_SIZE_ERROR;
} }
...@@ -2089,7 +2105,7 @@ static int session_prep_frame(nghttp2_session *session, ...@@ -2089,7 +2105,7 @@ static int session_prep_frame(nghttp2_session *session,
estimated_payloadlen = session_estimate_headers_payload( estimated_payloadlen = session_estimate_headers_payload(
session, frame->push_promise.nva, frame->push_promise.nvlen, 0); session, frame->push_promise.nva, frame->push_promise.nvlen, 0);
if (estimated_payloadlen > NGHTTP2_MAX_HEADERSLEN) { if (estimated_payloadlen > session->max_send_header_block_length) {
return NGHTTP2_ERR_FRAME_SIZE_ERROR; return NGHTTP2_ERR_FRAME_SIZE_ERROR;
} }
......
...@@ -256,6 +256,9 @@ struct nghttp2_session { ...@@ -256,6 +256,9 @@ struct nghttp2_session {
size_t nvbuflen; size_t nvbuflen;
/* Counter for detecting flooding in outbound queue */ /* Counter for detecting flooding in outbound queue */
size_t obq_flood_counter_; size_t obq_flood_counter_;
/* The maximum length of header block to send. Calculated by the
same way as nghttp2_hd_deflate_bound() does. */
size_t max_send_header_block_length;
/* Next Stream ID. Made unsigned int to detect >= (1 << 31). */ /* Next Stream ID. Made unsigned int to detect >= (1 << 31). */
uint32_t next_stream_id; uint32_t next_stream_id;
/* The last stream ID this session initiated. For client session, /* The last stream ID this session initiated. For client session,
......
...@@ -183,6 +183,8 @@ int main(int argc _U_, char *argv[] _U_) { ...@@ -183,6 +183,8 @@ int main(int argc _U_, char *argv[] _U_) {
!CU_add_test(pSuite, "submit_headers", test_nghttp2_submit_headers) || !CU_add_test(pSuite, "submit_headers", test_nghttp2_submit_headers) ||
!CU_add_test(pSuite, "submit_headers_continuation", !CU_add_test(pSuite, "submit_headers_continuation",
test_nghttp2_submit_headers_continuation) || test_nghttp2_submit_headers_continuation) ||
!CU_add_test(pSuite, "submit_headers_continuation_extra_large",
test_nghttp2_submit_headers_continuation_extra_large) ||
!CU_add_test(pSuite, "submit_priority", test_nghttp2_submit_priority) || !CU_add_test(pSuite, "submit_priority", test_nghttp2_submit_priority) ||
!CU_add_test(pSuite, "session_submit_settings", !CU_add_test(pSuite, "session_submit_settings",
test_nghttp2_submit_settings) || test_nghttp2_submit_settings) ||
......
...@@ -4842,6 +4842,51 @@ void test_nghttp2_submit_headers_continuation(void) { ...@@ -4842,6 +4842,51 @@ void test_nghttp2_submit_headers_continuation(void) {
nghttp2_session_del(session); nghttp2_session_del(session);
} }
void test_nghttp2_submit_headers_continuation_extra_large(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_nv nv[] = {
MAKE_NV("h1", ""), MAKE_NV("h1", ""), MAKE_NV("h1", ""),
MAKE_NV("h1", ""), MAKE_NV("h1", ""), MAKE_NV("h1", ""),
};
nghttp2_outbound_item *item;
uint8_t data[16384];
size_t i;
my_user_data ud;
nghttp2_option *opt;
memset(data, '0', sizeof(data));
for (i = 0; i < ARRLEN(nv); ++i) {
nv[i].valuelen = sizeof(data);
nv[i].value = data;
}
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback;
callbacks.on_frame_send_callback = on_frame_send_callback;
/* The default size of max send header block length is too small to
send these header fields. Expand it. */
nghttp2_option_new(&opt);
nghttp2_option_set_max_send_header_block_length(opt, 102400);
CU_ASSERT(0 == nghttp2_session_client_new2(&session, &callbacks, &ud, opt));
CU_ASSERT(1 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1,
NULL, nv, ARRLEN(nv), NULL));
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type);
CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS) ==
item->frame.hd.flags);
CU_ASSERT(0 == (item->frame.hd.flags & NGHTTP2_FLAG_PRIORITY));
ud.frame_send_cb_called = 0;
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(1 == ud.frame_send_cb_called);
nghttp2_session_del(session);
nghttp2_option_del(opt);
}
void test_nghttp2_submit_priority(void) { void test_nghttp2_submit_priority(void) {
nghttp2_session *session; nghttp2_session *session;
nghttp2_session_callbacks callbacks; nghttp2_session_callbacks callbacks;
......
...@@ -87,6 +87,7 @@ void test_nghttp2_submit_headers_reply(void); ...@@ -87,6 +87,7 @@ void test_nghttp2_submit_headers_reply(void);
void test_nghttp2_submit_headers_push_reply(void); void test_nghttp2_submit_headers_push_reply(void);
void test_nghttp2_submit_headers(void); void test_nghttp2_submit_headers(void);
void test_nghttp2_submit_headers_continuation(void); void test_nghttp2_submit_headers_continuation(void);
void test_nghttp2_submit_headers_continuation_extra_large(void);
void test_nghttp2_submit_priority(void); void test_nghttp2_submit_priority(void);
void test_nghttp2_submit_settings(void); void test_nghttp2_submit_settings(void);
void test_nghttp2_submit_settings_update_local_window_size(void); void test_nghttp2_submit_settings_update_local_window_size(void);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment