diff --git a/examples/client.c b/examples/client.c index 73b3ee57..181ad77c 100644 --- a/examples/client.c +++ b/examples/client.c @@ -270,10 +270,10 @@ static int on_stream_close_callback(nghttp2_session *session, req = nghttp2_session_get_stream_user_data(session, stream_id); if(req) { int rv; - rv = nghttp2_submit_goaway(session, NGHTTP2_FLAG_NONE, NGHTTP2_NO_ERROR, - NULL, 0); + rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR); + if(rv != 0) { - diec("nghttp2_submit_goaway", rv); + diec("nghttp2_session_terminate_session", rv); } } return 0; diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 5979cdd4..8af4d2a7 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -2003,13 +2003,13 @@ int32_t nghttp2_session_get_stream_remote_window_size(nghttp2_session* session, * * Signals the session so that the connection should be terminated. * - * GOAWAY frame with the given |error_code| will be submitted if it - * has not been transmitted. After the transmission, both - * `nghttp2_session_want_read()` and `nghttp2_session_want_write()` - * return 0. If GOAWAY frame has already transmitted at the time when - * this function is invoked, `nghttp2_session_want_read()` and - * `nghttp2_session_want_write()` returns 0 immediately after this - * function succeeds. + * The last stream ID is the ID of a stream for which + * :type:`nghttp2_on_frame_recv_callback` was called most recently. + * + * The |error_code| is the error code of this GOAWAY frame. + * + * After the transmission, both `nghttp2_session_want_read()` and + * `nghttp2_session_want_write()` return 0. * * This function should be called when the connection should be * terminated after sending GOAWAY. If the remaining streams should @@ -2024,6 +2024,25 @@ int32_t nghttp2_session_get_stream_remote_window_size(nghttp2_session* session, int nghttp2_session_terminate_session(nghttp2_session *session, nghttp2_error_code error_code); +/** + * @function + * + * Signals the session so that the connection should be terminated. + * + * This function behaves like `nghttp2_session_terminate_session()`, + * but the last stream ID can be specified by the application for fine + * grained control of stream. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +int nghttp2_session_terminate_session2(nghttp2_session *session, + int32_t last_stream_id, + nghttp2_error_code error_code); + /** * @function * @@ -2529,7 +2548,8 @@ int nghttp2_submit_ping(nghttp2_session *session, uint8_t flags, /** * @function * - * Submits GOAWAY frame with the error code |error_code|. + * Submits GOAWAY frame with the last stream ID |last_stream_id| and + * the error code |error_code|. * * The |flags| is currently ignored and should be * :enum:`NGHTTP2_FLAG_NONE`. @@ -2541,15 +2561,22 @@ int nghttp2_submit_ping(nghttp2_session *session, uint8_t flags, * keep this memory after the return of this function. If the * |opaque_data_len| is 0, the |opaque_data| could be ``NULL``. * + * To shutdown gracefully, first send GOAWAY with ``last_stream_id = + * (1u << 31) - 1``. After 1 RTT, call either + * `nghttp2_submit_goaway()`, `nghttp2_session_terminate_session()` or + * `nghttp2_session_terminate_session2()`. The latter 2 will close + * HTTP/2 session immediately after transmission of the frame. + * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`NGHTTP2_ERR_NOMEM` * Out of memory. - * NGHTTP2_ERR_INVALID_ARGUMENT + * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT` * The |opaque_data_len| is too large. */ int nghttp2_submit_goaway(nghttp2_session *session, uint8_t flags, + int32_t last_stream_id, nghttp2_error_code error_code, const uint8_t *opaque_data, size_t opaque_data_len); diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 04477cc3..afc2b275 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -134,14 +134,23 @@ static int session_detect_idle_stream(nghttp2_session *session, int nghttp2_session_terminate_session(nghttp2_session *session, nghttp2_error_code error_code) +{ + return nghttp2_session_terminate_session2(session, + session->last_proc_stream_id, + error_code); +} + +int nghttp2_session_terminate_session2(nghttp2_session *session, + int32_t last_stream_id, + nghttp2_error_code error_code) { if(session->goaway_flags & NGHTTP2_GOAWAY_FAIL_ON_SEND) { return 0; } session->goaway_flags |= NGHTTP2_GOAWAY_FAIL_ON_SEND; - return nghttp2_submit_goaway(session, NGHTTP2_FLAG_NONE, error_code, - NULL, 0); + return nghttp2_submit_goaway(session, NGHTTP2_FLAG_NONE, last_stream_id, + error_code, NULL, 0); } int nghttp2_session_is_my_stream_id(nghttp2_session *session, diff --git a/lib/nghttp2_submit.c b/lib/nghttp2_submit.c index 56faec4d..1a7a3b56 100644 --- a/lib/nghttp2_submit.c +++ b/lib/nghttp2_submit.c @@ -246,10 +246,11 @@ int nghttp2_submit_rst_stream(nghttp2_session *session, uint8_t flags, } int nghttp2_submit_goaway(nghttp2_session *session, uint8_t flags, + int32_t last_stream_id, nghttp2_error_code error_code, const uint8_t *opaque_data, size_t opaque_data_len) { - return nghttp2_session_add_goaway(session, session->last_proc_stream_id, + return nghttp2_session_add_goaway(session, last_stream_id, error_code, opaque_data, opaque_data_len); } diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 2bfea0dd..2609abe0 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -4429,7 +4429,8 @@ void test_nghttp2_session_on_ctrl_not_send(void) user_data.frame_not_send_cb_called = 0; /* Send GOAWAY */ CU_ASSERT(0 == nghttp2_submit_goaway(session, NGHTTP2_FLAG_NONE, - NGHTTP2_NO_ERROR, NULL, 0)); + (1u << 31) - 1, NGHTTP2_NO_ERROR, + NULL, 0)); session->next_stream_id = 9; @@ -4456,7 +4457,7 @@ void test_nghttp2_session_get_outbound_queue_size(void) CU_ASSERT(1 == nghttp2_session_get_outbound_queue_size(session)); CU_ASSERT(0 == nghttp2_submit_goaway(session, NGHTTP2_FLAG_NONE, - NGHTTP2_NO_ERROR, NULL, 0)); + 3, NGHTTP2_NO_ERROR, NULL, 0)); CU_ASSERT(2 == nghttp2_session_get_outbound_queue_size(session)); nghttp2_session_del(session);