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