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 #endif // HAVE_UNISTD_H
33 #endif // HAVE_FCNTL_H
34 #ifdef HAVE_NETINET_IN_H
35 # include <netinet/in.h>
36 #endif // HAVE_NETINET_IN_H
37 #include <netinet/tcp.h>
50 #include <openssl/err.h>
54 #endif // HAVE_JANSSON
56 #include "app_helper.h"
57 #include "HtmlParser.h"
62 #include "ssl_compat.h"
70 // The anchor stream nodes when --no-dep is not used. The stream ID =
71 // 1 is excluded since it is used as first stream in upgrade case. We
72 // follows the same dependency anchor nodes as Firefox does.
75 // stream ID this anchor depends on
76 int32_t dep_stream_id;
77 // .. with this weight.
81 // This is index into anchors. Firefox uses ANCHOR_FOLLOWERS for html
92 constexpr auto anchors = std::array<Anchor, 5>{{
102 : header_table_size(-1),
103 min_header_table_size(std::numeric_limits<uint32_t>::max()),
104 encoder_header_table_size(-1),
106 max_concurrent_streams(100),
107 peer_max_concurrent_streams(100),
111 connection_window_bits(-1),
120 no_content_length(false),
124 expect_continue(false),
126 nghttp2_option_new(&http2_option);
127 nghttp2_option_set_peer_max_concurrent_streams(http2_option,
128 peer_max_concurrent_streams);
129 nghttp2_option_set_builtin_recv_extension_type(http2_option, NGHTTP2_ALTSVC);
130 nghttp2_option_set_builtin_recv_extension_type(http2_option, NGHTTP2_ORIGIN);
133 Config::~Config() { nghttp2_option_del(http2_option); }
140 void print_protocol_nego_error() {
141 std::cerr << "[ERROR] HTTP/2 protocol was not selected."
142 << " (nghttp2 expects " << NGHTTP2_PROTO_VERSION_ID << ")"
148 std::string strip_fragment(const char *raw_uri) {
150 for (end = raw_uri; *end && *end != '#'; ++end)
152 size_t len = end - raw_uri;
153 return std::string(raw_uri, len);
157 Request::Request(const std::string &uri, const http_parser_url &u,
158 const nghttp2_data_provider *data_prd, int64_t data_length,
159 const nghttp2_priority_spec &pri_spec, int level)
163 data_length(data_length),
168 header_buffer_size(0),
172 expect_final_response(false) {
173 http2::init_hdidx(res_hdidx);
174 http2::init_hdidx(req_hdidx);
177 Request::~Request() { nghttp2_gzip_inflate_del(inflater); }
179 void Request::init_inflater() {
181 // This is required with --disable-assert.
183 rv = nghttp2_gzip_inflate_new(&inflater);
187 StringRef Request::get_real_scheme() const {
188 return config.scheme_override.empty()
189 ? util::get_uri_field(uri.c_str(), u, UF_SCHEMA)
190 : StringRef{config.scheme_override};
193 StringRef Request::get_real_host() const {
194 return config.host_override.empty()
195 ? util::get_uri_field(uri.c_str(), u, UF_HOST)
196 : StringRef{config.host_override};
199 uint16_t Request::get_real_port() const {
200 auto scheme = get_real_scheme();
201 return config.host_override.empty()
202 ? util::has_uri_field(u, UF_PORT) ? u.port
203 : scheme == "https" ? 443 : 80
204 : config.port_override == 0 ? scheme == "https" ? 443 : 80
205 : config.port_override;
208 void Request::init_html_parser() {
209 // We crawl HTML using overridden scheme, host, and port.
210 auto scheme = get_real_scheme();
211 auto host = get_real_host();
212 auto port = get_real_port();
214 std::find(std::begin(host), std::end(host), ':') != std::end(host);
216 auto base_uri = scheme.str();
225 if (!((scheme == "https" && port == 443) ||
226 (scheme == "http" && port == 80))) {
228 base_uri += util::utos(port);
230 base_uri += util::get_uri_field(uri.c_str(), u, UF_PATH);
231 if (util::has_uri_field(u, UF_QUERY)) {
233 base_uri += util::get_uri_field(uri.c_str(), u, UF_QUERY);
236 html_parser = std::make_unique<HtmlParser>(base_uri);
239 int Request::update_html_parser(const uint8_t *data, size_t len, int fin) {
243 return html_parser->parse_chunk(reinterpret_cast<const char *>(data), len,
247 std::string Request::make_reqpath() const {
248 std::string path = util::has_uri_field(u, UF_PATH)
249 ? util::get_uri_field(uri.c_str(), u, UF_PATH).str()
251 if (util::has_uri_field(u, UF_QUERY)) {
253 path.append(uri.c_str() + u.field_data[UF_QUERY].off,
254 u.field_data[UF_QUERY].len);
260 // Perform special handling |host| if it is IPv6 literal and includes
261 // zone ID per RFC 6874.
262 std::string decode_host(const StringRef &host) {
263 auto zone_start = std::find(std::begin(host), std::end(host), '%');
264 if (zone_start == std::end(host) ||
265 !util::ipv6_numeric_addr(
266 std::string(std::begin(host), zone_start).c_str())) {
270 if (zone_start + 1 == std::end(host)) {
271 return StringRef{host.c_str(), host.size() - 1}.str();
273 // case: ::1%12 or ::1%1
274 if (zone_start + 3 >= std::end(host)) {
277 // If we see "%25", followed by more characters, then decode %25 as
279 auto zone_id_src = (*(zone_start + 1) == '2' && *(zone_start + 2) == '5')
282 auto zone_id = util::percent_decode(zone_id_src, std::end(host));
283 auto res = std::string(std::begin(host), zone_start + 1);
290 nghttp2_priority_spec resolve_dep(int res_type) {
291 nghttp2_priority_spec pri_spec;
294 nghttp2_priority_spec_default_init(&pri_spec);
304 anchor_id = anchors[ANCHOR_LEADERS].stream_id;
308 anchor_id = anchors[ANCHOR_UNBLOCKED].stream_id;
312 anchor_id = anchors[ANCHOR_FOLLOWERS].stream_id;
316 anchor_id = anchors[ANCHOR_FOLLOWERS].stream_id;
320 nghttp2_priority_spec_init(&pri_spec, anchor_id, weight, 0);
325 bool Request::is_ipv6_literal_addr() const {
326 if (util::has_uri_field(u, UF_HOST)) {
327 return memchr(uri.c_str() + u.field_data[UF_HOST].off, ':',
328 u.field_data[UF_HOST].len);
334 Headers::value_type *Request::get_res_header(int32_t token) {
335 auto idx = res_hdidx[token];
339 return &res_nva[idx];
342 Headers::value_type *Request::get_req_header(int32_t token) {
343 auto idx = req_hdidx[token];
347 return &req_nva[idx];
350 void Request::record_request_start_time() {
351 timing.state = RequestState::ON_REQUEST;
352 timing.request_start_time = get_time();
355 void Request::record_response_start_time() {
356 timing.state = RequestState::ON_RESPONSE;
357 timing.response_start_time = get_time();
360 void Request::record_response_end_time() {
361 timing.state = RequestState::ON_COMPLETE;
362 timing.response_end_time = get_time();
366 void continue_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
367 auto client = static_cast<HttpClient *>(ev_userdata(loop));
368 auto req = static_cast<Request *>(w->data);
371 error = nghttp2_submit_data(client->session, NGHTTP2_FLAG_END_STREAM,
372 req->stream_id, req->data_prd);
375 std::cerr << "[ERROR] nghttp2_submit_data() returned error: "
376 << nghttp2_strerror(error) << std::endl;
377 nghttp2_submit_rst_stream(client->session, NGHTTP2_FLAG_NONE,
378 req->stream_id, NGHTTP2_INTERNAL_ERROR);
381 client->signal_write();
385 ContinueTimer::ContinueTimer(struct ev_loop *loop, Request *req) : loop(loop) {
386 ev_timer_init(&timer, continue_timeout_cb, 1., 0.);
390 ContinueTimer::~ContinueTimer() { stop(); }
392 void ContinueTimer::start() { ev_timer_start(loop, &timer); }
394 void ContinueTimer::stop() { ev_timer_stop(loop, &timer); }
396 void ContinueTimer::dispatch_continue() {
397 // Only dispatch the timeout callback if it hasn't already been called.
398 if (ev_is_active(&timer)) {
399 ev_feed_event(loop, &timer, 0);
404 int htp_msg_begincb(llhttp_t *htp) {
405 if (config.verbose) {
407 std::cout << " HTTP Upgrade response" << std::endl;
414 int htp_msg_completecb(llhttp_t *htp) {
415 auto client = static_cast<HttpClient *>(htp->data);
416 client->upgrade_response_status_code = htp->status_code;
417 client->upgrade_response_complete = true;
423 constexpr llhttp_settings_t htp_hooks = {
424 htp_msg_begincb, // llhttp_cb on_message_begin;
425 nullptr, // llhttp_data_cb on_url;
426 nullptr, // llhttp_data_cb on_status;
427 nullptr, // llhttp_data_cb on_header_field;
428 nullptr, // llhttp_data_cb on_header_value;
429 nullptr, // llhttp_cb on_headers_complete;
430 nullptr, // llhttp_data_cb on_body;
431 htp_msg_completecb, // llhttp_cb on_message_complete;
432 nullptr, // llhttp_cb on_chunk_header
433 nullptr, // llhttp_cb on_chunk_complete
438 int submit_request(HttpClient *client, const Headers &headers, Request *req) {
439 auto path = req->make_reqpath();
440 auto scheme = util::get_uri_field(req->uri.c_str(), req->u, UF_SCHEMA);
441 auto build_headers = Headers{{":method", req->data_prd ? "POST" : "GET"},
443 {":scheme", scheme.str()},
444 {":authority", client->hostport},
446 {"accept-encoding", "gzip, deflate"},
447 {"user-agent", "nghttp2/" NGHTTP2_VERSION}};
448 bool expect_continue = false;
450 if (config.continuation) {
451 for (size_t i = 0; i < 6; ++i) {
452 build_headers.emplace_back("continuation-test-" + util::utos(i + 1),
453 std::string(4_k, '-'));
457 auto num_initial_headers = build_headers.size();
460 if (!config.no_content_length) {
461 build_headers.emplace_back("content-length",
462 util::utos(req->data_length));
464 if (config.expect_continue) {
465 expect_continue = true;
466 build_headers.emplace_back("expect", "100-continue");
470 for (auto &kv : headers) {
472 for (i = 0; i < num_initial_headers; ++i) {
473 if (kv.name == build_headers[i].name) {
474 build_headers[i].value = kv.value;
478 if (i < num_initial_headers) {
482 build_headers.emplace_back(kv.name, kv.value, kv.no_index);
485 auto nva = std::vector<nghttp2_nv>();
486 nva.reserve(build_headers.size());
488 for (auto &kv : build_headers) {
489 nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
492 auto method = http2::get_header(build_headers, ":method");
495 req->method = method->value;
497 std::string trailer_names;
498 if (!config.trailer.empty()) {
499 trailer_names = config.trailer[0].name;
500 for (size_t i = 1; i < config.trailer.size(); ++i) {
501 trailer_names += ", ";
502 trailer_names += config.trailer[i].name;
504 nva.push_back(http2::make_nv_ls("trailer", trailer_names));
509 if (expect_continue) {
510 stream_id = nghttp2_submit_headers(client->session, 0, -1, &req->pri_spec,
511 nva.data(), nva.size(), req);
514 nghttp2_submit_request(client->session, &req->pri_spec, nva.data(),
515 nva.size(), req->data_prd, req);
519 std::cerr << "[ERROR] nghttp2_submit_"
520 << (expect_continue ? "headers" : "request")
521 << "() returned error: " << nghttp2_strerror(stream_id)
526 req->stream_id = stream_id;
527 client->request_done(req);
529 req->req_nva = std::move(build_headers);
531 if (expect_continue) {
532 auto timer = std::make_unique<ContinueTimer>(client->loop, req);
533 req->continue_timer = std::move(timer);
541 void readcb(struct ev_loop *loop, ev_io *w, int revents) {
542 auto client = static_cast<HttpClient *>(w->data);
543 if (client->do_read() != 0) {
544 client->disconnect();
550 void writecb(struct ev_loop *loop, ev_io *w, int revents) {
551 auto client = static_cast<HttpClient *>(w->data);
552 auto rv = client->do_write();
553 if (rv == HttpClient::ERR_CONNECT_FAIL) {
554 client->connect_fail();
558 client->disconnect();
564 void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
565 auto client = static_cast<HttpClient *>(w->data);
566 std::cerr << "[ERROR] Timeout" << std::endl;
567 client->disconnect();
572 void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
573 auto client = static_cast<HttpClient *>(w->data);
574 ev_timer_stop(loop, w);
576 nghttp2_session_terminate_session(client->session, NGHTTP2_SETTINGS_TIMEOUT);
578 client->signal_write();
582 HttpClient::HttpClient(const nghttp2_session_callbacks *callbacks,
583 struct ev_loop *loop, SSL_CTX *ssl_ctx)
586 callbacks(callbacks),
595 settings_payloadlen(0),
596 state(ClientState::IDLE),
597 upgrade_response_status_code(0),
599 upgrade_response_complete(false) {
600 ev_io_init(&wev, writecb, 0, EV_WRITE);
601 ev_io_init(&rev, readcb, 0, EV_READ);
606 ev_timer_init(&wt, timeoutcb, 0., config.timeout);
607 ev_timer_init(&rt, timeoutcb, 0., config.timeout);
612 ev_timer_init(&settings_timer, settings_timeout_cb, 0., 10.);
614 settings_timer.data = this;
617 HttpClient::~HttpClient() {
627 bool HttpClient::need_upgrade() const {
628 return config.upgrade && scheme == "http";
631 int HttpClient::resolve_host(const std::string &host, uint16_t port) {
635 hints.ai_family = AF_UNSPEC;
636 hints.ai_socktype = SOCK_STREAM;
637 hints.ai_protocol = 0;
638 hints.ai_flags = AI_ADDRCONFIG;
639 rv = getaddrinfo(host.c_str(), util::utos(port).c_str(), &hints, &addrs);
641 std::cerr << "[ERROR] getaddrinfo() failed: " << gai_strerror(rv)
645 if (addrs == nullptr) {
646 std::cerr << "[ERROR] No address returned" << std::endl;
654 // Just returns 1 to continue handshake.
655 int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) { return 1; }
658 int HttpClient::initiate_connection() {
663 cur_addr = next_addr;
664 next_addr = next_addr->ai_next;
665 fd = util::create_nonblock_socket(cur_addr->ai_family);
671 // We are establishing TLS connection.
672 ssl = SSL_new(ssl_ctx);
674 std::cerr << "[ERROR] SSL_new() failed: "
675 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
680 SSL_set_connect_state(ssl);
682 // If the user overrode the :authority or host header, use that
683 // value for the SNI extension
684 const auto &host_string =
685 config.host_override.empty() ? host : config.host_override;
687 #if LIBRESSL_2_7_API || \
688 (!LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L) || \
689 defined(OPENSSL_IS_BORINGSSL)
690 auto param = SSL_get0_param(ssl);
691 X509_VERIFY_PARAM_set_hostflags(param, 0);
692 X509_VERIFY_PARAM_set1_host(param, host_string.c_str(),
694 #endif // LIBRESSL_2_7_API || (!LIBRESSL_IN_USE &&
695 // OPENSSL_VERSION_NUMBER >= 0x10002000L) ||
696 // defined(OPENSSL_IS_BORINGSSL)
697 SSL_set_verify(ssl, SSL_VERIFY_PEER, verify_cb);
699 if (!util::numeric_host(host_string.c_str())) {
700 SSL_set_tlsext_host_name(ssl, host_string.c_str());
704 rv = connect(fd, cur_addr->ai_addr, cur_addr->ai_addrlen);
706 if (rv != 0 && errno != EINPROGRESS) {
722 writefn = &HttpClient::connected;
724 if (need_upgrade()) {
725 on_readfn = &HttpClient::on_upgrade_read;
726 on_writefn = &HttpClient::on_upgrade_connect;
728 on_readfn = &HttpClient::on_read;
729 on_writefn = &HttpClient::on_write;
732 ev_io_set(&rev, fd, EV_READ);
733 ev_io_set(&wev, fd, EV_WRITE);
735 ev_io_start(loop, &wev);
737 ev_timer_again(loop, &wt);
742 void HttpClient::disconnect() {
743 state = ClientState::IDLE;
745 for (auto req = std::begin(reqvec); req != std::end(reqvec); ++req) {
746 if ((*req)->continue_timer) {
747 (*req)->continue_timer->stop();
751 ev_timer_stop(loop, &settings_timer);
753 ev_timer_stop(loop, &rt);
754 ev_timer_stop(loop, &wt);
756 ev_io_stop(loop, &rev);
757 ev_io_stop(loop, &wev);
759 nghttp2_session_del(session);
763 SSL_set_shutdown(ssl, SSL_get_shutdown(ssl) | SSL_RECEIVED_SHUTDOWN);
771 shutdown(fd, SHUT_WR);
777 int HttpClient::read_clear() {
778 ev_timer_again(loop, &rt);
780 std::array<uint8_t, 8_k> buf;
784 while ((nread = read(fd, buf.data(), buf.size())) == -1 && errno == EINTR)
787 if (errno == EAGAIN || errno == EWOULDBLOCK) {
797 if (on_readfn(*this, buf.data(), nread) != 0) {
805 int HttpClient::write_clear() {
806 ev_timer_again(loop, &rt);
808 std::array<struct iovec, 2> iov;
811 if (on_writefn(*this) != 0) {
815 auto iovcnt = wb.riovec(iov.data(), iov.size());
822 while ((nwrite = writev(fd, iov.data(), iovcnt)) == -1 && errno == EINTR)
825 if (errno == EAGAIN || errno == EWOULDBLOCK) {
826 ev_io_start(loop, &wev);
827 ev_timer_again(loop, &wt);
836 ev_io_stop(loop, &wev);
837 ev_timer_stop(loop, &wt);
842 int HttpClient::noop() { return 0; }
844 void HttpClient::connect_fail() {
845 if (state == ClientState::IDLE) {
846 std::cerr << "[ERROR] Could not connect to the address "
847 << util::numeric_name(cur_addr->ai_addr, cur_addr->ai_addrlen)
850 auto cur_state = state;
852 if (cur_state == ClientState::IDLE) {
853 if (initiate_connection() == 0) {
854 std::cerr << "Trying next address "
855 << util::numeric_name(cur_addr->ai_addr, cur_addr->ai_addrlen)
861 int HttpClient::connected() {
862 if (!util::check_socket_connected(fd)) {
863 return ERR_CONNECT_FAIL;
866 if (config.verbose) {
868 std::cout << " Connected" << std::endl;
871 state = ClientState::CONNECTED;
873 ev_io_start(loop, &rev);
874 ev_io_stop(loop, &wev);
876 ev_timer_again(loop, &rt);
877 ev_timer_stop(loop, &wt);
880 readfn = &HttpClient::tls_handshake;
881 writefn = &HttpClient::tls_handshake;
886 readfn = &HttpClient::read_clear;
887 writefn = &HttpClient::write_clear;
889 if (need_upgrade()) {
890 htp = std::make_unique<llhttp_t>();
891 llhttp_init(htp.get(), HTTP_RESPONSE, &htp_hooks);
897 if (connection_made() != 0) {
905 size_t populate_settings(nghttp2_settings_entry *iv) {
908 iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
909 iv[0].value = config.max_concurrent_streams;
911 iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
912 if (config.window_bits != -1) {
913 iv[1].value = (1 << config.window_bits) - 1;
915 iv[1].value = NGHTTP2_INITIAL_WINDOW_SIZE;
918 if (config.header_table_size >= 0) {
919 if (config.min_header_table_size < config.header_table_size) {
920 iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
921 iv[niv].value = config.min_header_table_size;
925 iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
926 iv[niv].value = config.header_table_size;
930 if (config.no_push) {
931 iv[niv].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
940 int HttpClient::on_upgrade_connect() {
942 record_connect_end_time();
943 assert(!reqvec.empty());
944 std::array<nghttp2_settings_entry, 16> iv;
945 size_t niv = populate_settings(iv.data());
946 assert(settings_payload.size() >= 8 * niv);
947 rv = nghttp2_pack_settings_payload(settings_payload.data(),
948 settings_payload.size(), iv.data(), niv);
952 settings_payloadlen = rv;
954 base64::encode(std::begin(settings_payload),
955 std::begin(settings_payload) + settings_payloadlen);
956 util::to_token68(token68);
959 if (reqvec[0]->data_prd) {
960 // If the request contains upload data, use OPTIONS * to upgrade
963 auto meth = std::find_if(
964 std::begin(config.headers), std::end(config.headers),
965 [](const Header &kv) { return util::streq_l(":method", kv.name); });
967 if (meth == std::end(config.headers)) {
969 reqvec[0]->method = "GET";
973 reqvec[0]->method = (*meth).value;
975 req += reqvec[0]->make_reqpath();
978 auto headers = Headers{{"host", hostport},
979 {"connection", "Upgrade, HTTP2-Settings"},
980 {"upgrade", NGHTTP2_CLEARTEXT_PROTO_VERSION_ID},
981 {"http2-settings", token68},
983 {"user-agent", "nghttp2/" NGHTTP2_VERSION}};
984 auto initial_headerslen = headers.size();
986 for (auto &kv : config.headers) {
988 if (kv.name.empty() || kv.name[0] == ':') {
991 for (i = 0; i < initial_headerslen; ++i) {
992 if (kv.name == headers[i].name) {
993 headers[i].value = kv.value;
997 if (i < initial_headerslen) {
1000 headers.emplace_back(kv.name, kv.value, kv.no_index);
1003 req += " HTTP/1.1\r\n";
1005 for (auto &kv : headers) {
1015 if (config.verbose) {
1017 std::cout << " HTTP Upgrade request\n" << req << std::endl;
1020 if (!reqvec[0]->data_prd) {
1021 // record request time if this is a part of real request.
1022 reqvec[0]->record_request_start_time();
1023 reqvec[0]->req_nva = std::move(headers);
1026 on_writefn = &HttpClient::noop;
1033 int HttpClient::on_upgrade_read(const uint8_t *data, size_t len) {
1037 llhttp_execute(htp.get(), reinterpret_cast<const char *>(data), len);
1038 auto nread = htperr == HPE_OK
1040 : static_cast<size_t>(reinterpret_cast<const uint8_t *>(
1041 llhttp_get_error_pos(htp.get())) -
1044 if (config.verbose) {
1045 std::cout.write(reinterpret_cast<const char *>(data), nread);
1048 if (htperr != HPE_OK && htperr != HPE_PAUSED_UPGRADE) {
1049 std::cerr << "[ERROR] Failed to parse HTTP Upgrade response header: "
1050 << "(" << llhttp_errno_name(htperr) << ") "
1051 << llhttp_get_error_reason(htp.get()) << std::endl;
1055 if (!upgrade_response_complete) {
1059 if (config.verbose) {
1060 std::cout << std::endl;
1063 if (upgrade_response_status_code != 101) {
1064 std::cerr << "[ERROR] HTTP Upgrade failed" << std::endl;
1069 if (config.verbose) {
1071 std::cout << " HTTP Upgrade success" << std::endl;
1074 on_readfn = &HttpClient::on_read;
1075 on_writefn = &HttpClient::on_write;
1077 rv = connection_made();
1082 // Read remaining data in the buffer because it is not notified
1083 // callback anymore.
1084 rv = on_readfn(*this, data + nread, len - nread);
1092 int HttpClient::do_read() { return readfn(*this); }
1093 int HttpClient::do_write() { return writefn(*this); }
1095 int HttpClient::connection_made() {
1098 if (!need_upgrade()) {
1099 record_connect_end_time();
1103 // Check NPN or ALPN result
1104 const unsigned char *next_proto = nullptr;
1105 unsigned int next_proto_len;
1106 #ifndef OPENSSL_NO_NEXTPROTONEG
1107 SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len);
1108 #endif // !OPENSSL_NO_NEXTPROTONEG
1109 for (int i = 0; i < 2; ++i) {
1111 auto proto = StringRef{next_proto, next_proto_len};
1112 if (config.verbose) {
1113 std::cout << "The negotiated protocol: " << proto << std::endl;
1115 if (!util::check_h2_is_selected(proto)) {
1116 next_proto = nullptr;
1120 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
1121 SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
1122 #else // OPENSSL_VERSION_NUMBER < 0x10002000L
1124 #endif // OPENSSL_VERSION_NUMBER < 0x10002000L
1127 print_protocol_nego_error();
1132 rv = nghttp2_session_client_new2(&session, callbacks, this,
1133 config.http2_option);
1138 if (need_upgrade()) {
1139 // Adjust stream user-data depending on the existence of upload
1141 Request *stream_user_data = nullptr;
1142 if (!reqvec[0]->data_prd) {
1143 stream_user_data = reqvec[0].get();
1145 // If HEAD is used, that is only when user specified it with -H
1147 auto head_request = stream_user_data && stream_user_data->method == "HEAD";
1148 rv = nghttp2_session_upgrade2(session, settings_payload.data(),
1149 settings_payloadlen, head_request,
1152 std::cerr << "[ERROR] nghttp2_session_upgrade() returned error: "
1153 << nghttp2_strerror(rv) << std::endl;
1156 if (stream_user_data) {
1157 stream_user_data->stream_id = 1;
1158 request_done(stream_user_data);
1161 // If upgrade succeeds, the SETTINGS value sent with
1162 // HTTP2-Settings header field has already been submitted to
1164 if (!need_upgrade()) {
1165 std::array<nghttp2_settings_entry, 16> iv;
1166 auto niv = populate_settings(iv.data());
1167 rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv.data(), niv);
1172 if (!config.no_dep) {
1173 // Create anchor stream nodes
1174 nghttp2_priority_spec pri_spec;
1176 for (auto &anchor : anchors) {
1177 nghttp2_priority_spec_init(&pri_spec, anchor.dep_stream_id, anchor.weight,
1179 rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, anchor.stream_id,
1186 rv = nghttp2_session_set_next_stream_id(
1187 session, anchors[ANCHOR_FOLLOWERS].stream_id + 2);
1192 if (need_upgrade() && !reqvec[0]->data_prd) {
1193 // Amend the priority because we cannot send priority in
1194 // HTTP/1.1 Upgrade.
1195 auto &anchor = anchors[ANCHOR_FOLLOWERS];
1196 nghttp2_priority_spec_init(&pri_spec, anchor.stream_id,
1197 reqvec[0]->pri_spec.weight, 0);
1199 rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec);
1204 } else if (need_upgrade() && !reqvec[0]->data_prd &&
1205 reqvec[0]->pri_spec.weight != NGHTTP2_DEFAULT_WEIGHT) {
1206 // Amend the priority because we cannot send priority in HTTP/1.1
1208 nghttp2_priority_spec pri_spec;
1210 nghttp2_priority_spec_init(&pri_spec, 0, reqvec[0]->pri_spec.weight, 0);
1212 rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec);
1218 ev_timer_again(loop, &settings_timer);
1220 if (config.connection_window_bits != -1) {
1221 int32_t window_size = (1 << config.connection_window_bits) - 1;
1222 rv = nghttp2_session_set_local_window_size(session, NGHTTP2_FLAG_NONE, 0,
1228 // Adjust first request depending on the existence of the upload
1230 for (auto i = std::begin(reqvec) + (need_upgrade() && !reqvec[0]->data_prd);
1231 i != std::end(reqvec); ++i) {
1232 if (submit_request(this, config.headers, (*i).get()) != 0) {
1242 int HttpClient::on_read(const uint8_t *data, size_t len) {
1243 if (config.hexdump) {
1244 util::hexdump(stdout, data, len);
1247 auto rv = nghttp2_session_mem_recv(session, data, len);
1249 std::cerr << "[ERROR] nghttp2_session_mem_recv() returned error: "
1250 << nghttp2_strerror(rv) << std::endl;
1254 assert(static_cast<size_t>(rv) == len);
1256 if (nghttp2_session_want_read(session) == 0 &&
1257 nghttp2_session_want_write(session) == 0 && wb.rleft() == 0) {
1266 int HttpClient::on_write() {
1268 if (wb.rleft() >= 16384) {
1272 const uint8_t *data;
1273 auto len = nghttp2_session_mem_send(session, &data);
1275 std::cerr << "[ERROR] nghttp2_session_send() returned error: "
1276 << nghttp2_strerror(len) << std::endl;
1284 wb.append(data, len);
1287 if (nghttp2_session_want_read(session) == 0 &&
1288 nghttp2_session_want_write(session) == 0 && wb.rleft() == 0) {
1295 int HttpClient::tls_handshake() {
1296 ev_timer_again(loop, &rt);
1300 auto rv = SSL_do_handshake(ssl);
1303 auto err = SSL_get_error(ssl, rv);
1305 case SSL_ERROR_WANT_READ:
1306 ev_io_stop(loop, &wev);
1307 ev_timer_stop(loop, &wt);
1309 case SSL_ERROR_WANT_WRITE:
1310 ev_io_start(loop, &wev);
1311 ev_timer_again(loop, &wt);
1318 ev_io_stop(loop, &wev);
1319 ev_timer_stop(loop, &wt);
1321 readfn = &HttpClient::read_tls;
1322 writefn = &HttpClient::write_tls;
1324 if (config.verify_peer) {
1325 auto verify_res = SSL_get_verify_result(ssl);
1326 if (verify_res != X509_V_OK) {
1327 std::cerr << "[WARNING] Certificate verification failed: "
1328 << X509_verify_cert_error_string(verify_res) << std::endl;
1332 if (connection_made() != 0) {
1339 int HttpClient::read_tls() {
1340 ev_timer_again(loop, &rt);
1344 std::array<uint8_t, 8_k> buf;
1346 auto rv = SSL_read(ssl, buf.data(), buf.size());
1349 auto err = SSL_get_error(ssl, rv);
1351 case SSL_ERROR_WANT_READ:
1353 case SSL_ERROR_WANT_WRITE:
1354 // renegotiation started
1361 if (on_readfn(*this, buf.data(), rv) != 0) {
1367 int HttpClient::write_tls() {
1368 ev_timer_again(loop, &rt);
1375 if (on_writefn(*this) != 0) {
1379 auto iovcnt = wb.riovec(&iov, 1);
1385 auto rv = SSL_write(ssl, iov.iov_base, iov.iov_len);
1388 auto err = SSL_get_error(ssl, rv);
1390 case SSL_ERROR_WANT_READ:
1391 // renegotiation started
1393 case SSL_ERROR_WANT_WRITE:
1394 ev_io_start(loop, &wev);
1395 ev_timer_again(loop, &wt);
1405 ev_io_stop(loop, &wev);
1406 ev_timer_stop(loop, &wt);
1411 void HttpClient::signal_write() { ev_io_start(loop, &wev); }
1413 bool HttpClient::all_requests_processed() const {
1414 return complete == reqvec.size();
1417 void HttpClient::update_hostport() {
1418 if (reqvec.empty()) {
1421 scheme = util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_SCHEMA)
1423 std::stringstream ss;
1424 if (reqvec[0]->is_ipv6_literal_addr()) {
1425 // we may have zone ID, which must start with "%25", or "%". RFC
1426 // 6874 defines "%25" only, and just "%" is allowed for just
1427 // convenience to end-user input.
1429 util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST);
1430 auto end = std::find(std::begin(host), std::end(host), '%');
1432 ss.write(host.c_str(), end - std::begin(host));
1435 util::write_uri_field(ss, reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST);
1437 if (util::has_uri_field(reqvec[0]->u, UF_PORT) &&
1438 reqvec[0]->u.port !=
1439 util::get_default_port(reqvec[0]->uri.c_str(), reqvec[0]->u)) {
1440 ss << ":" << reqvec[0]->u.port;
1442 hostport = ss.str();
1445 bool HttpClient::add_request(const std::string &uri,
1446 const nghttp2_data_provider *data_prd,
1447 int64_t data_length,
1448 const nghttp2_priority_spec &pri_spec, int level) {
1449 http_parser_url u{};
1450 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
1453 if (path_cache.count(uri)) {
1457 if (config.multiply == 1) {
1458 path_cache.insert(uri);
1461 reqvec.push_back(std::make_unique<Request>(uri, u, data_prd, data_length,
1466 void HttpClient::record_start_time() {
1467 timing.system_start_time = std::chrono::system_clock::now();
1468 timing.start_time = get_time();
1471 void HttpClient::record_domain_lookup_end_time() {
1472 timing.domain_lookup_end_time = get_time();
1475 void HttpClient::record_connect_end_time() {
1476 timing.connect_end_time = get_time();
1479 void HttpClient::request_done(Request *req) {
1480 if (req->stream_id % 2 == 0) {
1486 void HttpClient::output_har(FILE *outfile) {
1487 static auto PAGE_ID = "page_0";
1489 auto root = json_object();
1490 auto log = json_object();
1491 json_object_set_new(root, "log", log);
1492 json_object_set_new(log, "version", json_string("1.2"));
1494 auto creator = json_object();
1495 json_object_set_new(log, "creator", creator);
1497 json_object_set_new(creator, "name", json_string("nghttp"));
1498 json_object_set_new(creator, "version", json_string(NGHTTP2_VERSION));
1500 auto pages = json_array();
1501 json_object_set_new(log, "pages", pages);
1503 auto page = json_object();
1504 json_array_append_new(pages, page);
1506 json_object_set_new(
1507 page, "startedDateTime",
1508 json_string(util::format_iso8601(timing.system_start_time).c_str()));
1509 json_object_set_new(page, "id", json_string(PAGE_ID));
1510 json_object_set_new(page, "title", json_string(""));
1512 json_object_set_new(page, "pageTimings", json_object());
1514 auto entries = json_array();
1515 json_object_set_new(log, "entries", entries);
1517 auto dns_delta = std::chrono::duration_cast<std::chrono::microseconds>(
1518 timing.domain_lookup_end_time - timing.start_time)
1521 auto connect_delta =
1522 std::chrono::duration_cast<std::chrono::microseconds>(
1523 timing.connect_end_time - timing.domain_lookup_end_time)
1527 for (size_t i = 0; i < reqvec.size(); ++i) {
1528 auto &req = reqvec[i];
1530 if (req->timing.state != RequestState::ON_COMPLETE) {
1534 auto entry = json_object();
1535 json_array_append_new(entries, entry);
1537 auto &req_timing = req->timing;
1539 (i == 0) ? timing.system_start_time
1540 : timing.system_start_time +
1541 std::chrono::duration_cast<
1542 std::chrono::system_clock::duration>(
1543 req_timing.request_start_time - timing.start_time);
1546 std::chrono::duration_cast<std::chrono::microseconds>(
1547 req_timing.response_start_time - req_timing.request_start_time)
1550 auto receive_delta =
1551 std::chrono::duration_cast<std::chrono::microseconds>(
1552 req_timing.response_end_time - req_timing.response_start_time)
1557 std::chrono::duration_cast<std::chrono::microseconds>(
1558 (i == 0) ? (req_timing.response_end_time - timing.start_time)
1559 : (req_timing.response_end_time -
1560 req_timing.request_start_time))
1564 json_object_set_new(
1565 entry, "startedDateTime",
1566 json_string(util::format_iso8601(request_time).c_str()));
1567 json_object_set_new(entry, "time", json_real(time_sum));
1569 auto pushed = req->stream_id % 2 == 0;
1571 json_object_set_new(entry, "comment",
1572 json_string(pushed ? "Pushed Object" : ""));
1574 auto request = json_object();
1575 json_object_set_new(entry, "request", request);
1577 auto req_headers = json_array();
1578 json_object_set_new(request, "headers", req_headers);
1580 for (auto &nv : req->req_nva) {
1581 auto hd = json_object();
1582 json_array_append_new(req_headers, hd);
1584 json_object_set_new(hd, "name", json_string(nv.name.c_str()));
1585 json_object_set_new(hd, "value", json_string(nv.value.c_str()));
1588 json_object_set_new(request, "method", json_string(req->method.c_str()));
1589 json_object_set_new(request, "url", json_string(req->uri.c_str()));
1590 json_object_set_new(request, "httpVersion", json_string("HTTP/2.0"));
1591 json_object_set_new(request, "cookies", json_array());
1592 json_object_set_new(request, "queryString", json_array());
1593 json_object_set_new(request, "headersSize", json_integer(-1));
1594 json_object_set_new(request, "bodySize", json_integer(-1));
1596 auto response = json_object();
1597 json_object_set_new(entry, "response", response);
1599 auto res_headers = json_array();
1600 json_object_set_new(response, "headers", res_headers);
1602 for (auto &nv : req->res_nva) {
1603 auto hd = json_object();
1604 json_array_append_new(res_headers, hd);
1606 json_object_set_new(hd, "name", json_string(nv.name.c_str()));
1607 json_object_set_new(hd, "value", json_string(nv.value.c_str()));
1610 json_object_set_new(response, "status", json_integer(req->status));
1611 json_object_set_new(response, "statusText", json_string(""));
1612 json_object_set_new(response, "httpVersion", json_string("HTTP/2.0"));
1613 json_object_set_new(response, "cookies", json_array());
1615 auto content = json_object();
1616 json_object_set_new(response, "content", content);
1618 json_object_set_new(content, "size", json_integer(req->response_len));
1620 auto content_type_ptr = http2::get_header(req->res_nva, "content-type");
1622 const char *content_type = "";
1623 if (content_type_ptr) {
1624 content_type = content_type_ptr->value.c_str();
1627 json_object_set_new(content, "mimeType", json_string(content_type));
1629 json_object_set_new(response, "redirectURL", json_string(""));
1630 json_object_set_new(response, "headersSize", json_integer(-1));
1631 json_object_set_new(response, "bodySize", json_integer(-1));
1632 json_object_set_new(entry, "cache", json_object());
1634 auto timings = json_object();
1635 json_object_set_new(entry, "timings", timings);
1637 auto dns_timing = (i == 0) ? dns_delta : 0;
1638 auto connect_timing = (i == 0) ? connect_delta : 0;
1640 json_object_set_new(timings, "dns", json_real(dns_timing));
1641 json_object_set_new(timings, "connect", json_real(connect_timing));
1643 json_object_set_new(timings, "blocked", json_real(0.0));
1644 json_object_set_new(timings, "send", json_real(0.0));
1645 json_object_set_new(timings, "wait", json_real(wait_delta));
1646 json_object_set_new(timings, "receive", json_real(receive_delta));
1648 json_object_set_new(entry, "pageref", json_string(PAGE_ID));
1649 json_object_set_new(entry, "connection",
1650 json_string(util::utos(req->stream_id).c_str()));
1653 json_dumpf(root, outfile, JSON_PRESERVE_ORDER | JSON_INDENT(2));
1656 #endif // HAVE_JANSSON
1659 void update_html_parser(HttpClient *client, Request *req, const uint8_t *data,
1660 size_t len, int fin) {
1661 if (!req->html_parser) {
1664 req->update_html_parser(data, len, fin);
1666 auto scheme = req->get_real_scheme();
1667 auto host = req->get_real_host();
1668 auto port = req->get_real_port();
1670 for (auto &p : req->html_parser->get_links()) {
1671 auto uri = strip_fragment(p.first.c_str());
1672 auto res_type = p.second;
1674 http_parser_url u{};
1675 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
1679 if (!util::fieldeq(uri.c_str(), u, UF_SCHEMA, scheme) ||
1680 !util::fieldeq(uri.c_str(), u, UF_HOST, host)) {
1685 util::has_uri_field(u, UF_PORT) ? u.port : scheme == "https" ? 443 : 80;
1687 if (port != link_port) {
1691 // No POST data for assets
1692 auto pri_spec = resolve_dep(res_type);
1694 if (client->add_request(uri, nullptr, 0, pri_spec, req->level + 1)) {
1695 submit_request(client, config.headers, client->reqvec.back().get());
1698 req->html_parser->clear_links();
1703 HttpClient *get_client(void *user_data) {
1704 return static_cast<HttpClient *>(user_data);
1709 int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
1710 int32_t stream_id, const uint8_t *data,
1711 size_t len, void *user_data) {
1712 auto client = get_client(user_data);
1713 auto req = static_cast<Request *>(
1714 nghttp2_session_get_stream_user_data(session, stream_id));
1720 if (config.verbose >= 2) {
1721 verbose_on_data_chunk_recv_callback(session, flags, stream_id, data, len,
1725 req->response_len += len;
1727 if (req->inflater) {
1729 const size_t MAX_OUTLEN = 4_k;
1730 std::array<uint8_t, MAX_OUTLEN> out;
1731 size_t outlen = MAX_OUTLEN;
1734 nghttp2_gzip_inflate(req->inflater, out.data(), &outlen, data, &tlen);
1736 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
1737 NGHTTP2_INTERNAL_ERROR);
1741 if (!config.null_out) {
1742 std::cout.write(reinterpret_cast<const char *>(out.data()), outlen);
1745 update_html_parser(client, req, out.data(), outlen, 0);
1753 if (!config.null_out) {
1754 std::cout.write(reinterpret_cast<const char *>(data), len);
1757 update_html_parser(client, req, data, len, 0);
1764 ssize_t select_padding_callback(nghttp2_session *session,
1765 const nghttp2_frame *frame, size_t max_payload,
1767 return std::min(max_payload, frame->hd.length + config.padding);
1772 void check_response_header(nghttp2_session *session, Request *req) {
1775 req->expect_final_response = false;
1777 auto status_hd = req->get_res_header(http2::HD__STATUS);
1780 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
1781 NGHTTP2_PROTOCOL_ERROR);
1785 auto status = http2::parse_http_status_code(StringRef{status_hd->value});
1787 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
1788 NGHTTP2_PROTOCOL_ERROR);
1792 req->status = status;
1794 for (auto &nv : req->res_nva) {
1795 if ("content-encoding" == nv.name) {
1796 gzip = util::strieq_l("gzip", nv.value) ||
1797 util::strieq_l("deflate", nv.value);
1802 if (req->status / 100 == 1) {
1803 if (req->continue_timer && (req->status == 100)) {
1804 // If the request is waiting for a 100 Continue, complete the handshake.
1805 req->continue_timer->dispatch_continue();
1808 req->expect_final_response = true;
1810 req->res_nva.clear();
1811 http2::init_hdidx(req->res_hdidx);
1813 } else if (req->continue_timer) {
1814 // A final response stops any pending Expect/Continue handshake.
1815 req->continue_timer->stop();
1819 if (!req->inflater) {
1820 req->init_inflater();
1823 if (config.get_assets && req->level == 0) {
1824 if (!req->html_parser) {
1825 req->init_html_parser();
1832 int on_begin_headers_callback(nghttp2_session *session,
1833 const nghttp2_frame *frame, void *user_data) {
1834 auto client = get_client(user_data);
1835 switch (frame->hd.type) {
1836 case NGHTTP2_HEADERS: {
1837 auto req = static_cast<Request *>(
1838 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1843 switch (frame->headers.cat) {
1844 case NGHTTP2_HCAT_RESPONSE:
1845 case NGHTTP2_HCAT_PUSH_RESPONSE:
1846 req->record_response_start_time();
1854 case NGHTTP2_PUSH_PROMISE: {
1855 auto stream_id = frame->push_promise.promised_stream_id;
1856 http_parser_url u{};
1857 // TODO Set pri and level
1858 nghttp2_priority_spec pri_spec;
1860 nghttp2_priority_spec_default_init(&pri_spec);
1862 auto req = std::make_unique<Request>("", u, nullptr, 0, pri_spec);
1863 req->stream_id = stream_id;
1865 nghttp2_session_set_stream_user_data(session, stream_id, req.get());
1867 client->request_done(req.get());
1868 req->record_request_start_time();
1869 client->reqvec.push_back(std::move(req));
1879 int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
1880 const uint8_t *name, size_t namelen,
1881 const uint8_t *value, size_t valuelen, uint8_t flags,
1883 if (config.verbose) {
1884 verbose_on_header_callback(session, frame, name, namelen, value, valuelen,
1888 switch (frame->hd.type) {
1889 case NGHTTP2_HEADERS: {
1890 auto req = static_cast<Request *>(
1891 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1897 /* ignore trailer header */
1898 if (frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
1899 !req->expect_final_response) {
1903 if (req->header_buffer_size + namelen + valuelen > 64_k) {
1904 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
1905 NGHTTP2_INTERNAL_ERROR);
1909 req->header_buffer_size += namelen + valuelen;
1911 auto token = http2::lookup_token(name, namelen);
1913 http2::index_header(req->res_hdidx, token, req->res_nva.size());
1914 http2::add_header(req->res_nva, name, namelen, value, valuelen,
1915 flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
1918 case NGHTTP2_PUSH_PROMISE: {
1919 auto req = static_cast<Request *>(nghttp2_session_get_stream_user_data(
1920 session, frame->push_promise.promised_stream_id));
1926 if (req->header_buffer_size + namelen + valuelen > 64_k) {
1927 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
1928 frame->push_promise.promised_stream_id,
1929 NGHTTP2_INTERNAL_ERROR);
1933 req->header_buffer_size += namelen + valuelen;
1935 auto token = http2::lookup_token(name, namelen);
1937 http2::index_header(req->req_hdidx, token, req->req_nva.size());
1938 http2::add_header(req->req_nva, name, namelen, value, valuelen,
1939 flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
1948 int on_frame_recv_callback2(nghttp2_session *session,
1949 const nghttp2_frame *frame, void *user_data) {
1952 if (config.verbose) {
1953 verbose_on_frame_recv_callback(session, frame, user_data);
1956 auto client = get_client(user_data);
1957 switch (frame->hd.type) {
1958 case NGHTTP2_DATA: {
1959 auto req = static_cast<Request *>(
1960 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1966 if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
1967 req->record_response_end_time();
1973 case NGHTTP2_HEADERS: {
1974 auto req = static_cast<Request *>(
1975 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1976 // If this is the HTTP Upgrade with OPTIONS method to avoid POST,
1983 switch (frame->headers.cat) {
1984 case NGHTTP2_HCAT_RESPONSE:
1985 case NGHTTP2_HCAT_PUSH_RESPONSE:
1986 check_response_header(session, req);
1988 case NGHTTP2_HCAT_HEADERS:
1989 if (req->expect_final_response) {
1990 check_response_header(session, req);
1993 if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
1994 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
1995 frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
2003 if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
2004 req->record_response_end_time();
2010 case NGHTTP2_SETTINGS:
2011 if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
2014 ev_timer_stop(client->loop, &client->settings_timer);
2016 case NGHTTP2_PUSH_PROMISE: {
2017 auto req = static_cast<Request *>(nghttp2_session_get_stream_user_data(
2018 session, frame->push_promise.promised_stream_id));
2023 // Reset for response header field reception
2024 req->header_buffer_size = 0;
2026 auto scheme = req->get_req_header(http2::HD__SCHEME);
2027 auto authority = req->get_req_header(http2::HD__AUTHORITY);
2028 auto path = req->get_req_header(http2::HD__PATH);
2031 authority = req->get_req_header(http2::HD_HOST);
2034 // libnghttp2 guarantees :scheme, :method, :path and (:authority |
2035 // host) exist and non-empty.
2036 if (path->value[0] != '/') {
2037 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
2038 frame->push_promise.promised_stream_id,
2039 NGHTTP2_PROTOCOL_ERROR);
2042 std::string uri = scheme->value;
2044 uri += authority->value;
2046 http_parser_url u{};
2047 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
2048 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
2049 frame->push_promise.promised_stream_id,
2050 NGHTTP2_PROTOCOL_ERROR);
2056 if (client->path_cache.count(uri)) {
2057 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
2058 frame->push_promise.promised_stream_id,
2063 if (config.multiply == 1) {
2064 client->path_cache.insert(uri);
2075 int before_frame_send_callback(nghttp2_session *session,
2076 const nghttp2_frame *frame, void *user_data) {
2077 if (frame->hd.type != NGHTTP2_HEADERS ||
2078 frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
2081 auto req = static_cast<Request *>(
2082 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
2084 req->record_request_start_time();
2091 int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
2093 if (config.verbose) {
2094 verbose_on_frame_send_callback(session, frame, user_data);
2097 if (frame->hd.type != NGHTTP2_HEADERS ||
2098 frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
2102 auto req = static_cast<Request *>(
2103 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
2108 // If this request is using Expect/Continue, start its ContinueTimer.
2109 if (req->continue_timer) {
2110 req->continue_timer->start();
2118 int on_frame_not_send_callback(nghttp2_session *session,
2119 const nghttp2_frame *frame, int lib_error_code,
2121 if (frame->hd.type != NGHTTP2_HEADERS ||
2122 frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
2126 auto req = static_cast<Request *>(
2127 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
2132 std::cerr << "[ERROR] request " << req->uri
2133 << " failed: " << nghttp2_strerror(lib_error_code) << std::endl;
2140 int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
2141 uint32_t error_code, void *user_data) {
2142 auto client = get_client(user_data);
2143 auto req = static_cast<Request *>(
2144 nghttp2_session_get_stream_user_data(session, stream_id));
2150 // If this request is using Expect/Continue, stop its ContinueTimer.
2151 if (req->continue_timer) {
2152 req->continue_timer->stop();
2155 update_html_parser(client, req, nullptr, 0, 1);
2158 if (client->all_requests_processed()) {
2159 nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
2166 struct RequestResult {
2167 std::chrono::microseconds time;
2171 void print_stats(const HttpClient &client) {
2172 std::cout << "***** Statistics *****" << std::endl;
2174 std::vector<Request *> reqs;
2175 reqs.reserve(client.reqvec.size());
2176 for (const auto &req : client.reqvec) {
2177 if (req->timing.state == RequestState::ON_COMPLETE) {
2178 reqs.push_back(req.get());
2182 std::sort(std::begin(reqs), std::end(reqs),
2183 [](const Request *lhs, const Request *rhs) {
2184 const auto <iming = lhs->timing;
2185 const auto &rtiming = rhs->timing;
2186 return ltiming.response_end_time < rtiming.response_end_time ||
2187 (ltiming.response_end_time == rtiming.response_end_time &&
2188 ltiming.request_start_time < rtiming.request_start_time);
2193 responseEnd: the time when last byte of response was received
2194 relative to connectEnd
2195 requestStart: the time just before first byte of request was sent
2196 relative to connectEnd. If '*' is shown, this was
2198 process: responseEnd - requestStart
2199 code: HTTP status code
2200 size: number of bytes received as response body without
2204 see http://www.w3.org/TR/resource-timing/#processing-model
2206 sorted by 'complete'
2208 id responseEnd requestStart process code size request path)"
2211 const auto &base = client.timing.connect_end_time;
2212 for (const auto &req : reqs) {
2213 auto response_end = std::chrono::duration_cast<std::chrono::microseconds>(
2214 req->timing.response_end_time - base);
2215 auto request_start = std::chrono::duration_cast<std::chrono::microseconds>(
2216 req->timing.request_start_time - base);
2217 auto total = std::chrono::duration_cast<std::chrono::microseconds>(
2218 req->timing.response_end_time - req->timing.request_start_time);
2219 auto pushed = req->stream_id % 2 == 0;
2221 std::cout << std::setw(3) << req->stream_id << " " << std::setw(11)
2222 << ("+" + util::format_duration(response_end)) << " "
2223 << (pushed ? "*" : " ") << std::setw(11)
2224 << ("+" + util::format_duration(request_start)) << " "
2225 << std::setw(8) << util::format_duration(total) << " "
2226 << std::setw(4) << req->status << " " << std::setw(4)
2227 << util::utos_unit(req->response_len) << " "
2228 << req->make_reqpath() << std::endl;
2233 #ifndef OPENSSL_NO_NEXTPROTONEG
2235 int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
2236 unsigned char *outlen, const unsigned char *in,
2237 unsigned int inlen, void *arg) {
2238 if (config.verbose) {
2240 std::cout << "[NPN] server offers:" << std::endl;
2242 for (unsigned int i = 0; i < inlen; i += in[i] + 1) {
2243 if (config.verbose) {
2245 std::cout.write(reinterpret_cast<const char *>(&in[i + 1]), in[i]);
2246 std::cout << std::endl;
2249 if (!util::select_h2(const_cast<const unsigned char **>(out), outlen, in,
2251 print_protocol_nego_error();
2252 return SSL_TLSEXT_ERR_NOACK;
2254 return SSL_TLSEXT_ERR_OK;
2257 #endif // !OPENSSL_NO_NEXTPROTONEG
2261 const std::string &scheme, const std::string &host, uint16_t port,
2263 std::tuple<std::string, nghttp2_data_provider *, int64_t, int32_t>>
2265 const nghttp2_session_callbacks *callbacks) {
2267 auto loop = EV_DEFAULT;
2268 SSL_CTX *ssl_ctx = nullptr;
2269 if (scheme == "https") {
2270 ssl_ctx = SSL_CTX_new(SSLv23_client_method());
2272 std::cerr << "[ERROR] Failed to create SSL_CTX: "
2273 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
2278 auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
2279 SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION |
2280 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
2282 SSL_CTX_set_options(ssl_ctx, ssl_opts);
2283 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
2284 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
2286 if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) {
2287 std::cerr << "[WARNING] Could not load system trusted CA certificates: "
2288 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
2291 if (nghttp2::tls::ssl_ctx_set_proto_versions(
2292 ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION,
2293 nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) {
2294 std::cerr << "[ERROR] Could not set TLS versions" << std::endl;
2299 if (SSL_CTX_set_cipher_list(ssl_ctx, tls::DEFAULT_CIPHER_LIST) == 0) {
2300 std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
2305 if (!config.keyfile.empty()) {
2306 if (SSL_CTX_use_PrivateKey_file(ssl_ctx, config.keyfile.c_str(),
2307 SSL_FILETYPE_PEM) != 1) {
2308 std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
2314 if (!config.certfile.empty()) {
2315 if (SSL_CTX_use_certificate_chain_file(ssl_ctx,
2316 config.certfile.c_str()) != 1) {
2317 std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
2323 #ifndef OPENSSL_NO_NEXTPROTONEG
2324 SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb,
2326 #endif // !OPENSSL_NO_NEXTPROTONEG
2328 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
2329 auto proto_list = util::get_default_alpn();
2331 SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
2332 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
2335 HttpClient client{callbacks, loop, ssl_ctx};
2337 int32_t dep_stream_id = 0;
2339 if (!config.no_dep) {
2340 dep_stream_id = anchors[ANCHOR_FOLLOWERS].stream_id;
2343 for (auto &req : requests) {
2344 nghttp2_priority_spec pri_spec;
2346 nghttp2_priority_spec_init(&pri_spec, dep_stream_id, std::get<3>(req), 0);
2348 for (int i = 0; i < config.multiply; ++i) {
2349 client.add_request(std::get<0>(req), std::get<1>(req), std::get<2>(req),
2353 client.update_hostport();
2355 client.record_start_time();
2357 if (client.resolve_host(host, port) != 0) {
2361 client.record_domain_lookup_end_time();
2363 if (client.initiate_connection() != 0) {
2364 std::cerr << "[ERROR] Could not connect to " << host << ", port " << port
2369 ev_set_userdata(loop, &client);
2371 ev_set_userdata(loop, nullptr);
2374 if (!config.harfile.empty()) {
2376 if (config.harfile == "-") {
2379 outfile = fopen(config.harfile.c_str(), "wb");
2383 client.output_har(outfile);
2385 if (outfile != stdout) {
2389 std::cerr << "Cannot open file " << config.harfile << ". "
2390 << "har file could not be created." << std::endl;
2393 #endif // HAVE_JANSSON
2395 if (client.success != client.reqvec.size()) {
2396 std::cerr << "Some requests were not processed. total="
2397 << client.reqvec.size() << ", processed=" << client.success
2401 print_stats(client);
2406 SSL_CTX_free(ssl_ctx);
2413 ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
2414 uint8_t *buf, size_t length, uint32_t *data_flags,
2415 nghttp2_data_source *source, void *user_data) {
2417 auto req = static_cast<Request *>(
2418 nghttp2_session_get_stream_user_data(session, stream_id));
2420 int fd = source->fd;
2423 while ((nread = pread(fd, buf, length, req->data_offset)) == -1 &&
2428 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
2431 req->data_offset += nread;
2433 if (req->data_offset == req->data_length) {
2434 *data_flags |= NGHTTP2_DATA_FLAG_EOF;
2435 if (!config.trailer.empty()) {
2436 std::vector<nghttp2_nv> nva;
2437 nva.reserve(config.trailer.size());
2438 for (auto &kv : config.trailer) {
2439 nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
2441 rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size());
2443 if (nghttp2_is_fatal(rv)) {
2444 return NGHTTP2_ERR_CALLBACK_FAILURE;
2447 *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
2454 if (req->data_offset > req->data_length || nread == 0) {
2455 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
2463 int run(char **uris, int n) {
2464 nghttp2_session_callbacks *callbacks;
2466 nghttp2_session_callbacks_new(&callbacks);
2467 auto cbsdel = defer(nghttp2_session_callbacks_del, callbacks);
2469 nghttp2_session_callbacks_set_on_stream_close_callback(
2470 callbacks, on_stream_close_callback);
2472 nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
2473 on_frame_recv_callback2);
2475 if (config.verbose) {
2476 nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
2477 callbacks, verbose_on_invalid_frame_recv_callback);
2479 nghttp2_session_callbacks_set_error_callback2(callbacks,
2480 verbose_error_callback);
2483 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
2484 callbacks, on_data_chunk_recv_callback);
2486 nghttp2_session_callbacks_set_on_begin_headers_callback(
2487 callbacks, on_begin_headers_callback);
2489 nghttp2_session_callbacks_set_on_header_callback(callbacks,
2490 on_header_callback);
2492 nghttp2_session_callbacks_set_before_frame_send_callback(
2493 callbacks, before_frame_send_callback);
2495 nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
2496 on_frame_send_callback);
2498 nghttp2_session_callbacks_set_on_frame_not_send_callback(
2499 callbacks, on_frame_not_send_callback);
2501 if (config.padding) {
2502 nghttp2_session_callbacks_set_select_padding_callback(
2503 callbacks, select_padding_callback);
2506 std::string prev_scheme;
2507 std::string prev_host;
2508 uint16_t prev_port = 0;
2511 nghttp2_data_provider data_prd;
2512 struct stat data_stat;
2514 if (!config.datafile.empty()) {
2515 if (config.datafile == "-") {
2516 if (fstat(0, &data_stat) == 0 &&
2517 (data_stat.st_mode & S_IFMT) == S_IFREG) {
2518 // use STDIN if it is a regular file
2521 // copy the contents of STDIN to a temporary file
2522 char tempfn[] = "/tmp/nghttp.temp.XXXXXX";
2523 data_fd = mkstemp(tempfn);
2524 if (data_fd == -1) {
2525 std::cerr << "[ERROR] Could not create a temporary file in /tmp"
2529 if (unlink(tempfn) != 0) {
2530 std::cerr << "[WARNING] failed to unlink temporary file:" << tempfn
2534 std::array<char, 1_k> buf;
2536 while ((rret = read(0, buf.data(), buf.size())) == -1 &&
2542 std::cerr << "[ERROR] I/O error while reading from STDIN"
2546 while ((wret = write(data_fd, buf.data(), rret)) == -1 &&
2550 std::cerr << "[ERROR] I/O error while writing to temporary file"
2555 if (fstat(data_fd, &data_stat) == -1) {
2557 std::cerr << "[ERROR] Could not stat temporary file" << std::endl;
2562 data_fd = open(config.datafile.c_str(), O_RDONLY | O_BINARY);
2563 if (data_fd == -1) {
2564 std::cerr << "[ERROR] Could not open file " << config.datafile
2568 if (fstat(data_fd, &data_stat) == -1) {
2570 std::cerr << "[ERROR] Could not stat file " << config.datafile
2575 data_prd.source.fd = data_fd;
2576 data_prd.read_callback = file_read_callback;
2579 std::tuple<std::string, nghttp2_data_provider *, int64_t, int32_t>>
2582 size_t next_weight_idx = 0;
2584 for (int i = 0; i < n; ++i) {
2585 http_parser_url u{};
2586 auto uri = strip_fragment(uris[i]);
2587 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
2589 std::cerr << "[ERROR] Could not parse URI " << uri << std::endl;
2592 if (!util::has_uri_field(u, UF_SCHEMA)) {
2594 std::cerr << "[ERROR] URI " << uri << " does not have scheme part"
2598 auto port = util::has_uri_field(u, UF_PORT)
2600 : util::get_default_port(uri.c_str(), u);
2601 auto host = decode_host(util::get_uri_field(uri.c_str(), u, UF_HOST));
2602 if (!util::fieldeq(uri.c_str(), u, UF_SCHEMA, prev_scheme.c_str()) ||
2603 host != prev_host || port != prev_port) {
2604 if (!requests.empty()) {
2605 if (communicate(prev_scheme, prev_host, prev_port, std::move(requests),
2611 prev_scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA).str();
2612 prev_host = std::move(host);
2615 requests.emplace_back(uri, data_fd == -1 ? nullptr : &data_prd,
2616 data_stat.st_size, config.weight[next_weight_idx++]);
2618 if (!requests.empty()) {
2619 if (communicate(prev_scheme, prev_host, prev_port, std::move(requests),
2629 void print_version(std::ostream &out) {
2630 out << "nghttp nghttp2/" NGHTTP2_VERSION << std::endl;
2635 void print_usage(std::ostream &out) {
2636 out << R"(Usage: nghttp [OPTIONS]... <URI>...
2643 void print_help(std::ostream &out) {
2646 <URI> Specify URI to access.
2649 Print debug information such as reception and
2650 transmission of frames and name/value pairs. Specifying
2651 this option multiple times increases verbosity.
2653 Discard downloaded data.
2655 Save download data in the current directory. The
2656 filename is derived from URI. If URI ends with '/',
2657 'index.html' is used as a filename. Not implemented
2659 -t, --timeout=<DURATION>
2660 Timeout each request after <DURATION>. Set 0 to disable
2662 -w, --window-bits=<N>
2663 Sets the stream level initial window size to 2**<N>-1.
2664 -W, --connection-window-bits=<N>
2665 Sets the connection level initial window size to
2668 Download assets such as stylesheets, images and script
2669 files linked from the downloaded resource. Only links
2670 whose origins are the same with the linking resource
2671 will be downloaded. nghttp prioritizes resources using
2672 HTTP/2 dependency based priority. The priority order,
2673 from highest to lowest, is html itself, css, javascript
2675 -s, --stat Print statistics.
2676 -H, --header=<HEADER>
2677 Add a header to the requests. Example: -H':method: PUT'
2679 Add a trailer header to the requests. <HEADER> must not
2680 include pseudo header field (header field name starting
2681 with ':'). To send trailer, one must use -d option to
2682 send request body. Example: --trailer 'foo: bar'.
2684 Use the specified client certificate file. The file
2685 must be in PEM format.
2686 --key=<KEY> Use the client private key file. The file must be in
2689 Post FILE to server. If '-' is given, data will be read
2692 Request each URI <N> times. By default, same URI is not
2693 requested twice. This option disables it too.
2695 Perform HTTP Upgrade for HTTP/2. This option is ignored
2696 if the request URI has https scheme. If -d is used, the
2697 HTTP upgrade request is performed with OPTIONS method.
2698 -p, --weight=<WEIGHT>
2699 Sets weight of given URI. This option can be used
2700 multiple times, and N-th -p option sets weight of N-th
2701 URI in the command line. If the number of -p option is
2702 less than the number of URI, the last -p option value is
2703 repeated. If there is no -p option, default weight, 16,
2704 is assumed. The valid value range is
2706 << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT << R"(], inclusive.
2707 -M, --peer-max-concurrent-streams=<N>
2708 Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value of
2709 remote endpoint as if it is received in SETTINGS frame.
2711 -c, --header-table-size=<SIZE>
2712 Specify decoder header table size. If this option is
2713 used multiple times, and the minimum value among the
2714 given values except for last one is strictly less than
2715 the last value, that minimum value is set in SETTINGS
2716 frame payload before the last value, to simulate
2717 multiple header table size change.
2718 --encoder-header-table-size=<SIZE>
2719 Specify encoder header table size. The decoder (server)
2720 specifies the maximum dynamic table size it accepts.
2721 Then the negotiated dynamic table size is the minimum of
2722 this option value and the value which server specified.
2724 Add at most <N> bytes to a frame payload as padding.
2725 Specify 0 to disable padding.
2727 Output HTTP transactions <PATH> in HAR format. If '-'
2728 is given, data is written to stdout.
2729 --color Force colored log output.
2731 Send large header to test CONTINUATION.
2733 Don't send content-length header field.
2734 --no-dep Don't send dependency based priority hint to server.
2735 --hexdump Display the incoming traffic in hexadecimal (Canonical
2736 hex+ASCII display). If SSL/TLS is used, decrypted data
2738 --no-push Disable server push.
2739 --max-concurrent-streams=<N>
2740 The number of concurrent pushed streams this client
2743 Perform an Expect/Continue handshake: wait to send DATA
2744 (up to a short timeout) until the server sends a 100
2745 Continue interim response. This option is ignored unless
2746 combined with the -d option.
2747 -y, --no-verify-peer
2748 Suppress warning on server certificate verification
2750 --version Display version information and exit.
2751 -h, --help Display this help and exit.
2755 The <SIZE> argument is an integer and an optional unit (e.g., 10K is
2756 10 * 1024). Units are K, M and G (powers of 1024).
2758 The <DURATION> argument is an integer and an optional unit (e.g., 1s
2759 is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
2760 (hours, minutes, seconds and milliseconds, respectively). If a unit
2761 is omitted, a second is used as unit.)"
2766 int main(int argc, char **argv) {
2771 static int flag = 0;
2772 constexpr static option long_options[] = {
2773 {"verbose", no_argument, nullptr, 'v'},
2774 {"null-out", no_argument, nullptr, 'n'},
2775 {"remote-name", no_argument, nullptr, 'O'},
2776 {"timeout", required_argument, nullptr, 't'},
2777 {"window-bits", required_argument, nullptr, 'w'},
2778 {"connection-window-bits", required_argument, nullptr, 'W'},
2779 {"get-assets", no_argument, nullptr, 'a'},
2780 {"stat", no_argument, nullptr, 's'},
2781 {"help", no_argument, nullptr, 'h'},
2782 {"header", required_argument, nullptr, 'H'},
2783 {"data", required_argument, nullptr, 'd'},
2784 {"multiply", required_argument, nullptr, 'm'},
2785 {"upgrade", no_argument, nullptr, 'u'},
2786 {"weight", required_argument, nullptr, 'p'},
2787 {"peer-max-concurrent-streams", required_argument, nullptr, 'M'},
2788 {"header-table-size", required_argument, nullptr, 'c'},
2789 {"padding", required_argument, nullptr, 'b'},
2790 {"har", required_argument, nullptr, 'r'},
2791 {"no-verify-peer", no_argument, nullptr, 'y'},
2792 {"cert", required_argument, &flag, 1},
2793 {"key", required_argument, &flag, 2},
2794 {"color", no_argument, &flag, 3},
2795 {"continuation", no_argument, &flag, 4},
2796 {"version", no_argument, &flag, 5},
2797 {"no-content-length", no_argument, &flag, 6},
2798 {"no-dep", no_argument, &flag, 7},
2799 {"trailer", required_argument, &flag, 9},
2800 {"hexdump", no_argument, &flag, 10},
2801 {"no-push", no_argument, &flag, 11},
2802 {"max-concurrent-streams", required_argument, &flag, 12},
2803 {"expect-continue", no_argument, &flag, 13},
2804 {"encoder-header-table-size", required_argument, &flag, 14},
2805 {nullptr, 0, nullptr, 0}};
2806 int option_index = 0;
2808 getopt_long(argc, argv, "M:Oab:c:d:m:np:r:hH:vst:uw:yW:", long_options,
2815 // peer-max-concurrent-streams option
2816 config.peer_max_concurrent_streams = strtoul(optarg, nullptr, 10);
2819 config.remote_name = true;
2822 print_help(std::cout);
2825 config.padding = strtol(optarg, nullptr, 10);
2828 config.null_out = true;
2832 auto n = strtoul(optarg, nullptr, 10);
2833 if (errno == 0 && NGHTTP2_MIN_WEIGHT <= n && n <= NGHTTP2_MAX_WEIGHT) {
2834 config.weight.push_back(n);
2836 std::cerr << "-p: specify the integer in the range ["
2837 << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT
2838 << "], inclusive" << std::endl;
2845 config.harfile = optarg;
2846 #else // !HAVE_JANSSON
2847 std::cerr << "[WARNING]: -r, --har option is ignored because\n"
2848 << "the binary was not compiled with libjansson." << std::endl;
2849 #endif // !HAVE_JANSSON
2855 config.timeout = util::parse_duration_with_unit(optarg);
2856 if (config.timeout == std::numeric_limits<double>::infinity()) {
2857 std::cerr << "-t: bad timeout value: " << optarg << std::endl;
2862 config.upgrade = true;
2867 char *endptr = nullptr;
2868 unsigned long int n = strtoul(optarg, &endptr, 10);
2869 if (errno == 0 && *endptr == '\0' && n < 31) {
2871 config.window_bits = n;
2873 config.connection_window_bits = n;
2876 std::cerr << "-" << static_cast<char>(c)
2877 << ": specify the integer in the range [0, 30], inclusive"
2884 char *header = optarg;
2885 // Skip first possible ':' in the header name
2886 char *value = strchr(optarg + 1, ':');
2887 if (!value || (header[0] == ':' && header + 1 == value)) {
2888 std::cerr << "-H: invalid header: " << optarg << std::endl;
2893 while (isspace(*value)) {
2897 // This could also be a valid case for suppressing a header
2899 std::cerr << "-H: invalid header - value missing: " << optarg
2903 config.headers.emplace_back(header, value, false);
2904 util::inp_strlower(config.headers.back().name);
2909 config.get_assets = true;
2910 #else // !HAVE_LIBXML2
2911 std::cerr << "[WARNING]: -a, --get-assets option is ignored because\n"
2912 << "the binary was not compiled with libxml2." << std::endl;
2913 #endif // !HAVE_LIBXML2
2919 config.datafile = optarg;
2922 config.multiply = strtoul(optarg, nullptr, 10);
2925 auto n = util::parse_uint_with_unit(optarg);
2927 std::cerr << "-c: Bad option value: " << optarg << std::endl;
2930 if (n > std::numeric_limits<uint32_t>::max()) {
2931 std::cerr << "-c: Value too large. It should be less than or equal to "
2932 << std::numeric_limits<uint32_t>::max() << std::endl;
2935 config.header_table_size = n;
2936 config.min_header_table_size = std::min(config.min_header_table_size, n);
2940 config.verify_peer = false;
2943 util::show_candidates(argv[optind - 1], long_options);
2949 config.certfile = optarg;
2953 config.keyfile = optarg;
2960 // continuation option
2961 config.continuation = true;
2965 print_version(std::cout);
2968 // no-content-length option
2969 config.no_content_length = true;
2973 config.no_dep = true;
2977 auto header = optarg;
2978 auto value = strchr(optarg, ':');
2980 std::cerr << "--trailer: invalid header: " << optarg << std::endl;
2985 while (isspace(*value)) {
2989 // This could also be a valid case for suppressing a header
2991 std::cerr << "--trailer: invalid header - value missing: " << optarg
2995 config.trailer.emplace_back(header, value, false);
2996 util::inp_strlower(config.trailer.back().name);
3001 config.hexdump = true;
3005 config.no_push = true;
3008 // max-concurrent-streams option
3009 config.max_concurrent_streams = strtoul(optarg, nullptr, 10);
3012 // expect-continue option
3013 config.expect_continue = true;
3016 // encoder-header-table-size option
3017 auto n = util::parse_uint_with_unit(optarg);
3019 std::cerr << "--encoder-header-table-size: Bad option value: "
3020 << optarg << std::endl;
3023 if (n > std::numeric_limits<uint32_t>::max()) {
3024 std::cerr << "--encoder-header-table-size: Value too large. It "
3025 "should be less than or equal to "
3026 << std::numeric_limits<uint32_t>::max() << std::endl;
3029 config.encoder_header_table_size = n;
3039 int32_t weight_to_fill;
3040 if (config.weight.empty()) {
3041 weight_to_fill = NGHTTP2_DEFAULT_WEIGHT;
3043 weight_to_fill = config.weight.back();
3045 config.weight.insert(std::end(config.weight), argc - optind, weight_to_fill);
3047 // Find scheme overridden by extra header fields.
3049 std::find_if(std::begin(config.headers), std::end(config.headers),
3050 [](const Header &nv) { return nv.name == ":scheme"; });
3051 if (scheme_it != std::end(config.headers)) {
3052 config.scheme_override = (*scheme_it).value;
3055 // Find host and port overridden by extra header fields.
3057 std::find_if(std::begin(config.headers), std::end(config.headers),
3058 [](const Header &nv) { return nv.name == ":authority"; });
3059 if (authority_it == std::end(config.headers)) {
3061 std::find_if(std::begin(config.headers), std::end(config.headers),
3062 [](const Header &nv) { return nv.name == "host"; });
3065 if (authority_it != std::end(config.headers)) {
3066 // authority_it may looks like "host:port".
3067 auto uri = "https://" + (*authority_it).value;
3068 http_parser_url u{};
3069 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
3070 std::cerr << "[ERROR] Could not parse authority in "
3071 << (*authority_it).name << ": " << (*authority_it).value
3076 config.host_override = util::get_uri_field(uri.c_str(), u, UF_HOST).str();
3077 if (util::has_uri_field(u, UF_PORT)) {
3078 config.port_override = u.port;
3082 set_color_output(color || isatty(fileno(stdout)));
3084 nghttp2_option_set_peer_max_concurrent_streams(
3085 config.http2_option, config.peer_max_concurrent_streams);
3087 if (config.encoder_header_table_size != -1) {
3088 nghttp2_option_set_max_deflate_dynamic_table_size(
3089 config.http2_option, config.encoder_header_table_size);
3092 struct sigaction act {};
3093 act.sa_handler = SIG_IGN;
3094 sigaction(SIGPIPE, &act, nullptr);
3096 return run(argv + optind, argc - optind);
3099 } // namespace nghttp2
3101 int main(int argc, char **argv) {
3102 return nghttp2::run_app(nghttp2::main, argc, argv);