Merge branch 'jchampio-dev/expect-continue'

This commit is contained in:
Tatsuhiro Tsujikawa 2016-03-31 20:05:08 +09:00
commit b26503f51c
2 changed files with 162 additions and 10 deletions

View File

@ -113,7 +113,8 @@ Config::Config()
no_content_length(false), no_content_length(false),
no_dep(false), no_dep(false),
hexdump(false), hexdump(false),
no_push(false) { no_push(false),
expect_continue(false) {
nghttp2_option_new(&http2_option); nghttp2_option_new(&http2_option);
nghttp2_option_set_peer_max_concurrent_streams(http2_option, nghttp2_option_set_peer_max_concurrent_streams(http2_option,
peer_max_concurrent_streams); peer_max_concurrent_streams);
@ -303,6 +304,51 @@ void Request::record_response_end_time() {
timing.response_end_time = get_time(); timing.response_end_time = get_time();
} }
namespace {
void continue_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
auto client = static_cast<HttpClient *>(ev_userdata(loop));
auto req = static_cast<Request *>(w->data);
int error;
error = nghttp2_submit_data(client->session, NGHTTP2_FLAG_END_STREAM,
req->stream_id, req->data_prd);
if (error) {
std::cerr << "[ERROR] nghttp2_submit_data() returned error: "
<< nghttp2_strerror(error) << std::endl;
nghttp2_submit_rst_stream(client->session, NGHTTP2_FLAG_NONE,
req->stream_id, NGHTTP2_INTERNAL_ERROR);
}
client->signal_write();
}
} // namespace
ContinueTimer::ContinueTimer(struct ev_loop *loop, Request *req)
: loop(loop) {
ev_timer_init(&timer, continue_timeout_cb, 1., 0.);
timer.data = req;
}
ContinueTimer::~ContinueTimer() {
stop();
}
void ContinueTimer::start() {
ev_timer_start(loop, &timer);
}
void ContinueTimer::stop() {
ev_timer_stop(loop, &timer);
}
void ContinueTimer::dispatch_continue() {
// Only dispatch the timeout callback if it hasn't already been called.
if (ev_is_active(&timer)) {
ev_feed_event(loop, &timer, 0);
}
}
namespace { namespace {
int htp_msg_begincb(http_parser *htp) { int htp_msg_begincb(http_parser *htp) {
if (config.verbose) { if (config.verbose) {
@ -353,16 +399,27 @@ int submit_request(HttpClient *client, const Headers &headers, Request *req) {
{"accept", "*/*"}, {"accept", "*/*"},
{"accept-encoding", "gzip, deflate"}, {"accept-encoding", "gzip, deflate"},
{"user-agent", "nghttp2/" NGHTTP2_VERSION}}; {"user-agent", "nghttp2/" NGHTTP2_VERSION}};
bool expect_continue = false;
if (config.continuation) { if (config.continuation) {
for (size_t i = 0; i < 6; ++i) { for (size_t i = 0; i < 6; ++i) {
build_headers.emplace_back("continuation-test-" + util::utos(i + 1), build_headers.emplace_back("continuation-test-" + util::utos(i + 1),
std::string(4_k, '-')); std::string(4_k, '-'));
} }
} }
auto num_initial_headers = build_headers.size(); auto num_initial_headers = build_headers.size();
if (!config.no_content_length && req->data_prd) {
build_headers.emplace_back("content-length", util::utos(req->data_length)); if (req->data_prd) {
if (!config.no_content_length) {
build_headers.emplace_back("content-length", util::utos(req->data_length));
}
if (config.expect_continue) {
expect_continue = true;
build_headers.emplace_back("expect", "100-continue");
}
} }
for (auto &kv : headers) { for (auto &kv : headers) {
size_t i; size_t i;
for (i = 0; i < num_initial_headers; ++i) { for (i = 0; i < num_initial_headers; ++i) {
@ -400,11 +457,21 @@ int submit_request(HttpClient *client, const Headers &headers, Request *req) {
nva.push_back(http2::make_nv_ls("trailer", trailer_names)); nva.push_back(http2::make_nv_ls("trailer", trailer_names));
} }
auto stream_id = int32_t stream_id;
nghttp2_submit_request(client->session, &req->pri_spec, nva.data(),
nva.size(), req->data_prd, req); if (expect_continue) {
stream_id = nghttp2_submit_headers(client->session, 0, -1, &req->pri_spec,
nva.data(), nva.size(), req);
} else {
stream_id =
nghttp2_submit_request(client->session, &req->pri_spec, nva.data(),
nva.size(), req->data_prd, req);
}
if (stream_id < 0) { if (stream_id < 0) {
std::cerr << "[ERROR] nghttp2_submit_request() returned error: " std::cerr << "[ERROR] nghttp2_submit_"
<< (expect_continue ? "headers" : "request")
<< "() returned error: "
<< nghttp2_strerror(stream_id) << std::endl; << nghttp2_strerror(stream_id) << std::endl;
return -1; return -1;
} }
@ -414,6 +481,11 @@ int submit_request(HttpClient *client, const Headers &headers, Request *req) {
req->req_nva = std::move(build_headers); req->req_nva = std::move(build_headers);
if (expect_continue) {
auto timer = make_unique<ContinueTimer>(client->loop, req);
req->continue_timer = std::move(timer);
}
return 0; return 0;
} }
} // namespace } // namespace
@ -614,6 +686,12 @@ int HttpClient::initiate_connection() {
void HttpClient::disconnect() { void HttpClient::disconnect() {
state = ClientState::IDLE; state = ClientState::IDLE;
for (auto req = std::begin(reqvec); req != std::end(reqvec); ++req) {
if ((*req)->continue_timer) {
(*req)->continue_timer->stop();
}
}
ev_timer_stop(loop, &settings_timer); ev_timer_stop(loop, &settings_timer);
ev_timer_stop(loop, &rt); ev_timer_stop(loop, &rt);
@ -1616,11 +1694,19 @@ void check_response_header(nghttp2_session *session, Request *req) {
} }
if (req->status / 100 == 1) { if (req->status / 100 == 1) {
if (req->continue_timer && (req->status == 100)) {
// If the request is waiting for a 100 Continue, complete the handshake.
req->continue_timer->dispatch_continue();
}
req->expect_final_response = true; req->expect_final_response = true;
req->status = 0; req->status = 0;
req->res_nva.clear(); req->res_nva.clear();
http2::init_hdidx(req->res_hdidx); http2::init_hdidx(req->res_hdidx);
return; return;
} else if (req->continue_timer) {
// A final response stops any pending Expect/Continue handshake.
req->continue_timer->stop();
} }
if (gzip) { if (gzip) {
@ -1895,6 +1981,34 @@ int before_frame_send_callback(nghttp2_session *session,
} // namespace } // namespace
namespace {
int on_frame_send_callback(nghttp2_session *session,
const nghttp2_frame *frame,
void *user_data) {
if (config.verbose) {
verbose_on_frame_send_callback(session, frame, user_data);
}
if (frame->hd.type != NGHTTP2_HEADERS ||
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
return 0;
}
auto req = static_cast<Request *>(
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
if (!req) {
return 0;
}
// If this request is using Expect/Continue, start its ContinueTimer.
if (req->continue_timer) {
req->continue_timer->start();
}
return 0;
}
} // namespace
namespace { namespace {
int on_frame_not_send_callback(nghttp2_session *session, int on_frame_not_send_callback(nghttp2_session *session,
const nghttp2_frame *frame, int lib_error_code, const nghttp2_frame *frame, int lib_error_code,
@ -1928,6 +2042,11 @@ int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
return 0; return 0;
} }
// If this request is using Expect/Continue, stop its ContinueTimer.
if (req->continue_timer) {
req->continue_timer->stop();
}
update_html_parser(client, req, nullptr, 0, 1); update_html_parser(client, req, nullptr, 0, 1);
++client->complete; ++client->complete;
@ -2138,7 +2257,10 @@ int communicate(
<< std::endl; << std::endl;
goto fin; goto fin;
} }
ev_set_userdata(loop, &client);
ev_run(loop, 0); ev_run(loop, 0);
ev_set_userdata(loop, nullptr);
#ifdef HAVE_JANSSON #ifdef HAVE_JANSSON
if (!config.harfile.empty()) { if (!config.harfile.empty()) {
@ -2251,9 +2373,6 @@ int run(char **uris, int n) {
on_frame_recv_callback2); on_frame_recv_callback2);
if (config.verbose) { if (config.verbose) {
nghttp2_session_callbacks_set_on_frame_send_callback(
callbacks, verbose_on_frame_send_callback);
nghttp2_session_callbacks_set_on_invalid_frame_recv_callback( nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
callbacks, verbose_on_invalid_frame_recv_callback); callbacks, verbose_on_invalid_frame_recv_callback);
@ -2273,6 +2392,9 @@ int run(char **uris, int n) {
nghttp2_session_callbacks_set_before_frame_send_callback( nghttp2_session_callbacks_set_before_frame_send_callback(
callbacks, before_frame_send_callback); callbacks, before_frame_send_callback);
nghttp2_session_callbacks_set_on_frame_send_callback(
callbacks, on_frame_send_callback);
nghttp2_session_callbacks_set_on_frame_not_send_callback( nghttp2_session_callbacks_set_on_frame_not_send_callback(
callbacks, on_frame_not_send_callback); callbacks, on_frame_not_send_callback);
@ -2503,6 +2625,11 @@ Options:
--max-concurrent-streams=<N> --max-concurrent-streams=<N>
The number of concurrent pushed streams this client The number of concurrent pushed streams this client
accepts. accepts.
--expect-continue
Perform an Expect/Continue handshake: wait to send DATA
(up to a short timeout) until the server sends a 100
Continue interim response. This option is ignored unless
combined with the -d option.
--version Display version information and exit. --version Display version information and exit.
-h, --help Display this help and exit. -h, --help Display this help and exit.
@ -2554,6 +2681,7 @@ int main(int argc, char **argv) {
{"hexdump", no_argument, &flag, 10}, {"hexdump", no_argument, &flag, 10},
{"no-push", no_argument, &flag, 11}, {"no-push", no_argument, &flag, 11},
{"max-concurrent-streams", required_argument, &flag, 12}, {"max-concurrent-streams", required_argument, &flag, 12},
{"expect-continue", no_argument, &flag, 13},
{nullptr, 0, nullptr, 0}}; {nullptr, 0, nullptr, 0}};
int option_index = 0; int option_index = 0;
int c = getopt_long(argc, argv, "M:Oab:c:d:gm:np:r:hH:vst:uw:W:", int c = getopt_long(argc, argv, "M:Oab:c:d:gm:np:r:hH:vst:uw:W:",
@ -2751,6 +2879,10 @@ int main(int argc, char **argv) {
// max-concurrent-streams option // max-concurrent-streams option
config.max_concurrent_streams = strtoul(optarg, nullptr, 10); config.max_concurrent_streams = strtoul(optarg, nullptr, 10);
break; break;
case 13:
// expect-continue option
config.expect_continue = true;
break;
} }
break; break;
default: default:

View File

@ -91,6 +91,7 @@ struct Config {
bool no_dep; bool no_dep;
bool hexdump; bool hexdump;
bool no_push; bool no_push;
bool expect_continue;
}; };
enum class RequestState { INITIAL, ON_REQUEST, ON_RESPONSE, ON_COMPLETE }; enum class RequestState { INITIAL, ON_REQUEST, ON_RESPONSE, ON_COMPLETE };
@ -109,6 +110,23 @@ struct RequestTiming {
RequestTiming() : state(RequestState::INITIAL) {} RequestTiming() : state(RequestState::INITIAL) {}
}; };
struct Request; // forward declaration for ContinueTimer
struct ContinueTimer {
ContinueTimer(struct ev_loop *loop, Request *req);
~ContinueTimer();
void start();
void stop();
// Schedules an immediate run of the continue callback on the loop, if the
// callback has not already been run
void dispatch_continue();
struct ev_loop *loop;
ev_timer timer;
};
struct Request { struct Request {
// For pushed request, |uri| is empty and |u| is zero-cleared. // For pushed request, |uri| is empty and |u| is zero-cleared.
Request(const std::string &uri, const http_parser_url &u, Request(const std::string &uri, const http_parser_url &u,
@ -156,6 +174,8 @@ struct Request {
// used for incoming PUSH_PROMISE // used for incoming PUSH_PROMISE
http2::HeaderIndex req_hdidx; http2::HeaderIndex req_hdidx;
bool expect_final_response; bool expect_final_response;
// only assigned if this request is using Expect/Continue
std::unique_ptr<ContinueTimer> continue_timer;
}; };
struct SessionTiming { struct SessionTiming {