Merge branch 'jchampio-dev/expect-continue'
This commit is contained in:
commit
b26503f51c
152
src/nghttp.cc
152
src/nghttp.cc
|
@ -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:
|
||||||
|
|
20
src/nghttp.h
20
src/nghttp.h
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue