Create stream object for pushed resource during nghttp2_submit_push_promise()

Previously, stream object for pushed resource was not created during
nghttp2_submit_push_promise().  It was created just before
nghttp2_before_frame_send_callback was called for that PUSH_PROMISE
frame.  This means that application could not call
nghttp2_submit_response for the pushed resource before
nghttp2_before_frame_send_callback was called.  This could be solved
by callback chaining, but for web server with back pressure from
backend stream, it is a bit unnecessarily hard to use.

This commit changes nghttp2_submit_push_promise() behaviour so that
stream object is created during that call.  It makes application call
nghttp2_submit_response right after successful
nghttp2_submit_push_promise call.
This commit is contained in:
Tatsuhiro Tsujikawa 2015-12-02 21:16:30 +09:00
parent 6beaf4d9f3
commit 2288ee8060
4 changed files with 52 additions and 39 deletions

View File

@ -3573,14 +3573,20 @@ NGHTTP2_EXTERN int nghttp2_submit_settings(nghttp2_session *session,
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT` * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
* The |stream_id| is 0; The |stream_id| does not designate stream * The |stream_id| is 0; The |stream_id| does not designate stream
* that peer initiated. * that peer initiated.
* :enum:`NGHTTP2_ERR_STREAM_CLOSED`
* The stream was alreay closed; or the |stream_id| is invalid.
* *
* .. warning:: * .. warning::
* *
* This function returns assigned promised stream ID if it succeeds. * This function returns assigned promised stream ID if it succeeds.
* But that stream is not opened yet. The application must not * As of 1.16.0, stream object for pushed resource is created when
* submit frame to that stream ID before * this function succeeds. In that case, the application can submit
* :type:`nghttp2_before_frame_send_callback` is called for this * push response for the promised frame.
* 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 NGHTTP2_EXTERN int32_t

View File

@ -750,6 +750,31 @@ int nghttp2_session_add_item(nghttp2_session *session,
nghttp2_outbound_queue_push(&session->ob_reg, item); nghttp2_outbound_queue_push(&session->ob_reg, item);
item->queued = 1; item->queued = 1;
break; 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: case NGHTTP2_WINDOW_UPDATE:
if (stream) { if (stream) {
stream->window_update_queued = 1; stream->window_update_queued = 1;
@ -1903,30 +1928,8 @@ static int session_prep_frame(nghttp2_session *session,
} }
case NGHTTP2_PUSH_PROMISE: { case NGHTTP2_PUSH_PROMISE: {
nghttp2_stream *stream; nghttp2_stream *stream;
nghttp2_headers_aux_data *aux_data;
nghttp2_priority_spec pri_spec;
size_t estimated_payloadlen; 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( estimated_payloadlen = session_estimate_headers_payload(
session, frame->push_promise.nva, frame->push_promise.nvlen, 0); 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; 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. */ /* predicte should fail if stream is NULL. */
rv = session_predicate_push_promise_send(session, stream); rv = session_predicate_push_promise_send(session, stream);
if (rv != 0) { if (rv != 0) {

View File

@ -334,7 +334,7 @@ int nghttp2_session_is_my_stream_id(nghttp2_session *session,
* NGHTTP2_ERR_NOMEM * NGHTTP2_ERR_NOMEM
* Out of memory. * Out of memory.
* NGHTTP2_ERR_STREAM_CLOSED * 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, int nghttp2_session_add_item(nghttp2_session *session,
nghttp2_outbound_item *item); nghttp2_outbound_item *item);

View File

@ -4554,28 +4554,28 @@ void test_nghttp2_submit_push_promise(void) {
CU_ASSERT(2 == nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1, CU_ASSERT(2 == nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1,
reqnv, ARRLEN(reqnv), &ud)); 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.frame_send_cb_called = 0;
ud.sent_frame_type = 0; ud.sent_frame_type = 0;
CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(1 == ud.frame_send_cb_called); CU_ASSERT(1 == ud.frame_send_cb_called);
CU_ASSERT(NGHTTP2_PUSH_PROMISE == ud.sent_frame_type); CU_ASSERT(NGHTTP2_PUSH_PROMISE == ud.sent_frame_type);
stream = nghttp2_session_get_stream(session, 2); stream = nghttp2_session_get_stream(session, 2);
CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state); CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state);
CU_ASSERT(&ud == nghttp2_session_get_stream_user_data(session, 2)); CU_ASSERT(&ud == nghttp2_session_get_stream_user_data(session, 2));
/* submit PUSH_PROMISE while associated stream is not opened */ /* submit PUSH_PROMISE while associated stream is not opened */
CU_ASSERT(4 == nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 3, CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSED ==
reqnv, ARRLEN(reqnv), &ud)); 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);
nghttp2_session_del(session); nghttp2_session_del(session);
} }