From 059280d1a5e5e64a334011a30704ce74976bfa7d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 18 Aug 2015 22:41:50 +0900 Subject: [PATCH] Add stream public API The intention of this stream API is give server application about stream dependency information, so that it can utilize it for better scheduling of stream processing. We have no plan to add object oriented API based on stream object. --- doc/Makefile.am | 9 ++ lib/includes/nghttp2/nghttp2.h | 128 ++++++++++++++++ lib/nghttp2_session.c | 13 +- lib/nghttp2_stream.c | 58 ++++++++ lib/nghttp2_stream.h | 4 - tests/main.c | 8 +- tests/nghttp2_session_test.c | 260 +++++++++++++++++++++++++++++++++ tests/nghttp2_session_test.h | 2 + 8 files changed, 474 insertions(+), 8 deletions(-) diff --git a/doc/Makefile.am b/doc/Makefile.am index 5b6272c1..7e3d292e 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -79,6 +79,7 @@ APIDOCS= \ nghttp2_session_consume_connection.rst \ nghttp2_session_consume_stream.rst \ nghttp2_session_del.rst \ + nghttp2_session_find_stream.rst \ nghttp2_session_get_effective_local_window_size.rst \ nghttp2_session_get_effective_recv_data_length.rst \ nghttp2_session_get_last_proc_stream_id.rst \ @@ -86,6 +87,7 @@ APIDOCS= \ nghttp2_session_get_outbound_queue_size.rst \ nghttp2_session_get_remote_settings.rst \ nghttp2_session_get_remote_window_size.rst \ + nghttp2_session_get_root_stream.rst \ nghttp2_session_get_stream_effective_local_window_size.rst \ nghttp2_session_get_stream_effective_recv_data_length.rst \ nghttp2_session_get_stream_local_close.rst \ @@ -107,6 +109,13 @@ APIDOCS= \ nghttp2_session_upgrade.rst \ nghttp2_session_want_read.rst \ nghttp2_session_want_write.rst \ + nghttp2_stream_get_first_child.rst \ + nghttp2_stream_get_next_sibling.rst \ + nghttp2_stream_get_parent.rst \ + nghttp2_stream_get_previous_sibling.rst \ + nghttp2_stream_get_state.rst \ + nghttp2_stream_get_sum_dependency_weight.rst \ + nghttp2_stream_get_weight.rst \ nghttp2_strerror.rst \ nghttp2_submit_data.rst \ nghttp2_submit_goaway.rst \ diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 6c3c2c43..d2d07fad 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -3907,6 +3907,134 @@ NGHTTP2_EXTERN ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, NGHTTP2_EXTERN int nghttp2_hd_inflate_end_headers(nghttp2_hd_inflater *inflater); +struct nghttp2_stream; + +/** + * @struct + * + * The structure to represent HTTP/2 stream. The details of this + * structure are intentionally hidden from the public API. + */ +typedef struct nghttp2_stream nghttp2_stream; + +/** + * @function + * + * Returns pointer to :type:`nghttp2_stream` object denoted by + * |stream_id|. If stream was not found, returns NULL. + * + * Returns imaginary root stream (see + * `nghttp2_session_get_root_stream()`) if 0 is given in |stream_id|. + * + * Unless |stream_id| == 0, the returned pointer is valid until next + * call of `nghttp2_session_send()`, `nghttp2_session_mem_send()`, + * `nghttp2_session_recv()`, and `nghttp2_session_mem_recv()`. + */ +nghttp2_stream *nghttp2_session_find_stream(nghttp2_session *session, + int32_t stream_id); + +/** + * @enum + * + * State of stream as described in RFC 7540. + */ +typedef enum { + /** + * idle state. + */ + NGHTTP2_STREAM_STATE_IDLE = 1, + /** + * open state. + */ + NGHTTP2_STREAM_STATE_OPEN, + /** + * reserved (local) state. + */ + NGHTTP2_STREAM_STATE_RESERVED_LOCAL, + /** + * reserved (remote) state. + */ + NGHTTP2_STREAM_STATE_RESERVED_REMOTE, + /** + * half closed (local) state. + */ + NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL, + /** + * half closed (remote) state. + */ + NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE, + /** + * closed state. + */ + NGHTTP2_STREAM_STATE_CLOSED +} nghttp2_stream_proto_state; + +/** + * @function + * + * Returns state of |stream|. The root stream retrieved by + * `nghttp2_session_get_root_stream()` will have stream state + * :enum:`NGHTTP2_STREAM_STATE_IDLE`. + */ +nghttp2_stream_proto_state nghttp2_stream_get_state(nghttp2_stream *stream); + +/** + * @function + * + * Returns root of dependency tree, which is imaginary stream with + * stream ID 0. The returned pointer is valid until |session| is + * freed by `nghttp2_session_del()`. + */ +nghttp2_stream *nghttp2_session_get_root_stream(nghttp2_session *session); + +/** + * @function + * + * Returns the parent stream of |stream| in dependency tree. Returns + * NULL if there is no such stream. + */ +nghttp2_stream *nghttp2_stream_get_parent(nghttp2_stream *stream); + +int32_t nghttp2_stream_get_stream_id(nghttp2_stream *stream); + +/** + * @function + * + * Returns the next sibling stream of |stream| in dependency tree. + * Returns NULL if there is no such stream. + */ +nghttp2_stream *nghttp2_stream_get_next_sibling(nghttp2_stream *stream); + +/** + * @function + * + * Returns the previous sibling stream of |stream| in dependency tree. + * Returns NULL if there is no such stream. + */ +nghttp2_stream *nghttp2_stream_get_previous_sibling(nghttp2_stream *stream); + +/** + * @function + * + * Returns the first child stream of |stream| in dependency tree. + * Returns NULL if there is no such stream. + */ +nghttp2_stream *nghttp2_stream_get_first_child(nghttp2_stream *stream); + +/** + * @function + * + * Returns dependency weight to the parent stream of |stream|. + */ +int32_t nghttp2_stream_get_weight(nghttp2_stream *stream); + +/** + * @function + * + * Returns the sum of the weight for |stream|'s children. + */ +int32_t nghttp2_stream_get_sum_dependency_weight(nghttp2_stream *stream); + #ifdef __cplusplus } #endif diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 54fe19db..8254187a 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -338,8 +338,8 @@ static int session_new(nghttp2_session **session_ptr, } nghttp2_stream_init(&(*session_ptr)->root, 0, NGHTTP2_STREAM_FLAG_NONE, - NGHTTP2_STREAM_INITIAL, NGHTTP2_DEFAULT_WEIGHT, 0, 0, - NULL, mem); + NGHTTP2_STREAM_IDLE, NGHTTP2_DEFAULT_WEIGHT, 0, 0, NULL, + mem); (*session_ptr)->remote_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE; (*session_ptr)->recv_window_size = 0; @@ -6527,3 +6527,12 @@ uint32_t nghttp2_session_get_next_stream_id(nghttp2_session *session) { int32_t nghttp2_session_get_last_proc_stream_id(nghttp2_session *session) { return session->last_proc_stream_id; } + +nghttp2_stream *nghttp2_session_find_stream(nghttp2_session *session, + int32_t stream_id) { + return nghttp2_session_get_stream_raw(session, stream_id); +} + +nghttp2_stream *nghttp2_session_get_root_stream(nghttp2_session *session) { + return &session->root; +} diff --git a/lib/nghttp2_stream.c b/lib/nghttp2_stream.c index 0b97b1a7..05d3e410 100644 --- a/lib/nghttp2_stream.c +++ b/lib/nghttp2_stream.c @@ -856,3 +856,61 @@ nghttp2_stream_next_outbound_item(nghttp2_stream *stream) { stream = nghttp2_struct_of(ent, nghttp2_stream, pq_entry); } } + +nghttp2_stream_proto_state nghttp2_stream_get_state(nghttp2_stream *stream) { + if (stream->flags & NGHTTP2_STREAM_FLAG_CLOSED) { + return NGHTTP2_STREAM_STATE_CLOSED; + } + + if (stream->flags & NGHTTP2_STREAM_FLAG_PUSH) { + if (stream->shut_flags & NGHTTP2_SHUT_RD) { + return NGHTTP2_STREAM_STATE_RESERVED_LOCAL; + } + + if (stream->shut_flags & NGHTTP2_SHUT_WR) { + return NGHTTP2_STREAM_STATE_RESERVED_REMOTE; + } + } + + if (stream->shut_flags & NGHTTP2_SHUT_RD) { + return NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE; + } + + if (stream->shut_flags & NGHTTP2_SHUT_WR) { + return NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL; + } + + if (stream->state == NGHTTP2_STREAM_IDLE) { + return NGHTTP2_STREAM_STATE_IDLE; + } + + return NGHTTP2_STREAM_STATE_OPEN; +} + +nghttp2_stream *nghttp2_stream_get_parent(nghttp2_stream *stream) { + return stream->dep_prev; +} + +nghttp2_stream *nghttp2_stream_get_next_sibling(nghttp2_stream *stream) { + return stream->sib_next; +} + +nghttp2_stream *nghttp2_stream_get_previous_sibling(nghttp2_stream *stream) { + return stream->sib_prev; +} + +nghttp2_stream *nghttp2_stream_get_first_child(nghttp2_stream *stream) { + return stream->dep_next; +} + +int32_t nghttp2_stream_get_weight(nghttp2_stream *stream) { + return stream->weight; +} + +int32_t nghttp2_stream_get_sum_dependency_weight(nghttp2_stream *stream) { + return stream->sum_dep_weight; +} + +int32_t nghttp2_stream_get_stream_id(nghttp2_stream *stream) { + return stream->stream_id; +} diff --git a/lib/nghttp2_stream.h b/lib/nghttp2_stream.h index 00024026..a464f68c 100644 --- a/lib/nghttp2_stream.h +++ b/lib/nghttp2_stream.h @@ -131,10 +131,6 @@ typedef enum { NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE = 1 << 13 } nghttp2_http_flag; -struct nghttp2_stream; - -typedef struct nghttp2_stream nghttp2_stream; - struct nghttp2_stream { /* Intrusive Map */ nghttp2_map_entry map_entry; diff --git a/tests/main.c b/tests/main.c index 54821193..9ca387d2 100644 --- a/tests/main.c +++ b/tests/main.c @@ -244,9 +244,13 @@ int main(int argc _U_, char *argv[] _U_) { test_nghttp2_session_stream_attach_item) || !CU_add_test(pSuite, "session_stream_attach_item_subtree", test_nghttp2_session_stream_attach_item_subtree) || - !CU_add_test(pSuite, "session_stream_keep_closed_stream", + !CU_add_test(pSuite, "session_stream_get_state", + test_nghttp2_session_stream_get_state) || + !CU_add_test(pSuite, "session_stream_get_something", + test_nghttp2_session_stream_get_something) || + !CU_add_test(pSuite, "session_keep_closed_stream", test_nghttp2_session_keep_closed_stream) || - !CU_add_test(pSuite, "session_stream_keep_idle_stream", + !CU_add_test(pSuite, "session_keep_idle_stream", test_nghttp2_session_keep_idle_stream) || !CU_add_test(pSuite, "session_detach_idle_stream", test_nghttp2_session_detach_idle_stream) || diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 7815ce68..9cc0114a 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -6843,6 +6843,266 @@ void test_nghttp2_session_stream_attach_item_subtree(void) { nghttp2_session_del(session); } +void test_nghttp2_session_stream_get_state(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_mem *mem; + nghttp2_hd_deflater deflater; + nghttp2_bufs bufs; + nghttp2_buf *buf; + nghttp2_stream *stream; + ssize_t rv; + nghttp2_data_provider data_prd; + nghttp2_frame frame; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + memset(&data_prd, 0, sizeof(data_prd)); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + nghttp2_hd_deflate_init(&deflater, mem); + + CU_ASSERT(NGHTTP2_STREAM_STATE_IDLE == + nghttp2_stream_get_state(nghttp2_session_get_root_stream(session))); + + /* stream 1 HEADERS; without END_STREAM flag set */ + pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT(nghttp2_buf_len(buf) == rv); + + stream = nghttp2_session_find_stream(session, 1); + + CU_ASSERT(NULL != stream); + CU_ASSERT(1 == stream->stream_id); + CU_ASSERT(NGHTTP2_STREAM_STATE_OPEN == nghttp2_stream_get_state(stream)); + + nghttp2_bufs_reset(&bufs); + + /* stream 3 HEADERS; with END_STREAM flag set */ + pack_headers(&bufs, &deflater, 3, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, reqnv, + ARRLEN(reqnv), mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT(nghttp2_buf_len(buf) == rv); + + stream = nghttp2_session_find_stream(session, 3); + + CU_ASSERT(NULL != stream); + CU_ASSERT(3 == stream->stream_id); + CU_ASSERT(NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE == + nghttp2_stream_get_state(stream)); + + nghttp2_bufs_reset(&bufs); + + /* Respond to stream 1 */ + nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), NULL); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_find_stream(session, 1); + + CU_ASSERT(NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL == + nghttp2_stream_get_state(stream)); + + /* Respond to stream 3 */ + nghttp2_submit_response(session, 3, resnv, ARRLEN(resnv), NULL); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_find_stream(session, 3); + + CU_ASSERT(NGHTTP2_STREAM_STATE_CLOSED == nghttp2_stream_get_state(stream)); + + /* stream 5 HEADERS; with END_STREAM flag set */ + pack_headers(&bufs, &deflater, 5, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, reqnv, + ARRLEN(reqnv), mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT(nghttp2_buf_len(buf) == rv); + + nghttp2_bufs_reset(&bufs); + + /* Push stream 2 associated to stream 5 */ + rv = nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 5, reqnv, + ARRLEN(reqnv), NULL); + + CU_ASSERT(2 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_find_stream(session, 2); + + CU_ASSERT(NGHTTP2_STREAM_STATE_RESERVED_LOCAL == + nghttp2_stream_get_state(stream)); + + /* Send resposne to push stream 2 with END_STREAM set */ + nghttp2_submit_response(session, 2, resnv, ARRLEN(resnv), NULL); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_find_stream(session, 2); + + CU_ASSERT(NGHTTP2_STREAM_STATE_CLOSED == nghttp2_stream_get_state(stream)); + + /* Push stream 4 associated to stream 5 */ + rv = nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 5, reqnv, + ARRLEN(reqnv), NULL); + + CU_ASSERT(4 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_find_stream(session, 4); + + CU_ASSERT(NGHTTP2_STREAM_STATE_RESERVED_LOCAL == + nghttp2_stream_get_state(stream)); + + /* Send response to push stream 4 without closing */ + data_prd.read_callback = defer_data_source_read_callback; + + nghttp2_submit_response(session, 4, resnv, ARRLEN(resnv), &data_prd); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_find_stream(session, 4); + + CU_ASSERT(NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE == + nghttp2_stream_get_state(stream)); + + /* Create idle stream by PRIORITY frame */ + nghttp2_frame_priority_init(&frame.priority, 7, &pri_spec_default); + + 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(nghttp2_buf_len(buf) == rv); + + stream = nghttp2_session_find_stream(session, 7); + + CU_ASSERT(NGHTTP2_STREAM_STATE_IDLE == nghttp2_stream_get_state(stream)); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* Test for client side */ + + nghttp2_session_client_new(&session, &callbacks, NULL); + nghttp2_hd_deflate_init(&deflater, mem); + + nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + /* Receive PUSH_PROMISE 2 associated to stream 1 */ + pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 2, reqnv, + ARRLEN(reqnv), mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT(nghttp2_buf_len(buf) == rv); + + stream = nghttp2_session_find_stream(session, 2); + + CU_ASSERT(NGHTTP2_STREAM_STATE_RESERVED_REMOTE == + nghttp2_stream_get_state(stream)); + + nghttp2_bufs_reset(&bufs); + + /* Receive push response for stream 2 without END_STREAM set */ + pack_headers(&bufs, &deflater, 2, NGHTTP2_FLAG_END_HEADERS, resnv, + ARRLEN(resnv), mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT(nghttp2_buf_len(buf) == rv); + + stream = nghttp2_session_find_stream(session, 2); + + CU_ASSERT(NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL == + nghttp2_stream_get_state(stream)); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_stream_get_something(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c; + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + + CU_ASSERT(nghttp2_session_get_root_stream(session) == + nghttp2_stream_get_parent(a)); + CU_ASSERT(NULL == nghttp2_stream_get_previous_sibling(a)); + CU_ASSERT(NULL == nghttp2_stream_get_next_sibling(a)); + CU_ASSERT(NULL == nghttp2_stream_get_first_child(a)); + + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep_weight(session, 5, 11, a); + + CU_ASSERT(a == nghttp2_stream_get_parent(c)); + CU_ASSERT(a == nghttp2_stream_get_parent(b)); + + CU_ASSERT(c == nghttp2_stream_get_first_child(a)); + + CU_ASSERT(b == nghttp2_stream_get_next_sibling(c)); + CU_ASSERT(c == nghttp2_stream_get_previous_sibling(b)); + + CU_ASSERT(27 == nghttp2_stream_get_sum_dependency_weight(a)); + + CU_ASSERT(11 == nghttp2_stream_get_weight(c)); + CU_ASSERT(5 == nghttp2_stream_get_stream_id(c)); + CU_ASSERT(0 == nghttp2_stream_get_stream_id(&session->root)); + + nghttp2_session_del(session); +} + void test_nghttp2_session_keep_closed_stream(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index 6702f46d..20774695 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -114,6 +114,8 @@ void test_nghttp2_session_stream_dep_remove_subtree(void); void test_nghttp2_session_stream_dep_all_your_stream_are_belong_to_us(void); void test_nghttp2_session_stream_attach_item(void); void test_nghttp2_session_stream_attach_item_subtree(void); +void test_nghttp2_session_stream_get_state(void); +void test_nghttp2_session_stream_get_something(void); void test_nghttp2_session_keep_closed_stream(void); void test_nghttp2_session_keep_idle_stream(void); void test_nghttp2_session_detach_idle_stream(void);