diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 9914aab4..9dcd1157 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -3573,14 +3573,20 @@ NGHTTP2_EXTERN int nghttp2_submit_settings(nghttp2_session *session, * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT` * The |stream_id| is 0; The |stream_id| does not designate stream * that peer initiated. + * :enum:`NGHTTP2_ERR_STREAM_CLOSED` + * The stream was alreay closed; or the |stream_id| is invalid. * * .. warning:: * * This function returns assigned promised stream ID if it succeeds. - * But that stream is not opened yet. The application must not - * submit frame to that stream ID before - * :type:`nghttp2_before_frame_send_callback` is called for this - * frame. + * As of 1.16.0, stream object for pushed resource is created when + * this function succeeds. In that case, the application can submit + * push response for the promised frame. + * + * In 1.15.0 or prior versions, pushed stream is not opened yet when + * this function succeeds. The application must not submit frame to + * that stream ID before :type:`nghttp2_before_frame_send_callback` + * is called for this frame. * */ NGHTTP2_EXTERN int32_t diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index fd007c93..7483c9b3 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -750,6 +750,31 @@ int nghttp2_session_add_item(nghttp2_session *session, nghttp2_outbound_queue_push(&session->ob_reg, item); item->queued = 1; break; + case NGHTTP2_PUSH_PROMISE: { + nghttp2_headers_aux_data *aux_data; + nghttp2_priority_spec pri_spec; + + aux_data = &item->aux_data.headers; + + if (!stream) { + return NGHTTP2_ERR_STREAM_CLOSED; + } + + nghttp2_priority_spec_init(&pri_spec, stream->stream_id, + NGHTTP2_DEFAULT_WEIGHT, 0); + + if (!nghttp2_session_open_stream( + session, frame->push_promise.promised_stream_id, + NGHTTP2_STREAM_FLAG_NONE, &pri_spec, NGHTTP2_STREAM_RESERVED, + aux_data->stream_user_data)) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_outbound_queue_push(&session->ob_reg, item); + item->queued = 1; + + break; + } case NGHTTP2_WINDOW_UPDATE: if (stream) { stream->window_update_queued = 1; @@ -1903,30 +1928,8 @@ static int session_prep_frame(nghttp2_session *session, } case NGHTTP2_PUSH_PROMISE: { nghttp2_stream *stream; - nghttp2_headers_aux_data *aux_data; - nghttp2_priority_spec pri_spec; size_t estimated_payloadlen; - aux_data = &item->aux_data.headers; - - stream = nghttp2_session_get_stream(session, frame->hd.stream_id); - - /* stream could be NULL if associated stream was already - closed. */ - if (stream) { - nghttp2_priority_spec_init(&pri_spec, stream->stream_id, - NGHTTP2_DEFAULT_WEIGHT, 0); - } else { - nghttp2_priority_spec_default_init(&pri_spec); - } - - if (!nghttp2_session_open_stream( - session, frame->push_promise.promised_stream_id, - NGHTTP2_STREAM_FLAG_NONE, &pri_spec, NGHTTP2_STREAM_RESERVED, - aux_data->stream_user_data)) { - return NGHTTP2_ERR_NOMEM; - } - estimated_payloadlen = session_estimate_headers_payload( session, frame->push_promise.nva, frame->push_promise.nvlen, 0); @@ -1934,6 +1937,10 @@ static int session_prep_frame(nghttp2_session *session, return NGHTTP2_ERR_FRAME_SIZE_ERROR; } + /* stream could be NULL if associated stream was already + closed. */ + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + /* predicte should fail if stream is NULL. */ rv = session_predicate_push_promise_send(session, stream); if (rv != 0) { diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index 179799a5..34c06e53 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -334,7 +334,7 @@ int nghttp2_session_is_my_stream_id(nghttp2_session *session, * NGHTTP2_ERR_NOMEM * Out of memory. * NGHTTP2_ERR_STREAM_CLOSED - * Stream already closed (DATA frame only) + * Stream already closed (DATA and PUSH_PROMISE frame only) */ int nghttp2_session_add_item(nghttp2_session *session, nghttp2_outbound_item *item); diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index aae1cb25..36f36d05 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -4554,28 +4554,28 @@ void test_nghttp2_submit_push_promise(void) { CU_ASSERT(2 == nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1, reqnv, ARRLEN(reqnv), &ud)); + stream = nghttp2_session_get_stream(session, 2); + + CU_ASSERT(NULL != stream); + CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state); + CU_ASSERT(&ud == nghttp2_session_get_stream_user_data(session, 2)); + ud.frame_send_cb_called = 0; ud.sent_frame_type = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(1 == ud.frame_send_cb_called); CU_ASSERT(NGHTTP2_PUSH_PROMISE == ud.sent_frame_type); + stream = nghttp2_session_get_stream(session, 2); + CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state); CU_ASSERT(&ud == nghttp2_session_get_stream_user_data(session, 2)); /* submit PUSH_PROMISE while associated stream is not opened */ - CU_ASSERT(4 == nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 3, - reqnv, ARRLEN(reqnv), &ud)); - - ud.frame_not_send_cb_called = 0; - - CU_ASSERT(0 == nghttp2_session_send(session)); - CU_ASSERT(1 == ud.frame_not_send_cb_called); - CU_ASSERT(NGHTTP2_PUSH_PROMISE == ud.not_sent_frame_type); - - stream = nghttp2_session_get_stream(session, 4); - - CU_ASSERT(NULL == stream); + CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSED == + nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 3, reqnv, + ARRLEN(reqnv), &ud)); nghttp2_session_del(session); }