diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index ec241740..1d660a01 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -860,8 +860,13 @@ typedef enum { * achieved by returning :enum:`NGHTTP2_ERR_DEFERRED` without reading * any data in this invocation. The library removes DATA frame from * the outgoing queue temporarily. To move back deferred DATA frame - * to outgoing queue, call `nghttp2_session_resume_data()`. In case - * of error, there are 2 choices. Returning + * to outgoing queue, call `nghttp2_session_resume_data()`. + * + * If the application just wants to return from + * `nghttp2_session_send()` or `nghttp2_session_mem_send()` without + * sending anything, return :enum:`NGHTTP2_ERR_PAUSE`. + * + * In case of error, there are 2 choices. Returning * :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will close the stream * by issuing RST_STREAM with :enum:`NGHTTP2_INTERNAL_ERROR`. If a * different error code is desirable, use diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 0d8734c4..a9829be2 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -2263,6 +2263,9 @@ static int session_prep_frame(nghttp2_session *session, rv = nghttp2_session_pack_data(session, &session->aob.framebufs, next_readmax, frame, &item->aux_data.data, stream); + if (rv == NGHTTP2_ERR_PAUSE) { + return rv; + } if (rv == NGHTTP2_ERR_DEFERRED) { rv = nghttp2_stream_defer_item(stream, NGHTTP2_STREAM_FLAG_DEFERRED_USER); @@ -2918,6 +2921,9 @@ static ssize_t nghttp2_session_mem_send_internal(nghttp2_session *session, } rv = session_prep_frame(session, item); + if (rv == NGHTTP2_ERR_PAUSE) { + return 0; + } if (rv == NGHTTP2_ERR_DEFERRED) { DEBUGF(fprintf(stderr, "send: frame transmission deferred\n")); break; @@ -7020,7 +7026,8 @@ int nghttp2_session_pack_data(nghttp2_session *session, nghttp2_bufs *bufs, &aux_data->data_prd.source, session->user_data); if (payloadlen == NGHTTP2_ERR_DEFERRED || - payloadlen == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + payloadlen == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE || + payloadlen == NGHTTP2_ERR_PAUSE) { DEBUGF(fprintf(stderr, "send: DATA postponed due to %s\n", nghttp2_strerror((int)payloadlen))); diff --git a/tests/main.c b/tests/main.c index e4f4900f..20ff40ae 100644 --- a/tests/main.c +++ b/tests/main.c @@ -310,6 +310,8 @@ int main(int argc _U_, char *argv[] _U_) { test_nghttp2_session_cancel_from_before_frame_send) || !CU_add_test(pSuite, "session_removed_closed_stream", test_nghttp2_session_removed_closed_stream) || + !CU_add_test(pSuite, "session_pause_data", + test_nghttp2_session_pause_data) || !CU_add_test(pSuite, "http_mandatory_headers", test_nghttp2_http_mandatory_headers) || !CU_add_test(pSuite, "http_content_length", diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 95117092..bd3ca66d 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -77,6 +77,7 @@ typedef struct { size_t padlen; int begin_frame_cb_called; nghttp2_buf scratchbuf; + size_t data_source_read_cb_paused; } my_user_data; static const nghttp2_nv reqnv[] = { @@ -9934,6 +9935,53 @@ void test_nghttp2_session_removed_closed_stream(void) { nghttp2_bufs_free(&bufs); } +static ssize_t pause_once_data_source_read_callback( + nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len, + uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { + my_user_data *ud = user_data; + if (ud->data_source_read_cb_paused == 0) { + ++ud->data_source_read_cb_paused; + return NGHTTP2_ERR_PAUSE; + } + + return fixed_length_data_source_read_callback(session, stream_id, buf, len, + data_flags, source, user_data); +} + +void test_nghttp2_session_pause_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + data_prd.read_callback = pause_once_data_source_read_callback; + ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + open_recv_stream(session, 1); + + CU_ASSERT( + 0 == nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd)); + + ud.frame_send_cb_called = 0; + ud.data_source_read_cb_paused = 0; + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == ud.frame_send_cb_called); + CU_ASSERT(NULL == session->aob.item); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(NGHTTP2_DATA == ud.sent_frame_type); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_session_del(session); +} + 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) { diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index 5e81b215..b37713cb 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -153,6 +153,7 @@ void test_nghttp2_session_repeated_priority_submission(void); void test_nghttp2_session_set_local_window_size(void); void test_nghttp2_session_cancel_from_before_frame_send(void); void test_nghttp2_session_removed_closed_stream(void); +void test_nghttp2_session_pause_data(void); void test_nghttp2_http_mandatory_headers(void); void test_nghttp2_http_content_length(void); void test_nghttp2_http_content_length_mismatch(void);