Add nghttp2_on_begin_frame_callback

nghttp2_on_begin_frame_callback will be invoked when a frame header is
received.
This commit is contained in:
Tatsuhiro Tsujikawa 2014-08-23 21:08:35 +09:00
parent 3daa6f2c30
commit a36c4c6f5f
5 changed files with 155 additions and 5 deletions

View File

@ -1163,6 +1163,9 @@ typedef ssize_t (*nghttp2_recv_callback)
* If ``frame->hd.flags & NGHTTP2_FLAG_END_STREAM`` is nonzero, the * If ``frame->hd.flags & NGHTTP2_FLAG_END_STREAM`` is nonzero, the
* |frame| is the last frame from the remote peer in this stream. * |frame| is the last frame from the remote peer in this stream.
* *
* This callback won't be called for CONTINUATION frames.
* HEADERS/PUSH_PROMISE + CONTINUATIONs are treated as single frame.
*
* The implementation of this function must return 0 if it succeeds. * The implementation of this function must return 0 if it succeeds.
* If nonzero value is returned, it is treated as fatal error and * If nonzero value is returned, it is treated as fatal error and
* `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions
@ -1440,6 +1443,31 @@ typedef ssize_t (*nghttp2_select_padding_callback)
size_t max_payloadlen, size_t max_payloadlen,
void *user_data); void *user_data);
/**
* @functypedef
*
* Callback function invoked when a frame header is received. The
* |hd| points to received frame header.
*
* Unlike :type:`nghttp2_on_frame_recv_callback`, this callback will
* also be called when frame header of CONTINUATION frame is received.
*
* If both :type:`nghttp2_on_begin_frame_callback` and
* :type:`nghttp2_on_begin_headers_callback` are set and HEADERS or
* PUSH_PROMISE is received, :type:`nghttp2_on_begin_frame_callback`
* will be called first.
*
* The implementation of this function must return 0 if it succeeds.
* If nonzero value is returned, it is treated as fatal error and
* `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions
* immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.
*
* To set this callback to :type:`nghttp2_session_callbacks`, use
* `nghttp2_session_callbacks_set_on_begin_frame_callback()`.
*/
typedef int (*nghttp2_on_begin_frame_callback)
(nghttp2_session *session, const nghttp2_frame_hd *hd, void *user_data);
struct nghttp2_session_callbacks; struct nghttp2_session_callbacks;
/** /**
@ -1608,6 +1636,15 @@ void nghttp2_session_callbacks_set_data_source_read_length_callback
(nghttp2_session_callbacks *cbs, (nghttp2_session_callbacks *cbs,
nghttp2_data_source_read_length_callback data_source_read_length_callback); nghttp2_data_source_read_length_callback data_source_read_length_callback);
/**
* @function
*
* Sets callback function invoked when a frame header is received.
*/
void nghttp2_session_callbacks_set_on_begin_frame_callback
(nghttp2_session_callbacks *cbs,
nghttp2_on_begin_frame_callback on_begin_frame_callback);
struct nghttp2_option; struct nghttp2_option;
/** /**
@ -1879,7 +1916,10 @@ ssize_t nghttp2_session_mem_send(nghttp2_session *session,
* 1. :type:`nghttp2_recv_callback` is invoked one or more times to * 1. :type:`nghttp2_recv_callback` is invoked one or more times to
* receive frame header. * receive frame header.
* *
* 2. If the frame is DATA frame: * 2. When frame header is received,
* :type:`nghttp2_on_begin_frame_callback` is invoked.
*
* 3. If the frame is DATA frame:
* *
* 1. :type:`nghttp2_recv_callback` is invoked to receive DATA * 1. :type:`nghttp2_recv_callback` is invoked to receive DATA
* payload. For each chunk of data, * payload. For each chunk of data,
@ -1890,7 +1930,7 @@ ssize_t nghttp2_session_mem_send(nghttp2_session *session,
* reception of the frame triggers the closure of the stream, * reception of the frame triggers the closure of the stream,
* :type:`nghttp2_on_stream_close_callback` is invoked. * :type:`nghttp2_on_stream_close_callback` is invoked.
* *
* 3. If the frame is the control frame: * 4. If the frame is the control frame:
* *
* 1. :type:`nghttp2_recv_callback` is invoked one or more times to * 1. :type:`nghttp2_recv_callback` is invoked one or more times to
* receive whole frame. * receive whole frame.

View File

@ -130,3 +130,10 @@ void nghttp2_session_callbacks_set_data_source_read_length_callback
{ {
cbs->read_length_callback = data_source_read_length_callback; cbs->read_length_callback = data_source_read_length_callback;
} }
void nghttp2_session_callbacks_set_on_begin_frame_callback
(nghttp2_session_callbacks *cbs,
nghttp2_on_begin_frame_callback on_begin_frame_callback)
{
cbs->on_begin_frame_callback = on_begin_frame_callback;
}

View File

@ -102,6 +102,10 @@ struct nghttp2_session_callbacks {
* `nghttp2_data_source_read_callback()` * `nghttp2_data_source_read_callback()`
*/ */
nghttp2_data_source_read_length_callback read_length_callback; nghttp2_data_source_read_length_callback read_length_callback;
/**
* Sets callback function invoked when a frame header is received.
*/
nghttp2_on_begin_frame_callback on_begin_frame_callback;
}; };
#endif /* NGHTTP2_CALLBACKS_H */ #endif /* NGHTTP2_CALLBACKS_H */

View File

@ -2453,6 +2453,24 @@ static ssize_t session_recv(nghttp2_session *session, uint8_t *buf, size_t len)
return rv; return rv;
} }
static int session_call_on_begin_frame
(nghttp2_session *session, const nghttp2_frame_hd *hd)
{
int rv;
if(session->callbacks.on_begin_frame_callback) {
rv = session->callbacks.on_begin_frame_callback
(session, hd, session->user_data);
if(rv != 0) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
}
return 0;
}
static int session_call_on_frame_received static int session_call_on_frame_received
(nghttp2_session *session, nghttp2_frame *frame) (nghttp2_session *session, nghttp2_frame *frame)
{ {
@ -4295,7 +4313,9 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
for(;;) { for(;;) {
switch(iframe->state) { switch(iframe->state) {
case NGHTTP2_IB_READ_HEAD: case NGHTTP2_IB_READ_HEAD: {
int on_begin_frame_called = 0;
DEBUGF(fprintf(stderr, "recv: [IB_READ_HEAD]\n")); DEBUGF(fprintf(stderr, "recv: [IB_READ_HEAD]\n"));
readlen = inbound_frame_buf_read(iframe, in, last); readlen = inbound_frame_buf_read(iframe, in, last);
@ -4424,6 +4444,17 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
break; break;
} }
/* Call on_begin_frame_callback here because
session_process_headers_frame() may call
on_begin_headers_callback */
rv = session_call_on_begin_frame(session, &iframe->frame.hd);
if(nghttp2_is_fatal(rv)) {
return rv;
}
on_begin_frame_called = 1;
rv = session_process_headers_frame(session); rv = session_process_headers_frame(session);
if(nghttp2_is_fatal(rv)) { if(nghttp2_is_fatal(rv)) {
return rv; return rv;
@ -4632,7 +4663,25 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
break; break;
} }
if(!on_begin_frame_called) {
switch(iframe->state) {
case NGHTTP2_IB_IGN_HEADER_BLOCK:
case NGHTTP2_IB_IGN_PAYLOAD:
case NGHTTP2_IB_FRAME_SIZE_ERROR:
case NGHTTP2_IB_IGN_DATA:
break;
default:
rv = session_call_on_begin_frame(session, &iframe->frame.hd);
if(nghttp2_is_fatal(rv)) {
return rv;
}
}
}
break; break;
}
case NGHTTP2_IB_READ_NBYTE: case NGHTTP2_IB_READ_NBYTE:
DEBUGF(fprintf(stderr, "recv: [IB_READ_NBYTE]\n")); DEBUGF(fprintf(stderr, "recv: [IB_READ_NBYTE]\n"));
@ -5105,6 +5154,12 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
if(iframe->state == NGHTTP2_IB_EXPECT_CONTINUATION) { if(iframe->state == NGHTTP2_IB_EXPECT_CONTINUATION) {
iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK; iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK;
rv = session_call_on_begin_frame(session, &cont_hd);
if(nghttp2_is_fatal(rv)) {
return rv;
}
} else { } else {
iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK;
} }

View File

@ -75,6 +75,7 @@ typedef struct {
nghttp2_nv nv; nghttp2_nv nv;
size_t data_chunk_len; size_t data_chunk_len;
size_t padlen; size_t padlen;
int begin_frame_cb_called;
} my_user_data; } my_user_data;
static void scripted_data_feed_init2(scripted_data_feed *df, static void scripted_data_feed_init2(scripted_data_feed *df,
@ -156,6 +157,15 @@ static ssize_t accumulator_send_callback(nghttp2_session *session,
return len; return len;
} }
static int on_begin_frame_callback(nghttp2_session *session,
const nghttp2_frame_hd *hd,
void *user_data)
{
my_user_data *ud = (my_user_data*)user_data;
++ud->begin_frame_cb_called;
return 0;
}
static int on_frame_recv_callback(nghttp2_session *session, static int on_frame_recv_callback(nghttp2_session *session,
const nghttp2_frame *frame, const nghttp2_frame *frame,
void *user_data) void *user_data)
@ -409,6 +419,7 @@ void test_nghttp2_session_recv(void)
callbacks.send_callback = null_send_callback; callbacks.send_callback = null_send_callback;
callbacks.recv_callback = scripted_recv_callback; callbacks.recv_callback = scripted_recv_callback;
callbacks.on_frame_recv_callback = on_frame_recv_callback; callbacks.on_frame_recv_callback = on_frame_recv_callback;
callbacks.on_begin_frame_callback = on_begin_frame_callback;
user_data.df = &df; user_data.df = &df;
@ -435,10 +446,13 @@ void test_nghttp2_session_recv(void)
nghttp2_frame_headers_free(&frame.headers); nghttp2_frame_headers_free(&frame.headers);
user_data.frame_recv_cb_called = 0; user_data.frame_recv_cb_called = 0;
user_data.begin_frame_cb_called = 0;
while((ssize_t)df.seqidx < framelen) { while((ssize_t)df.seqidx < framelen) {
CU_ASSERT(0 == nghttp2_session_recv(session)); CU_ASSERT(0 == nghttp2_session_recv(session));
} }
CU_ASSERT(1 == user_data.frame_recv_cb_called); CU_ASSERT(1 == user_data.frame_recv_cb_called);
CU_ASSERT(1 == user_data.begin_frame_cb_called);
nghttp2_bufs_reset(&bufs); nghttp2_bufs_reset(&bufs);
@ -453,9 +467,33 @@ void test_nghttp2_session_recv(void)
scripted_data_feed_init2(&df, &bufs); scripted_data_feed_init2(&df, &bufs);
user_data.frame_recv_cb_called = 0; user_data.frame_recv_cb_called = 0;
user_data.begin_frame_cb_called = 0;
CU_ASSERT(0 == nghttp2_session_recv(session)); CU_ASSERT(0 == nghttp2_session_recv(session));
CU_ASSERT(1 == user_data.frame_recv_cb_called); CU_ASSERT(1 == user_data.frame_recv_cb_called);
CU_ASSERT(1 == user_data.begin_frame_cb_called);
nghttp2_bufs_reset(&bufs);
/* Receive PRIORITY */
nghttp2_frame_priority_init(&frame.priority, 5, &pri_spec_default);
rv = nghttp2_frame_pack_priority(&bufs, &frame.priority);
CU_ASSERT(0 == rv);
nghttp2_frame_priority_free(&frame.priority);
scripted_data_feed_init2(&df, &bufs);
user_data.frame_recv_cb_called = 0;
user_data.begin_frame_cb_called = 0;
CU_ASSERT(0 == nghttp2_session_recv(session));
CU_ASSERT(1 == user_data.frame_recv_cb_called);
CU_ASSERT(1 == user_data.begin_frame_cb_called);
nghttp2_bufs_reset(&bufs);
nghttp2_hd_deflate_free(&deflater); nghttp2_hd_deflate_free(&deflater);
nghttp2_session_del(session); nghttp2_session_del(session);
@ -463,8 +501,6 @@ void test_nghttp2_session_recv(void)
/* Some tests for frame too large */ /* Some tests for frame too large */
nghttp2_session_server_new(&session, &callbacks, &user_data); nghttp2_session_server_new(&session, &callbacks, &user_data);
nghttp2_bufs_reset(&bufs);
/* Receive PING with too large payload */ /* Receive PING with too large payload */
nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_NONE, NULL); nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_NONE, NULL);
@ -485,8 +521,12 @@ void test_nghttp2_session_recv(void)
scripted_data_feed_init2(&df, &bufs); scripted_data_feed_init2(&df, &bufs);
user_data.frame_recv_cb_called = 0; user_data.frame_recv_cb_called = 0;
user_data.begin_frame_cb_called = 0;
CU_ASSERT(0 == nghttp2_session_recv(session)); CU_ASSERT(0 == nghttp2_session_recv(session));
CU_ASSERT(0 == user_data.frame_recv_cb_called); CU_ASSERT(0 == user_data.frame_recv_cb_called);
CU_ASSERT(0 == user_data.begin_frame_cb_called);
item = nghttp2_session_get_next_ob_item(session); item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item)); CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item));
CU_ASSERT(NGHTTP2_FRAME_SIZE_ERROR == OB_CTRL(item)->goaway.error_code); CU_ASSERT(NGHTTP2_FRAME_SIZE_ERROR == OB_CTRL(item)->goaway.error_code);
@ -760,6 +800,7 @@ void test_nghttp2_session_recv_continuation(void)
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.on_header_callback = on_header_callback; callbacks.on_header_callback = on_header_callback;
callbacks.on_begin_headers_callback = on_begin_headers_callback; callbacks.on_begin_headers_callback = on_begin_headers_callback;
callbacks.on_begin_frame_callback = on_begin_frame_callback;
nghttp2_session_server_new(&session, &callbacks, &ud); nghttp2_session_server_new(&session, &callbacks, &ud);
@ -816,9 +857,12 @@ void test_nghttp2_session_recv_continuation(void)
CU_ASSERT(0 == nghttp2_buf_len(buf)); CU_ASSERT(0 == nghttp2_buf_len(buf));
ud.header_cb_called = 0; ud.header_cb_called = 0;
ud.begin_frame_cb_called = 0;
rv = nghttp2_session_mem_recv(session, data, datalen); rv = nghttp2_session_mem_recv(session, data, datalen);
CU_ASSERT((ssize_t)datalen == rv); CU_ASSERT((ssize_t)datalen == rv);
CU_ASSERT(2 == ud.header_cb_called); CU_ASSERT(2 == ud.header_cb_called);
CU_ASSERT(3 == ud.begin_frame_cb_called);
nghttp2_hd_deflate_free(&deflater); nghttp2_hd_deflate_free(&deflater);
nghttp2_session_del(session); nghttp2_session_del(session);