diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 91380ca5..6d1d360a 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -682,7 +682,14 @@ typedef enum { /** * Indicates EOF was sensed. */ - NGHTTP2_DATA_FLAG_EOF = 0x01 + NGHTTP2_DATA_FLAG_EOF = 0x01, + /** + * Indicates that END_STREAM flag must not be set even if + * NGHTTP2_DATA_FLAG_EOF is set. Usually this flag is used to send + * trailer header fields with `nghttp2_submit_request()` or + * `nghttp2_submit_response()`. + */ + NGHTTP2_DATA_FLAG_NO_END_STREAM = 0x02, } nghttp2_data_flag; /** @@ -2834,6 +2841,53 @@ int nghttp2_submit_response(nghttp2_session *session, int32_t stream_id, const nghttp2_nv *nva, size_t nvlen, const nghttp2_data_provider *data_prd); +/** + * @function + * + * Submits trailer HEADERS against the stream |stream_id|. + * + * The |nva| is an array of name/value pair :type:`nghttp2_nv` with + * |nvlen| elements. The application is responsible not to include + * required pseudo-header fields (header field whose name starts with + * ":") in |nva|. + * + * This function creates copies of all name/value pairs in |nva|. It + * also lower-cases all names in |nva|. The order of elements in + * |nva| is preserved. + * + * For server, trailer must be followed by response HEADERS or + * response DATA. The library does not check that response HEADERS + * has already sent and if `nghttp2_submit_trailer()` is called before + * any response HEADERS submission (usually by + * `nghttp2_submit_response()`), the content of |nva| will be sent as + * reponse headers, which will result in error. + * + * This function has the same effect with `nghttp2_submit_headers()`, + * with flags = :enum:`NGHTTP2_FLAG_END_HEADERS` and both pri_spec and + * stream_user_data to NULL. + * + * To submit trailer after `nghttp2_submit_response()` is called, the + * application has to specify :type:`nghttp2_data_provider` to + * `nghttp2_submit_response()`. In side + * :type:`nghttp2_data_source_read_callback`, when setting + * :enum:`NGHTTP2_DATA_FLAG_EOF`, also set + * :enum:`NGHTTP2_DATA_FLAG_NO_END_STREAM`. After that, the + * application can send trailer using `nghttp2_submit_trailer()`. + * `nghttp2_submit_trailer()` can be used inside + * :type:`nghttp2_data_source_read_callback`. + * + * This function returns 0 if it succeeds and |stream_id| is -1. + * Otherwise, this function returns 0 if it succeeds, or one of the + * following negative error codes: + * + * :enum:`NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0. + */ +int nghttp2_submit_trailer(nghttp2_session *session, int32_t stream_id, + const nghttp2_nv *nva, size_t nvlen); + /** * @function * diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 0e5170bf..5ef2b746 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -6222,7 +6222,10 @@ int nghttp2_session_pack_data(nghttp2_session *session, nghttp2_bufs *bufs, if (data_flags & NGHTTP2_DATA_FLAG_EOF) { aux_data->eof = 1; - if (aux_data->flags & NGHTTP2_FLAG_END_STREAM) { + /* If NGHTTP2_DATA_FLAG_NO_END_STREAM is set, don't set + NGHTTP2_FLAG_END_STREAM */ + if ((aux_data->flags & NGHTTP2_FLAG_END_STREAM) && + (data_flags & NGHTTP2_DATA_FLAG_NO_END_STREAM) == 0) { frame->hd.flags |= NGHTTP2_FLAG_END_STREAM; } } diff --git a/lib/nghttp2_submit.c b/lib/nghttp2_submit.c index c61982ec..bde78a20 100644 --- a/lib/nghttp2_submit.c +++ b/lib/nghttp2_submit.c @@ -155,6 +155,12 @@ static int32_t submit_headers_shared_nva(nghttp2_session *session, attach_stream); } +int32_t nghttp2_submit_trailer(nghttp2_session *session, int32_t stream_id, + const nghttp2_nv *nva, size_t nvlen) { + return submit_headers_shared_nva(session, NGHTTP2_FLAG_END_STREAM, stream_id, + NULL, nva, nvlen, NULL, NULL, 0); +} + int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags, int32_t stream_id, const nghttp2_priority_spec *pri_spec, diff --git a/tests/main.c b/tests/main.c index e5656d66..b359d41e 100644 --- a/tests/main.c +++ b/tests/main.c @@ -154,6 +154,7 @@ int main(int argc _U_, char *argv[] _U_) { test_nghttp2_submit_response_with_data) || !CU_add_test(pSuite, "submit_response_without_data", test_nghttp2_submit_response_without_data) || + !CU_add_test(pSuite, "submit_trailer", test_nghttp2_submit_trailer) || !CU_add_test(pSuite, "submit_headers_start_stream", test_nghttp2_submit_headers_start_stream) || !CU_add_test(pSuite, "submit_headers_reply", diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index ae8f7f5a..f1debab9 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -88,6 +88,13 @@ static const nghttp2_nv resnv[] = { MAKE_NV(":status", "200"), }; +static const nghttp2_nv trailernv[] = { + // from http://tools.ietf.org/html/rfc6249#section-7 + MAKE_NV("digest", "SHA-256=" + "MWVkMWQxYTRiMzk5MDQ0MzI3NGU5NDEyZTk5OWY1ZGFmNzgyZTJlODYz" + "YjRjYzFhOTlmNTQwYzI2M2QwM2U2MQ=="), +}; + static void scripted_data_feed_init2(scripted_data_feed *df, nghttp2_bufs *bufs) { nghttp2_buf_chain *ci; @@ -279,6 +286,14 @@ static ssize_t fail_data_source_read_callback(nghttp2_session *session _U_, return NGHTTP2_ERR_CALLBACK_FAILURE; } +static ssize_t no_end_stream_data_source_read_callback( + nghttp2_session *session _U_, int32_t stream_id _U_, uint8_t *buf _U_, + size_t len _U_, uint32_t *data_flags, nghttp2_data_source *source _U_, + void *user_data _U_) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF | NGHTTP2_DATA_FLAG_NO_END_STREAM; + return 0; +} + /* static void no_stream_user_data_stream_close_callback */ /* (nghttp2_session *session, */ /* int32_t stream_id, */ @@ -3472,6 +3487,63 @@ void test_nghttp2_submit_response_without_data(void) { nghttp2_session_del(session); } +void test_nghttp2_submit_trailer(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + accumulator acc; + nghttp2_data_provider data_prd; + nghttp2_outbound_item *item; + my_user_data ud; + nghttp2_frame frame; + nghttp2_hd_inflater inflater; + nva_out out; + nghttp2_bufs bufs; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + data_prd.read_callback = no_end_stream_data_source_read_callback; + nva_out_init(&out); + acc.length = 0; + ud.acc = &acc; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); + + nghttp2_hd_inflate_init(&inflater, mem); + nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_END_STREAM, + &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL); + CU_ASSERT(0 == nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), + &data_prd)); + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(0 == + nghttp2_submit_trailer(session, 1, trailernv, ARRLEN(trailernv))); + + session->callbacks.send_callback = accumulator_send_callback; + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + CU_ASSERT(NGHTTP2_HCAT_HEADERS == item->frame.headers.cat); + CU_ASSERT(item->frame.hd.flags & NGHTTP2_FLAG_END_STREAM); + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == unpack_frame(&frame, acc.buf, acc.length)); + + nghttp2_bufs_add(&bufs, acc.buf, acc.length); + inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN, mem); + + CU_ASSERT(ARRLEN(trailernv) == out.nvlen); + assert_nv_equal(trailernv, out.nva, out.nvlen, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); + nghttp2_frame_headers_free(&frame.headers, mem); + nghttp2_hd_inflate_free(&inflater); + nghttp2_session_del(session); +} + void test_nghttp2_submit_headers_start_stream(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index 65230d63..d2767a60 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -69,6 +69,7 @@ void test_nghttp2_submit_request_with_data(void); void test_nghttp2_submit_request_without_data(void); void test_nghttp2_submit_response_with_data(void); void test_nghttp2_submit_response_without_data(void); +void test_nghttp2_submit_trailer(void); void test_nghttp2_submit_headers_start_stream(void); void test_nghttp2_submit_headers_reply(void); void test_nghttp2_submit_headers_push_reply(void);