Add a server option to fallback to RFC 7540 priorities
Add nghttp2_option_set_server_fallback_rfc7540_priorities. If it is set to nonzero, and server submits SETTINGS_NO_RFC7540_PRIORITIES = 1, but it does not receive SETTINGS_NO_RFC7540_PRIORITIES from client, server falls back to RFC 7540 priorities. Only minimal set of features are enabled in this fallback case.
This commit is contained in:
parent
d1e07ab6b7
commit
8c2386c221
|
@ -69,6 +69,7 @@ APIDOCS= \
|
|||
nghttp2_option_set_no_http_messaging.rst \
|
||||
nghttp2_option_set_no_recv_client_magic.rst \
|
||||
nghttp2_option_set_peer_max_concurrent_streams.rst \
|
||||
nghttp2_option_set_server_fallback_rfc7540_priorities.rst \
|
||||
nghttp2_option_set_user_recv_extension_type.rst \
|
||||
nghttp2_option_set_max_outbound_ack.rst \
|
||||
nghttp2_option_set_max_settings.rst \
|
||||
|
|
|
@ -2732,6 +2732,24 @@ NGHTTP2_EXTERN void nghttp2_option_set_max_outbound_ack(nghttp2_option *option,
|
|||
NGHTTP2_EXTERN void nghttp2_option_set_max_settings(nghttp2_option *option,
|
||||
size_t val);
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
* This option, if set to nonzero, allows server to fallback to
|
||||
* :rfc:`7540` priorities if SETTINGS_NO_RFC7540_PRIORITIES was not
|
||||
* received from client, and server submitted
|
||||
* :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES`
|
||||
* = 1 via `nghttp2_submit_settings()`. Most of the advanced
|
||||
* functionality for RFC 7540 priorities are still disabled. This
|
||||
* fallback only enables the minimal feature set of RFC 7540
|
||||
* priorities to deal with priority signaling from client.
|
||||
*
|
||||
* Client session ignores this option.
|
||||
*/
|
||||
NGHTTP2_EXTERN void
|
||||
nghttp2_option_set_server_fallback_rfc7540_priorities(nghttp2_option *option,
|
||||
int val);
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
|
|
|
@ -130,3 +130,9 @@ void nghttp2_option_set_max_settings(nghttp2_option *option, size_t val) {
|
|||
option->opt_set_mask |= NGHTTP2_OPT_MAX_SETTINGS;
|
||||
option->max_settings = val;
|
||||
}
|
||||
|
||||
void nghttp2_option_set_server_fallback_rfc7540_priorities(
|
||||
nghttp2_option *option, int val) {
|
||||
option->opt_set_mask |= NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES;
|
||||
option->server_fallback_rfc7540_priorities = val;
|
||||
}
|
||||
|
|
|
@ -68,6 +68,7 @@ typedef enum {
|
|||
NGHTTP2_OPT_NO_CLOSED_STREAMS = 1 << 10,
|
||||
NGHTTP2_OPT_MAX_OUTBOUND_ACK = 1 << 11,
|
||||
NGHTTP2_OPT_MAX_SETTINGS = 1 << 12,
|
||||
NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES = 1 << 13,
|
||||
} nghttp2_option_flag;
|
||||
|
||||
/**
|
||||
|
@ -127,6 +128,10 @@ struct nghttp2_option {
|
|||
* NGHTTP2_OPT_NO_CLOSED_STREAMS
|
||||
*/
|
||||
int no_closed_streams;
|
||||
/**
|
||||
* NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES
|
||||
*/
|
||||
int server_fallback_rfc7540_priorities;
|
||||
/**
|
||||
* NGHTTP2_OPT_USER_RECV_EXT_TYPES
|
||||
*/
|
||||
|
|
|
@ -144,6 +144,11 @@ static int session_detect_idle_stream(nghttp2_session *session,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int session_no_rfc7540_pri_no_fallback(nghttp2_session *session) {
|
||||
return session->pending_no_rfc7540_priorities == 1 &&
|
||||
!session->fallback_rfc7540_priorities;
|
||||
}
|
||||
|
||||
static int check_ext_type_set(const uint8_t *ext_types, uint8_t type) {
|
||||
return (ext_types[type / 8] & (1 << (type & 0x7))) > 0;
|
||||
}
|
||||
|
@ -554,6 +559,13 @@ static int session_new(nghttp2_session **session_ptr,
|
|||
option->max_settings) {
|
||||
(*session_ptr)->max_settings = option->max_settings;
|
||||
}
|
||||
|
||||
if ((option->opt_set_mask &
|
||||
NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES) &&
|
||||
option->server_fallback_rfc7540_priorities) {
|
||||
(*session_ptr)->opt_flags |=
|
||||
NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES;
|
||||
}
|
||||
}
|
||||
|
||||
rv = nghttp2_hd_deflate_init2(&(*session_ptr)->hd_deflater,
|
||||
|
@ -810,7 +822,8 @@ int nghttp2_session_reprioritize_stream(
|
|||
nghttp2_priority_spec pri_spec_default;
|
||||
const nghttp2_priority_spec *pri_spec = pri_spec_in;
|
||||
|
||||
assert(session->pending_no_rfc7540_priorities != 1);
|
||||
assert((!session->server && session->pending_no_rfc7540_priorities != 1) ||
|
||||
(session->server && !session_no_rfc7540_pri_no_fallback(session)));
|
||||
assert(pri_spec->stream_id != stream->stream_id);
|
||||
|
||||
if (!nghttp2_stream_in_dep_tree(stream)) {
|
||||
|
@ -1296,7 +1309,7 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
if (session->pending_no_rfc7540_priorities == 1) {
|
||||
if (session_no_rfc7540_pri_no_fallback(session)) {
|
||||
stream->flags |= NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES;
|
||||
}
|
||||
}
|
||||
|
@ -1309,7 +1322,7 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
|
|||
stream_alloc = 1;
|
||||
}
|
||||
|
||||
if (session->pending_no_rfc7540_priorities == 1 ||
|
||||
if (session_no_rfc7540_pri_no_fallback(session) ||
|
||||
session->remote_settings.no_rfc7540_priorities == 1) {
|
||||
/* For client which has not received server
|
||||
SETTINGS_NO_RFC7540_PRIORITIES = 1, send a priority signal
|
||||
|
@ -1369,7 +1382,7 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
|
|||
(int32_t)session->local_settings.initial_window_size,
|
||||
stream_user_data, mem);
|
||||
|
||||
if (session->pending_no_rfc7540_priorities == 1) {
|
||||
if (session_no_rfc7540_pri_no_fallback(session)) {
|
||||
stream->seq = session->stream_seq++;
|
||||
}
|
||||
|
||||
|
@ -4440,7 +4453,7 @@ int nghttp2_session_on_priority_received(nghttp2_session *session,
|
|||
int rv;
|
||||
nghttp2_stream *stream;
|
||||
|
||||
assert(session->pending_no_rfc7540_priorities != 1);
|
||||
assert(!session_no_rfc7540_pri_no_fallback(session));
|
||||
|
||||
if (frame->hd.stream_id == 0) {
|
||||
return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO,
|
||||
|
@ -4499,7 +4512,7 @@ static int session_process_priority_frame(nghttp2_session *session) {
|
|||
nghttp2_inbound_frame *iframe = &session->iframe;
|
||||
nghttp2_frame *frame = &iframe->frame;
|
||||
|
||||
assert(session->pending_no_rfc7540_priorities != 1);
|
||||
assert(!session_no_rfc7540_pri_no_fallback(session));
|
||||
|
||||
nghttp2_frame_unpack_priority_payload(&frame->priority, iframe->sbuf.pos);
|
||||
|
||||
|
@ -4927,6 +4940,12 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
|
|||
|
||||
if (session->remote_settings.no_rfc7540_priorities == UINT32_MAX) {
|
||||
session->remote_settings.no_rfc7540_priorities = 0;
|
||||
|
||||
if (session->server && session->pending_no_rfc7540_priorities &&
|
||||
(session->opt_flags &
|
||||
NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES)) {
|
||||
session->fallback_rfc7540_priorities = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!noack && !session_is_closing(session)) {
|
||||
|
@ -6380,7 +6399,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
|
|||
break;
|
||||
}
|
||||
|
||||
if (session->pending_no_rfc7540_priorities != 1 ||
|
||||
if (!session_no_rfc7540_pri_no_fallback(session) ||
|
||||
iframe->payloadleft > sizeof(iframe->raw_sbuf)) {
|
||||
busy = 1;
|
||||
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
|
||||
|
@ -6498,7 +6517,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
|
|||
|
||||
break;
|
||||
case NGHTTP2_PRIORITY:
|
||||
if (session->pending_no_rfc7540_priorities != 1 &&
|
||||
if (!session_no_rfc7540_pri_no_fallback(session) &&
|
||||
session->remote_settings.no_rfc7540_priorities != 1) {
|
||||
rv = session_process_priority_frame(session);
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
|
|
|
@ -52,7 +52,8 @@ typedef enum {
|
|||
NGHTTP2_OPTMASK_NO_RECV_CLIENT_MAGIC = 1 << 1,
|
||||
NGHTTP2_OPTMASK_NO_HTTP_MESSAGING = 1 << 2,
|
||||
NGHTTP2_OPTMASK_NO_AUTO_PING_ACK = 1 << 3,
|
||||
NGHTTP2_OPTMASK_NO_CLOSED_STREAMS = 1 << 4
|
||||
NGHTTP2_OPTMASK_NO_CLOSED_STREAMS = 1 << 4,
|
||||
NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES = 1 << 5
|
||||
} nghttp2_optmask;
|
||||
|
||||
/*
|
||||
|
@ -340,6 +341,8 @@ struct nghttp2_session {
|
|||
/* Unacked local SETTINGS_NO_RFC7540_PRIORITIES value, which is
|
||||
effective before it is acknowledged. */
|
||||
uint8_t pending_no_rfc7540_priorities;
|
||||
/* Turn on fallback to RFC 7540 priorities; for server use only. */
|
||||
uint8_t fallback_rfc7540_priorities;
|
||||
/* Nonzero if the session is server side. */
|
||||
uint8_t server;
|
||||
/* Flags indicating GOAWAY is sent and/or received. The flags are
|
||||
|
|
|
@ -339,6 +339,8 @@ int main(void) {
|
|||
test_nghttp2_session_set_stream_user_data) ||
|
||||
!CU_add_test(pSuite, "session_no_rfc7540_priorities",
|
||||
test_nghttp2_session_no_rfc7540_priorities) ||
|
||||
!CU_add_test(pSuite, "session_server_fallback_rfc7540_priorities",
|
||||
test_nghttp2_session_server_fallback_rfc7540_priorities) ||
|
||||
!CU_add_test(pSuite, "http_mandatory_headers",
|
||||
test_nghttp2_http_mandatory_headers) ||
|
||||
!CU_add_test(pSuite, "http_content_length",
|
||||
|
|
|
@ -11637,6 +11637,196 @@ void test_nghttp2_session_no_rfc7540_priorities(void) {
|
|||
nghttp2_session_del(session);
|
||||
}
|
||||
|
||||
void test_nghttp2_session_server_fallback_rfc7540_priorities(void) {
|
||||
nghttp2_session *session;
|
||||
nghttp2_option *option;
|
||||
nghttp2_session_callbacks callbacks;
|
||||
nghttp2_frame frame;
|
||||
nghttp2_bufs bufs;
|
||||
nghttp2_buf *buf;
|
||||
ssize_t rv;
|
||||
nghttp2_settings_entry iv;
|
||||
nghttp2_mem *mem;
|
||||
nghttp2_hd_deflater deflater;
|
||||
nghttp2_nv *nva;
|
||||
size_t nvlen;
|
||||
nghttp2_priority_spec pri_spec;
|
||||
nghttp2_stream *anchor_stream, *stream;
|
||||
my_user_data ud;
|
||||
nghttp2_ext_priority_update priority_update;
|
||||
static const uint8_t field_value[] = "u=0";
|
||||
|
||||
mem = nghttp2_mem_default();
|
||||
frame_pack_bufs_init(&bufs);
|
||||
|
||||
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
|
||||
callbacks.send_callback = null_send_callback;
|
||||
callbacks.on_frame_recv_callback = on_frame_recv_callback;
|
||||
|
||||
nghttp2_option_new(&option);
|
||||
nghttp2_option_set_server_fallback_rfc7540_priorities(option, 1);
|
||||
|
||||
iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
|
||||
iv.value = 1;
|
||||
|
||||
/* Server falls back to RFC 7540 priorities. */
|
||||
nghttp2_session_server_new2(&session, &callbacks, &ud, option);
|
||||
|
||||
rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
rv = nghttp2_session_send(session);
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, NULL, 0);
|
||||
rv = nghttp2_frame_pack_settings(&bufs, &frame.settings);
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
nghttp2_frame_settings_free(&frame.settings, mem);
|
||||
|
||||
buf = &bufs.head->buf;
|
||||
rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
|
||||
|
||||
CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
|
||||
CU_ASSERT(1 == session->fallback_rfc7540_priorities);
|
||||
|
||||
nghttp2_hd_deflate_init(&deflater, mem);
|
||||
|
||||
nvlen = ARRLEN(reqnv);
|
||||
nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem);
|
||||
nghttp2_priority_spec_init(&pri_spec, 3, 111, 1);
|
||||
nghttp2_frame_headers_init(&frame.headers,
|
||||
NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY,
|
||||
1, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen);
|
||||
nghttp2_bufs_reset(&bufs);
|
||||
rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
nghttp2_frame_headers_free(&frame.headers, mem);
|
||||
|
||||
buf = &bufs.head->buf;
|
||||
rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
|
||||
|
||||
CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
|
||||
|
||||
anchor_stream = nghttp2_session_get_stream_raw(session, 3);
|
||||
|
||||
CU_ASSERT(NGHTTP2_STREAM_IDLE == anchor_stream->state);
|
||||
CU_ASSERT(
|
||||
!(anchor_stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES));
|
||||
CU_ASSERT(&session->root == anchor_stream->dep_prev);
|
||||
|
||||
stream = nghttp2_session_get_stream(session, 1);
|
||||
|
||||
CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state);
|
||||
CU_ASSERT(!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES));
|
||||
CU_ASSERT(anchor_stream == stream->dep_prev);
|
||||
|
||||
/* Make sure that PRIORITY frame updates stream priority. */
|
||||
nghttp2_priority_spec_init(&pri_spec, 5, 1, 0);
|
||||
nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);
|
||||
nghttp2_bufs_reset(&bufs);
|
||||
rv = nghttp2_frame_pack_priority(&bufs, &frame.priority);
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
nghttp2_frame_priority_free(&frame.priority);
|
||||
|
||||
buf = &bufs.head->buf;
|
||||
rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
|
||||
|
||||
CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
|
||||
|
||||
anchor_stream = nghttp2_session_get_stream_raw(session, 5);
|
||||
|
||||
CU_ASSERT(NGHTTP2_STREAM_IDLE == anchor_stream->state);
|
||||
CU_ASSERT(&session->root == anchor_stream->dep_prev);
|
||||
CU_ASSERT(anchor_stream == stream->dep_prev);
|
||||
|
||||
/* Make sure that PRIORITY_UPDATE frame is ignored. */
|
||||
frame.ext.payload = &priority_update;
|
||||
nghttp2_frame_priority_update_init(&frame.ext, 1, (uint8_t *)field_value,
|
||||
sizeof(field_value) - 1);
|
||||
nghttp2_bufs_reset(&bufs);
|
||||
nghttp2_frame_pack_priority_update(&bufs, &frame.ext);
|
||||
|
||||
ud.frame_recv_cb_called = 0;
|
||||
buf = &bufs.head->buf;
|
||||
rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
|
||||
|
||||
CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
|
||||
CU_ASSERT(0 == ud.frame_recv_cb_called);
|
||||
CU_ASSERT(NGHTTP2_EXTPRI_DEFAULT_URGENCY == stream->extpri);
|
||||
|
||||
nghttp2_hd_deflate_free(&deflater);
|
||||
nghttp2_session_del(session);
|
||||
|
||||
/* Server does not fallback to RFC 7540 priorities. */
|
||||
nghttp2_session_server_new2(&session, &callbacks, &ud, option);
|
||||
|
||||
rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
rv = nghttp2_session_send(session);
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
|
||||
iv.value = 0;
|
||||
|
||||
nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE,
|
||||
dup_iv(&iv, 1), 1);
|
||||
nghttp2_bufs_reset(&bufs);
|
||||
rv = nghttp2_frame_pack_settings(&bufs, &frame.settings);
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
nghttp2_frame_settings_free(&frame.settings, mem);
|
||||
|
||||
buf = &bufs.head->buf;
|
||||
rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
|
||||
|
||||
CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
|
||||
CU_ASSERT(0 == session->fallback_rfc7540_priorities);
|
||||
|
||||
nghttp2_hd_deflate_init(&deflater, mem);
|
||||
|
||||
nvlen = ARRLEN(reqnv);
|
||||
nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem);
|
||||
nghttp2_priority_spec_init(&pri_spec, 3, 111, 1);
|
||||
nghttp2_frame_headers_init(&frame.headers,
|
||||
NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY,
|
||||
1, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen);
|
||||
nghttp2_bufs_reset(&bufs);
|
||||
rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
nghttp2_frame_headers_free(&frame.headers, mem);
|
||||
|
||||
buf = &bufs.head->buf;
|
||||
rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
|
||||
|
||||
CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
|
||||
CU_ASSERT(NULL == nghttp2_session_get_stream_raw(session, 3));
|
||||
|
||||
stream = nghttp2_session_get_stream(session, 1);
|
||||
|
||||
CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state);
|
||||
CU_ASSERT(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES);
|
||||
|
||||
nghttp2_hd_deflate_free(&deflater);
|
||||
nghttp2_session_del(session);
|
||||
|
||||
nghttp2_option_del(option);
|
||||
nghttp2_bufs_free(&bufs);
|
||||
}
|
||||
|
||||
static void check_nghttp2_http_recv_headers_fail(
|
||||
nghttp2_session *session, nghttp2_hd_deflater *deflater, int32_t stream_id,
|
||||
int stream_state, const nghttp2_nv *nva, size_t nvlen) {
|
||||
|
|
|
@ -166,6 +166,7 @@ void test_nghttp2_session_pause_data(void);
|
|||
void test_nghttp2_session_no_closed_streams(void);
|
||||
void test_nghttp2_session_set_stream_user_data(void);
|
||||
void test_nghttp2_session_no_rfc7540_priorities(void);
|
||||
void test_nghttp2_session_server_fallback_rfc7540_priorities(void);
|
||||
void test_nghttp2_http_mandatory_headers(void);
|
||||
void test_nghttp2_http_content_length(void);
|
||||
void test_nghttp2_http_content_length_mismatch(void);
|
||||
|
|
Loading…
Reference in New Issue