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;