From 6d5f40238028f2d8cf3db9760aa63c92be8560d9 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 27 Apr 2014 14:48:43 +0900 Subject: [PATCH] Add nghttp2_adjust_priority_callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Callback function invoked to adjust priority value for request HEADERS. Since the application doesn’t know stream ID when it submits requests, it may not be able to add correct priority value to HEADERS frame and forced to use follwing PRIORITY frame. The purpose of this callback is give the chance to the application to adjust priority value with the latest information it has just before transmission so that correct priority is included in HEADERS frame and it doesn’t have to send additional PRIORITY frame. --- lib/includes/nghttp2/nghttp2.h | 57 +++++++++++++++++---- lib/nghttp2_session.c | 92 ++++++++++++++++++++++++++++------ src/nghttp.cc | 28 +++++++---- 3 files changed, 144 insertions(+), 33 deletions(-) diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index f59060b4..d2fd057d 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -1419,6 +1419,37 @@ typedef ssize_t (*nghttp2_select_padding_callback) size_t max_payloadlen, void *user_data); +/** + * @functypedef + * + * Callback function invoked to adjust priority value for request + * HEADERS. This callback is called only for request HEADERS (which + * means, `frame->hd.type == NGHTTP2_HEADERS && frame->headers.cat == + * NGHTTP2_HCAT_REQUEST` hold) and before + * :type:`nghttp2_before_frame_send_callback()`. The application can + * adjust priority value |pri_spec|. Initially, |pri_spec| is filled + * with the current priority value, which is equal to + * `frame->headers.pri_spec`. If the application doesn't alter + * priority value, just return 0 without updating |pri_spec|. + * + * Since the application doesn't know stream ID when it submits + * requests, it may not be able to add correct priority value to + * HEADERS frame and forced to use follwing PRIORITY frame. The + * purpose of this callback is give the chance to the application to + * adjust priority value with the latest information it has just + * before transmission so that correct priority is included in HEADERS + * frame and it doesn't have to send additional PRIORITY frame. + * + * Returning :enum:`NGHTTP2_ERR_CALLBACK_FAILURE` will make + * `nghttp2_session_send()` function immediately return + * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp2_adjust_priority_callback) +(nghttp2_session *session, + const nghttp2_frame *frame, + nghttp2_priority_spec *pri_spec, + void *user_data); + /** * @struct * @@ -1492,6 +1523,11 @@ typedef struct { * frame. */ nghttp2_select_padding_callback select_padding_callback; + /** + * Callback function invoked to adjust priority value just before + * request HEADERS is serialized to the wire format. + */ + nghttp2_adjust_priority_callback adjust_priority_callback; } nghttp2_session_callbacks; struct nghttp2_option; @@ -1698,20 +1734,23 @@ void nghttp2_session_del(nghttp2_session *session); * are not met (e.g., request HEADERS cannot be sent after GOAWAY), * :member:`nghttp2_session_callbacks.on_frame_not_send_callback` * is invoked. Abort the following steps. - * 4. If the frame is HEADERS, PUSH_PROMISE or DATA, + * 4. If the frame is request HEADERS, + * :member:`nghttp2_session_callbacks.adjust_priority_callback` is + * invoked. + * 5. If the frame is HEADERS, PUSH_PROMISE or DATA, * :member:`nghttp2_session_callbacks.select_padding_callback` is * invoked. - * 5. If the frame is request HEADERS, the stream is opened here. - * 6. :member:`nghttp2_session_callbacks.before_frame_send_callback` is + * 6. If the frame is request HEADERS, the stream is opened here. + * 7. :member:`nghttp2_session_callbacks.before_frame_send_callback` is * invoked. - * 7. :member:`nghttp2_session_callbacks.send_callback` is invoked one + * 8. :member:`nghttp2_session_callbacks.send_callback` is invoked one * or more times to send the frame. - * 8. :member:`nghttp2_session_callbacks.on_frame_send_callback` is - * invoked. - * 9. If the transmission of the frame triggers closure of the stream, - * the stream is closed and - * :member:`nghttp2_session_callbacks.on_stream_close_callback` is + * 9. :member:`nghttp2_session_callbacks.on_frame_send_callback` is * invoked. + * 10. If the transmission of the frame triggers closure of the stream, + * the stream is closed and + * :member:`nghttp2_session_callbacks.on_stream_close_callback` is + * invoked. * * This function returns 0 if it succeeds, or one of the following * negative error codes: diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 2c9c7e51..0c6dd8f9 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -1540,6 +1540,43 @@ static int session_consider_blocked(nghttp2_session *session, return 0; } +static int session_call_adjust_priority(nghttp2_session *session, + nghttp2_frame *frame, + nghttp2_stream *stream) +{ + int rv; + + if(session->callbacks.adjust_priority_callback) { + nghttp2_priority_spec pri_spec; + + pri_spec = frame->headers.pri_spec; + + rv = session->callbacks.adjust_priority_callback(session, frame, + &pri_spec, + session->user_data); + + if(rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + frame->headers.pri_spec = pri_spec; + + if(nghttp2_priority_spec_check_default(&pri_spec)) { + rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec); + + if(nghttp2_is_fatal(rv)) { + return rv; + } + + frame->hd.flags &= ~NGHTTP2_FLAG_PRIORITY; + } else { + frame->hd.flags |= NGHTTP2_FLAG_PRIORITY; + } + } + + return 0; +} + /* * This function serializes frame for transmission. * @@ -1558,8 +1595,13 @@ static int nghttp2_session_prep_frame(nghttp2_session *session, switch(frame->hd.type) { case NGHTTP2_HEADERS: { nghttp2_headers_aux_data *aux_data; + aux_data = (nghttp2_headers_aux_data*)item->aux_data; + if(frame->hd.stream_id == -1) { + nghttp2_priority_spec pri_spec_default; + nghttp2_stream *stream; + /* initial HEADERS, which opens stream */ frame->headers.cat = NGHTTP2_HCAT_REQUEST; rv = nghttp2_session_predicate_request_headers_send(session, @@ -1570,6 +1612,29 @@ static int nghttp2_session_prep_frame(nghttp2_session *session, frame->hd.stream_id = session->next_stream_id; session->next_stream_id += 2; + // We first open strea with default priority. This is because + // priority may be adjusted in callback. + nghttp2_priority_spec_default_init(&pri_spec_default); + + stream = nghttp2_session_open_stream + (session, frame->hd.stream_id, + NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, + NGHTTP2_STREAM_INITIAL, + aux_data ? aux_data->stream_user_data : NULL); + + if(stream == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + /* We need to call this after stream was opened so that we can + use nghttp2_session_get_stream_user_data() */ + rv = session_call_adjust_priority(session, frame, stream); + + if(nghttp2_is_fatal(rv)) { + return rv; + } + } else if(nghttp2_session_predicate_push_response_headers_send (session, frame->hd.stream_id) == 0) { frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE; @@ -1584,10 +1649,21 @@ static int nghttp2_session_prep_frame(nghttp2_session *session, return rv; } } + framerv = nghttp2_frame_pack_headers(&session->aob.framebufs, &frame->headers, &session->hd_deflater); + if(framerv < 0) { + if(!nghttp2_is_fatal(framerv)) { + rv = nghttp2_session_close_stream(session, frame->hd.stream_id, + NGHTTP2_NO_ERROR); + + if(nghttp2_is_fatal(rv)) { + return rv; + } + } + return framerv; } @@ -1600,26 +1676,12 @@ static int nghttp2_session_prep_frame(nghttp2_session *session, return framerv; } - switch(frame->headers.cat) { - case NGHTTP2_HCAT_REQUEST: - if(!nghttp2_session_open_stream(session, frame->hd.stream_id, - NGHTTP2_STREAM_FLAG_NONE, - &frame->headers.pri_spec, - NGHTTP2_STREAM_INITIAL, - aux_data ? - aux_data->stream_user_data : NULL)) { - return NGHTTP2_ERR_NOMEM; - } - break; - case NGHTTP2_HCAT_PUSH_RESPONSE: + if(frame->headers.cat == NGHTTP2_HCAT_PUSH_RESPONSE) { if(aux_data && aux_data->stream_user_data) { nghttp2_stream *stream; stream = nghttp2_session_get_stream(session, frame->hd.stream_id); stream->stream_user_data = aux_data->stream_user_data; } - break; - default: - break; } DEBUGF(fprintf(stderr, "send: HEADERS finally serialized in %zd bytes\n", diff --git a/src/nghttp.cc b/src/nghttp.cc index 0aa1b4c8..d9b676e4 100644 --- a/src/nghttp.cc +++ b/src/nghttp.cc @@ -253,9 +253,7 @@ struct Request { { for(auto i = start; i >= 0; --i) { for(auto req : dep->deps[i]) { - if(req->stat.stage != STAT_ON_COMPLETE) { - return req->stream_id; - } + return req->stream_id; } } return -1; @@ -1108,12 +1106,6 @@ void check_stream_id(nghttp2_session *session, int32_t stream_id, return; } - auto pri_spec = req->resolve_dep(req->pri); - - if(!nghttp2_priority_spec_check_default(&pri_spec)) { - nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, stream_id, &pri_spec); - } - auto itr = std::begin(req->dep->deps); for(; itr != std::end(req->dep->deps); ++itr) { if((*itr)[0]->pri == req->pri) { @@ -1149,6 +1141,23 @@ void settings_timeout_cb(evutil_socket_t fd, short what, void *arg) } } // namespace +namespace { +int adjust_priority_callback +(nghttp2_session *session, const nghttp2_frame *frame, + nghttp2_priority_spec *pri_spec, void *user_data) +{ + auto req = (Request*)nghttp2_session_get_stream_user_data + (session, frame->hd.stream_id); + auto pri_spec_adjusted = req->resolve_dep(req->pri); + + if(!nghttp2_priority_spec_check_default(&pri_spec_adjusted)) { + *pri_spec = pri_spec_adjusted; + } + + return 0; +} +} // namespace + namespace { int before_frame_send_callback (nghttp2_session *session, const nghttp2_frame *frame, void *user_data) @@ -1718,6 +1727,7 @@ int run(char **uris, int n) if(config.padding) { callbacks.select_padding_callback = select_padding_callback; } + callbacks.adjust_priority_callback = adjust_priority_callback; std::string prev_scheme; std::string prev_host;