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_dep(false),
hexdump(false),
no_push(false) {
no_push(false),
expect_continue(false) {
nghttp2_option_new(&http2_option);
nghttp2_option_set_peer_max_concurrent_streams(http2_option,
peer_max_concurrent_streams);
@ -303,6 +304,51 @@ void Request::record_response_end_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 {
int htp_msg_begincb(http_parser *htp) {
if (config.verbose) {
@ -353,16 +399,27 @@ int submit_request(HttpClient *client, const Headers &headers, Request *req) {
{"accept", "*/*"},
{"accept-encoding", "gzip, deflate"},
{"user-agent", "nghttp2/" NGHTTP2_VERSION}};
bool expect_continue = false;
if (config.continuation) {
for (size_t i = 0; i < 6; ++i) {
build_headers.emplace_back("continuation-test-" + util::utos(i + 1),
std::string(4_k, '-'));
}
}
auto num_initial_headers = build_headers.size();
if (!config.no_content_length && req->data_prd) {
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) {
size_t 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));
}
auto stream_id =
int32_t stream_id;
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) {
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;
return -1;
}
@ -414,6 +481,11 @@ int submit_request(HttpClient *client, const Headers &headers, Request *req) {
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;
}
} // namespace
@ -614,6 +686,12 @@ int HttpClient::initiate_connection() {
void HttpClient::disconnect() {
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, &rt);
@ -1616,11 +1694,19 @@ void check_response_header(nghttp2_session *session, Request *req) {
}
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->status = 0;
req->res_nva.clear();
http2::init_hdidx(req->res_hdidx);
return;
} else if (req->continue_timer) {
// A final response stops any pending Expect/Continue handshake.
req->continue_timer->stop();
}
if (gzip) {
@ -1895,6 +1981,34 @@ int before_frame_send_callback(nghttp2_session *session,
} // 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 {
int on_frame_not_send_callback(nghttp2_session *session,
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;
}
// 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);
++client->complete;
@ -2138,7 +2257,10 @@ int communicate(
<< std::endl;
goto fin;
}
ev_set_userdata(loop, &client);
ev_run(loop, 0);
ev_set_userdata(loop, nullptr);
#ifdef HAVE_JANSSON
if (!config.harfile.empty()) {
@ -2251,9 +2373,6 @@ int run(char **uris, int n) {
on_frame_recv_callback2);
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(
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(
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(
callbacks, on_frame_not_send_callback);
@ -2503,6 +2625,11 @@ Options:
--max-concurrent-streams=<N>
The number of concurrent pushed streams this client
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.
-h, --help Display this help and exit.
@ -2554,6 +2681,7 @@ int main(int argc, char **argv) {
{"hexdump", no_argument, &flag, 10},
{"no-push", no_argument, &flag, 11},
{"max-concurrent-streams", required_argument, &flag, 12},
{"expect-continue", no_argument, &flag, 13},
{nullptr, 0, nullptr, 0}};
int option_index = 0;
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
config.max_concurrent_streams = strtoul(optarg, nullptr, 10);
break;
case 13:
// expect-continue option
config.expect_continue = true;
break;
}
break;
default:

View File

@ -91,6 +91,7 @@ struct Config {
bool no_dep;
bool hexdump;
bool no_push;
bool expect_continue;
};
enum class RequestState { INITIAL, ON_REQUEST, ON_RESPONSE, ON_COMPLETE };
@ -109,6 +110,23 @@ struct RequestTiming {
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 {
// For pushed request, |uri| is empty and |u| is zero-cleared.
Request(const std::string &uri, const http_parser_url &u,
@ -156,6 +174,8 @@ struct Request {
// used for incoming PUSH_PROMISE
http2::HeaderIndex req_hdidx;
bool expect_final_response;
// only assigned if this request is using Expect/Continue
std::unique_ptr<ContinueTimer> continue_timer;
};
struct SessionTiming {