2 * nghttp2 - HTTP/2 C Library
4 * Copyright (c) 2013 Tatsuhiro Tsujikawa
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 #include <netinet/in.h>
31 #include <netinet/tcp.h>
44 #include <openssl/err.h>
45 #include <openssl/conf.h>
49 #endif // HAVE_JANSSON
51 #include "app_helper.h"
52 #include "HtmlParser.h"
64 // stream ID of anchor stream node when --dep-idle is enabled. These
65 // * portion of ANCHOR_ID_* matches RequestPriority in HtmlParser.h.
66 // The stream ID = 1 is excluded since it is used as first stream in
76 : output_upper_thres(1024 * 1024), padding(0),
77 peer_max_concurrent_streams(NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS),
78 header_table_size(-1), weight(NGHTTP2_DEFAULT_WEIGHT), multiply(1),
79 timeout(0.), window_bits(-1), connection_window_bits(-1), verbose(0),
80 null_out(false), remote_name(false), get_assets(false), stat(false),
81 upgrade(false), continuation(false), no_content_length(false),
82 no_dep(false), dep_idle(false) {
83 nghttp2_option_new(&http2_option);
84 nghttp2_option_set_peer_max_concurrent_streams(http2_option,
85 peer_max_concurrent_streams);
88 Config::~Config() { nghttp2_option_del(http2_option); }
95 void print_protocol_nego_error() {
96 std::cerr << "[ERROR] HTTP/2 protocol was not selected."
97 << " (nghttp2 expects " << NGHTTP2_PROTO_VERSION_ID << ")"
103 std::string strip_fragment(const char *raw_uri) {
105 for (end = raw_uri; *end && *end != '#'; ++end)
107 size_t len = end - raw_uri;
108 return std::string(raw_uri, len);
113 // Returns numeric address string of |addr|. If getnameinfo() is
114 // failed, "unknown" is returned.
115 std::string numeric_name(addrinfo *addr) {
116 std::array<char, NI_MAXHOST> host;
117 auto rv = getnameinfo(addr->ai_addr, addr->ai_addrlen, host.data(),
118 host.size(), nullptr, 0, NI_NUMERICHOST);
126 Request::Request(const std::string &uri, const http_parser_url &u,
127 const nghttp2_data_provider *data_prd, int64_t data_length,
128 const nghttp2_priority_spec &pri_spec,
129 std::shared_ptr<Dependency> dep, int pri, int level)
130 : uri(uri), u(u), dep(std::move(dep)), pri_spec(pri_spec),
131 data_length(data_length), data_offset(0), response_len(0),
132 inflater(nullptr), html_parser(nullptr), data_prd(data_prd),
133 stream_id(-1), status(0), level(level), pri(pri),
134 expect_final_response(false) {
135 http2::init_hdidx(res_hdidx);
136 http2::init_hdidx(req_hdidx);
139 Request::~Request() {
140 nghttp2_gzip_inflate_del(inflater);
144 void Request::init_inflater() {
146 rv = nghttp2_gzip_inflate_new(&inflater);
150 void Request::init_html_parser() { html_parser = new HtmlParser(uri); }
152 int Request::update_html_parser(const uint8_t *data, size_t len, int fin) {
156 return html_parser->parse_chunk(reinterpret_cast<const char *>(data), len,
160 std::string Request::make_reqpath() const {
161 std::string path = util::has_uri_field(u, UF_PATH)
162 ? util::get_uri_field(uri.c_str(), u, UF_PATH)
164 if (util::has_uri_field(u, UF_QUERY)) {
166 path.append(uri.c_str() + u.field_data[UF_QUERY].off,
167 u.field_data[UF_QUERY].len);
172 int32_t Request::find_dep_stream_id(int start) {
173 for (auto i = start; i >= 0; --i) {
174 for (auto req : dep->deps[i]) {
175 return req->stream_id;
181 nghttp2_priority_spec Request::resolve_dep(int32_t pri) {
182 nghttp2_priority_spec pri_spec;
184 int32_t stream_id = -1;
186 nghttp2_priority_spec_default_init(&pri_spec);
192 if (config.dep_idle) {
193 int32_t anchor_id = 0;
196 anchor_id = ANCHOR_ID_HIGH;
199 anchor_id = ANCHOR_ID_MEDIUM;
202 anchor_id = ANCHOR_ID_LOW;
205 anchor_id = ANCHOR_ID_LOWEST;
208 nghttp2_priority_spec_init(&pri_spec, anchor_id, NGHTTP2_DEFAULT_WEIGHT, 0);
216 auto start = std::min(pri, (int)dep->deps.size() - 1);
218 for (auto i = start; i >= 0; --i) {
219 if (dep->deps[i][0]->pri < pri) {
220 stream_id = find_dep_stream_id(i);
222 if (i != (int)dep->deps.size() - 1) {
227 } else if (dep->deps[i][0]->pri == pri) {
228 stream_id = find_dep_stream_id(i - 1);
234 if (stream_id == -1) {
238 nghttp2_priority_spec_init(&pri_spec, stream_id, NGHTTP2_DEFAULT_WEIGHT,
244 bool Request::is_ipv6_literal_addr() const {
245 if (util::has_uri_field(u, UF_HOST)) {
246 return memchr(uri.c_str() + u.field_data[UF_HOST].off, ':',
247 u.field_data[UF_HOST].len);
253 bool Request::response_pseudo_header_allowed(int16_t token) const {
254 if (!res_nva.empty() && res_nva.back().name.c_str()[0] != ':') {
258 case http2::HD__STATUS:
259 return res_hdidx[token] == -1;
265 bool Request::push_request_pseudo_header_allowed(int16_t token) const {
266 if (!req_nva.empty() && req_nva.back().name.c_str()[0] != ':') {
270 case http2::HD__AUTHORITY:
271 case http2::HD__METHOD:
272 case http2::HD__PATH:
273 case http2::HD__SCHEME:
274 return req_hdidx[token] == -1;
280 Headers::value_type *Request::get_res_header(int16_t token) {
281 auto idx = res_hdidx[token];
285 return &res_nva[idx];
288 Headers::value_type *Request::get_req_header(int16_t token) {
289 auto idx = req_hdidx[token];
293 return &req_nva[idx];
296 void Request::record_request_time() {
297 timing.state = RequestState::ON_REQUEST;
298 timing.on_request_time = get_time();
301 void Request::record_response_time() {
302 timing.state = RequestState::ON_RESPONSE;
303 timing.on_response_time = get_time();
306 void Request::record_complete_time() {
307 timing.state = RequestState::ON_COMPLETE;
308 timing.on_complete_time = get_time();
312 int htp_msg_begincb(http_parser *htp) {
313 if (config.verbose) {
315 std::cout << " HTTP Upgrade response" << std::endl;
322 int htp_statuscb(http_parser *htp, const char *at, size_t length) {
323 auto client = static_cast<HttpClient *>(htp->data);
324 client->upgrade_response_status_code = htp->status_code;
330 int htp_msg_completecb(http_parser *htp) {
331 auto client = static_cast<HttpClient *>(htp->data);
332 client->upgrade_response_complete = true;
338 http_parser_settings htp_hooks = {
339 htp_msg_begincb, // http_cb on_message_begin;
340 nullptr, // http_data_cb on_url;
341 htp_statuscb, // http_data_cb on_status;
342 nullptr, // http_data_cb on_header_field;
343 nullptr, // http_data_cb on_header_value;
344 nullptr, // http_cb on_headers_complete;
345 nullptr, // http_data_cb on_body;
346 htp_msg_completecb // http_cb on_message_complete;
351 int submit_request(HttpClient *client, const Headers &headers, Request *req) {
352 auto path = req->make_reqpath();
353 auto scheme = util::get_uri_field(req->uri.c_str(), req->u, UF_SCHEMA);
354 auto build_headers = Headers{{":method", req->data_prd ? "POST" : "GET"},
357 {":authority", client->hostport},
359 {"accept-encoding", "gzip, deflate"},
360 {"user-agent", "nghttp2/" NGHTTP2_VERSION}};
361 if (config.continuation) {
362 for (size_t i = 0; i < 6; ++i) {
363 build_headers.emplace_back("continuation-test-" + util::utos(i + 1),
364 std::string(4096, '-'));
367 auto num_initial_headers = build_headers.size();
368 if (!config.no_content_length && req->data_prd) {
369 build_headers.emplace_back("content-length", util::utos(req->data_length));
371 for (auto &kv : headers) {
373 for (i = 0; i < num_initial_headers; ++i) {
374 if (kv.name == build_headers[i].name) {
375 build_headers[i].value = kv.value;
379 if (i < num_initial_headers) {
383 build_headers.emplace_back(kv.name, kv.value, kv.no_index);
386 auto nva = std::vector<nghttp2_nv>();
387 nva.reserve(build_headers.size());
389 for (auto &kv : build_headers) {
390 nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
394 nghttp2_submit_request(client->session, &req->pri_spec, nva.data(),
395 nva.size(), req->data_prd, req);
397 std::cerr << "[ERROR] nghttp2_submit_request() returned error: "
398 << nghttp2_strerror(stream_id) << std::endl;
402 req->stream_id = stream_id;
403 client->on_request(req);
405 req->req_nva = std::move(build_headers);
412 void readcb(struct ev_loop *loop, ev_io *w, int revents) {
413 auto client = static_cast<HttpClient *>(w->data);
414 if (client->do_read() != 0) {
415 client->disconnect();
421 void writecb(struct ev_loop *loop, ev_io *w, int revents) {
422 auto client = static_cast<HttpClient *>(w->data);
423 auto rv = client->do_write();
424 if (rv == HttpClient::ERR_CONNECT_FAIL) {
425 client->on_connect_fail();
429 client->disconnect();
435 void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
436 auto client = static_cast<HttpClient *>(w->data);
437 std::cerr << "[ERROR] Timeout" << std::endl;
438 client->disconnect();
443 void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
444 auto client = static_cast<HttpClient *>(w->data);
445 ev_timer_stop(loop, w);
447 nghttp2_session_terminate_session(client->session, NGHTTP2_SETTINGS_TIMEOUT);
449 client->signal_write();
453 HttpClient::HttpClient(const nghttp2_session_callbacks *callbacks,
454 struct ev_loop *loop, SSL_CTX *ssl_ctx)
455 : session(nullptr), callbacks(callbacks), loop(loop), ssl_ctx(ssl_ctx),
456 ssl(nullptr), addrs(nullptr), next_addr(nullptr), cur_addr(nullptr),
457 complete(0), settings_payloadlen(0), state(ClientState::IDLE),
458 upgrade_response_status_code(0), fd(-1),
459 upgrade_response_complete(false) {
460 ev_io_init(&wev, writecb, 0, EV_WRITE);
461 ev_io_init(&rev, readcb, 0, EV_READ);
466 ev_timer_init(&wt, timeoutcb, 0., config.timeout);
467 ev_timer_init(&rt, timeoutcb, 0., config.timeout);
472 ev_timer_init(&settings_timer, settings_timeout_cb, 0., 10.);
474 settings_timer.data = this;
477 HttpClient::~HttpClient() {
487 bool HttpClient::need_upgrade() const {
488 return config.upgrade && scheme == "http";
491 int HttpClient::resolve_host(const std::string &host, uint16_t port) {
495 memset(&hints, 0, sizeof(hints));
496 hints.ai_family = AF_UNSPEC;
497 hints.ai_socktype = SOCK_STREAM;
498 hints.ai_protocol = 0;
499 hints.ai_flags = AI_ADDRCONFIG;
500 rv = getaddrinfo(host.c_str(), util::utos(port).c_str(), &hints, &addrs);
502 std::cerr << "[ERROR] getaddrinfo() failed: " << gai_strerror(rv)
506 if (addrs == nullptr) {
507 std::cerr << "[ERROR] No address returned" << std::endl;
514 int HttpClient::initiate_connection() {
519 cur_addr = next_addr;
520 next_addr = next_addr->ai_next;
521 fd = util::create_nonblock_socket(cur_addr->ai_family);
527 // We are establishing TLS connection.
528 ssl = SSL_new(ssl_ctx);
530 std::cerr << "[ERROR] SSL_new() failed: "
531 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
536 SSL_set_connect_state(ssl);
538 // If the user overrode the host header, use that value for
540 const char *host_string = nullptr;
542 std::find_if(std::begin(config.headers), std::end(config.headers),
543 [](const Header &nv) { return "host" == nv.name; });
544 if (i != std::end(config.headers)) {
545 host_string = (*i).value.c_str();
547 host_string = host.c_str();
550 if (!util::numeric_host(host_string)) {
551 SSL_set_tlsext_host_name(ssl, host_string);
555 rv = connect(fd, cur_addr->ai_addr, cur_addr->ai_addrlen);
557 if (rv != 0 && errno != EINPROGRESS) {
573 writefn = &HttpClient::connected;
575 if (need_upgrade()) {
576 on_readfn = &HttpClient::on_upgrade_read;
577 on_writefn = &HttpClient::on_upgrade_connect;
579 on_readfn = &HttpClient::on_read;
580 on_writefn = &HttpClient::on_write;
583 ev_io_set(&rev, fd, EV_READ);
584 ev_io_set(&wev, fd, EV_WRITE);
586 ev_io_start(loop, &wev);
588 ev_timer_again(loop, &wt);
593 void HttpClient::disconnect() {
594 state = ClientState::IDLE;
596 ev_timer_stop(loop, &settings_timer);
598 ev_timer_stop(loop, &rt);
599 ev_timer_stop(loop, &wt);
601 ev_io_stop(loop, &rev);
602 ev_io_stop(loop, &wev);
604 nghttp2_session_del(session);
608 SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN);
616 shutdown(fd, SHUT_WR);
622 int HttpClient::read_clear() {
623 ev_timer_again(loop, &rt);
625 std::array<uint8_t, 8192> buf;
629 while ((nread = read(fd, buf.data(), buf.size())) == -1 && errno == EINTR)
632 if (errno == EAGAIN || errno == EWOULDBLOCK) {
642 if (on_readfn(*this, buf.data(), nread) != 0) {
650 int HttpClient::write_clear() {
651 ev_timer_again(loop, &rt);
654 if (wb.rleft() > 0) {
656 while ((nwrite = write(fd, wb.pos, wb.rleft())) == -1 && errno == EINTR)
659 if (errno == EAGAIN || errno == EWOULDBLOCK) {
660 ev_io_start(loop, &wev);
661 ev_timer_again(loop, &wt);
670 if (on_writefn(*this) != 0) {
673 if (wb.rleft() == 0) {
679 ev_io_stop(loop, &wev);
680 ev_timer_stop(loop, &wt);
685 int HttpClient::noop() { return 0; }
687 void HttpClient::on_connect_fail() {
688 if (state == ClientState::IDLE) {
689 std::cerr << "[ERROR] Could not connect to the address "
690 << numeric_name(cur_addr) << std::endl;
692 auto cur_state = state;
694 if (cur_state == ClientState::IDLE) {
695 if (initiate_connection() == 0) {
696 std::cerr << "Trying next address " << numeric_name(cur_addr)
702 int HttpClient::connected() {
703 if (!util::check_socket_connected(fd)) {
704 return ERR_CONNECT_FAIL;
707 if (config.verbose) {
709 std::cout << " Connected" << std::endl;
712 record_connect_time();
713 state = ClientState::CONNECTED;
715 ev_io_start(loop, &rev);
716 ev_io_stop(loop, &wev);
718 ev_timer_again(loop, &rt);
719 ev_timer_stop(loop, &wt);
722 readfn = &HttpClient::tls_handshake;
723 writefn = &HttpClient::tls_handshake;
728 readfn = &HttpClient::read_clear;
729 writefn = &HttpClient::write_clear;
731 if (need_upgrade()) {
732 htp = make_unique<http_parser>();
733 http_parser_init(htp.get(), HTTP_RESPONSE);
739 if (on_connect() != 0) {
747 size_t populate_settings(nghttp2_settings_entry *iv) {
750 iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
753 iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
754 if (config.window_bits != -1) {
755 iv[1].value = (1 << config.window_bits) - 1;
757 iv[1].value = NGHTTP2_INITIAL_WINDOW_SIZE;
760 if (config.header_table_size >= 0) {
761 iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
762 iv[niv].value = config.header_table_size;
769 int HttpClient::on_upgrade_connect() {
771 record_handshake_time();
772 assert(!reqvec.empty());
773 std::array<nghttp2_settings_entry, 32> iv;
774 size_t niv = populate_settings(iv.data());
775 assert(settings_payload.size() >= 8 * niv);
776 rv = nghttp2_pack_settings_payload(settings_payload.data(),
777 settings_payload.size(), iv.data(), niv);
781 settings_payloadlen = rv;
783 base64::encode(std::begin(settings_payload),
784 std::begin(settings_payload) + settings_payloadlen);
785 util::to_token68(token68);
787 if (reqvec[0]->data_prd) {
788 // If the request contains upload data, use OPTIONS * to upgrade
792 req += reqvec[0]->make_reqpath();
794 req += " HTTP/1.1\r\n"
798 "Connection: Upgrade, HTTP2-Settings\r\n"
799 "Upgrade: " NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "\r\n"
804 "User-Agent: nghttp2/" NGHTTP2_VERSION "\r\n"
807 wb.write(req.c_str(), req.size());
809 if (config.verbose) {
811 std::cout << " HTTP Upgrade request\n" << req << std::endl;
814 // record request time if this is GET request
815 if (!reqvec[0]->data_prd) {
816 reqvec[0]->record_request_time();
819 on_writefn = &HttpClient::noop;
826 int HttpClient::on_upgrade_read(const uint8_t *data, size_t len) {
829 auto nread = http_parser_execute(htp.get(), &htp_hooks,
830 reinterpret_cast<const char *>(data), len);
832 if (config.verbose) {
833 std::cout.write(reinterpret_cast<const char *>(data), nread);
836 auto htperr = HTTP_PARSER_ERRNO(htp.get());
838 if (htperr != HPE_OK) {
839 std::cerr << "[ERROR] Failed to parse HTTP Upgrade response header: "
840 << "(" << http_errno_name(htperr) << ") "
841 << http_errno_description(htperr) << std::endl;
845 if (!upgrade_response_complete) {
849 if (config.verbose) {
850 std::cout << std::endl;
853 if (upgrade_response_status_code != 101) {
854 std::cerr << "[ERROR] HTTP Upgrade failed" << std::endl;
859 if (config.verbose) {
861 std::cout << " HTTP Upgrade success" << std::endl;
864 on_readfn = &HttpClient::on_read;
865 on_writefn = &HttpClient::on_write;
872 // Read remaining data in the buffer because it is not notified
874 rv = on_readfn(*this, data + nread, len - nread);
882 int HttpClient::do_read() { return readfn(*this); }
883 int HttpClient::do_write() { return writefn(*this); }
885 int HttpClient::on_connect() {
889 // Check NPN or ALPN result
890 const unsigned char *next_proto = nullptr;
891 unsigned int next_proto_len;
892 SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len);
893 for (int i = 0; i < 2; ++i) {
895 if (config.verbose) {
896 std::cout << "The negotiated protocol: ";
897 std::cout.write(reinterpret_cast<const char *>(next_proto),
899 std::cout << std::endl;
901 if (!util::check_h2_is_selected(next_proto, next_proto_len)) {
902 next_proto = nullptr;
906 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
907 SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
908 #else // OPENSSL_VERSION_NUMBER < 0x10002000L
910 #endif // OPENSSL_VERSION_NUMBER < 0x10002000L
913 print_protocol_nego_error();
918 if (!need_upgrade()) {
919 record_handshake_time();
922 rv = nghttp2_session_client_new2(&session, callbacks, this,
923 config.http2_option);
928 if (need_upgrade()) {
929 // Adjust stream user-data depending on the existence of upload
931 Request *stream_user_data = nullptr;
932 if (!reqvec[0]->data_prd) {
933 stream_user_data = reqvec[0].get();
935 rv = nghttp2_session_upgrade(session, settings_payload.data(),
936 settings_payloadlen, stream_user_data);
938 std::cerr << "[ERROR] nghttp2_session_upgrade() returned error: "
939 << nghttp2_strerror(rv) << std::endl;
942 if (stream_user_data) {
943 stream_user_data->stream_id = 1;
944 on_request(stream_user_data);
947 // Send connection header here
948 wb.write(NGHTTP2_CLIENT_CONNECTION_PREFACE,
949 NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
950 // If upgrade succeeds, the SETTINGS value sent with
951 // HTTP2-Settings header field has already been submitted to
953 if (!need_upgrade()) {
954 std::array<nghttp2_settings_entry, 16> iv;
955 auto niv = populate_settings(iv.data());
956 rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv.data(), niv);
961 if (!config.no_dep && config.dep_idle) {
962 // Create anchor stream nodes
963 nghttp2_priority_spec pri_spec;
964 int32_t dep_stream_id = 0;
966 for (auto stream_id :
967 {ANCHOR_ID_HIGH, ANCHOR_ID_MEDIUM, ANCHOR_ID_LOW, ANCHOR_ID_LOWEST}) {
969 nghttp2_priority_spec_init(&pri_spec, dep_stream_id,
970 NGHTTP2_DEFAULT_WEIGHT, 0);
971 rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, stream_id,
977 dep_stream_id = stream_id;
980 rv = nghttp2_session_set_next_stream_id(session, ANCHOR_ID_LOWEST + 2);
985 if (need_upgrade()) {
986 // Amend the priority because we cannot send priority in
988 nghttp2_priority_spec_init(&pri_spec, ANCHOR_ID_HIGH, config.weight, 0);
990 rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec);
995 } else if (need_upgrade() && config.weight != NGHTTP2_DEFAULT_WEIGHT) {
996 // Amend the priority because we cannot send priority in
998 nghttp2_priority_spec pri_spec;
1000 nghttp2_priority_spec_init(&pri_spec, 0, config.weight, 0);
1002 rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec);
1008 ev_timer_again(loop, &settings_timer);
1010 if (config.connection_window_bits != -1) {
1011 int32_t wininc = (1 << config.connection_window_bits) - 1 -
1012 NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
1013 rv = nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, wininc);
1018 // Adjust first request depending on the existence of the upload
1020 for (auto i = std::begin(reqvec) + (need_upgrade() && !reqvec[0]->data_prd);
1021 i != std::end(reqvec); ++i) {
1022 if (submit_request(this, config.headers, (*i).get()) != 0) {
1032 int HttpClient::on_read(const uint8_t *data, size_t len) {
1033 auto rv = nghttp2_session_mem_recv(session, data, len);
1035 std::cerr << "[ERROR] nghttp2_session_mem_recv() returned error: "
1036 << nghttp2_strerror(rv) << std::endl;
1040 assert(static_cast<size_t>(rv) == len);
1042 if (nghttp2_session_want_read(session) == 0 &&
1043 nghttp2_session_want_write(session) == 0 && wb.rleft() == 0) {
1052 int HttpClient::on_write() {
1053 auto rv = nghttp2_session_send(session);
1055 std::cerr << "[ERROR] nghttp2_session_send() returned error: "
1056 << nghttp2_strerror(rv) << std::endl;
1060 if (nghttp2_session_want_read(session) == 0 &&
1061 nghttp2_session_want_write(session) == 0 && wb.rleft() == 0) {
1068 int HttpClient::tls_handshake() {
1069 ev_timer_again(loop, &rt);
1073 auto rv = SSL_do_handshake(ssl);
1080 auto err = SSL_get_error(ssl, rv);
1082 case SSL_ERROR_WANT_READ:
1083 ev_io_stop(loop, &wev);
1084 ev_timer_stop(loop, &wt);
1086 case SSL_ERROR_WANT_WRITE:
1087 ev_io_start(loop, &wev);
1088 ev_timer_again(loop, &wt);
1095 ev_io_stop(loop, &wev);
1096 ev_timer_stop(loop, &wt);
1098 readfn = &HttpClient::read_tls;
1099 writefn = &HttpClient::write_tls;
1101 if (on_connect() != 0) {
1108 int HttpClient::read_tls() {
1109 ev_timer_again(loop, &rt);
1113 std::array<uint8_t, 8192> buf;
1115 auto rv = SSL_read(ssl, buf.data(), buf.size());
1122 auto err = SSL_get_error(ssl, rv);
1124 case SSL_ERROR_WANT_READ:
1126 case SSL_ERROR_WANT_WRITE:
1127 // renegotiation started
1134 if (on_readfn(*this, buf.data(), rv) != 0) {
1140 int HttpClient::write_tls() {
1141 ev_timer_again(loop, &rt);
1146 if (wb.rleft() > 0) {
1147 auto rv = SSL_write(ssl, wb.pos, wb.rleft());
1154 auto err = SSL_get_error(ssl, rv);
1156 case SSL_ERROR_WANT_READ:
1157 // renegotiation started
1159 case SSL_ERROR_WANT_WRITE:
1160 ev_io_start(loop, &wev);
1161 ev_timer_again(loop, &wt);
1172 if (on_writefn(*this) != 0) {
1175 if (wb.rleft() == 0) {
1180 ev_io_stop(loop, &wev);
1181 ev_timer_stop(loop, &wt);
1186 void HttpClient::signal_write() { ev_io_start(loop, &wev); }
1188 bool HttpClient::all_requests_processed() const {
1189 return complete == reqvec.size();
1192 void HttpClient::update_hostport() {
1193 if (reqvec.empty()) {
1196 scheme = util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_SCHEMA);
1197 std::stringstream ss;
1198 if (reqvec[0]->is_ipv6_literal_addr()) {
1200 util::write_uri_field(ss, reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST);
1203 util::write_uri_field(ss, reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST);
1205 if (util::has_uri_field(reqvec[0]->u, UF_PORT) &&
1206 reqvec[0]->u.port !=
1207 util::get_default_port(reqvec[0]->uri.c_str(), reqvec[0]->u)) {
1208 ss << ":" << reqvec[0]->u.port;
1210 hostport = ss.str();
1213 bool HttpClient::add_request(const std::string &uri,
1214 const nghttp2_data_provider *data_prd,
1215 int64_t data_length,
1216 const nghttp2_priority_spec &pri_spec,
1217 std::shared_ptr<Dependency> dep, int pri,
1220 memset(&u, 0, sizeof(u));
1221 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
1224 if (path_cache.count(uri)) {
1228 if (config.multiply == 1) {
1229 path_cache.insert(uri);
1232 reqvec.push_back(make_unique<Request>(uri, u, data_prd, data_length, pri_spec,
1233 std::move(dep), pri, level));
1237 void HttpClient::record_handshake_time() {
1238 timing.on_handshake_time = get_time();
1241 void HttpClient::record_started_time() {
1242 timing.started_system_time = std::chrono::system_clock::now();
1243 timing.on_started_time = get_time();
1246 void HttpClient::record_dns_complete_time() {
1247 timing.on_dns_complete_time = get_time();
1250 void HttpClient::record_connect_time() { timing.on_connect_time = get_time(); }
1252 void HttpClient::on_request(Request *req) {
1253 if (req->pri == 0 && req->dep) {
1254 assert(req->dep->deps.empty());
1256 req->dep->deps.push_back(std::vector<Request *>{req});
1261 if (req->stream_id % 2 == 0) {
1265 auto itr = std::begin(req->dep->deps);
1266 for (; itr != std::end(req->dep->deps); ++itr) {
1267 if ((*itr)[0]->pri == req->pri) {
1268 (*itr).push_back(req);
1273 if ((*itr)[0]->pri > req->pri) {
1274 auto v = std::vector<Request *>{req};
1275 req->dep->deps.insert(itr, std::move(v));
1281 if (itr == std::end(req->dep->deps)) {
1282 req->dep->deps.push_back(std::vector<Request *>{req});
1287 void HttpClient::output_har(FILE *outfile) {
1288 static auto PAGE_ID = "page_0";
1290 auto root = json_object();
1291 auto log = json_object();
1292 json_object_set_new(root, "log", log);
1293 json_object_set_new(log, "version", json_string("1.2"));
1295 auto creator = json_object();
1296 json_object_set_new(log, "creator", creator);
1298 json_object_set_new(creator, "name", json_string("nghttp"));
1299 json_object_set_new(creator, "version", json_string(NGHTTP2_VERSION));
1301 auto pages = json_array();
1302 json_object_set_new(log, "pages", pages);
1304 auto page = json_object();
1305 json_array_append_new(pages, page);
1307 json_object_set_new(
1308 page, "startedDateTime",
1309 json_string(util::format_iso8601(timing.started_system_time).c_str()));
1310 json_object_set_new(page, "id", json_string(PAGE_ID));
1311 json_object_set_new(page, "title", json_string(""));
1313 json_object_set_new(page, "pageTimings", json_object());
1315 auto entries = json_array();
1316 json_object_set_new(log, "entries", entries);
1319 std::chrono::duration_cast<std::chrono::microseconds>(
1320 timing.on_dns_complete_time - timing.on_started_time).count() /
1322 auto connect_delta =
1323 std::chrono::duration_cast<std::chrono::microseconds>(
1324 timing.on_connect_time - timing.on_dns_complete_time).count() /
1327 for (size_t i = 0; i < reqvec.size(); ++i) {
1328 auto &req = reqvec[i];
1330 if (req->timing.state != RequestState::ON_COMPLETE) {
1334 auto entry = json_object();
1335 json_array_append_new(entries, entry);
1337 auto &req_timing = req->timing;
1339 (i == 0) ? timing.started_system_time
1340 : timing.started_system_time +
1341 std::chrono::duration_cast<
1342 std::chrono::system_clock::duration>(
1343 req_timing.on_request_time - timing.on_started_time);
1346 std::chrono::duration_cast<std::chrono::microseconds>(
1347 req_timing.on_response_time - req_timing.on_request_time).count() /
1349 auto receive_delta =
1350 std::chrono::duration_cast<std::chrono::microseconds>(
1351 req_timing.on_complete_time - req_timing.on_response_time).count() /
1355 std::chrono::duration_cast<std::chrono::microseconds>(
1356 (i == 0) ? (req_timing.on_complete_time - timing.on_started_time)
1357 : (req_timing.on_complete_time -
1358 req_timing.on_request_time)).count() /
1361 json_object_set_new(
1362 entry, "startedDateTime",
1363 json_string(util::format_iso8601(request_time).c_str()));
1364 json_object_set_new(entry, "time", json_real(time_sum));
1366 auto request = json_object();
1367 json_object_set_new(entry, "request", request);
1369 auto method_ptr = http2::get_header(req->req_nva, ":method");
1371 const char *method = "GET";
1373 method = (*method_ptr).value.c_str();
1376 auto req_headers = json_array();
1377 json_object_set_new(request, "headers", req_headers);
1379 for (auto &nv : req->req_nva) {
1380 auto hd = json_object();
1381 json_array_append_new(req_headers, hd);
1383 json_object_set_new(hd, "name", json_string(nv.name.c_str()));
1384 json_object_set_new(hd, "value", json_string(nv.value.c_str()));
1387 json_object_set_new(request, "method", json_string(method));
1388 json_object_set_new(request, "url", json_string(req->uri.c_str()));
1389 json_object_set_new(request, "httpVersion", json_string("HTTP/2.0"));
1390 json_object_set_new(request, "cookies", json_array());
1391 json_object_set_new(request, "queryString", json_array());
1392 json_object_set_new(request, "headersSize", json_integer(-1));
1393 json_object_set_new(request, "bodySize", json_integer(-1));
1395 auto response = json_object();
1396 json_object_set_new(entry, "response", response);
1398 auto res_headers = json_array();
1399 json_object_set_new(response, "headers", res_headers);
1401 for (auto &nv : req->res_nva) {
1402 auto hd = json_object();
1403 json_array_append_new(res_headers, hd);
1405 json_object_set_new(hd, "name", json_string(nv.name.c_str()));
1406 json_object_set_new(hd, "value", json_string(nv.value.c_str()));
1409 json_object_set_new(response, "status", json_integer(req->status));
1410 json_object_set_new(response, "statusText", json_string(""));
1411 json_object_set_new(response, "httpVersion", json_string("HTTP/2.0"));
1412 json_object_set_new(response, "cookies", json_array());
1414 auto content = json_object();
1415 json_object_set_new(response, "content", content);
1417 json_object_set_new(content, "size", json_integer(req->response_len));
1419 auto content_type_ptr = http2::get_header(req->res_nva, "content-type");
1421 const char *content_type = "";
1422 if (content_type_ptr) {
1423 content_type = content_type_ptr->value.c_str();
1426 json_object_set_new(content, "mimeType", json_string(content_type));
1428 json_object_set_new(response, "redirectURL", json_string(""));
1429 json_object_set_new(response, "headersSize", json_integer(-1));
1430 json_object_set_new(response, "bodySize", json_integer(-1));
1432 json_object_set_new(entry, "cache", json_object());
1434 auto timings = json_object();
1435 json_object_set_new(entry, "timings", timings);
1437 auto dns_timing = (i == 0) ? dns_delta : 0;
1438 auto connect_timing = (i == 0) ? connect_delta : 0;
1440 json_object_set_new(timings, "dns", json_real(dns_timing));
1441 json_object_set_new(timings, "connect", json_real(connect_timing));
1443 json_object_set_new(timings, "blocked", json_real(0.0));
1444 json_object_set_new(timings, "send", json_real(0.0));
1445 json_object_set_new(timings, "wait", json_real(wait_delta));
1446 json_object_set_new(timings, "receive", json_real(receive_delta));
1448 json_object_set_new(entry, "pageref", json_string(PAGE_ID));
1451 json_dumpf(root, outfile, JSON_PRESERVE_ORDER | JSON_INDENT(2));
1454 #endif // HAVE_JANSSON
1457 void update_html_parser(HttpClient *client, Request *req, const uint8_t *data,
1458 size_t len, int fin) {
1459 if (!req->html_parser) {
1462 req->update_html_parser(data, len, fin);
1464 for (auto &p : req->html_parser->get_links()) {
1465 auto uri = strip_fragment(p.first.c_str());
1466 auto pri = p.second;
1469 memset(&u, 0, sizeof(u));
1470 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) == 0 &&
1471 util::fieldeq(uri.c_str(), u, req->uri.c_str(), req->u, UF_SCHEMA) &&
1472 util::fieldeq(uri.c_str(), u, req->uri.c_str(), req->u, UF_HOST) &&
1473 util::porteq(uri.c_str(), u, req->uri.c_str(), req->u)) {
1474 // No POST data for assets
1475 auto pri_spec = req->resolve_dep(pri);
1477 if (client->add_request(uri, nullptr, 0, pri_spec, req->dep, pri,
1480 submit_request(client, config.headers, client->reqvec.back().get());
1484 req->html_parser->clear_links();
1489 HttpClient *get_client(void *user_data) {
1490 return static_cast<HttpClient *>(user_data);
1495 int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
1496 int32_t stream_id, const uint8_t *data,
1497 size_t len, void *user_data) {
1498 auto client = get_client(user_data);
1499 auto req = static_cast<Request *>(
1500 nghttp2_session_get_stream_user_data(session, stream_id));
1506 if (config.verbose >= 2) {
1507 verbose_on_data_chunk_recv_callback(session, flags, stream_id, data, len,
1511 if (req->status == 0) {
1512 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
1513 NGHTTP2_PROTOCOL_ERROR);
1517 if (req->inflater) {
1519 const size_t MAX_OUTLEN = 4096;
1520 std::array<uint8_t, MAX_OUTLEN> out;
1521 size_t outlen = MAX_OUTLEN;
1524 nghttp2_gzip_inflate(req->inflater, out.data(), &outlen, data, &tlen);
1526 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
1527 NGHTTP2_INTERNAL_ERROR);
1531 req->response_len += outlen;
1533 if (!config.null_out) {
1534 std::cout.write(reinterpret_cast<const char *>(out.data()), outlen);
1537 update_html_parser(client, req, out.data(), outlen, 0);
1545 req->response_len += len;
1547 if (!config.null_out) {
1548 std::cout.write(reinterpret_cast<const char *>(data), len);
1551 update_html_parser(client, req, data, len, 0);
1558 ssize_t select_padding_callback(nghttp2_session *session,
1559 const nghttp2_frame *frame, size_t max_payload,
1561 return std::min(max_payload, frame->hd.length + config.padding);
1566 void check_response_header(nghttp2_session *session, Request *req) {
1569 req->expect_final_response = false;
1571 auto status_hd = req->get_res_header(http2::HD__STATUS);
1574 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
1575 NGHTTP2_PROTOCOL_ERROR);
1579 auto status = http2::parse_http_status_code(status_hd->value);
1581 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
1582 NGHTTP2_PROTOCOL_ERROR);
1586 req->status = status;
1588 for (auto &nv : req->res_nva) {
1589 if ("content-encoding" == nv.name) {
1591 util::strieq("gzip", nv.value) || util::strieq("deflate", nv.value);
1596 if (req->status / 100 == 1) {
1597 req->expect_final_response = true;
1599 req->res_nva.clear();
1600 http2::init_hdidx(req->res_hdidx);
1605 if (!req->inflater) {
1606 req->init_inflater();
1609 if (config.get_assets && req->level == 0) {
1610 if (!req->html_parser) {
1611 req->init_html_parser();
1618 int on_begin_headers_callback(nghttp2_session *session,
1619 const nghttp2_frame *frame, void *user_data) {
1620 auto client = get_client(user_data);
1621 switch (frame->hd.type) {
1622 case NGHTTP2_PUSH_PROMISE: {
1623 auto stream_id = frame->push_promise.promised_stream_id;
1625 memset(&u, 0, sizeof(u));
1626 // TODO Set pri and level
1627 nghttp2_priority_spec pri_spec;
1629 nghttp2_priority_spec_default_init(&pri_spec);
1631 auto req = make_unique<Request>("", u, nullptr, 0, pri_spec, nullptr);
1632 req->stream_id = stream_id;
1634 nghttp2_session_set_stream_user_data(session, stream_id, req.get());
1636 client->on_request(req.get());
1637 req->record_request_time();
1638 client->reqvec.push_back(std::move(req));
1648 int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
1649 const uint8_t *name, size_t namelen,
1650 const uint8_t *value, size_t valuelen, uint8_t flags,
1652 if (config.verbose) {
1653 verbose_on_header_callback(session, frame, name, namelen, value, valuelen,
1657 if (!http2::check_nv(name, namelen, value, valuelen)) {
1658 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
1659 NGHTTP2_PROTOCOL_ERROR);
1660 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
1663 switch (frame->hd.type) {
1664 case NGHTTP2_HEADERS: {
1665 auto req = static_cast<Request *>(
1666 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1672 if (frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
1673 frame->headers.cat != NGHTTP2_HCAT_PUSH_RESPONSE &&
1674 (frame->headers.cat != NGHTTP2_HCAT_HEADERS ||
1675 !req->expect_final_response)) {
1679 auto token = http2::lookup_token(name, namelen);
1681 if (name[0] == ':') {
1682 if (!req->response_pseudo_header_allowed(token)) {
1683 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
1684 frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
1685 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
1689 http2::index_header(req->res_hdidx, token, req->res_nva.size());
1690 http2::add_header(req->res_nva, name, namelen, value, valuelen,
1691 flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
1694 case NGHTTP2_PUSH_PROMISE: {
1695 auto req = static_cast<Request *>(nghttp2_session_get_stream_user_data(
1696 session, frame->push_promise.promised_stream_id));
1702 auto token = http2::lookup_token(name, namelen);
1704 if (name[0] == ':') {
1705 if (!req->push_request_pseudo_header_allowed(token)) {
1706 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
1707 frame->push_promise.promised_stream_id,
1708 NGHTTP2_PROTOCOL_ERROR);
1713 http2::index_header(req->req_hdidx, token, req->req_nva.size());
1714 http2::add_header(req->req_nva, name, namelen, value, valuelen,
1715 flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
1724 int on_frame_recv_callback2(nghttp2_session *session,
1725 const nghttp2_frame *frame, void *user_data) {
1728 if (config.verbose) {
1729 verbose_on_frame_recv_callback(session, frame, user_data);
1732 auto client = get_client(user_data);
1733 switch (frame->hd.type) {
1734 case NGHTTP2_HEADERS: {
1735 auto req = static_cast<Request *>(
1736 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1737 // If this is the HTTP Upgrade with OPTIONS method to avoid POST,
1743 if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE ||
1744 frame->headers.cat == NGHTTP2_HCAT_PUSH_RESPONSE) {
1745 req->record_response_time();
1746 check_response_header(session, req);
1751 if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) {
1752 if (req->expect_final_response) {
1753 check_response_header(session, req);
1755 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
1756 frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
1761 if (req->status == 0 && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
1762 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
1763 NGHTTP2_PROTOCOL_ERROR);
1769 case NGHTTP2_SETTINGS:
1770 if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
1773 ev_timer_stop(client->loop, &client->settings_timer);
1775 case NGHTTP2_PUSH_PROMISE: {
1776 auto req = static_cast<Request *>(nghttp2_session_get_stream_user_data(
1777 session, frame->push_promise.promised_stream_id));
1781 auto scheme = req->get_req_header(http2::HD__SCHEME);
1782 auto authority = req->get_req_header(http2::HD__AUTHORITY);
1783 auto method = req->get_req_header(http2::HD__METHOD);
1784 auto path = req->get_req_header(http2::HD__PATH);
1787 authority = req->get_req_header(http2::HD_HOST);
1790 if (!scheme || !authority || !method || !path || scheme->value.empty() ||
1791 authority->value.empty() || method->value.empty() ||
1792 path->value.empty() || path->value[0] != '/') {
1793 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
1794 frame->push_promise.promised_stream_id,
1795 NGHTTP2_PROTOCOL_ERROR);
1798 std::string uri = scheme->value;
1800 uri += authority->value;
1803 memset(&u, 0, sizeof(u));
1804 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
1805 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
1806 frame->push_promise.promised_stream_id,
1807 NGHTTP2_PROTOCOL_ERROR);
1820 int before_frame_send_callback(nghttp2_session *session,
1821 const nghttp2_frame *frame, void *user_data) {
1822 if (frame->hd.type != NGHTTP2_HEADERS ||
1823 frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
1826 auto req = static_cast<Request *>(
1827 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1828 req->record_request_time();
1835 int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
1836 uint32_t error_code, void *user_data) {
1837 auto client = get_client(user_data);
1838 auto req = static_cast<Request *>(
1839 nghttp2_session_get_stream_user_data(session, stream_id));
1845 update_html_parser(client, req, nullptr, 0, 1);
1846 req->record_complete_time();
1849 if (client->all_requests_processed()) {
1850 nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
1857 struct RequestResult {
1858 std::chrono::microseconds time;
1862 void print_stats(const HttpClient &client) {
1863 std::cout << "***** Statistics *****" << std::endl;
1865 std::vector<Request *> reqs;
1866 reqs.reserve(client.reqvec.size());
1867 for (const auto &req : client.reqvec) {
1868 if (req->timing.state == RequestState::ON_COMPLETE) {
1869 reqs.push_back(req.get());
1873 std::sort(std::begin(reqs), std::end(reqs),
1874 [](const Request *lhs, const Request *rhs) {
1875 const auto <iming = lhs->timing;
1876 const auto &rtiming = rhs->timing;
1877 return ltiming.on_complete_time < rtiming.on_complete_time ||
1878 (ltiming.on_complete_time == rtiming.on_complete_time &&
1879 ltiming.on_request_time < rtiming.on_request_time);
1884 complete: relative time from protocol handshake to stream close
1885 request: relative time from protocol handshake to request
1887 process: time for request and response
1888 code: HTTP status code
1891 sorted by 'complete'
1893 complete request process code request path)" << std::endl;
1895 const auto &base = client.timing.on_handshake_time;
1896 for (const auto &req : reqs) {
1897 auto completed_delta =
1898 std::chrono::duration_cast<std::chrono::microseconds>(
1899 req->timing.on_complete_time - base);
1900 auto request_delta = std::chrono::duration_cast<std::chrono::microseconds>(
1901 req->timing.on_request_time - base);
1902 auto total = std::chrono::duration_cast<std::chrono::microseconds>(
1903 req->timing.on_complete_time - req->timing.on_request_time);
1905 std::cout << std::setw(9) << ("+" + util::format_duration(completed_delta))
1906 << " " << std::setw(9)
1907 << ("+" + util::format_duration(request_delta)) << " "
1908 << std::setw(8) << util::format_duration(total) << " "
1909 << std::setw(4) << req->status << " " << req->make_reqpath()
1916 int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
1917 unsigned char *outlen, const unsigned char *in,
1918 unsigned int inlen, void *arg) {
1919 if (config.verbose) {
1921 std::cout << "[NPN] server offers:" << std::endl;
1923 for (unsigned int i = 0; i < inlen; i += in [i] + 1) {
1924 if (config.verbose) {
1926 std::cout.write(reinterpret_cast<const char *>(&in[i + 1]), in[i]);
1927 std::cout << std::endl;
1930 if (!util::select_h2(const_cast<const unsigned char **>(out), outlen, in,
1932 print_protocol_nego_error();
1933 return SSL_TLSEXT_ERR_NOACK;
1935 return SSL_TLSEXT_ERR_OK;
1940 // Recommended general purpose "Intermediate compatibility" cipher by
1943 // https://wiki.mozilla.org/Security/Server_Side_TLS
1944 const char *const CIPHER_LIST =
1945 "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-"
1946 "AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:"
1947 "DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-"
1948 "AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-"
1949 "AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-"
1950 "AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:"
1951 "DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-"
1952 "SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-"
1953 "SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!"
1954 "aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA";
1959 const std::string &scheme, const std::string &host, uint16_t port,
1960 std::vector<std::tuple<std::string, nghttp2_data_provider *, int64_t>>
1961 requests, const nghttp2_session_callbacks *callbacks) {
1963 auto loop = EV_DEFAULT;
1964 SSL_CTX *ssl_ctx = nullptr;
1965 if (scheme == "https") {
1966 ssl_ctx = SSL_CTX_new(SSLv23_client_method());
1968 std::cerr << "[ERROR] Failed to create SSL_CTX: "
1969 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
1973 SSL_CTX_set_options(ssl_ctx,
1974 SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
1975 SSL_OP_NO_COMPRESSION |
1976 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
1977 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
1978 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
1979 if (SSL_CTX_set_cipher_list(ssl_ctx, CIPHER_LIST) == 0) {
1980 std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
1985 if (!config.keyfile.empty()) {
1986 if (SSL_CTX_use_PrivateKey_file(ssl_ctx, config.keyfile.c_str(),
1987 SSL_FILETYPE_PEM) != 1) {
1988 std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
1994 if (!config.certfile.empty()) {
1995 if (SSL_CTX_use_certificate_chain_file(ssl_ctx,
1996 config.certfile.c_str()) != 1) {
1997 std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
2003 SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb,
2006 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
2007 auto proto_list = util::get_default_alpn();
2009 SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
2010 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
2013 HttpClient client{callbacks, loop, ssl_ctx};
2015 nghttp2_priority_spec pri_spec;
2016 int32_t dep_stream_id = 0;
2018 if (!config.no_dep && config.dep_idle) {
2019 dep_stream_id = ANCHOR_ID_HIGH;
2022 nghttp2_priority_spec_init(&pri_spec, dep_stream_id, config.weight, 0);
2024 for (auto req : requests) {
2025 for (int i = 0; i < config.multiply; ++i) {
2026 auto dep = std::make_shared<Dependency>();
2027 client.add_request(std::get<0>(req), std::get<1>(req), std::get<2>(req),
2028 pri_spec, std::move(dep));
2031 client.update_hostport();
2033 client.record_started_time();
2035 if (client.resolve_host(host, port) != 0) {
2039 client.record_dns_complete_time();
2041 if (client.initiate_connection() != 0) {
2047 if (!config.harfile.empty()) {
2049 if (config.harfile == "-") {
2052 outfile = fopen(config.harfile.c_str(), "wb");
2056 client.output_har(outfile);
2058 if (outfile != stdout) {
2062 std::cerr << "Cannot open file " << config.harfile << ". "
2063 << "har file could not be created." << std::endl;
2066 #endif // HAVE_JANSSON
2068 if (!client.all_requests_processed()) {
2069 std::cerr << "Some requests were not processed. total="
2070 << client.reqvec.size() << ", processed=" << client.complete
2074 print_stats(client);
2079 SSL_CTX_free(ssl_ctx);
2086 ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
2087 uint8_t *buf, size_t length, uint32_t *data_flags,
2088 nghttp2_data_source *source, void *user_data) {
2089 auto req = static_cast<Request *>(
2090 nghttp2_session_get_stream_user_data(session, stream_id));
2092 int fd = source->fd;
2095 while ((nread = pread(fd, buf, length, req->data_offset)) == -1 &&
2100 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
2104 *data_flags |= NGHTTP2_DATA_FLAG_EOF;
2106 req->data_offset += nread;
2114 ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
2115 size_t length, int flags, void *user_data) {
2116 auto client = static_cast<HttpClient *>(user_data);
2117 auto &wb = client->wb;
2119 if (wb.wleft() == 0) {
2120 return NGHTTP2_ERR_WOULDBLOCK;
2123 return wb.write(data, length);
2128 int run(char **uris, int n) {
2129 nghttp2_session_callbacks *callbacks;
2131 nghttp2_session_callbacks_new(&callbacks);
2132 auto cbsdel = defer(nghttp2_session_callbacks_del, callbacks);
2134 nghttp2_session_callbacks_set_on_stream_close_callback(
2135 callbacks, on_stream_close_callback);
2137 nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
2138 on_frame_recv_callback2);
2140 if (config.verbose) {
2141 nghttp2_session_callbacks_set_on_frame_send_callback(
2142 callbacks, verbose_on_frame_send_callback);
2144 nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
2145 callbacks, verbose_on_invalid_frame_recv_callback);
2148 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
2149 callbacks, on_data_chunk_recv_callback);
2151 nghttp2_session_callbacks_set_on_begin_headers_callback(
2152 callbacks, on_begin_headers_callback);
2154 nghttp2_session_callbacks_set_on_header_callback(callbacks,
2155 on_header_callback);
2157 nghttp2_session_callbacks_set_before_frame_send_callback(
2158 callbacks, before_frame_send_callback);
2160 nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
2162 if (config.padding) {
2163 nghttp2_session_callbacks_set_select_padding_callback(
2164 callbacks, select_padding_callback);
2167 std::string prev_scheme;
2168 std::string prev_host;
2169 uint16_t prev_port = 0;
2172 nghttp2_data_provider data_prd;
2173 struct stat data_stat;
2175 if (!config.datafile.empty()) {
2176 if (config.datafile == "-") {
2177 if (fstat(0, &data_stat) == 0 &&
2178 (data_stat.st_mode & S_IFMT) == S_IFREG) {
2179 // use STDIN if it is a regular file
2182 // copy the contents of STDIN to a temporary file
2183 char tempfn[] = "/tmp/nghttp.temp.XXXXXX";
2184 data_fd = mkstemp(tempfn);
2185 if (data_fd == -1) {
2186 std::cerr << "[ERROR] Could not create a temporary file in /tmp"
2190 if (unlink(tempfn) != 0) {
2191 std::cerr << "[WARNING] failed to unlink temporary file:" << tempfn
2195 std::array<char, 1024> buf;
2197 while ((rret = read(0, buf.data(), buf.size())) == -1 &&
2203 std::cerr << "[ERROR] I/O error while reading from STDIN"
2207 while ((wret = write(data_fd, buf.data(), rret)) == -1 &&
2211 std::cerr << "[ERROR] I/O error while writing to temporary file"
2216 if (fstat(data_fd, &data_stat) == -1) {
2218 std::cerr << "[ERROR] Could not stat temporary file" << std::endl;
2223 data_fd = open(config.datafile.c_str(), O_RDONLY | O_BINARY);
2224 if (data_fd == -1) {
2225 std::cerr << "[ERROR] Could not open file " << config.datafile
2229 if (fstat(data_fd, &data_stat) == -1) {
2231 std::cerr << "[ERROR] Could not stat file " << config.datafile
2236 data_prd.source.fd = data_fd;
2237 data_prd.read_callback = file_read_callback;
2239 std::vector<std::tuple<std::string, nghttp2_data_provider *, int64_t>>
2241 for (int i = 0; i < n; ++i) {
2243 memset(&u, 0, sizeof(u));
2244 auto uri = strip_fragment(uris[i]);
2245 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) == 0 &&
2246 util::has_uri_field(u, UF_SCHEMA)) {
2247 uint16_t port = util::has_uri_field(u, UF_PORT)
2249 : util::get_default_port(uri.c_str(), u);
2250 if (!util::fieldeq(uri.c_str(), u, UF_SCHEMA, prev_scheme.c_str()) ||
2251 !util::fieldeq(uri.c_str(), u, UF_HOST, prev_host.c_str()) ||
2252 port != prev_port) {
2253 if (!requests.empty()) {
2254 if (communicate(prev_scheme, prev_host, prev_port,
2255 std::move(requests), callbacks) != 0) {
2260 prev_scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA);
2261 prev_host = util::get_uri_field(uri.c_str(), u, UF_HOST);
2264 requests.emplace_back(uri, data_fd == -1 ? nullptr : &data_prd,
2268 if (!requests.empty()) {
2269 if (communicate(prev_scheme, prev_host, prev_port, std::move(requests),
2279 void print_version(std::ostream &out) {
2280 out << "nghttp nghttp2/" NGHTTP2_VERSION << std::endl;
2285 void print_usage(std::ostream &out) {
2286 out << R"(Usage: nghttp [OPTIONS]... <URI>...
2287 HTTP/2 experimental client)" << std::endl;
2292 void print_help(std::ostream &out) {
2295 <URI> Specify URI to access.
2298 Print debug information such as reception and
2299 transmission of frames and name/value pairs. Specifying
2300 this option multiple times increases verbosity.
2302 Discard downloaded data.
2304 Save download data in the current directory. The
2305 filename is dereived from URI. If URI ends with '/',
2306 'index.html' is used as a filename. Not implemented
2309 Timeout each request after <SEC> seconds.
2310 -w, --window-bits=<N>
2311 Sets the stream level initial window size to 2**<N>-1.
2312 -W, --connection-window-bits=<N>
2313 Sets the connection level initial window size to
2316 Download assets such as stylesheets, images and script
2317 files linked from the downloaded resource. Only links
2318 whose origins are the same with the linking resource
2319 will be downloaded. nghttp prioritizes resources using
2320 HTTP/2 dependency based priority. The priority order,
2321 from highest to lowest, is html itself, css, javascript
2323 -s, --stat Print statistics.
2324 -H, --header=<HEADER>
2325 Add a header to the requests. Example: -H':method: PUT'
2327 Use the specified client certificate file. The file
2328 must be in PEM format.
2329 --key=<KEY> Use the client private key file. The file must be in
2332 Post FILE to server. If '-' is given, data will be read
2335 Request each URI <N> times. By default, same URI is not
2336 requested twice. This option disables it too.
2338 Perform HTTP Upgrade for HTTP/2. This option is ignored
2339 if the request URI has https scheme. If -d is used, the
2340 HTTP upgrade request is performed with OPTIONS method.
2341 -p, --weight=<WEIGHT>
2342 Sets priority group weight. The valid value range is
2343 [)" << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT
2345 Default: )" << NGHTTP2_DEFAULT_WEIGHT << R"(
2346 -M, --peer-max-concurrent-streams=<N>
2347 Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value of
2348 remote endpoint as if it is received in SETTINGS frame.
2349 The default is large enough as it is seen as unlimited.
2350 -c, --header-table-size=<SIZE>
2351 Specify decoder header table size.
2353 Add at most <N> bytes to a frame payload as padding.
2354 Specify 0 to disable padding.
2356 Output HTTP transactions <FILE> in HAR format. If '-'
2357 is given, data is written to stdout.
2358 --color Force colored log output.
2360 Send large header to test CONTINUATION.
2362 Don't send content-length header field.
2363 --no-dep Don't send dependency based priority hint to server.
2364 --dep-idle Use idle streams as anchor nodes to express priority.
2365 --version Display version information and exit.
2366 -h, --help Display this help and exit.
2368 The <SIZE> argument is an integer and an optional unit (e.g., 10K is
2369 10 * 1024). Units are K, M and G (powers of 1024).)" << std::endl;
2373 int main(int argc, char **argv) {
2376 static int flag = 0;
2377 static option long_options[] = {
2378 {"verbose", no_argument, nullptr, 'v'},
2379 {"null-out", no_argument, nullptr, 'n'},
2380 {"remote-name", no_argument, nullptr, 'O'},
2381 {"timeout", required_argument, nullptr, 't'},
2382 {"window-bits", required_argument, nullptr, 'w'},
2383 {"connection-window-bits", required_argument, nullptr, 'W'},
2384 {"get-assets", no_argument, nullptr, 'a'},
2385 {"stat", no_argument, nullptr, 's'},
2386 {"help", no_argument, nullptr, 'h'},
2387 {"header", required_argument, nullptr, 'H'},
2388 {"data", required_argument, nullptr, 'd'},
2389 {"multiply", required_argument, nullptr, 'm'},
2390 {"upgrade", no_argument, nullptr, 'u'},
2391 {"weight", required_argument, nullptr, 'p'},
2392 {"peer-max-concurrent-streams", required_argument, nullptr, 'M'},
2393 {"header-table-size", required_argument, nullptr, 'c'},
2394 {"padding", required_argument, nullptr, 'b'},
2395 {"har", required_argument, nullptr, 'r'},
2396 {"cert", required_argument, &flag, 1},
2397 {"key", required_argument, &flag, 2},
2398 {"color", no_argument, &flag, 3},
2399 {"continuation", no_argument, &flag, 4},
2400 {"version", no_argument, &flag, 5},
2401 {"no-content-length", no_argument, &flag, 6},
2402 {"no-dep", no_argument, &flag, 7},
2403 {"dep-idle", no_argument, &flag, 8},
2404 {nullptr, 0, nullptr, 0}};
2405 int option_index = 0;
2406 int c = getopt_long(argc, argv, "M:Oab:c:d:gm:np:r:hH:vst:uw:W:",
2407 long_options, &option_index);
2413 // peer-max-concurrent-streams option
2414 config.peer_max_concurrent_streams = strtoul(optarg, nullptr, 10);
2417 config.remote_name = true;
2420 print_help(std::cout);
2423 config.padding = strtol(optarg, nullptr, 10);
2426 config.null_out = true;
2430 auto n = strtoul(optarg, nullptr, 10);
2431 if (errno == 0 && NGHTTP2_MIN_WEIGHT <= n && n <= NGHTTP2_MAX_WEIGHT) {
2434 std::cerr << "-p: specify the integer in the range ["
2435 << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT
2436 << "], inclusive" << std::endl;
2443 config.harfile = optarg;
2444 #else // !HAVE_JANSSON
2445 std::cerr << "[WARNING]: -r, --har option is ignored because\n"
2446 << "the binary was not compiled with libjansson." << std::endl;
2447 #endif // !HAVE_JANSSON
2453 config.timeout = atoi(optarg);
2456 config.upgrade = true;
2461 char *endptr = nullptr;
2462 unsigned long int n = strtoul(optarg, &endptr, 10);
2463 if (errno == 0 && *endptr == '\0' && n < 31) {
2465 config.window_bits = n;
2467 config.connection_window_bits = n;
2470 std::cerr << "-" << static_cast<char>(c)
2471 << ": specify the integer in the range [0, 30], inclusive"
2478 char *header = optarg;
2479 // Skip first possible ':' in the header name
2480 char *value = strchr(optarg + 1, ':');
2481 if (!value || (header[0] == ':' && header + 1 == value)) {
2482 std::cerr << "-H: invalid header: " << optarg << std::endl;
2487 while (isspace(*value)) {
2491 // This could also be a valid case for suppressing a header
2493 std::cerr << "-H: invalid header - value missing: " << optarg
2497 // To test "never index" repr, don't index authorization header
2498 // field unconditionally.
2499 auto no_index = util::strieq("authorization", header);
2500 config.headers.emplace_back(header, value, no_index);
2501 util::inp_strlower(config.headers.back().name);
2506 config.get_assets = true;
2507 #else // !HAVE_LIBXML2
2508 std::cerr << "[WARNING]: -a, --get-assets option is ignored because\n"
2509 << "the binary was not compiled with libxml2." << std::endl;
2510 #endif // !HAVE_LIBXML2
2516 config.datafile = optarg;
2519 config.multiply = strtoul(optarg, nullptr, 10);
2523 config.header_table_size = util::parse_uint_with_unit(optarg);
2524 if (config.header_table_size == -1) {
2525 std::cerr << "-c: Bad option value: " << optarg << std::endl;
2530 util::show_candidates(argv[optind - 1], long_options);
2536 config.certfile = optarg;
2540 config.keyfile = optarg;
2547 // continuation option
2548 config.continuation = true;
2552 print_version(std::cout);
2555 // no-content-length option
2556 config.no_content_length = true;
2560 config.no_dep = true;
2564 config.dep_idle = true;
2573 set_color_output(color || isatty(fileno(stdout)));
2575 nghttp2_option_set_peer_max_concurrent_streams(
2576 config.http2_option, config.peer_max_concurrent_streams);
2578 struct sigaction act;
2579 memset(&act, 0, sizeof(struct sigaction));
2580 act.sa_handler = SIG_IGN;
2581 sigaction(SIGPIPE, &act, nullptr);
2582 OPENSSL_config(nullptr);
2583 OpenSSL_add_all_algorithms();
2584 SSL_load_error_strings();
2587 return run(argv + optind, argc - optind);
2590 } // namespace nghttp2
2592 int main(int argc, char **argv) { return nghttp2::main(argc, argv); }