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() ? util::has_uri_field(u, UF_PORT) ? u.port
202 : scheme == "https" ? 443
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)) {
1684 auto link_port = util::has_uri_field(u, UF_PORT) ? u.port
1685 : scheme == "https" ? 443
1688 if (port != link_port) {
1692 // No POST data for assets
1693 auto pri_spec = resolve_dep(res_type);
1695 if (client->add_request(uri, nullptr, 0, pri_spec, req->level + 1)) {
1696 submit_request(client, config.headers, client->reqvec.back().get());
1699 req->html_parser->clear_links();
1704 HttpClient *get_client(void *user_data) {
1705 return static_cast<HttpClient *>(user_data);
1710 int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
1711 int32_t stream_id, const uint8_t *data,
1712 size_t len, void *user_data) {
1713 auto client = get_client(user_data);
1714 auto req = static_cast<Request *>(
1715 nghttp2_session_get_stream_user_data(session, stream_id));
1721 if (config.verbose >= 2) {
1722 verbose_on_data_chunk_recv_callback(session, flags, stream_id, data, len,
1726 req->response_len += len;
1728 if (req->inflater) {
1730 const size_t MAX_OUTLEN = 4_k;
1731 std::array<uint8_t, MAX_OUTLEN> out;
1732 size_t outlen = MAX_OUTLEN;
1735 nghttp2_gzip_inflate(req->inflater, out.data(), &outlen, data, &tlen);
1737 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
1738 NGHTTP2_INTERNAL_ERROR);
1742 if (!config.null_out) {
1743 std::cout.write(reinterpret_cast<const char *>(out.data()), outlen);
1746 update_html_parser(client, req, out.data(), outlen, 0);
1754 if (!config.null_out) {
1755 std::cout.write(reinterpret_cast<const char *>(data), len);
1758 update_html_parser(client, req, data, len, 0);
1765 ssize_t select_padding_callback(nghttp2_session *session,
1766 const nghttp2_frame *frame, size_t max_payload,
1768 return std::min(max_payload, frame->hd.length + config.padding);
1773 void check_response_header(nghttp2_session *session, Request *req) {
1776 req->expect_final_response = false;
1778 auto status_hd = req->get_res_header(http2::HD__STATUS);
1781 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
1782 NGHTTP2_PROTOCOL_ERROR);
1786 auto status = http2::parse_http_status_code(StringRef{status_hd->value});
1788 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
1789 NGHTTP2_PROTOCOL_ERROR);
1793 req->status = status;
1795 for (auto &nv : req->res_nva) {
1796 if ("content-encoding" == nv.name) {
1797 gzip = util::strieq_l("gzip", nv.value) ||
1798 util::strieq_l("deflate", nv.value);
1803 if (req->status / 100 == 1) {
1804 if (req->continue_timer && (req->status == 100)) {
1805 // If the request is waiting for a 100 Continue, complete the handshake.
1806 req->continue_timer->dispatch_continue();
1809 req->expect_final_response = true;
1811 req->res_nva.clear();
1812 http2::init_hdidx(req->res_hdidx);
1814 } else if (req->continue_timer) {
1815 // A final response stops any pending Expect/Continue handshake.
1816 req->continue_timer->stop();
1820 if (!req->inflater) {
1821 req->init_inflater();
1824 if (config.get_assets && req->level == 0) {
1825 if (!req->html_parser) {
1826 req->init_html_parser();
1833 int on_begin_headers_callback(nghttp2_session *session,
1834 const nghttp2_frame *frame, void *user_data) {
1835 auto client = get_client(user_data);
1836 switch (frame->hd.type) {
1837 case NGHTTP2_HEADERS: {
1838 auto req = static_cast<Request *>(
1839 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1844 switch (frame->headers.cat) {
1845 case NGHTTP2_HCAT_RESPONSE:
1846 case NGHTTP2_HCAT_PUSH_RESPONSE:
1847 req->record_response_start_time();
1855 case NGHTTP2_PUSH_PROMISE: {
1856 auto stream_id = frame->push_promise.promised_stream_id;
1857 http_parser_url u{};
1858 // TODO Set pri and level
1859 nghttp2_priority_spec pri_spec;
1861 nghttp2_priority_spec_default_init(&pri_spec);
1863 auto req = std::make_unique<Request>("", u, nullptr, 0, pri_spec);
1864 req->stream_id = stream_id;
1866 nghttp2_session_set_stream_user_data(session, stream_id, req.get());
1868 client->request_done(req.get());
1869 req->record_request_start_time();
1870 client->reqvec.push_back(std::move(req));
1880 int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
1881 const uint8_t *name, size_t namelen,
1882 const uint8_t *value, size_t valuelen, uint8_t flags,
1884 if (config.verbose) {
1885 verbose_on_header_callback(session, frame, name, namelen, value, valuelen,
1889 switch (frame->hd.type) {
1890 case NGHTTP2_HEADERS: {
1891 auto req = static_cast<Request *>(
1892 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1898 /* ignore trailer header */
1899 if (frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
1900 !req->expect_final_response) {
1904 if (req->header_buffer_size + namelen + valuelen > 64_k) {
1905 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
1906 NGHTTP2_INTERNAL_ERROR);
1910 req->header_buffer_size += namelen + valuelen;
1912 auto token = http2::lookup_token(name, namelen);
1914 http2::index_header(req->res_hdidx, token, req->res_nva.size());
1915 http2::add_header(req->res_nva, name, namelen, value, valuelen,
1916 flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
1919 case NGHTTP2_PUSH_PROMISE: {
1920 auto req = static_cast<Request *>(nghttp2_session_get_stream_user_data(
1921 session, frame->push_promise.promised_stream_id));
1927 if (req->header_buffer_size + namelen + valuelen > 64_k) {
1928 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
1929 frame->push_promise.promised_stream_id,
1930 NGHTTP2_INTERNAL_ERROR);
1934 req->header_buffer_size += namelen + valuelen;
1936 auto token = http2::lookup_token(name, namelen);
1938 http2::index_header(req->req_hdidx, token, req->req_nva.size());
1939 http2::add_header(req->req_nva, name, namelen, value, valuelen,
1940 flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
1949 int on_frame_recv_callback2(nghttp2_session *session,
1950 const nghttp2_frame *frame, void *user_data) {
1953 if (config.verbose) {
1954 verbose_on_frame_recv_callback(session, frame, user_data);
1957 auto client = get_client(user_data);
1958 switch (frame->hd.type) {
1959 case NGHTTP2_DATA: {
1960 auto req = static_cast<Request *>(
1961 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1967 if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
1968 req->record_response_end_time();
1974 case NGHTTP2_HEADERS: {
1975 auto req = static_cast<Request *>(
1976 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1977 // If this is the HTTP Upgrade with OPTIONS method to avoid POST,
1984 switch (frame->headers.cat) {
1985 case NGHTTP2_HCAT_RESPONSE:
1986 case NGHTTP2_HCAT_PUSH_RESPONSE:
1987 check_response_header(session, req);
1989 case NGHTTP2_HCAT_HEADERS:
1990 if (req->expect_final_response) {
1991 check_response_header(session, req);
1994 if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
1995 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
1996 frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
2004 if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
2005 req->record_response_end_time();
2011 case NGHTTP2_SETTINGS:
2012 if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
2015 ev_timer_stop(client->loop, &client->settings_timer);
2017 case NGHTTP2_PUSH_PROMISE: {
2018 auto req = static_cast<Request *>(nghttp2_session_get_stream_user_data(
2019 session, frame->push_promise.promised_stream_id));
2024 // Reset for response header field reception
2025 req->header_buffer_size = 0;
2027 auto scheme = req->get_req_header(http2::HD__SCHEME);
2028 auto authority = req->get_req_header(http2::HD__AUTHORITY);
2029 auto path = req->get_req_header(http2::HD__PATH);
2032 authority = req->get_req_header(http2::HD_HOST);
2035 // libnghttp2 guarantees :scheme, :method, :path and (:authority |
2036 // host) exist and non-empty.
2037 if (path->value[0] != '/') {
2038 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
2039 frame->push_promise.promised_stream_id,
2040 NGHTTP2_PROTOCOL_ERROR);
2043 std::string uri = scheme->value;
2045 uri += authority->value;
2047 http_parser_url u{};
2048 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
2049 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
2050 frame->push_promise.promised_stream_id,
2051 NGHTTP2_PROTOCOL_ERROR);
2057 if (client->path_cache.count(uri)) {
2058 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
2059 frame->push_promise.promised_stream_id,
2064 if (config.multiply == 1) {
2065 client->path_cache.insert(uri);
2076 int before_frame_send_callback(nghttp2_session *session,
2077 const nghttp2_frame *frame, void *user_data) {
2078 if (frame->hd.type != NGHTTP2_HEADERS ||
2079 frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
2082 auto req = static_cast<Request *>(
2083 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
2085 req->record_request_start_time();
2092 int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
2094 if (config.verbose) {
2095 verbose_on_frame_send_callback(session, frame, user_data);
2098 if (frame->hd.type != NGHTTP2_HEADERS ||
2099 frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
2103 auto req = static_cast<Request *>(
2104 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
2109 // If this request is using Expect/Continue, start its ContinueTimer.
2110 if (req->continue_timer) {
2111 req->continue_timer->start();
2119 int on_frame_not_send_callback(nghttp2_session *session,
2120 const nghttp2_frame *frame, int lib_error_code,
2122 if (frame->hd.type != NGHTTP2_HEADERS ||
2123 frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
2127 auto req = static_cast<Request *>(
2128 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
2133 std::cerr << "[ERROR] request " << req->uri
2134 << " failed: " << nghttp2_strerror(lib_error_code) << std::endl;
2141 int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
2142 uint32_t error_code, void *user_data) {
2143 auto client = get_client(user_data);
2144 auto req = static_cast<Request *>(
2145 nghttp2_session_get_stream_user_data(session, stream_id));
2151 // If this request is using Expect/Continue, stop its ContinueTimer.
2152 if (req->continue_timer) {
2153 req->continue_timer->stop();
2156 update_html_parser(client, req, nullptr, 0, 1);
2159 if (client->all_requests_processed()) {
2160 nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
2167 struct RequestResult {
2168 std::chrono::microseconds time;
2172 void print_stats(const HttpClient &client) {
2173 std::cout << "***** Statistics *****" << std::endl;
2175 std::vector<Request *> reqs;
2176 reqs.reserve(client.reqvec.size());
2177 for (const auto &req : client.reqvec) {
2178 if (req->timing.state == RequestState::ON_COMPLETE) {
2179 reqs.push_back(req.get());
2183 std::sort(std::begin(reqs), std::end(reqs),
2184 [](const Request *lhs, const Request *rhs) {
2185 const auto <iming = lhs->timing;
2186 const auto &rtiming = rhs->timing;
2187 return ltiming.response_end_time < rtiming.response_end_time ||
2188 (ltiming.response_end_time == rtiming.response_end_time &&
2189 ltiming.request_start_time < rtiming.request_start_time);
2194 responseEnd: the time when last byte of response was received
2195 relative to connectEnd
2196 requestStart: the time just before first byte of request was sent
2197 relative to connectEnd. If '*' is shown, this was
2199 process: responseEnd - requestStart
2200 code: HTTP status code
2201 size: number of bytes received as response body without
2205 see http://www.w3.org/TR/resource-timing/#processing-model
2207 sorted by 'complete'
2209 id responseEnd requestStart process code size request path)"
2212 const auto &base = client.timing.connect_end_time;
2213 for (const auto &req : reqs) {
2214 auto response_end = std::chrono::duration_cast<std::chrono::microseconds>(
2215 req->timing.response_end_time - base);
2216 auto request_start = std::chrono::duration_cast<std::chrono::microseconds>(
2217 req->timing.request_start_time - base);
2218 auto total = std::chrono::duration_cast<std::chrono::microseconds>(
2219 req->timing.response_end_time - req->timing.request_start_time);
2220 auto pushed = req->stream_id % 2 == 0;
2222 std::cout << std::setw(3) << req->stream_id << " " << std::setw(11)
2223 << ("+" + util::format_duration(response_end)) << " "
2224 << (pushed ? "*" : " ") << std::setw(11)
2225 << ("+" + util::format_duration(request_start)) << " "
2226 << std::setw(8) << util::format_duration(total) << " "
2227 << std::setw(4) << req->status << " " << std::setw(4)
2228 << util::utos_unit(req->response_len) << " "
2229 << req->make_reqpath() << std::endl;
2234 #ifndef OPENSSL_NO_NEXTPROTONEG
2236 int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
2237 unsigned char *outlen, const unsigned char *in,
2238 unsigned int inlen, void *arg) {
2239 if (config.verbose) {
2241 std::cout << "[NPN] server offers:" << std::endl;
2243 for (unsigned int i = 0; i < inlen; i += in[i] + 1) {
2244 if (config.verbose) {
2246 std::cout.write(reinterpret_cast<const char *>(&in[i + 1]), in[i]);
2247 std::cout << std::endl;
2250 if (!util::select_h2(const_cast<const unsigned char **>(out), outlen, in,
2252 print_protocol_nego_error();
2253 return SSL_TLSEXT_ERR_NOACK;
2255 return SSL_TLSEXT_ERR_OK;
2258 #endif // !OPENSSL_NO_NEXTPROTONEG
2262 const std::string &scheme, const std::string &host, uint16_t port,
2264 std::tuple<std::string, nghttp2_data_provider *, int64_t, int32_t>>
2266 const nghttp2_session_callbacks *callbacks) {
2268 auto loop = EV_DEFAULT;
2269 SSL_CTX *ssl_ctx = nullptr;
2270 if (scheme == "https") {
2271 ssl_ctx = SSL_CTX_new(TLS_client_method());
2273 std::cerr << "[ERROR] Failed to create SSL_CTX: "
2274 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
2279 auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
2280 SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION |
2281 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
2283 SSL_CTX_set_options(ssl_ctx, ssl_opts);
2284 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
2285 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
2287 if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) {
2288 std::cerr << "[WARNING] Could not load system trusted CA certificates: "
2289 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
2292 if (nghttp2::tls::ssl_ctx_set_proto_versions(
2293 ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION,
2294 nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) {
2295 std::cerr << "[ERROR] Could not set TLS versions" << std::endl;
2300 if (SSL_CTX_set_cipher_list(ssl_ctx, tls::DEFAULT_CIPHER_LIST) == 0) {
2301 std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
2306 if (!config.keyfile.empty()) {
2307 if (SSL_CTX_use_PrivateKey_file(ssl_ctx, config.keyfile.c_str(),
2308 SSL_FILETYPE_PEM) != 1) {
2309 std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
2315 if (!config.certfile.empty()) {
2316 if (SSL_CTX_use_certificate_chain_file(ssl_ctx,
2317 config.certfile.c_str()) != 1) {
2318 std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
2324 #ifndef OPENSSL_NO_NEXTPROTONEG
2325 SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb,
2327 #endif // !OPENSSL_NO_NEXTPROTONEG
2329 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
2330 auto proto_list = util::get_default_alpn();
2332 SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
2333 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
2336 HttpClient client{callbacks, loop, ssl_ctx};
2338 int32_t dep_stream_id = 0;
2340 if (!config.no_dep) {
2341 dep_stream_id = anchors[ANCHOR_FOLLOWERS].stream_id;
2344 for (auto &req : requests) {
2345 nghttp2_priority_spec pri_spec;
2347 nghttp2_priority_spec_init(&pri_spec, dep_stream_id, std::get<3>(req), 0);
2349 for (int i = 0; i < config.multiply; ++i) {
2350 client.add_request(std::get<0>(req), std::get<1>(req), std::get<2>(req),
2354 client.update_hostport();
2356 client.record_start_time();
2358 if (client.resolve_host(host, port) != 0) {
2362 client.record_domain_lookup_end_time();
2364 if (client.initiate_connection() != 0) {
2365 std::cerr << "[ERROR] Could not connect to " << host << ", port " << port
2370 ev_set_userdata(loop, &client);
2372 ev_set_userdata(loop, nullptr);
2375 if (!config.harfile.empty()) {
2377 if (config.harfile == "-") {
2380 outfile = fopen(config.harfile.c_str(), "wb");
2384 client.output_har(outfile);
2386 if (outfile != stdout) {
2390 std::cerr << "Cannot open file " << config.harfile << ". "
2391 << "har file could not be created." << std::endl;
2394 #endif // HAVE_JANSSON
2396 if (client.success != client.reqvec.size()) {
2397 std::cerr << "Some requests were not processed. total="
2398 << client.reqvec.size() << ", processed=" << client.success
2402 print_stats(client);
2407 SSL_CTX_free(ssl_ctx);
2414 ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
2415 uint8_t *buf, size_t length, uint32_t *data_flags,
2416 nghttp2_data_source *source, void *user_data) {
2418 auto req = static_cast<Request *>(
2419 nghttp2_session_get_stream_user_data(session, stream_id));
2421 int fd = source->fd;
2424 while ((nread = pread(fd, buf, length, req->data_offset)) == -1 &&
2429 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
2432 req->data_offset += nread;
2434 if (req->data_offset == req->data_length) {
2435 *data_flags |= NGHTTP2_DATA_FLAG_EOF;
2436 if (!config.trailer.empty()) {
2437 std::vector<nghttp2_nv> nva;
2438 nva.reserve(config.trailer.size());
2439 for (auto &kv : config.trailer) {
2440 nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
2442 rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size());
2444 if (nghttp2_is_fatal(rv)) {
2445 return NGHTTP2_ERR_CALLBACK_FAILURE;
2448 *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
2455 if (req->data_offset > req->data_length || nread == 0) {
2456 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
2464 int run(char **uris, int n) {
2465 nghttp2_session_callbacks *callbacks;
2467 nghttp2_session_callbacks_new(&callbacks);
2468 auto cbsdel = defer(nghttp2_session_callbacks_del, callbacks);
2470 nghttp2_session_callbacks_set_on_stream_close_callback(
2471 callbacks, on_stream_close_callback);
2473 nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
2474 on_frame_recv_callback2);
2476 if (config.verbose) {
2477 nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
2478 callbacks, verbose_on_invalid_frame_recv_callback);
2480 nghttp2_session_callbacks_set_error_callback2(callbacks,
2481 verbose_error_callback);
2484 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
2485 callbacks, on_data_chunk_recv_callback);
2487 nghttp2_session_callbacks_set_on_begin_headers_callback(
2488 callbacks, on_begin_headers_callback);
2490 nghttp2_session_callbacks_set_on_header_callback(callbacks,
2491 on_header_callback);
2493 nghttp2_session_callbacks_set_before_frame_send_callback(
2494 callbacks, before_frame_send_callback);
2496 nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
2497 on_frame_send_callback);
2499 nghttp2_session_callbacks_set_on_frame_not_send_callback(
2500 callbacks, on_frame_not_send_callback);
2502 if (config.padding) {
2503 nghttp2_session_callbacks_set_select_padding_callback(
2504 callbacks, select_padding_callback);
2507 std::string prev_scheme;
2508 std::string prev_host;
2509 uint16_t prev_port = 0;
2512 nghttp2_data_provider data_prd;
2513 struct stat data_stat;
2515 if (!config.datafile.empty()) {
2516 if (config.datafile == "-") {
2517 if (fstat(0, &data_stat) == 0 &&
2518 (data_stat.st_mode & S_IFMT) == S_IFREG) {
2519 // use STDIN if it is a regular file
2522 // copy the contents of STDIN to a temporary file
2523 char tempfn[] = "/tmp/nghttp.temp.XXXXXX";
2524 data_fd = mkstemp(tempfn);
2525 if (data_fd == -1) {
2526 std::cerr << "[ERROR] Could not create a temporary file in /tmp"
2530 if (unlink(tempfn) != 0) {
2531 std::cerr << "[WARNING] failed to unlink temporary file:" << tempfn
2535 std::array<char, 1_k> buf;
2537 while ((rret = read(0, buf.data(), buf.size())) == -1 &&
2543 std::cerr << "[ERROR] I/O error while reading from STDIN"
2547 while ((wret = write(data_fd, buf.data(), rret)) == -1 &&
2551 std::cerr << "[ERROR] I/O error while writing to temporary file"
2556 if (fstat(data_fd, &data_stat) == -1) {
2558 std::cerr << "[ERROR] Could not stat temporary file" << std::endl;
2563 data_fd = open(config.datafile.c_str(), O_RDONLY | O_BINARY);
2564 if (data_fd == -1) {
2565 std::cerr << "[ERROR] Could not open file " << config.datafile
2569 if (fstat(data_fd, &data_stat) == -1) {
2571 std::cerr << "[ERROR] Could not stat file " << config.datafile
2576 data_prd.source.fd = data_fd;
2577 data_prd.read_callback = file_read_callback;
2580 std::tuple<std::string, nghttp2_data_provider *, int64_t, int32_t>>
2583 size_t next_weight_idx = 0;
2585 for (int i = 0; i < n; ++i) {
2586 http_parser_url u{};
2587 auto uri = strip_fragment(uris[i]);
2588 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
2590 std::cerr << "[ERROR] Could not parse URI " << uri << std::endl;
2593 if (!util::has_uri_field(u, UF_SCHEMA)) {
2595 std::cerr << "[ERROR] URI " << uri << " does not have scheme part"
2599 auto port = util::has_uri_field(u, UF_PORT)
2601 : util::get_default_port(uri.c_str(), u);
2602 auto host = decode_host(util::get_uri_field(uri.c_str(), u, UF_HOST));
2603 if (!util::fieldeq(uri.c_str(), u, UF_SCHEMA, prev_scheme.c_str()) ||
2604 host != prev_host || port != prev_port) {
2605 if (!requests.empty()) {
2606 if (communicate(prev_scheme, prev_host, prev_port, std::move(requests),
2612 prev_scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA).str();
2613 prev_host = std::move(host);
2616 requests.emplace_back(uri, data_fd == -1 ? nullptr : &data_prd,
2617 data_stat.st_size, config.weight[next_weight_idx++]);
2619 if (!requests.empty()) {
2620 if (communicate(prev_scheme, prev_host, prev_port, std::move(requests),
2630 void print_version(std::ostream &out) {
2631 out << "nghttp nghttp2/" NGHTTP2_VERSION << std::endl;
2636 void print_usage(std::ostream &out) {
2637 out << R"(Usage: nghttp [OPTIONS]... <URI>...
2644 void print_help(std::ostream &out) {
2647 <URI> Specify URI to access.
2650 Print debug information such as reception and
2651 transmission of frames and name/value pairs. Specifying
2652 this option multiple times increases verbosity.
2654 Discard downloaded data.
2656 Save download data in the current directory. The
2657 filename is derived from URI. If URI ends with '/',
2658 'index.html' is used as a filename. Not implemented
2660 -t, --timeout=<DURATION>
2661 Timeout each request after <DURATION>. Set 0 to disable
2663 -w, --window-bits=<N>
2664 Sets the stream level initial window size to 2**<N>-1.
2665 -W, --connection-window-bits=<N>
2666 Sets the connection level initial window size to
2669 Download assets such as stylesheets, images and script
2670 files linked from the downloaded resource. Only links
2671 whose origins are the same with the linking resource
2672 will be downloaded. nghttp prioritizes resources using
2673 HTTP/2 dependency based priority. The priority order,
2674 from highest to lowest, is html itself, css, javascript
2676 -s, --stat Print statistics.
2677 -H, --header=<HEADER>
2678 Add a header to the requests. Example: -H':method: PUT'
2680 Add a trailer header to the requests. <HEADER> must not
2681 include pseudo header field (header field name starting
2682 with ':'). To send trailer, one must use -d option to
2683 send request body. Example: --trailer 'foo: bar'.
2685 Use the specified client certificate file. The file
2686 must be in PEM format.
2687 --key=<KEY> Use the client private key file. The file must be in
2690 Post FILE to server. If '-' is given, data will be read
2693 Request each URI <N> times. By default, same URI is not
2694 requested twice. This option disables it too.
2696 Perform HTTP Upgrade for HTTP/2. This option is ignored
2697 if the request URI has https scheme. If -d is used, the
2698 HTTP upgrade request is performed with OPTIONS method.
2699 -p, --weight=<WEIGHT>
2700 Sets weight of given URI. This option can be used
2701 multiple times, and N-th -p option sets weight of N-th
2702 URI in the command line. If the number of -p option is
2703 less than the number of URI, the last -p option value is
2704 repeated. If there is no -p option, default weight, 16,
2705 is assumed. The valid value range is
2707 << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT << R"(], inclusive.
2708 -M, --peer-max-concurrent-streams=<N>
2709 Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value of
2710 remote endpoint as if it is received in SETTINGS frame.
2712 -c, --header-table-size=<SIZE>
2713 Specify decoder header table size. If this option is
2714 used multiple times, and the minimum value among the
2715 given values except for last one is strictly less than
2716 the last value, that minimum value is set in SETTINGS
2717 frame payload before the last value, to simulate
2718 multiple header table size change.
2719 --encoder-header-table-size=<SIZE>
2720 Specify encoder header table size. The decoder (server)
2721 specifies the maximum dynamic table size it accepts.
2722 Then the negotiated dynamic table size is the minimum of
2723 this option value and the value which server specified.
2725 Add at most <N> bytes to a frame payload as padding.
2726 Specify 0 to disable padding.
2728 Output HTTP transactions <PATH> in HAR format. If '-'
2729 is given, data is written to stdout.
2730 --color Force colored log output.
2732 Send large header to test CONTINUATION.
2734 Don't send content-length header field.
2735 --no-dep Don't send dependency based priority hint to server.
2736 --hexdump Display the incoming traffic in hexadecimal (Canonical
2737 hex+ASCII display). If SSL/TLS is used, decrypted data
2739 --no-push Disable server push.
2740 --max-concurrent-streams=<N>
2741 The number of concurrent pushed streams this client
2744 Perform an Expect/Continue handshake: wait to send DATA
2745 (up to a short timeout) until the server sends a 100
2746 Continue interim response. This option is ignored unless
2747 combined with the -d option.
2748 -y, --no-verify-peer
2749 Suppress warning on server certificate verification
2751 --version Display version information and exit.
2752 -h, --help Display this help and exit.
2756 The <SIZE> argument is an integer and an optional unit (e.g., 10K is
2757 10 * 1024). Units are K, M and G (powers of 1024).
2759 The <DURATION> argument is an integer and an optional unit (e.g., 1s
2760 is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
2761 (hours, minutes, seconds and milliseconds, respectively). If a unit
2762 is omitted, a second is used as unit.)"
2767 int main(int argc, char **argv) {
2772 static int flag = 0;
2773 constexpr static option long_options[] = {
2774 {"verbose", no_argument, nullptr, 'v'},
2775 {"null-out", no_argument, nullptr, 'n'},
2776 {"remote-name", no_argument, nullptr, 'O'},
2777 {"timeout", required_argument, nullptr, 't'},
2778 {"window-bits", required_argument, nullptr, 'w'},
2779 {"connection-window-bits", required_argument, nullptr, 'W'},
2780 {"get-assets", no_argument, nullptr, 'a'},
2781 {"stat", no_argument, nullptr, 's'},
2782 {"help", no_argument, nullptr, 'h'},
2783 {"header", required_argument, nullptr, 'H'},
2784 {"data", required_argument, nullptr, 'd'},
2785 {"multiply", required_argument, nullptr, 'm'},
2786 {"upgrade", no_argument, nullptr, 'u'},
2787 {"weight", required_argument, nullptr, 'p'},
2788 {"peer-max-concurrent-streams", required_argument, nullptr, 'M'},
2789 {"header-table-size", required_argument, nullptr, 'c'},
2790 {"padding", required_argument, nullptr, 'b'},
2791 {"har", required_argument, nullptr, 'r'},
2792 {"no-verify-peer", no_argument, nullptr, 'y'},
2793 {"cert", required_argument, &flag, 1},
2794 {"key", required_argument, &flag, 2},
2795 {"color", no_argument, &flag, 3},
2796 {"continuation", no_argument, &flag, 4},
2797 {"version", no_argument, &flag, 5},
2798 {"no-content-length", no_argument, &flag, 6},
2799 {"no-dep", no_argument, &flag, 7},
2800 {"trailer", required_argument, &flag, 9},
2801 {"hexdump", no_argument, &flag, 10},
2802 {"no-push", no_argument, &flag, 11},
2803 {"max-concurrent-streams", required_argument, &flag, 12},
2804 {"expect-continue", no_argument, &flag, 13},
2805 {"encoder-header-table-size", required_argument, &flag, 14},
2806 {nullptr, 0, nullptr, 0}};
2807 int option_index = 0;
2809 getopt_long(argc, argv, "M:Oab:c:d:m:np:r:hH:vst:uw:yW:", long_options,
2816 // peer-max-concurrent-streams option
2817 config.peer_max_concurrent_streams = strtoul(optarg, nullptr, 10);
2820 config.remote_name = true;
2823 print_help(std::cout);
2826 config.padding = strtol(optarg, nullptr, 10);
2829 config.null_out = true;
2833 auto n = strtoul(optarg, nullptr, 10);
2834 if (errno == 0 && NGHTTP2_MIN_WEIGHT <= n && n <= NGHTTP2_MAX_WEIGHT) {
2835 config.weight.push_back(n);
2837 std::cerr << "-p: specify the integer in the range ["
2838 << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT
2839 << "], inclusive" << std::endl;
2846 config.harfile = optarg;
2847 #else // !HAVE_JANSSON
2848 std::cerr << "[WARNING]: -r, --har option is ignored because\n"
2849 << "the binary was not compiled with libjansson." << std::endl;
2850 #endif // !HAVE_JANSSON
2856 config.timeout = util::parse_duration_with_unit(optarg);
2857 if (config.timeout == std::numeric_limits<double>::infinity()) {
2858 std::cerr << "-t: bad timeout value: " << optarg << std::endl;
2863 config.upgrade = true;
2868 char *endptr = nullptr;
2869 unsigned long int n = strtoul(optarg, &endptr, 10);
2870 if (errno == 0 && *endptr == '\0' && n < 31) {
2872 config.window_bits = n;
2874 config.connection_window_bits = n;
2877 std::cerr << "-" << static_cast<char>(c)
2878 << ": specify the integer in the range [0, 30], inclusive"
2885 char *header = optarg;
2886 // Skip first possible ':' in the header name
2887 char *value = strchr(optarg + 1, ':');
2888 if (!value || (header[0] == ':' && header + 1 == value)) {
2889 std::cerr << "-H: invalid header: " << optarg << std::endl;
2894 while (isspace(*value)) {
2898 // This could also be a valid case for suppressing a header
2900 std::cerr << "-H: invalid header - value missing: " << optarg
2904 config.headers.emplace_back(header, value, false);
2905 util::inp_strlower(config.headers.back().name);
2910 config.get_assets = true;
2911 #else // !HAVE_LIBXML2
2912 std::cerr << "[WARNING]: -a, --get-assets option is ignored because\n"
2913 << "the binary was not compiled with libxml2." << std::endl;
2914 #endif // !HAVE_LIBXML2
2920 config.datafile = optarg;
2923 config.multiply = strtoul(optarg, nullptr, 10);
2926 auto n = util::parse_uint_with_unit(optarg);
2928 std::cerr << "-c: Bad option value: " << optarg << std::endl;
2931 if (n > std::numeric_limits<uint32_t>::max()) {
2932 std::cerr << "-c: Value too large. It should be less than or equal to "
2933 << std::numeric_limits<uint32_t>::max() << std::endl;
2936 config.header_table_size = n;
2937 config.min_header_table_size = std::min(config.min_header_table_size, n);
2941 config.verify_peer = false;
2944 util::show_candidates(argv[optind - 1], long_options);
2950 config.certfile = optarg;
2954 config.keyfile = optarg;
2961 // continuation option
2962 config.continuation = true;
2966 print_version(std::cout);
2969 // no-content-length option
2970 config.no_content_length = true;
2974 config.no_dep = true;
2978 auto header = optarg;
2979 auto value = strchr(optarg, ':');
2981 std::cerr << "--trailer: invalid header: " << optarg << std::endl;
2986 while (isspace(*value)) {
2990 // This could also be a valid case for suppressing a header
2992 std::cerr << "--trailer: invalid header - value missing: " << optarg
2996 config.trailer.emplace_back(header, value, false);
2997 util::inp_strlower(config.trailer.back().name);
3002 config.hexdump = true;
3006 config.no_push = true;
3009 // max-concurrent-streams option
3010 config.max_concurrent_streams = strtoul(optarg, nullptr, 10);
3013 // expect-continue option
3014 config.expect_continue = true;
3017 // encoder-header-table-size option
3018 auto n = util::parse_uint_with_unit(optarg);
3020 std::cerr << "--encoder-header-table-size: Bad option value: "
3021 << optarg << std::endl;
3024 if (n > std::numeric_limits<uint32_t>::max()) {
3025 std::cerr << "--encoder-header-table-size: Value too large. It "
3026 "should be less than or equal to "
3027 << std::numeric_limits<uint32_t>::max() << std::endl;
3030 config.encoder_header_table_size = n;
3040 int32_t weight_to_fill;
3041 if (config.weight.empty()) {
3042 weight_to_fill = NGHTTP2_DEFAULT_WEIGHT;
3044 weight_to_fill = config.weight.back();
3046 config.weight.insert(std::end(config.weight), argc - optind, weight_to_fill);
3048 // Find scheme overridden by extra header fields.
3050 std::find_if(std::begin(config.headers), std::end(config.headers),
3051 [](const Header &nv) { return nv.name == ":scheme"; });
3052 if (scheme_it != std::end(config.headers)) {
3053 config.scheme_override = (*scheme_it).value;
3056 // Find host and port overridden by extra header fields.
3058 std::find_if(std::begin(config.headers), std::end(config.headers),
3059 [](const Header &nv) { return nv.name == ":authority"; });
3060 if (authority_it == std::end(config.headers)) {
3062 std::find_if(std::begin(config.headers), std::end(config.headers),
3063 [](const Header &nv) { return nv.name == "host"; });
3066 if (authority_it != std::end(config.headers)) {
3067 // authority_it may looks like "host:port".
3068 auto uri = "https://" + (*authority_it).value;
3069 http_parser_url u{};
3070 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
3071 std::cerr << "[ERROR] Could not parse authority in "
3072 << (*authority_it).name << ": " << (*authority_it).value
3077 config.host_override = util::get_uri_field(uri.c_str(), u, UF_HOST).str();
3078 if (util::has_uri_field(u, UF_PORT)) {
3079 config.port_override = u.port;
3083 set_color_output(color || isatty(fileno(stdout)));
3085 nghttp2_option_set_peer_max_concurrent_streams(
3086 config.http2_option, config.peer_max_concurrent_streams);
3088 if (config.encoder_header_table_size != -1) {
3089 nghttp2_option_set_max_deflate_dynamic_table_size(
3090 config.http2_option, config.encoder_header_table_size);
3093 struct sigaction act {};
3094 act.sa_handler = SIG_IGN;
3095 sigaction(SIGPIPE, &act, nullptr);
3097 return run(argv + optind, argc - optind);
3100 } // namespace nghttp2
3102 int main(int argc, char **argv) {
3103 return nghttp2::run_app(nghttp2::main, argc, argv);