Add a way to send trailer with nghttp2_submit_request/nghttp2_submit_response

nghttp2_submit_request and nghttp2_submit_response will set
NGHTTP2_FLAG_END_STREAM after all given data is sent (data could be
0).  This means we have no way to send trailers.  In this commit, we
added NGHTTP2_DATA_FLAG_NO_END_STREAM flag.  The application can set
this flag in *data_flags inside nghttp2_data_source_read_callback.  If
NGHTTP2_DATA_FLAG_EOF is set, library automatically set
NGHTTP2_FLAG_END_STREAM.  But if both NGHTTP2_DATA_FLAG_EOF and
NGHTTP2_DATA_FLAG_NO_END_STREAM are set, NGHTTP2_FLAG_END_STREAM will
not set by library.  Then application can use new
nghttp2_submit_trailer() to send trailers.  nghttp2_submit_trailer()
will set NGHTTP2_FLAG_END_STREAM and it is actually thing wrapper of
nghttp2_submit_headers().
This commit is contained in:
Tatsuhiro Tsujikawa 2015-03-07 17:02:00 +09:00
parent 505a300d93
commit 2f2a535113
6 changed files with 139 additions and 2 deletions

View File

@ -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
*

View File

@ -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;
}
}

View File

@ -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,

View File

@ -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",

View File

@ -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;

View File

@ -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);