Add nghttp2_adjust_priority_callback

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.
This commit is contained in:
Tatsuhiro Tsujikawa 2014-04-27 14:48:43 +09:00
parent cc7929bdcc
commit 6d5f402380
3 changed files with 144 additions and 33 deletions

View File

@ -1419,6 +1419,37 @@ typedef ssize_t (*nghttp2_select_padding_callback)
size_t max_payloadlen, size_t max_payloadlen,
void *user_data); 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 * @struct
* *
@ -1492,6 +1523,11 @@ typedef struct {
* frame. * frame.
*/ */
nghttp2_select_padding_callback select_padding_callback; 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; } nghttp2_session_callbacks;
struct nghttp2_option; struct nghttp2_option;
@ -1698,17 +1734,20 @@ void nghttp2_session_del(nghttp2_session *session);
* are not met (e.g., request HEADERS cannot be sent after GOAWAY), * are not met (e.g., request HEADERS cannot be sent after GOAWAY),
* :member:`nghttp2_session_callbacks.on_frame_not_send_callback` * :member:`nghttp2_session_callbacks.on_frame_not_send_callback`
* is invoked. Abort the following steps. * 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 * :member:`nghttp2_session_callbacks.select_padding_callback` is
* invoked. * invoked.
* 5. If the frame is request HEADERS, the stream is opened here. * 6. If the frame is request HEADERS, the stream is opened here.
* 6. :member:`nghttp2_session_callbacks.before_frame_send_callback` is * 7. :member:`nghttp2_session_callbacks.before_frame_send_callback` is
* invoked. * 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. * or more times to send the frame.
* 8. :member:`nghttp2_session_callbacks.on_frame_send_callback` is * 9. :member:`nghttp2_session_callbacks.on_frame_send_callback` is
* invoked. * invoked.
* 9. If the transmission of the frame triggers closure of the stream, * 10. If the transmission of the frame triggers closure of the stream,
* the stream is closed and * the stream is closed and
* :member:`nghttp2_session_callbacks.on_stream_close_callback` is * :member:`nghttp2_session_callbacks.on_stream_close_callback` is
* invoked. * invoked.

View File

@ -1540,6 +1540,43 @@ static int session_consider_blocked(nghttp2_session *session,
return 0; 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. * This function serializes frame for transmission.
* *
@ -1558,8 +1595,13 @@ static int nghttp2_session_prep_frame(nghttp2_session *session,
switch(frame->hd.type) { switch(frame->hd.type) {
case NGHTTP2_HEADERS: { case NGHTTP2_HEADERS: {
nghttp2_headers_aux_data *aux_data; nghttp2_headers_aux_data *aux_data;
aux_data = (nghttp2_headers_aux_data*)item->aux_data; aux_data = (nghttp2_headers_aux_data*)item->aux_data;
if(frame->hd.stream_id == -1) { if(frame->hd.stream_id == -1) {
nghttp2_priority_spec pri_spec_default;
nghttp2_stream *stream;
/* initial HEADERS, which opens stream */ /* initial HEADERS, which opens stream */
frame->headers.cat = NGHTTP2_HCAT_REQUEST; frame->headers.cat = NGHTTP2_HCAT_REQUEST;
rv = nghttp2_session_predicate_request_headers_send(session, 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; frame->hd.stream_id = session->next_stream_id;
session->next_stream_id += 2; 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 } else if(nghttp2_session_predicate_push_response_headers_send
(session, frame->hd.stream_id) == 0) { (session, frame->hd.stream_id) == 0) {
frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE; frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE;
@ -1584,10 +1649,21 @@ static int nghttp2_session_prep_frame(nghttp2_session *session,
return rv; return rv;
} }
} }
framerv = nghttp2_frame_pack_headers(&session->aob.framebufs, framerv = nghttp2_frame_pack_headers(&session->aob.framebufs,
&frame->headers, &frame->headers,
&session->hd_deflater); &session->hd_deflater);
if(framerv < 0) { 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; return framerv;
} }
@ -1600,26 +1676,12 @@ static int nghttp2_session_prep_frame(nghttp2_session *session,
return framerv; return framerv;
} }
switch(frame->headers.cat) { if(frame->headers.cat == NGHTTP2_HCAT_PUSH_RESPONSE) {
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(aux_data && aux_data->stream_user_data) { if(aux_data && aux_data->stream_user_data) {
nghttp2_stream *stream; nghttp2_stream *stream;
stream = nghttp2_session_get_stream(session, frame->hd.stream_id); stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
stream->stream_user_data = aux_data->stream_user_data; stream->stream_user_data = aux_data->stream_user_data;
} }
break;
default:
break;
} }
DEBUGF(fprintf(stderr, "send: HEADERS finally serialized in %zd bytes\n", DEBUGF(fprintf(stderr, "send: HEADERS finally serialized in %zd bytes\n",

View File

@ -253,11 +253,9 @@ struct Request {
{ {
for(auto i = start; i >= 0; --i) { for(auto i = start; i >= 0; --i) {
for(auto req : dep->deps[i]) { for(auto req : dep->deps[i]) {
if(req->stat.stage != STAT_ON_COMPLETE) {
return req->stream_id; return req->stream_id;
} }
} }
}
return -1; return -1;
} }
@ -1108,12 +1106,6 @@ void check_stream_id(nghttp2_session *session, int32_t stream_id,
return; 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); auto itr = std::begin(req->dep->deps);
for(; itr != std::end(req->dep->deps); ++itr) { for(; itr != std::end(req->dep->deps); ++itr) {
if((*itr)[0]->pri == req->pri) { if((*itr)[0]->pri == req->pri) {
@ -1149,6 +1141,23 @@ void settings_timeout_cb(evutil_socket_t fd, short what, void *arg)
} }
} // namespace } // 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 { namespace {
int before_frame_send_callback int before_frame_send_callback
(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) (nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
@ -1718,6 +1727,7 @@ int run(char **uris, int n)
if(config.padding) { if(config.padding) {
callbacks.select_padding_callback = select_padding_callback; callbacks.select_padding_callback = select_padding_callback;
} }
callbacks.adjust_priority_callback = adjust_priority_callback;
std::string prev_scheme; std::string prev_scheme;
std::string prev_host; std::string prev_host;