From 6a70bf45a8038cb51c6e31b5f3754b2e27d87abc Mon Sep 17 00:00:00 2001 From: dengjie <1171276417@qq.com> Date: Thu, 28 Sep 2023 15:17:32 +0800 Subject: [PATCH 1/2] Store-a-copy-of-the-nghttp-Client --- Store-a-copy-of-the-nghttp-Client.patch | 3502 +++++++++++++++++++++++ nghttp2.spec | 10 +- 2 files changed, 3511 insertions(+), 1 deletion(-) create mode 100644 Store-a-copy-of-the-nghttp-Client.patch diff --git a/Store-a-copy-of-the-nghttp-Client.patch b/Store-a-copy-of-the-nghttp-Client.patch new file mode 100644 index 0000000..355516b --- /dev/null +++ b/Store-a-copy-of-the-nghttp-Client.patch @@ -0,0 +1,3502 @@ +From e4beaf0404e1ff2582457615169f9adf76b513cf Mon Sep 17 00:00:00 2001 +From: dengjie <1171276417@qq.com> +Date: Thu, 28 Sep 2023 15:10:08 +0800 +Subject: [PATCH] Store a copy of the nghttp Client in nghttp_test.h and + nghttp_test.cc files + +--- + src/nghttp_test.cc | 3164 ++++++++++++++++++++++++++++++++++++++++++++ + src/nghttp_test.h | 310 +++++ + 2 files changed, 3474 insertions(+) + create mode 100644 src/nghttp_test.cc + create mode 100644 src/nghttp_test.h + +diff --git a/src/nghttp_test.cc b/src/nghttp_test.cc +new file mode 100644 +index 0000000..3bd7c25 +--- /dev/null ++++ b/src/nghttp_test.cc +@@ -0,0 +1,3164 @@ ++ /* ++ * nghttp2 - HTTP/2 C Library ++ * ++ * Copyright (c) 2013 Tatsuhiro Tsujikawa ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ */ ++#include "nghttp.h" ++ ++#include ++#ifdef HAVE_UNISTD_H ++# include ++#endif // HAVE_UNISTD_H ++#ifdef HAVE_FCNTL_H ++# include ++#endif // HAVE_FCNTL_H ++#ifdef HAVE_NETINET_IN_H ++# include ++#endif // HAVE_NETINET_IN_H ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#ifdef HAVE_JANSSON ++# include ++#endif // HAVE_JANSSON ++ ++#include "app_helper.h" ++#include "HtmlParser.h" ++#include "util.h" ++#include "base64.h" ++#include "tls.h" ++#include "template.h" ++#include "ssl_compat.h" ++ ++#ifndef O_BINARY ++# define O_BINARY (0) ++#endif // O_BINARY ++ ++namespace nghttp2 { ++ ++// The anchor stream nodes when --no-dep is not used. The stream ID = ++// 1 is excluded since it is used as first stream in upgrade case. We ++// follows the same dependency anchor nodes as Firefox does. ++struct Anchor { ++ int32_t stream_id; ++ // stream ID this anchor depends on ++ int32_t dep_stream_id; ++ // .. with this weight. ++ int32_t weight; ++}; ++ ++// This is index into anchors. Firefox uses ANCHOR_FOLLOWERS for html ++// file. ++enum { ++ ANCHOR_LEADERS, ++ ANCHOR_UNBLOCKED, ++ ANCHOR_BACKGROUND, ++ ANCHOR_SPECULATIVE, ++ ANCHOR_FOLLOWERS, ++}; ++ ++namespace { ++constexpr auto anchors = std::array{{ ++ {3, 0, 201}, ++ {5, 0, 101}, ++ {7, 0, 1}, ++ {9, 7, 1}, ++ {11, 3, 1}, ++}}; ++} // namespace ++ ++Config::Config() ++ : header_table_size(-1), ++ min_header_table_size(std::numeric_limits::max()), ++ encoder_header_table_size(-1), ++ padding(0), ++ max_concurrent_streams(100), ++ peer_max_concurrent_streams(100), ++ multiply(1), ++ timeout(0.), ++ window_bits(-1), ++ connection_window_bits(-1), ++ verbose(0), ++ port_override(0), ++ null_out(false), ++ remote_name(false), ++ get_assets(false), ++ stat(false), ++ upgrade(false), ++ continuation(false), ++ no_content_length(false), ++ no_dep(false), ++ hexdump(false), ++ no_push(false), ++ expect_continue(false), ++ verify_peer(true), ++ ktls(false), ++ no_rfc7540_pri(false) { ++ nghttp2_option_new(&http2_option); ++ nghttp2_option_set_peer_max_concurrent_streams(http2_option, ++ peer_max_concurrent_streams); ++ nghttp2_option_set_builtin_recv_extension_type(http2_option, NGHTTP2_ALTSVC); ++ nghttp2_option_set_builtin_recv_extension_type(http2_option, NGHTTP2_ORIGIN); ++} ++ ++Config::~Config() { nghttp2_option_del(http2_option); } ++ ++namespace { ++Config config; ++} // namespace ++ ++namespace { ++void print_protocol_nego_error() { ++ std::cerr << "[ERROR] HTTP/2 protocol was not selected." ++ << " (nghttp2 expects " << NGHTTP2_PROTO_VERSION_ID << ")" ++ << std::endl; ++} ++} // namespace ++ ++namespace { ++std::string strip_fragment(const char *raw_uri) { ++ const char *end; ++ for (end = raw_uri; *end && *end != '#'; ++end) ++ ; ++ size_t len = end - raw_uri; ++ return std::string(raw_uri, len); ++} ++} // namespace ++ ++Request::Request(const std::string &uri, const http_parser_url &u, ++ const nghttp2_data_provider *data_prd, int64_t data_length, ++ const nghttp2_priority_spec &pri_spec, int level) ++ : uri(uri), ++ u(u), ++ pri_spec(pri_spec), ++ data_length(data_length), ++ data_offset(0), ++ response_len(0), ++ inflater(nullptr), ++ data_prd(data_prd), ++ header_buffer_size(0), ++ stream_id(-1), ++ status(0), ++ level(level), ++ expect_final_response(false) { ++ http2::init_hdidx(res_hdidx); ++ http2::init_hdidx(req_hdidx); ++} ++ ++Request::~Request() { nghttp2_gzip_inflate_del(inflater); } ++ ++void Request::init_inflater() { ++ int rv; ++ // This is required with --disable-assert. ++ (void)rv; ++ rv = nghttp2_gzip_inflate_new(&inflater); ++ assert(rv == 0); ++} ++ ++StringRef Request::get_real_scheme() const { ++ return config.scheme_override.empty() ++ ? util::get_uri_field(uri.c_str(), u, UF_SCHEMA) ++ : StringRef{config.scheme_override}; ++} ++ ++StringRef Request::get_real_host() const { ++ return config.host_override.empty() ++ ? util::get_uri_field(uri.c_str(), u, UF_HOST) ++ : StringRef{config.host_override}; ++} ++ ++uint16_t Request::get_real_port() const { ++ auto scheme = get_real_scheme(); ++ return config.host_override.empty() ? util::has_uri_field(u, UF_PORT) ? u.port ++ : scheme == "https" ? 443 ++ : 80 ++ : config.port_override == 0 ? scheme == "https" ? 443 : 80 ++ : config.port_override; ++} ++ ++void Request::init_html_parser() { ++ // We crawl HTML using overridden scheme, host, and port. ++ auto scheme = get_real_scheme(); ++ auto host = get_real_host(); ++ auto port = get_real_port(); ++ auto ipv6_lit = ++ std::find(std::begin(host), std::end(host), ':') != std::end(host); ++ ++ auto base_uri = scheme.str(); ++ base_uri += "://"; ++ if (ipv6_lit) { ++ base_uri += '['; ++ } ++ base_uri += host; ++ if (ipv6_lit) { ++ base_uri += ']'; ++ } ++ if (!((scheme == "https" && port == 443) || ++ (scheme == "http" && port == 80))) { ++ base_uri += ':'; ++ base_uri += util::utos(port); ++ } ++ base_uri += util::get_uri_field(uri.c_str(), u, UF_PATH); ++ if (util::has_uri_field(u, UF_QUERY)) { ++ base_uri += '?'; ++ base_uri += util::get_uri_field(uri.c_str(), u, UF_QUERY); ++ } ++ ++ html_parser = std::make_unique(base_uri); ++} ++ ++int Request::update_html_parser(const uint8_t *data, size_t len, int fin) { ++ if (!html_parser) { ++ return 0; ++ } ++ return html_parser->parse_chunk(reinterpret_cast(data), len, ++ fin); ++} ++ ++std::string Request::make_reqpath() const { ++ std::string path = util::has_uri_field(u, UF_PATH) ++ ? util::get_uri_field(uri.c_str(), u, UF_PATH).str() ++ : "/"; ++ if (util::has_uri_field(u, UF_QUERY)) { ++ path += '?'; ++ path.append(uri.c_str() + u.field_data[UF_QUERY].off, ++ u.field_data[UF_QUERY].len); ++ } ++ return path; ++} ++ ++namespace { ++// Perform special handling |host| if it is IPv6 literal and includes ++// zone ID per RFC 6874. ++std::string decode_host(const StringRef &host) { ++ auto zone_start = std::find(std::begin(host), std::end(host), '%'); ++ if (zone_start == std::end(host) || ++ !util::ipv6_numeric_addr( ++ std::string(std::begin(host), zone_start).c_str())) { ++ return host.str(); ++ } ++ // case: ::1% ++ if (zone_start + 1 == std::end(host)) { ++ return StringRef{host.c_str(), host.size() - 1}.str(); ++ } ++ // case: ::1%12 or ::1%1 ++ if (zone_start + 3 >= std::end(host)) { ++ return host.str(); ++ } ++ // If we see "%25", followed by more characters, then decode %25 as ++ // '%'. ++ auto zone_id_src = (*(zone_start + 1) == '2' && *(zone_start + 2) == '5') ++ ? zone_start + 3 ++ : zone_start + 1; ++ auto zone_id = util::percent_decode(zone_id_src, std::end(host)); ++ auto res = std::string(std::begin(host), zone_start + 1); ++ res += zone_id; ++ return res; ++} ++} // namespace ++ ++namespace { ++nghttp2_priority_spec resolve_dep(int res_type) { ++ nghttp2_priority_spec pri_spec; ++ ++ if (config.no_dep) { ++ nghttp2_priority_spec_default_init(&pri_spec); ++ ++ return pri_spec; ++ } ++ ++ int32_t anchor_id; ++ int32_t weight; ++ switch (res_type) { ++ case REQ_CSS: ++ case REQ_JS: ++ anchor_id = anchors[ANCHOR_LEADERS].stream_id; ++ weight = 32; ++ break; ++ case REQ_UNBLOCK_JS: ++ anchor_id = anchors[ANCHOR_UNBLOCKED].stream_id; ++ weight = 32; ++ break; ++ case REQ_IMG: ++ anchor_id = anchors[ANCHOR_FOLLOWERS].stream_id; ++ weight = 12; ++ break; ++ default: ++ anchor_id = anchors[ANCHOR_FOLLOWERS].stream_id; ++ weight = 32; ++ } ++ ++ nghttp2_priority_spec_init(&pri_spec, anchor_id, weight, 0); ++ return pri_spec; ++} ++} // namespace ++ ++bool Request::is_ipv6_literal_addr() const { ++ if (util::has_uri_field(u, UF_HOST)) { ++ return memchr(uri.c_str() + u.field_data[UF_HOST].off, ':', ++ u.field_data[UF_HOST].len); ++ } else { ++ return false; ++ } ++} ++ ++Headers::value_type *Request::get_res_header(int32_t token) { ++ auto idx = res_hdidx[token]; ++ if (idx == -1) { ++ return nullptr; ++ } ++ return &res_nva[idx]; ++} ++ ++Headers::value_type *Request::get_req_header(int32_t token) { ++ auto idx = req_hdidx[token]; ++ if (idx == -1) { ++ return nullptr; ++ } ++ return &req_nva[idx]; ++} ++ ++void Request::record_request_start_time() { ++ timing.state = RequestState::ON_REQUEST; ++ timing.request_start_time = get_time(); ++} ++ ++void Request::record_response_start_time() { ++ timing.state = RequestState::ON_RESPONSE; ++ timing.response_start_time = get_time(); ++} ++ ++void Request::record_response_end_time() { ++ timing.state = RequestState::ON_COMPLETE; ++ timing.response_end_time = get_time(); ++} ++ ++namespace { ++void continue_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { ++ auto client = static_cast(ev_userdata(loop)); ++ auto req = static_cast(w->data); ++ int error; ++ ++ error = nghttp2_submit_data(client->session, NGHTTP2_FLAG_END_STREAM, ++ req->stream_id, req->data_prd); ++ ++ if (error) { ++ std::cerr << "[ERROR] nghttp2_submit_data() returned error: " ++ << nghttp2_strerror(error) << std::endl; ++ nghttp2_submit_rst_stream(client->session, NGHTTP2_FLAG_NONE, ++ req->stream_id, NGHTTP2_INTERNAL_ERROR); ++ } ++ ++ client->signal_write(); ++} ++} // namespace ++ ++ContinueTimer::ContinueTimer(struct ev_loop *loop, Request *req) : loop(loop) { ++ ev_timer_init(&timer, continue_timeout_cb, 1., 0.); ++ timer.data = req; ++} ++ ++ContinueTimer::~ContinueTimer() { stop(); } ++ ++void ContinueTimer::start() { ev_timer_start(loop, &timer); } ++ ++void ContinueTimer::stop() { ev_timer_stop(loop, &timer); } ++ ++void ContinueTimer::dispatch_continue() { ++ // Only dispatch the timeout callback if it hasn't already been called. ++ if (ev_is_active(&timer)) { ++ ev_feed_event(loop, &timer, 0); ++ } ++} ++ ++namespace { ++int htp_msg_begincb(llhttp_t *htp) { ++ if (config.verbose) { ++ print_timer(); ++ std::cout << " HTTP Upgrade response" << std::endl; ++ } ++ return 0; ++} ++} // namespace ++ ++namespace { ++int htp_msg_completecb(llhttp_t *htp) { ++ auto client = static_cast(htp->data); ++ client->upgrade_response_status_code = htp->status_code; ++ client->upgrade_response_complete = true; ++ return 0; ++} ++} // namespace ++ ++namespace { ++constexpr llhttp_settings_t htp_hooks = { ++ htp_msg_begincb, // llhttp_cb on_message_begin; ++ nullptr, // llhttp_data_cb on_url; ++ nullptr, // llhttp_data_cb on_status; ++ nullptr, // llhttp_data_cb on_method; ++ nullptr, // llhttp_data_cb on_version; ++ nullptr, // llhttp_data_cb on_header_field; ++ nullptr, // llhttp_data_cb on_header_value; ++ nullptr, // llhttp_data_cb on_chunk_extension_name; ++ nullptr, // llhttp_data_cb on_chunk_extension_value; ++ nullptr, // llhttp_cb on_headers_complete; ++ nullptr, // llhttp_data_cb on_body; ++ htp_msg_completecb, // llhttp_cb on_message_complete; ++ nullptr, // llhttp_cb on_url_complete; ++ nullptr, // llhttp_cb on_status_complete; ++ nullptr, // llhttp_cb on_method_complete; ++ nullptr, // llhttp_cb on_version_complete; ++ nullptr, // llhttp_cb on_header_field_complete; ++ nullptr, // llhttp_cb on_header_value_complete; ++ nullptr, // llhttp_cb on_chunk_extension_name_complete; ++ nullptr, // llhttp_cb on_chunk_extension_value_complete; ++ nullptr, // llhttp_cb on_chunk_header; ++ nullptr, // llhttp_cb on_chunk_complete; ++ nullptr, // llhttp_cb on_reset; ++}; ++} // namespace ++ ++namespace { ++int submit_request(HttpClient *client, const Headers &headers, Request *req) { ++ auto scheme = util::get_uri_field(req->uri.c_str(), req->u, UF_SCHEMA); ++ auto build_headers = Headers{{":method", req->data_prd ? "POST" : "GET"}, ++ {":path", req->make_reqpath()}, ++ {":scheme", scheme.str()}, ++ {":authority", client->hostport}, ++ {"accept", "*/*"}, ++ {"accept-encoding", "gzip, deflate"}, ++ {"user-agent", "nghttp2/" NGHTTP2_VERSION}}; ++ bool expect_continue = false; ++ ++ if (config.continuation) { ++ for (size_t i = 0; i < 6; ++i) { ++ build_headers.emplace_back("continuation-test-" + util::utos(i + 1), ++ std::string(4_k, '-')); ++ } ++ } ++ ++ auto num_initial_headers = build_headers.size(); ++ ++ if (req->data_prd) { ++ if (!config.no_content_length) { ++ build_headers.emplace_back("content-length", ++ util::utos(req->data_length)); ++ } ++ if (config.expect_continue) { ++ expect_continue = true; ++ build_headers.emplace_back("expect", "100-continue"); ++ } ++ } ++ ++ for (auto &kv : headers) { ++ size_t i; ++ for (i = 0; i < num_initial_headers; ++i) { ++ if (kv.name == build_headers[i].name) { ++ build_headers[i].value = kv.value; ++ break; ++ } ++ } ++ if (i < num_initial_headers) { ++ continue; ++ } ++ ++ build_headers.emplace_back(kv.name, kv.value, kv.no_index); ++ } ++ ++ auto nva = std::vector(); ++ nva.reserve(build_headers.size()); ++ ++ for (auto &kv : build_headers) { ++ nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index)); ++ } ++ ++ auto method = http2::get_header(build_headers, ":method"); ++ assert(method); ++ ++ req->method = method->value; ++ ++ std::string trailer_names; ++ if (!config.trailer.empty()) { ++ trailer_names = config.trailer[0].name; ++ for (size_t i = 1; i < config.trailer.size(); ++i) { ++ trailer_names += ", "; ++ trailer_names += config.trailer[i].name; ++ } ++ nva.push_back(http2::make_nv_ls("trailer", trailer_names)); ++ } ++ ++ int32_t stream_id; ++ ++ if (expect_continue) { ++ stream_id = nghttp2_submit_headers(client->session, 0, -1, &req->pri_spec, ++ nva.data(), nva.size(), req); ++ } else { ++ stream_id = ++ nghttp2_submit_request(client->session, &req->pri_spec, nva.data(), ++ nva.size(), req->data_prd, req); ++ } ++ ++ if (stream_id < 0) { ++ std::cerr << "[ERROR] nghttp2_submit_" ++ << (expect_continue ? "headers" : "request") ++ << "() returned error: " << nghttp2_strerror(stream_id) ++ << std::endl; ++ return -1; ++ } ++ ++ req->stream_id = stream_id; ++ client->request_done(req); ++ ++ req->req_nva = std::move(build_headers); ++ ++ if (expect_continue) { ++ auto timer = std::make_unique(client->loop, req); ++ req->continue_timer = std::move(timer); ++ } ++ ++ return 0; ++} ++} // namespace ++ ++namespace { ++void readcb(struct ev_loop *loop, ev_io *w, int revents) { ++ auto client = static_cast(w->data); ++ if (client->do_read() != 0) { ++ client->disconnect(); ++ } ++} ++} // namespace ++ ++namespace { ++void writecb(struct ev_loop *loop, ev_io *w, int revents) { ++ auto client = static_cast(w->data); ++ auto rv = client->do_write(); ++ if (rv == HttpClient::ERR_CONNECT_FAIL) { ++ client->connect_fail(); ++ return; ++ } ++ if (rv != 0) { ++ client->disconnect(); ++ } ++} ++} // namespace ++ ++namespace { ++void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { ++ auto client = static_cast(w->data); ++ std::cerr << "[ERROR] Timeout" << std::endl; ++ client->disconnect(); ++} ++} // namespace ++ ++namespace { ++void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { ++ auto client = static_cast(w->data); ++ ev_timer_stop(loop, w); ++ ++ nghttp2_session_terminate_session(client->session, NGHTTP2_SETTINGS_TIMEOUT); ++ ++ client->signal_write(); ++} ++} // namespace ++ ++HttpClient::HttpClient(const nghttp2_session_callbacks *callbacks, ++ struct ev_loop *loop, SSL_CTX *ssl_ctx) ++ : wb(&mcpool), ++ session(nullptr), ++ callbacks(callbacks), ++ loop(loop), ++ ssl_ctx(ssl_ctx), ++ ssl(nullptr), ++ addrs(nullptr), ++ next_addr(nullptr), ++ cur_addr(nullptr), ++ complete(0), ++ success(0), ++ settings_payloadlen(0), ++ state(ClientState::IDLE), ++ upgrade_response_status_code(0), ++ fd(-1), ++ upgrade_response_complete(false) { ++ ev_io_init(&wev, writecb, 0, EV_WRITE); ++ ev_io_init(&rev, readcb, 0, EV_READ); ++ ++ wev.data = this; ++ rev.data = this; ++ ++ ev_timer_init(&wt, timeoutcb, 0., config.timeout); ++ ev_timer_init(&rt, timeoutcb, 0., config.timeout); ++ ++ wt.data = this; ++ rt.data = this; ++ ++ ev_timer_init(&settings_timer, settings_timeout_cb, 0., 10.); ++ ++ settings_timer.data = this; ++} ++ ++HttpClient::~HttpClient() { ++ disconnect(); ++ ++ if (addrs) { ++ freeaddrinfo(addrs); ++ addrs = nullptr; ++ next_addr = nullptr; ++ } ++} ++ ++bool HttpClient::need_upgrade() const { ++ return config.upgrade && scheme == "http"; ++} ++ ++int HttpClient::resolve_host(const std::string &host, uint16_t port) { ++ int rv; ++ this->host = host; ++ addrinfo hints{}; ++ hints.ai_family = AF_UNSPEC; ++ hints.ai_socktype = SOCK_STREAM; ++ hints.ai_protocol = 0; ++ hints.ai_flags = AI_ADDRCONFIG; ++ rv = getaddrinfo(host.c_str(), util::utos(port).c_str(), &hints, &addrs); ++ if (rv != 0) { ++ std::cerr << "[ERROR] getaddrinfo() failed: " << gai_strerror(rv) ++ << std::endl; ++ return -1; ++ } ++ if (addrs == nullptr) { ++ std::cerr << "[ERROR] No address returned" << std::endl; ++ return -1; ++ } ++ next_addr = addrs; ++ return 0; ++} ++ ++namespace { ++// Just returns 1 to continue handshake. ++int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) { return 1; } ++} // namespace ++ ++int HttpClient::initiate_connection() { ++ int rv; ++ ++ cur_addr = nullptr; ++ while (next_addr) { ++ cur_addr = next_addr; ++ next_addr = next_addr->ai_next; ++ fd = util::create_nonblock_socket(cur_addr->ai_family); ++ if (fd == -1) { ++ continue; ++ } ++ ++ if (ssl_ctx) { ++ // We are establishing TLS connection. ++ ssl = SSL_new(ssl_ctx); ++ if (!ssl) { ++ std::cerr << "[ERROR] SSL_new() failed: " ++ << ERR_error_string(ERR_get_error(), nullptr) << std::endl; ++ return -1; ++ } ++ ++ SSL_set_connect_state(ssl); ++ ++ // If the user overrode the :authority or host header, use that ++ // value for the SNI extension ++ const auto &host_string = ++ config.host_override.empty() ? host : config.host_override; ++ ++#if LIBRESSL_2_7_API || \ ++ (!LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L) || \ ++ defined(OPENSSL_IS_BORINGSSL) ++ auto param = SSL_get0_param(ssl); ++ X509_VERIFY_PARAM_set_hostflags(param, 0); ++ X509_VERIFY_PARAM_set1_host(param, host_string.c_str(), ++ host_string.size()); ++#endif // LIBRESSL_2_7_API || (!LIBRESSL_IN_USE && ++ // OPENSSL_VERSION_NUMBER >= 0x10002000L) || ++ // defined(OPENSSL_IS_BORINGSSL) ++ SSL_set_verify(ssl, SSL_VERIFY_PEER, verify_cb); ++ ++ if (!util::numeric_host(host_string.c_str())) { ++ SSL_set_tlsext_host_name(ssl, host_string.c_str()); ++ } ++ } ++ ++ rv = connect(fd, cur_addr->ai_addr, cur_addr->ai_addrlen); ++ ++ if (rv != 0 && errno != EINPROGRESS) { ++ if (ssl) { ++ SSL_free(ssl); ++ ssl = nullptr; ++ } ++ close(fd); ++ fd = -1; ++ continue; ++ } ++ break; ++ } ++ ++ if (fd == -1) { ++ return -1; ++ } ++ ++ writefn = &HttpClient::connected; ++ ++ if (need_upgrade()) { ++ on_readfn = &HttpClient::on_upgrade_read; ++ on_writefn = &HttpClient::on_upgrade_connect; ++ } else { ++ on_readfn = &HttpClient::on_read; ++ on_writefn = &HttpClient::on_write; ++ } ++ ++ ev_io_set(&rev, fd, EV_READ); ++ ev_io_set(&wev, fd, EV_WRITE); ++ ++ ev_io_start(loop, &wev); ++ ++ ev_timer_again(loop, &wt); ++ ++ return 0; ++} ++ ++void HttpClient::disconnect() { ++ state = ClientState::IDLE; ++ ++ for (auto req = std::begin(reqvec); req != std::end(reqvec); ++req) { ++ if ((*req)->continue_timer) { ++ (*req)->continue_timer->stop(); ++ } ++ } ++ ++ ev_timer_stop(loop, &settings_timer); ++ ++ ev_timer_stop(loop, &rt); ++ ev_timer_stop(loop, &wt); ++ ++ ev_io_stop(loop, &rev); ++ ev_io_stop(loop, &wev); ++ ++ nghttp2_session_del(session); ++ session = nullptr; ++ ++ if (ssl) { ++ SSL_set_shutdown(ssl, SSL_get_shutdown(ssl) | SSL_RECEIVED_SHUTDOWN); ++ ERR_clear_error(); ++ SSL_shutdown(ssl); ++ SSL_free(ssl); ++ ssl = nullptr; ++ } ++ ++ if (fd != -1) { ++ shutdown(fd, SHUT_WR); ++ close(fd); ++ fd = -1; ++ } ++} ++ ++int HttpClient::read_clear() { ++ ev_timer_again(loop, &rt); ++ ++ std::array buf; ++ ++ for (;;) { ++ ssize_t nread; ++ while ((nread = read(fd, buf.data(), buf.size())) == -1 && errno == EINTR) ++ ; ++ if (nread == -1) { ++ if (errno == EAGAIN || errno == EWOULDBLOCK) { ++ return 0; ++ } ++ return -1; ++ } ++ ++ if (nread == 0) { ++ return -1; ++ } ++ ++ if (on_readfn(*this, buf.data(), nread) != 0) { ++ return -1; ++ } ++ } ++ ++ return 0; ++} ++ ++int HttpClient::write_clear() { ++ ev_timer_again(loop, &rt); ++ ++ std::array iov; ++ ++ for (;;) { ++ if (on_writefn(*this) != 0) { ++ return -1; ++ } ++ ++ auto iovcnt = wb.riovec(iov.data(), iov.size()); ++ ++ if (iovcnt == 0) { ++ break; ++ } ++ ++ ssize_t nwrite; ++ while ((nwrite = writev(fd, iov.data(), iovcnt)) == -1 && errno == EINTR) ++ ; ++ if (nwrite == -1) { ++ if (errno == EAGAIN || errno == EWOULDBLOCK) { ++ ev_io_start(loop, &wev); ++ ev_timer_again(loop, &wt); ++ return 0; ++ } ++ return -1; ++ } ++ ++ wb.drain(nwrite); ++ } ++ ++ ev_io_stop(loop, &wev); ++ ev_timer_stop(loop, &wt); ++ ++ return 0; ++} ++ ++int HttpClient::noop() { return 0; } ++ ++void HttpClient::connect_fail() { ++ if (state == ClientState::IDLE) { ++ std::cerr << "[ERROR] Could not connect to the address " ++ << util::numeric_name(cur_addr->ai_addr, cur_addr->ai_addrlen) ++ << std::endl; ++ } ++ auto cur_state = state; ++ disconnect(); ++ if (cur_state == ClientState::IDLE) { ++ if (initiate_connection() == 0) { ++ std::cerr << "Trying next address " ++ << util::numeric_name(cur_addr->ai_addr, cur_addr->ai_addrlen) ++ << std::endl; ++ } ++ } ++} ++ ++int HttpClient::connected() { ++ if (!util::check_socket_connected(fd)) { ++ return ERR_CONNECT_FAIL; ++ } ++ ++ if (config.verbose) { ++ print_timer(); ++ std::cout << " Connected" << std::endl; ++ } ++ ++ state = ClientState::CONNECTED; ++ ++ ev_io_start(loop, &rev); ++ ev_io_stop(loop, &wev); ++ ++ ev_timer_again(loop, &rt); ++ ev_timer_stop(loop, &wt); ++ ++ if (ssl) { ++ SSL_set_fd(ssl, fd); ++ ++ readfn = &HttpClient::tls_handshake; ++ writefn = &HttpClient::tls_handshake; ++ ++ return do_write(); ++ } ++ ++ readfn = &HttpClient::read_clear; ++ writefn = &HttpClient::write_clear; ++ ++ if (need_upgrade()) { ++ htp = std::make_unique(); ++ llhttp_init(htp.get(), HTTP_RESPONSE, &htp_hooks); ++ htp->data = this; ++ ++ return do_write(); ++ } ++ ++ if (connection_made() != 0) { ++ return -1; ++ } ++ ++ return 0; ++} ++ ++namespace { ++size_t populate_settings(nghttp2_settings_entry *iv) { ++ size_t niv = 2; ++ ++ iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; ++ iv[0].value = config.max_concurrent_streams; ++ ++ iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; ++ if (config.window_bits != -1) { ++ iv[1].value = (1 << config.window_bits) - 1; ++ } else { ++ iv[1].value = NGHTTP2_INITIAL_WINDOW_SIZE; ++ } ++ ++ if (config.header_table_size >= 0) { ++ if (config.min_header_table_size < config.header_table_size) { ++ iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; ++ iv[niv].value = config.min_header_table_size; ++ ++niv; ++ } ++ ++ iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; ++ iv[niv].value = config.header_table_size; ++ ++niv; ++ } ++ ++ if (config.no_push) { ++ iv[niv].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; ++ iv[niv].value = 0; ++ ++niv; ++ } ++ ++ if (config.no_rfc7540_pri) { ++ iv[niv].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; ++ iv[niv].value = 1; ++ ++niv; ++ } ++ ++ return niv; ++} ++} // namespace ++ ++int HttpClient::on_upgrade_connect() { ++ ssize_t rv; ++ record_connect_end_time(); ++ assert(!reqvec.empty()); ++ std::array iv; ++ size_t niv = populate_settings(iv.data()); ++ assert(settings_payload.size() >= 8 * niv); ++ rv = nghttp2_pack_settings_payload(settings_payload.data(), ++ settings_payload.size(), iv.data(), niv); ++ if (rv < 0) { ++ return -1; ++ } ++ settings_payloadlen = rv; ++ auto token68 = ++ base64::encode(std::begin(settings_payload), ++ std::begin(settings_payload) + settings_payloadlen); ++ util::to_token68(token68); ++ ++ std::string req; ++ if (reqvec[0]->data_prd) { ++ // If the request contains upload data, use OPTIONS * to upgrade ++ req = "OPTIONS *"; ++ } else { ++ auto meth = std::find_if( ++ std::begin(config.headers), std::end(config.headers), ++ [](const Header &kv) { return util::streq_l(":method", kv.name); }); ++ ++ if (meth == std::end(config.headers)) { ++ req = "GET "; ++ reqvec[0]->method = "GET"; ++ } else { ++ req = (*meth).value; ++ req += ' '; ++ reqvec[0]->method = (*meth).value; ++ } ++ req += reqvec[0]->make_reqpath(); ++ } ++ ++ auto headers = Headers{{"host", hostport}, ++ {"connection", "Upgrade, HTTP2-Settings"}, ++ {"upgrade", NGHTTP2_CLEARTEXT_PROTO_VERSION_ID}, ++ {"http2-settings", token68}, ++ {"accept", "*/*"}, ++ {"user-agent", "nghttp2/" NGHTTP2_VERSION}}; ++ auto initial_headerslen = headers.size(); ++ ++ for (auto &kv : config.headers) { ++ size_t i; ++ if (kv.name.empty() || kv.name[0] == ':') { ++ continue; ++ } ++ for (i = 0; i < initial_headerslen; ++i) { ++ if (kv.name == headers[i].name) { ++ headers[i].value = kv.value; ++ break; ++ } ++ } ++ if (i < initial_headerslen) { ++ continue; ++ } ++ headers.emplace_back(kv.name, kv.value, kv.no_index); ++ } ++ ++ req += " HTTP/1.1\r\n"; ++ ++ for (auto &kv : headers) { ++ req += kv.name; ++ req += ": "; ++ req += kv.value; ++ req += "\r\n"; ++ } ++ req += "\r\n"; ++ ++ wb.append(req); ++ ++ if (config.verbose) { ++ print_timer(); ++ std::cout << " HTTP Upgrade request\n" << req << std::endl; ++ } ++ ++ if (!reqvec[0]->data_prd) { ++ // record request time if this is a part of real request. ++ reqvec[0]->record_request_start_time(); ++ reqvec[0]->req_nva = std::move(headers); ++ } ++ ++ on_writefn = &HttpClient::noop; ++ ++ signal_write(); ++ ++ return 0; ++} ++ ++int HttpClient::on_upgrade_read(const uint8_t *data, size_t len) { ++ int rv; ++ ++ auto htperr = ++ llhttp_execute(htp.get(), reinterpret_cast(data), len); ++ auto nread = htperr == HPE_OK ++ ? len ++ : static_cast(reinterpret_cast( ++ llhttp_get_error_pos(htp.get())) - ++ data); ++ ++ if (config.verbose) { ++ std::cout.write(reinterpret_cast(data), nread); ++ } ++ ++ if (htperr != HPE_OK && htperr != HPE_PAUSED_UPGRADE) { ++ std::cerr << "[ERROR] Failed to parse HTTP Upgrade response header: " ++ << "(" << llhttp_errno_name(htperr) << ") " ++ << llhttp_get_error_reason(htp.get()) << std::endl; ++ return -1; ++ } ++ ++ if (!upgrade_response_complete) { ++ return 0; ++ } ++ ++ if (config.verbose) { ++ std::cout << std::endl; ++ } ++ ++ if (upgrade_response_status_code != 101) { ++ std::cerr << "[ERROR] HTTP Upgrade failed" << std::endl; ++ ++ return -1; ++ } ++ ++ if (config.verbose) { ++ print_timer(); ++ std::cout << " HTTP Upgrade success" << std::endl; ++ } ++ ++ on_readfn = &HttpClient::on_read; ++ on_writefn = &HttpClient::on_write; ++ ++ rv = connection_made(); ++ if (rv != 0) { ++ return rv; ++ } ++ ++ // Read remaining data in the buffer because it is not notified ++ // callback anymore. ++ rv = on_readfn(*this, data + nread, len - nread); ++ if (rv != 0) { ++ return rv; ++ } ++ ++ return 0; ++} ++ ++int HttpClient::do_read() { return readfn(*this); } ++int HttpClient::do_write() { return writefn(*this); } ++ ++int HttpClient::connection_made() { ++ int rv; ++ ++ if (!need_upgrade()) { ++ record_connect_end_time(); ++ } ++ ++ if (ssl) { ++ // Check NPN or ALPN result ++ const unsigned char *next_proto = nullptr; ++ unsigned int next_proto_len; ++#ifndef OPENSSL_NO_NEXTPROTONEG ++ SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len); ++#endif // !OPENSSL_NO_NEXTPROTONEG ++ for (int i = 0; i < 2; ++i) { ++ if (next_proto) { ++ auto proto = StringRef{next_proto, next_proto_len}; ++ if (config.verbose) { ++ std::cout << "The negotiated protocol: " << proto << std::endl; ++ } ++ if (!util::check_h2_is_selected(proto)) { ++ next_proto = nullptr; ++ } ++ break; ++ } ++#if OPENSSL_VERSION_NUMBER >= 0x10002000L ++ SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len); ++#else // OPENSSL_VERSION_NUMBER < 0x10002000L ++ break; ++#endif // OPENSSL_VERSION_NUMBER < 0x10002000L ++ } ++ if (!next_proto) { ++ print_protocol_nego_error(); ++ return -1; ++ } ++ } ++ ++ rv = nghttp2_session_client_new2(&session, callbacks, this, ++ config.http2_option); ++ ++ if (rv != 0) { ++ return -1; ++ } ++ if (need_upgrade()) { ++ // Adjust stream user-data depending on the existence of upload ++ // data ++ Request *stream_user_data = nullptr; ++ if (!reqvec[0]->data_prd) { ++ stream_user_data = reqvec[0].get(); ++ } ++ // If HEAD is used, that is only when user specified it with -H ++ // option. ++ auto head_request = stream_user_data && stream_user_data->method == "HEAD"; ++ rv = nghttp2_session_upgrade2(session, settings_payload.data(), ++ settings_payloadlen, head_request, ++ stream_user_data); ++ if (rv != 0) { ++ std::cerr << "[ERROR] nghttp2_session_upgrade() returned error: " ++ << nghttp2_strerror(rv) << std::endl; ++ return -1; ++ } ++ if (stream_user_data) { ++ stream_user_data->stream_id = 1; ++ request_done(stream_user_data); ++ } ++ } ++ // If upgrade succeeds, the SETTINGS value sent with ++ // HTTP2-Settings header field has already been submitted to ++ // session object. ++ if (!need_upgrade()) { ++ std::array iv; ++ auto niv = populate_settings(iv.data()); ++ rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv.data(), niv); ++ if (rv != 0) { ++ return -1; ++ } ++ } ++ if (!config.no_dep) { ++ // Create anchor stream nodes ++ nghttp2_priority_spec pri_spec; ++ ++ for (auto &anchor : anchors) { ++ nghttp2_priority_spec_init(&pri_spec, anchor.dep_stream_id, anchor.weight, ++ 0); ++ rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, anchor.stream_id, ++ &pri_spec); ++ if (rv != 0) { ++ return -1; ++ } ++ } ++ ++ rv = nghttp2_session_set_next_stream_id( ++ session, anchors[ANCHOR_FOLLOWERS].stream_id + 2); ++ if (rv != 0) { ++ return -1; ++ } ++ ++ if (need_upgrade() && !reqvec[0]->data_prd) { ++ // Amend the priority because we cannot send priority in ++ // HTTP/1.1 Upgrade. ++ auto &anchor = anchors[ANCHOR_FOLLOWERS]; ++ nghttp2_priority_spec_init(&pri_spec, anchor.stream_id, ++ reqvec[0]->pri_spec.weight, 0); ++ ++ rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec); ++ if (rv != 0) { ++ return -1; ++ } ++ } ++ } else if (need_upgrade() && !reqvec[0]->data_prd && ++ reqvec[0]->pri_spec.weight != NGHTTP2_DEFAULT_WEIGHT) { ++ // Amend the priority because we cannot send priority in HTTP/1.1 ++ // Upgrade. ++ nghttp2_priority_spec pri_spec; ++ ++ nghttp2_priority_spec_init(&pri_spec, 0, reqvec[0]->pri_spec.weight, 0); ++ ++ rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec); ++ if (rv != 0) { ++ return -1; ++ } ++ } ++ ++ ev_timer_again(loop, &settings_timer); ++ ++ if (config.connection_window_bits != -1) { ++ int32_t window_size = (1 << config.connection_window_bits) - 1; ++ rv = nghttp2_session_set_local_window_size(session, NGHTTP2_FLAG_NONE, 0, ++ window_size); ++ if (rv != 0) { ++ return -1; ++ } ++ } ++ // Adjust first request depending on the existence of the upload ++ // data ++ for (auto i = std::begin(reqvec) + (need_upgrade() && !reqvec[0]->data_prd); ++ i != std::end(reqvec); ++i) { ++ if (submit_request(this, config.headers, (*i).get()) != 0) { ++ return -1; ++ } ++ } ++ ++ signal_write(); ++ ++ return 0; ++} ++ ++int HttpClient::on_read(const uint8_t *data, size_t len) { ++ if (config.hexdump) { ++ util::hexdump(stdout, data, len); ++ } ++ ++ auto rv = nghttp2_session_mem_recv(session, data, len); ++ if (rv < 0) { ++ std::cerr << "[ERROR] nghttp2_session_mem_recv() returned error: " ++ << nghttp2_strerror(rv) << std::endl; ++ return -1; ++ } ++ ++ assert(static_cast(rv) == len); ++ ++ if (nghttp2_session_want_read(session) == 0 && ++ nghttp2_session_want_write(session) == 0 && wb.rleft() == 0) { ++ return -1; ++ } ++ ++ signal_write(); ++ ++ return 0; ++} ++ ++int HttpClient::on_write() { ++ for (;;) { ++ if (wb.rleft() >= 16384) { ++ return 0; ++ } ++ ++ const uint8_t *data; ++ auto len = nghttp2_session_mem_send(session, &data); ++ if (len < 0) { ++ std::cerr << "[ERROR] nghttp2_session_send() returned error: " ++ << nghttp2_strerror(len) << std::endl; ++ return -1; ++ } ++ ++ if (len == 0) { ++ break; ++ } ++ ++ wb.append(data, len); ++ } ++ ++ if (nghttp2_session_want_read(session) == 0 && ++ nghttp2_session_want_write(session) == 0 && wb.rleft() == 0) { ++ return -1; ++ } ++ ++ return 0; ++} ++ ++int HttpClient::tls_handshake() { ++ ev_timer_again(loop, &rt); ++ ++ ERR_clear_error(); ++ ++ auto rv = SSL_do_handshake(ssl); ++ ++ if (rv <= 0) { ++ auto err = SSL_get_error(ssl, rv); ++ switch (err) { ++ case SSL_ERROR_WANT_READ: ++ ev_io_stop(loop, &wev); ++ ev_timer_stop(loop, &wt); ++ return 0; ++ case SSL_ERROR_WANT_WRITE: ++ ev_io_start(loop, &wev); ++ ev_timer_again(loop, &wt); ++ return 0; ++ default: ++ return -1; ++ } ++ } ++ ++ ev_io_stop(loop, &wev); ++ ev_timer_stop(loop, &wt); ++ ++ readfn = &HttpClient::read_tls; ++ writefn = &HttpClient::write_tls; ++ ++ if (config.verify_peer) { ++ auto verify_res = SSL_get_verify_result(ssl); ++ if (verify_res != X509_V_OK) { ++ std::cerr << "[WARNING] Certificate verification failed: " ++ << X509_verify_cert_error_string(verify_res) << std::endl; ++ } ++ } ++ ++ if (connection_made() != 0) { ++ return -1; ++ } ++ ++ return 0; ++} ++ ++int HttpClient::read_tls() { ++ ev_timer_again(loop, &rt); ++ ++ ERR_clear_error(); ++ ++ std::array buf; ++ for (;;) { ++ auto rv = SSL_read(ssl, buf.data(), buf.size()); ++ ++ if (rv <= 0) { ++ auto err = SSL_get_error(ssl, rv); ++ switch (err) { ++ case SSL_ERROR_WANT_READ: ++ return 0; ++ case SSL_ERROR_WANT_WRITE: ++ // renegotiation started ++ return -1; ++ default: ++ return -1; ++ } ++ } ++ ++ if (on_readfn(*this, buf.data(), rv) != 0) { ++ return -1; ++ } ++ } ++} ++ ++int HttpClient::write_tls() { ++ ev_timer_again(loop, &rt); ++ ++ ERR_clear_error(); ++ ++ struct iovec iov; ++ ++ for (;;) { ++ if (on_writefn(*this) != 0) { ++ return -1; ++ } ++ ++ auto iovcnt = wb.riovec(&iov, 1); ++ ++ if (iovcnt == 0) { ++ break; ++ } ++ ++ auto rv = SSL_write(ssl, iov.iov_base, iov.iov_len); ++ ++ if (rv <= 0) { ++ auto err = SSL_get_error(ssl, rv); ++ switch (err) { ++ case SSL_ERROR_WANT_READ: ++ // renegotiation started ++ return -1; ++ case SSL_ERROR_WANT_WRITE: ++ ev_io_start(loop, &wev); ++ ev_timer_again(loop, &wt); ++ return 0; ++ default: ++ return -1; ++ } ++ } ++ ++ wb.drain(rv); ++ } ++ ++ ev_io_stop(loop, &wev); ++ ev_timer_stop(loop, &wt); ++ ++ return 0; ++} ++ ++void HttpClient::signal_write() { ev_io_start(loop, &wev); } ++ ++bool HttpClient::all_requests_processed() const { ++ return complete == reqvec.size(); ++} ++ ++void HttpClient::update_hostport() { ++ if (reqvec.empty()) { ++ return; ++ } ++ scheme = util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_SCHEMA) ++ .str(); ++ std::stringstream ss; ++ if (reqvec[0]->is_ipv6_literal_addr()) { ++ // we may have zone ID, which must start with "%25", or "%". RFC ++ // 6874 defines "%25" only, and just "%" is allowed for just ++ // convenience to end-user input. ++ auto host = ++ util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST); ++ auto end = std::find(std::begin(host), std::end(host), '%'); ++ ss << "["; ++ ss.write(host.c_str(), end - std::begin(host)); ++ ss << "]"; ++ } else { ++ util::write_uri_field(ss, reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST); ++ } ++ if (util::has_uri_field(reqvec[0]->u, UF_PORT) && ++ reqvec[0]->u.port != ++ util::get_default_port(reqvec[0]->uri.c_str(), reqvec[0]->u)) { ++ ss << ":" << reqvec[0]->u.port; ++ } ++ hostport = ss.str(); ++} ++ ++bool HttpClient::add_request(const std::string &uri, ++ const nghttp2_data_provider *data_prd, ++ int64_t data_length, ++ const nghttp2_priority_spec &pri_spec, int level) { ++ http_parser_url u{}; ++ if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) { ++ return false; ++ } ++ if (path_cache.count(uri)) { ++ return false; ++ } ++ ++ if (config.multiply == 1) { ++ path_cache.insert(uri); ++ } ++ ++ reqvec.push_back(std::make_unique(uri, u, data_prd, data_length, ++ pri_spec, level)); ++ return true; ++} ++ ++void HttpClient::record_start_time() { ++ timing.system_start_time = std::chrono::system_clock::now(); ++ timing.start_time = get_time(); ++} ++ ++void HttpClient::record_domain_lookup_end_time() { ++ timing.domain_lookup_end_time = get_time(); ++} ++ ++void HttpClient::record_connect_end_time() { ++ timing.connect_end_time = get_time(); ++} ++ ++void HttpClient::request_done(Request *req) { ++ if (req->stream_id % 2 == 0) { ++ return; ++ } ++} ++ ++#ifdef HAVE_JANSSON ++void HttpClient::output_har(FILE *outfile) { ++ static auto PAGE_ID = "page_0"; ++ ++ auto root = json_object(); ++ auto log = json_object(); ++ json_object_set_new(root, "log", log); ++ json_object_set_new(log, "version", json_string("1.2")); ++ ++ auto creator = json_object(); ++ json_object_set_new(log, "creator", creator); ++ ++ json_object_set_new(creator, "name", json_string("nghttp")); ++ json_object_set_new(creator, "version", json_string(NGHTTP2_VERSION)); ++ ++ auto pages = json_array(); ++ json_object_set_new(log, "pages", pages); ++ ++ auto page = json_object(); ++ json_array_append_new(pages, page); ++ ++ json_object_set_new( ++ page, "startedDateTime", ++ json_string(util::format_iso8601(timing.system_start_time).c_str())); ++ json_object_set_new(page, "id", json_string(PAGE_ID)); ++ json_object_set_new(page, "title", json_string("")); ++ ++ json_object_set_new(page, "pageTimings", json_object()); ++ ++ auto entries = json_array(); ++ json_object_set_new(log, "entries", entries); ++ ++ auto dns_delta = std::chrono::duration_cast( ++ timing.domain_lookup_end_time - timing.start_time) ++ .count() / ++ 1000.0; ++ auto connect_delta = ++ std::chrono::duration_cast( ++ timing.connect_end_time - timing.domain_lookup_end_time) ++ .count() / ++ 1000.0; ++ ++ for (size_t i = 0; i < reqvec.size(); ++i) { ++ auto &req = reqvec[i]; ++ ++ if (req->timing.state != RequestState::ON_COMPLETE) { ++ continue; ++ } ++ ++ auto entry = json_object(); ++ json_array_append_new(entries, entry); ++ ++ auto &req_timing = req->timing; ++ auto request_time = ++ (i == 0) ? timing.system_start_time ++ : timing.system_start_time + ++ std::chrono::duration_cast< ++ std::chrono::system_clock::duration>( ++ req_timing.request_start_time - timing.start_time); ++ ++ auto wait_delta = ++ std::chrono::duration_cast( ++ req_timing.response_start_time - req_timing.request_start_time) ++ .count() / ++ 1000.0; ++ auto receive_delta = ++ std::chrono::duration_cast( ++ req_timing.response_end_time - req_timing.response_start_time) ++ .count() / ++ 1000.0; ++ ++ auto time_sum = ++ std::chrono::duration_cast( ++ (i == 0) ? (req_timing.response_end_time - timing.start_time) ++ : (req_timing.response_end_time - ++ req_timing.request_start_time)) ++ .count() / ++ 1000.0; ++ ++ json_object_set_new( ++ entry, "startedDateTime", ++ json_string(util::format_iso8601(request_time).c_str())); ++ json_object_set_new(entry, "time", json_real(time_sum)); ++ ++ auto pushed = req->stream_id % 2 == 0; ++ ++ json_object_set_new(entry, "comment", ++ json_string(pushed ? "Pushed Object" : "")); ++ ++ auto request = json_object(); ++ json_object_set_new(entry, "request", request); ++ ++ auto req_headers = json_array(); ++ json_object_set_new(request, "headers", req_headers); ++ ++ for (auto &nv : req->req_nva) { ++ auto hd = json_object(); ++ json_array_append_new(req_headers, hd); ++ ++ json_object_set_new(hd, "name", json_string(nv.name.c_str())); ++ json_object_set_new(hd, "value", json_string(nv.value.c_str())); ++ } ++ ++ json_object_set_new(request, "method", json_string(req->method.c_str())); ++ json_object_set_new(request, "url", json_string(req->uri.c_str())); ++ json_object_set_new(request, "httpVersion", json_string("HTTP/2.0")); ++ json_object_set_new(request, "cookies", json_array()); ++ json_object_set_new(request, "queryString", json_array()); ++ json_object_set_new(request, "headersSize", json_integer(-1)); ++ json_object_set_new(request, "bodySize", json_integer(-1)); ++ ++ auto response = json_object(); ++ json_object_set_new(entry, "response", response); ++ ++ auto res_headers = json_array(); ++ json_object_set_new(response, "headers", res_headers); ++ ++ for (auto &nv : req->res_nva) { ++ auto hd = json_object(); ++ json_array_append_new(res_headers, hd); ++ ++ json_object_set_new(hd, "name", json_string(nv.name.c_str())); ++ json_object_set_new(hd, "value", json_string(nv.value.c_str())); ++ } ++ ++ json_object_set_new(response, "status", json_integer(req->status)); ++ json_object_set_new(response, "statusText", json_string("")); ++ json_object_set_new(response, "httpVersion", json_string("HTTP/2.0")); ++ json_object_set_new(response, "cookies", json_array()); ++ ++ auto content = json_object(); ++ json_object_set_new(response, "content", content); ++ ++ json_object_set_new(content, "size", json_integer(req->response_len)); ++ ++ auto content_type_ptr = http2::get_header(req->res_nva, "content-type"); ++ ++ const char *content_type = ""; ++ if (content_type_ptr) { ++ content_type = content_type_ptr->value.c_str(); ++ } ++ ++ json_object_set_new(content, "mimeType", json_string(content_type)); ++ ++ json_object_set_new(response, "redirectURL", json_string("")); ++ json_object_set_new(response, "headersSize", json_integer(-1)); ++ json_object_set_new(response, "bodySize", json_integer(-1)); ++ json_object_set_new(entry, "cache", json_object()); ++ ++ auto timings = json_object(); ++ json_object_set_new(entry, "timings", timings); ++ ++ auto dns_timing = (i == 0) ? dns_delta : 0; ++ auto connect_timing = (i == 0) ? connect_delta : 0; ++ ++ json_object_set_new(timings, "dns", json_real(dns_timing)); ++ json_object_set_new(timings, "connect", json_real(connect_timing)); ++ ++ json_object_set_new(timings, "blocked", json_real(0.0)); ++ json_object_set_new(timings, "send", json_real(0.0)); ++ json_object_set_new(timings, "wait", json_real(wait_delta)); ++ json_object_set_new(timings, "receive", json_real(receive_delta)); ++ ++ json_object_set_new(entry, "pageref", json_string(PAGE_ID)); ++ json_object_set_new(entry, "connection", ++ json_string(util::utos(req->stream_id).c_str())); ++ } ++ ++ json_dumpf(root, outfile, JSON_PRESERVE_ORDER | JSON_INDENT(2)); ++ json_decref(root); ++} ++#endif // HAVE_JANSSON ++ ++namespace { ++void update_html_parser(HttpClient *client, Request *req, const uint8_t *data, ++ size_t len, int fin) { ++ if (!req->html_parser) { ++ return; ++ } ++ req->update_html_parser(data, len, fin); ++ ++ auto scheme = req->get_real_scheme(); ++ auto host = req->get_real_host(); ++ auto port = req->get_real_port(); ++ ++ for (auto &p : req->html_parser->get_links()) { ++ auto uri = strip_fragment(p.first.c_str()); ++ auto res_type = p.second; ++ ++ http_parser_url u{}; ++ if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) { ++ continue; ++ } ++ ++ if (!util::fieldeq(uri.c_str(), u, UF_SCHEMA, scheme) || ++ !util::fieldeq(uri.c_str(), u, UF_HOST, host)) { ++ continue; ++ } ++ ++ auto link_port = util::has_uri_field(u, UF_PORT) ? u.port ++ : scheme == "https" ? 443 ++ : 80; ++ ++ if (port != link_port) { ++ continue; ++ } ++ ++ // No POST data for assets ++ auto pri_spec = resolve_dep(res_type); ++ ++ if (client->add_request(uri, nullptr, 0, pri_spec, req->level + 1)) { ++ submit_request(client, config.headers, client->reqvec.back().get()); ++ } ++ } ++ req->html_parser->clear_links(); ++} ++} // namespace ++ ++namespace { ++HttpClient *get_client(void *user_data) { ++ return static_cast(user_data); ++} ++} // namespace ++ ++namespace { ++int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, ++ int32_t stream_id, const uint8_t *data, ++ size_t len, void *user_data) { ++ auto client = get_client(user_data); ++ auto req = static_cast( ++ nghttp2_session_get_stream_user_data(session, stream_id)); ++ ++ if (!req) { ++ return 0; ++ } ++ ++ if (config.verbose >= 2) { ++ verbose_on_data_chunk_recv_callback(session, flags, stream_id, data, len, ++ user_data); ++ } ++ ++ req->response_len += len; ++ ++ if (req->inflater) { ++ while (len > 0) { ++ const size_t MAX_OUTLEN = 4_k; ++ std::array out; ++ size_t outlen = MAX_OUTLEN; ++ size_t tlen = len; ++ int rv = ++ nghttp2_gzip_inflate(req->inflater, out.data(), &outlen, data, &tlen); ++ if (rv != 0) { ++ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id, ++ NGHTTP2_INTERNAL_ERROR); ++ break; ++ } ++ ++ if (!config.null_out) { ++ std::cout.write(reinterpret_cast(out.data()), outlen); ++ } ++ ++ update_html_parser(client, req, out.data(), outlen, 0); ++ data += tlen; ++ len -= tlen; ++ } ++ ++ return 0; ++ } ++ ++ if (!config.null_out) { ++ std::cout.write(reinterpret_cast(data), len); ++ } ++ ++ update_html_parser(client, req, data, len, 0); ++ ++ return 0; ++} ++} // namespace ++ ++namespace { ++ssize_t select_padding_callback(nghttp2_session *session, ++ const nghttp2_frame *frame, size_t max_payload, ++ void *user_data) { ++ return std::min(max_payload, frame->hd.length + config.padding); ++} ++} // namespace ++ ++namespace { ++void check_response_header(nghttp2_session *session, Request *req) { ++ bool gzip = false; ++ ++ req->expect_final_response = false; ++ ++ auto status_hd = req->get_res_header(http2::HD__STATUS); ++ ++ if (!status_hd) { ++ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id, ++ NGHTTP2_PROTOCOL_ERROR); ++ return; ++ } ++ ++ auto status = http2::parse_http_status_code(StringRef{status_hd->value}); ++ if (status == -1) { ++ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id, ++ NGHTTP2_PROTOCOL_ERROR); ++ return; ++ } ++ ++ req->status = status; ++ ++ for (auto &nv : req->res_nva) { ++ if ("content-encoding" == nv.name) { ++ gzip = util::strieq_l("gzip", nv.value) || ++ util::strieq_l("deflate", nv.value); ++ continue; ++ } ++ } ++ ++ if (req->status / 100 == 1) { ++ if (req->continue_timer && (req->status == 100)) { ++ // If the request is waiting for a 100 Continue, complete the handshake. ++ req->continue_timer->dispatch_continue(); ++ } ++ ++ req->expect_final_response = true; ++ req->status = 0; ++ req->res_nva.clear(); ++ http2::init_hdidx(req->res_hdidx); ++ return; ++ } else if (req->continue_timer) { ++ // A final response stops any pending Expect/Continue handshake. ++ req->continue_timer->stop(); ++ } ++ ++ if (gzip) { ++ if (!req->inflater) { ++ req->init_inflater(); ++ } ++ } ++ if (config.get_assets && req->level == 0) { ++ if (!req->html_parser) { ++ req->init_html_parser(); ++ } ++ } ++} ++} // namespace ++ ++namespace { ++int on_begin_headers_callback(nghttp2_session *session, ++ const nghttp2_frame *frame, void *user_data) { ++ auto client = get_client(user_data); ++ switch (frame->hd.type) { ++ case NGHTTP2_HEADERS: { ++ auto req = static_cast( ++ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); ++ if (!req) { ++ break; ++ } ++ ++ switch (frame->headers.cat) { ++ case NGHTTP2_HCAT_RESPONSE: ++ case NGHTTP2_HCAT_PUSH_RESPONSE: ++ req->record_response_start_time(); ++ break; ++ default: ++ break; ++ } ++ ++ break; ++ } ++ case NGHTTP2_PUSH_PROMISE: { ++ auto stream_id = frame->push_promise.promised_stream_id; ++ http_parser_url u{}; ++ // TODO Set pri and level ++ nghttp2_priority_spec pri_spec; ++ ++ nghttp2_priority_spec_default_init(&pri_spec); ++ ++ auto req = std::make_unique("", u, nullptr, 0, pri_spec); ++ req->stream_id = stream_id; ++ ++ nghttp2_session_set_stream_user_data(session, stream_id, req.get()); ++ ++ client->request_done(req.get()); ++ req->record_request_start_time(); ++ client->reqvec.push_back(std::move(req)); ++ ++ break; ++ } ++ } ++ return 0; ++} ++} // namespace ++ ++namespace { ++int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, ++ const uint8_t *name, size_t namelen, ++ const uint8_t *value, size_t valuelen, uint8_t flags, ++ void *user_data) { ++ if (config.verbose) { ++ verbose_on_header_callback(session, frame, name, namelen, value, valuelen, ++ flags, user_data); ++ } ++ ++ switch (frame->hd.type) { ++ case NGHTTP2_HEADERS: { ++ auto req = static_cast( ++ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); ++ ++ if (!req) { ++ break; ++ } ++ ++ /* ignore trailer header */ ++ if (frame->headers.cat == NGHTTP2_HCAT_HEADERS && ++ !req->expect_final_response) { ++ break; ++ } ++ ++ if (req->header_buffer_size + namelen + valuelen > 64_k) { ++ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, ++ NGHTTP2_INTERNAL_ERROR); ++ return 0; ++ } ++ ++ req->header_buffer_size += namelen + valuelen; ++ ++ auto token = http2::lookup_token(name, namelen); ++ ++ http2::index_header(req->res_hdidx, token, req->res_nva.size()); ++ http2::add_header(req->res_nva, name, namelen, value, valuelen, ++ flags & NGHTTP2_NV_FLAG_NO_INDEX, token); ++ break; ++ } ++ case NGHTTP2_PUSH_PROMISE: { ++ auto req = static_cast(nghttp2_session_get_stream_user_data( ++ session, frame->push_promise.promised_stream_id)); ++ ++ if (!req) { ++ break; ++ } ++ ++ if (req->header_buffer_size + namelen + valuelen > 64_k) { ++ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, ++ frame->push_promise.promised_stream_id, ++ NGHTTP2_INTERNAL_ERROR); ++ return 0; ++ } ++ ++ req->header_buffer_size += namelen + valuelen; ++ ++ auto token = http2::lookup_token(name, namelen); ++ ++ http2::index_header(req->req_hdidx, token, req->req_nva.size()); ++ http2::add_header(req->req_nva, name, namelen, value, valuelen, ++ flags & NGHTTP2_NV_FLAG_NO_INDEX, token); ++ break; ++ } ++ } ++ return 0; ++} ++} // namespace ++ ++namespace { ++int on_frame_recv_callback2(nghttp2_session *session, ++ const nghttp2_frame *frame, void *user_data) { ++ int rv = 0; ++ ++ if (config.verbose) { ++ verbose_on_frame_recv_callback(session, frame, user_data); ++ } ++ ++ auto client = get_client(user_data); ++ switch (frame->hd.type) { ++ case NGHTTP2_DATA: { ++ auto req = static_cast( ++ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); ++ if (!req) { ++ return 0; ++ ; ++ } ++ ++ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { ++ req->record_response_end_time(); ++ ++client->success; ++ } ++ ++ break; ++ } ++ case NGHTTP2_HEADERS: { ++ auto req = static_cast( ++ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); ++ // If this is the HTTP Upgrade with OPTIONS method to avoid POST, ++ // req is nullptr. ++ if (!req) { ++ return 0; ++ ; ++ } ++ ++ switch (frame->headers.cat) { ++ case NGHTTP2_HCAT_RESPONSE: ++ case NGHTTP2_HCAT_PUSH_RESPONSE: ++ check_response_header(session, req); ++ break; ++ case NGHTTP2_HCAT_HEADERS: ++ if (req->expect_final_response) { ++ check_response_header(session, req); ++ break; ++ } ++ if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { ++ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, ++ frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); ++ return 0; ++ } ++ break; ++ default: ++ assert(0); ++ } ++ ++ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { ++ req->record_response_end_time(); ++ ++client->success; ++ } ++ ++ break; ++ } ++ case NGHTTP2_SETTINGS: ++ if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) { ++ break; ++ } ++ ev_timer_stop(client->loop, &client->settings_timer); ++ break; ++ case NGHTTP2_PUSH_PROMISE: { ++ auto req = static_cast(nghttp2_session_get_stream_user_data( ++ session, frame->push_promise.promised_stream_id)); ++ if (!req) { ++ break; ++ } ++ ++ // Reset for response header field reception ++ req->header_buffer_size = 0; ++ ++ auto scheme = req->get_req_header(http2::HD__SCHEME); ++ auto authority = req->get_req_header(http2::HD__AUTHORITY); ++ auto path = req->get_req_header(http2::HD__PATH); ++ ++ if (!authority) { ++ authority = req->get_req_header(http2::HD_HOST); ++ } ++ ++ // libnghttp2 guarantees :scheme, :method, :path and (:authority | ++ // host) exist and non-empty. ++ if (path->value[0] != '/') { ++ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, ++ frame->push_promise.promised_stream_id, ++ NGHTTP2_PROTOCOL_ERROR); ++ break; ++ } ++ std::string uri = scheme->value; ++ uri += "://"; ++ uri += authority->value; ++ uri += path->value; ++ http_parser_url u{}; ++ if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) { ++ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, ++ frame->push_promise.promised_stream_id, ++ NGHTTP2_PROTOCOL_ERROR); ++ break; ++ } ++ req->uri = uri; ++ req->u = u; ++ ++ if (client->path_cache.count(uri)) { ++ nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, ++ frame->push_promise.promised_stream_id, ++ NGHTTP2_CANCEL); ++ break; ++ } ++ ++ if (config.multiply == 1) { ++ client->path_cache.insert(uri); ++ } ++ ++ break; ++ } ++ } ++ return rv; ++} ++} // namespace ++ ++namespace { ++int before_frame_send_callback(nghttp2_session *session, ++ const nghttp2_frame *frame, void *user_data) { ++ if (frame->hd.type != NGHTTP2_HEADERS || ++ frame->headers.cat != NGHTTP2_HCAT_REQUEST) { ++ return 0; ++ } ++ auto req = static_cast( ++ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); ++ assert(req); ++ req->record_request_start_time(); ++ return 0; ++} ++ ++} // namespace ++ ++namespace { ++int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, ++ void *user_data) { ++ if (config.verbose) { ++ verbose_on_frame_send_callback(session, frame, user_data); ++ } ++ ++ if (frame->hd.type != NGHTTP2_HEADERS || ++ frame->headers.cat != NGHTTP2_HCAT_REQUEST) { ++ return 0; ++ } ++ ++ auto req = static_cast( ++ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); ++ if (!req) { ++ return 0; ++ } ++ ++ // If this request is using Expect/Continue, start its ContinueTimer. ++ if (req->continue_timer) { ++ req->continue_timer->start(); ++ } ++ ++ return 0; ++} ++} // namespace ++ ++namespace { ++int on_frame_not_send_callback(nghttp2_session *session, ++ const nghttp2_frame *frame, int lib_error_code, ++ void *user_data) { ++ if (frame->hd.type != NGHTTP2_HEADERS || ++ frame->headers.cat != NGHTTP2_HCAT_REQUEST) { ++ return 0; ++ } ++ ++ auto req = static_cast( ++ nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); ++ if (!req) { ++ return 0; ++ } ++ ++ std::cerr << "[ERROR] request " << req->uri ++ << " failed: " << nghttp2_strerror(lib_error_code) << std::endl; ++ ++ return 0; ++} ++} // namespace ++ ++namespace { ++int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, ++ uint32_t error_code, void *user_data) { ++ auto client = get_client(user_data); ++ auto req = static_cast( ++ nghttp2_session_get_stream_user_data(session, stream_id)); ++ ++ if (!req) { ++ return 0; ++ } ++ ++ // If this request is using Expect/Continue, stop its ContinueTimer. ++ if (req->continue_timer) { ++ req->continue_timer->stop(); ++ } ++ ++ update_html_parser(client, req, nullptr, 0, 1); ++ ++client->complete; ++ ++ if (client->all_requests_processed()) { ++ nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR); ++ } ++ ++ return 0; ++} ++} // namespace ++ ++struct RequestResult { ++ std::chrono::microseconds time; ++}; ++ ++namespace { ++void print_stats(const HttpClient &client) { ++ std::cout << "***** Statistics *****" << std::endl; ++ ++ std::vector reqs; ++ reqs.reserve(client.reqvec.size()); ++ for (const auto &req : client.reqvec) { ++ if (req->timing.state == RequestState::ON_COMPLETE) { ++ reqs.push_back(req.get()); ++ } ++ } ++ ++ std::sort(std::begin(reqs), std::end(reqs), ++ [](const Request *lhs, const Request *rhs) { ++ const auto <iming = lhs->timing; ++ const auto &rtiming = rhs->timing; ++ return ltiming.response_end_time < rtiming.response_end_time || ++ (ltiming.response_end_time == rtiming.response_end_time && ++ ltiming.request_start_time < rtiming.request_start_time); ++ }); ++ ++ std::cout << R"( ++Request timing: ++ responseEnd: the time when last byte of response was received ++ relative to connectEnd ++ requestStart: the time just before first byte of request was sent ++ relative to connectEnd. If '*' is shown, this was ++ pushed by server. ++ process: responseEnd - requestStart ++ code: HTTP status code ++ size: number of bytes received as response body without ++ inflation. ++ URI: request URI ++ ++see http://www.w3.org/TR/resource-timing/#processing-model ++ ++sorted by 'complete' ++ ++id responseEnd requestStart process code size request path)" ++ << std::endl; ++ ++ const auto &base = client.timing.connect_end_time; ++ for (const auto &req : reqs) { ++ auto response_end = std::chrono::duration_cast( ++ req->timing.response_end_time - base); ++ auto request_start = std::chrono::duration_cast( ++ req->timing.request_start_time - base); ++ auto total = std::chrono::duration_cast( ++ req->timing.response_end_time - req->timing.request_start_time); ++ auto pushed = req->stream_id % 2 == 0; ++ ++ std::cout << std::setw(3) << req->stream_id << " " << std::setw(11) ++ << ("+" + util::format_duration(response_end)) << " " ++ << (pushed ? "*" : " ") << std::setw(11) ++ << ("+" + util::format_duration(request_start)) << " " ++ << std::setw(8) << util::format_duration(total) << " " ++ << std::setw(4) << req->status << " " << std::setw(4) ++ << util::utos_unit(req->response_len) << " " ++ << req->make_reqpath() << std::endl; ++ } ++} ++} // namespace ++ ++#ifndef OPENSSL_NO_NEXTPROTONEG ++namespace { ++int client_select_next_proto_cb(SSL *ssl, unsigned char **out, ++ unsigned char *outlen, const unsigned char *in, ++ unsigned int inlen, void *arg) { ++ if (config.verbose) { ++ print_timer(); ++ std::cout << "[NPN] server offers:" << std::endl; ++ } ++ for (unsigned int i = 0; i < inlen; i += in[i] + 1) { ++ if (config.verbose) { ++ std::cout << " * "; ++ std::cout.write(reinterpret_cast(&in[i + 1]), in[i]); ++ std::cout << std::endl; ++ } ++ } ++ if (!util::select_h2(const_cast(out), outlen, in, ++ inlen)) { ++ print_protocol_nego_error(); ++ return SSL_TLSEXT_ERR_NOACK; ++ } ++ return SSL_TLSEXT_ERR_OK; ++} ++} // namespace ++#endif // !OPENSSL_NO_NEXTPROTONEG ++ ++namespace { ++int communicate( ++ const std::string &scheme, const std::string &host, uint16_t port, ++ std::vector< ++ std::tuple> ++ requests, ++ const nghttp2_session_callbacks *callbacks) { ++ int result = 0; ++ auto loop = EV_DEFAULT; ++ SSL_CTX *ssl_ctx = nullptr; ++ if (scheme == "https") { ++ ssl_ctx = SSL_CTX_new(TLS_client_method()); ++ if (!ssl_ctx) { ++ std::cerr << "[ERROR] Failed to create SSL_CTX: " ++ << ERR_error_string(ERR_get_error(), nullptr) << std::endl; ++ result = -1; ++ goto fin; ++ } ++ ++ auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | ++ SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION | ++ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; ++ ++#ifdef SSL_OP_ENABLE_KTLS ++ if (config.ktls) { ++ ssl_opts |= SSL_OP_ENABLE_KTLS; ++ } ++#endif // SSL_OP_ENABLE_KTLS ++ ++ SSL_CTX_set_options(ssl_ctx, ssl_opts); ++ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); ++ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); ++ ++ if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) { ++ std::cerr << "[WARNING] Could not load system trusted CA certificates: " ++ << ERR_error_string(ERR_get_error(), nullptr) << std::endl; ++ } ++ ++ if (nghttp2::tls::ssl_ctx_set_proto_versions( ++ ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION, ++ nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) { ++ std::cerr << "[ERROR] Could not set TLS versions" << std::endl; ++ result = -1; ++ goto fin; ++ } ++ ++ if (SSL_CTX_set_cipher_list(ssl_ctx, tls::DEFAULT_CIPHER_LIST) == 0) { ++ std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr) ++ << std::endl; ++ result = -1; ++ goto fin; ++ } ++ if (!config.keyfile.empty()) { ++ if (SSL_CTX_use_PrivateKey_file(ssl_ctx, config.keyfile.c_str(), ++ SSL_FILETYPE_PEM) != 1) { ++ std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr) ++ << std::endl; ++ result = -1; ++ goto fin; ++ } ++ } ++ if (!config.certfile.empty()) { ++ if (SSL_CTX_use_certificate_chain_file(ssl_ctx, ++ config.certfile.c_str()) != 1) { ++ std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr) ++ << std::endl; ++ result = -1; ++ goto fin; ++ } ++ } ++#ifndef OPENSSL_NO_NEXTPROTONEG ++ SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb, ++ nullptr); ++#endif // !OPENSSL_NO_NEXTPROTONEG ++ ++#if OPENSSL_VERSION_NUMBER >= 0x10002000L ++ auto proto_list = util::get_default_alpn(); ++ ++ SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size()); ++#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L ++ } ++ { ++ HttpClient client{callbacks, loop, ssl_ctx}; ++ ++ int32_t dep_stream_id = 0; ++ ++ if (!config.no_dep) { ++ dep_stream_id = anchors[ANCHOR_FOLLOWERS].stream_id; ++ } ++ ++ for (auto &req : requests) { ++ nghttp2_priority_spec pri_spec; ++ ++ nghttp2_priority_spec_init(&pri_spec, dep_stream_id, std::get<3>(req), 0); ++ ++ for (int i = 0; i < config.multiply; ++i) { ++ client.add_request(std::get<0>(req), std::get<1>(req), std::get<2>(req), ++ pri_spec); ++ } ++ } ++ client.update_hostport(); ++ ++ client.record_start_time(); ++ ++ if (client.resolve_host(host, port) != 0) { ++ goto fin; ++ } ++ ++ client.record_domain_lookup_end_time(); ++ ++ if (client.initiate_connection() != 0) { ++ std::cerr << "[ERROR] Could not connect to " << host << ", port " << port ++ << std::endl; ++ goto fin; ++ } ++ ++ ev_set_userdata(loop, &client); ++ ev_run(loop, 0); ++ ev_set_userdata(loop, nullptr); ++ ++#ifdef HAVE_JANSSON ++ if (!config.harfile.empty()) { ++ FILE *outfile; ++ if (config.harfile == "-") { ++ outfile = stdout; ++ } else { ++ outfile = fopen(config.harfile.c_str(), "wb"); ++ } ++ ++ if (outfile) { ++ client.output_har(outfile); ++ ++ if (outfile != stdout) { ++ fclose(outfile); ++ } ++ } else { ++ std::cerr << "Cannot open file " << config.harfile << ". " ++ << "har file could not be created." << std::endl; ++ } ++ } ++#endif // HAVE_JANSSON ++ ++ if (client.success != client.reqvec.size()) { ++ std::cerr << "Some requests were not processed. total=" ++ << client.reqvec.size() << ", processed=" << client.success ++ << std::endl; ++ } ++ if (config.stat) { ++ print_stats(client); ++ } ++ } ++fin: ++ if (ssl_ctx) { ++ SSL_CTX_free(ssl_ctx); ++ } ++ return result; ++} ++} // namespace ++ ++namespace { ++ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id, ++ uint8_t *buf, size_t length, uint32_t *data_flags, ++ nghttp2_data_source *source, void *user_data) { ++ int rv; ++ auto req = static_cast( ++ nghttp2_session_get_stream_user_data(session, stream_id)); ++ assert(req); ++ int fd = source->fd; ++ ssize_t nread; ++ ++ while ((nread = pread(fd, buf, length, req->data_offset)) == -1 && ++ errno == EINTR) ++ ; ++ ++ if (nread == -1) { ++ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; ++ } ++ ++ req->data_offset += nread; ++ ++ if (req->data_offset == req->data_length) { ++ *data_flags |= NGHTTP2_DATA_FLAG_EOF; ++ if (!config.trailer.empty()) { ++ std::vector nva; ++ nva.reserve(config.trailer.size()); ++ for (auto &kv : config.trailer) { ++ nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index)); ++ } ++ rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size()); ++ if (rv != 0) { ++ if (nghttp2_is_fatal(rv)) { ++ return NGHTTP2_ERR_CALLBACK_FAILURE; ++ } ++ } else { ++ *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM; ++ } ++ } ++ ++ return nread; ++ } ++ ++ if (req->data_offset > req->data_length || nread == 0) { ++ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; ++ } ++ ++ return nread; ++} ++} // namespace ++ ++namespace { ++int run(char **uris, int n) { ++ nghttp2_session_callbacks *callbacks; ++ ++ nghttp2_session_callbacks_new(&callbacks); ++ auto cbsdel = defer(nghttp2_session_callbacks_del, callbacks); ++ ++ nghttp2_session_callbacks_set_on_stream_close_callback( ++ callbacks, on_stream_close_callback); ++ ++ nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, ++ on_frame_recv_callback2); ++ ++ if (config.verbose) { ++ nghttp2_session_callbacks_set_on_invalid_frame_recv_callback( ++ callbacks, verbose_on_invalid_frame_recv_callback); ++ ++ nghttp2_session_callbacks_set_error_callback2(callbacks, ++ verbose_error_callback); ++ } ++ ++ nghttp2_session_callbacks_set_on_data_chunk_recv_callback( ++ callbacks, on_data_chunk_recv_callback); ++ ++ nghttp2_session_callbacks_set_on_begin_headers_callback( ++ callbacks, on_begin_headers_callback); ++ ++ nghttp2_session_callbacks_set_on_header_callback(callbacks, ++ on_header_callback); ++ ++ nghttp2_session_callbacks_set_before_frame_send_callback( ++ callbacks, before_frame_send_callback); ++ ++ nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, ++ on_frame_send_callback); ++ ++ nghttp2_session_callbacks_set_on_frame_not_send_callback( ++ callbacks, on_frame_not_send_callback); ++ ++ if (config.padding) { ++ nghttp2_session_callbacks_set_select_padding_callback( ++ callbacks, select_padding_callback); ++ } ++ ++ std::string prev_scheme; ++ std::string prev_host; ++ uint16_t prev_port = 0; ++ int failures = 0; ++ int data_fd = -1; ++ nghttp2_data_provider data_prd; ++ struct stat data_stat; ++ ++ if (!config.datafile.empty()) { ++ if (config.datafile == "-") { ++ if (fstat(0, &data_stat) == 0 && ++ (data_stat.st_mode & S_IFMT) == S_IFREG) { ++ // use STDIN if it is a regular file ++ data_fd = 0; ++ } else { ++ // copy the contents of STDIN to a temporary file ++ char tempfn[] = "/tmp/nghttp.temp.XXXXXX"; ++ data_fd = mkstemp(tempfn); ++ if (data_fd == -1) { ++ std::cerr << "[ERROR] Could not create a temporary file in /tmp" ++ << std::endl; ++ return 1; ++ } ++ if (unlink(tempfn) != 0) { ++ std::cerr << "[WARNING] failed to unlink temporary file:" << tempfn ++ << std::endl; ++ } ++ while (1) { ++ std::array buf; ++ ssize_t rret, wret; ++ while ((rret = read(0, buf.data(), buf.size())) == -1 && ++ errno == EINTR) ++ ; ++ if (rret == 0) ++ break; ++ if (rret == -1) { ++ std::cerr << "[ERROR] I/O error while reading from STDIN" ++ << std::endl; ++ return 1; ++ } ++ while ((wret = write(data_fd, buf.data(), rret)) == -1 && ++ errno == EINTR) ++ ; ++ if (wret != rret) { ++ std::cerr << "[ERROR] I/O error while writing to temporary file" ++ << std::endl; ++ return 1; ++ } ++ } ++ if (fstat(data_fd, &data_stat) == -1) { ++ close(data_fd); ++ std::cerr << "[ERROR] Could not stat temporary file" << std::endl; ++ return 1; ++ } ++ } ++ } else { ++ data_fd = open(config.datafile.c_str(), O_RDONLY | O_BINARY); ++ if (data_fd == -1) { ++ std::cerr << "[ERROR] Could not open file " << config.datafile ++ << std::endl; ++ return 1; ++ } ++ if (fstat(data_fd, &data_stat) == -1) { ++ close(data_fd); ++ std::cerr << "[ERROR] Could not stat file " << config.datafile ++ << std::endl; ++ return 1; ++ } ++ } ++ data_prd.source.fd = data_fd; ++ data_prd.read_callback = file_read_callback; ++ } ++ std::vector< ++ std::tuple> ++ requests; ++ ++ size_t next_weight_idx = 0; ++ ++ for (int i = 0; i < n; ++i) { ++ http_parser_url u{}; ++ auto uri = strip_fragment(uris[i]); ++ if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) { ++ ++next_weight_idx; ++ std::cerr << "[ERROR] Could not parse URI " << uri << std::endl; ++ continue; ++ } ++ if (!util::has_uri_field(u, UF_SCHEMA)) { ++ ++next_weight_idx; ++ std::cerr << "[ERROR] URI " << uri << " does not have scheme part" ++ << std::endl; ++ continue; ++ } ++ auto port = util::has_uri_field(u, UF_PORT) ++ ? u.port ++ : util::get_default_port(uri.c_str(), u); ++ auto host = decode_host(util::get_uri_field(uri.c_str(), u, UF_HOST)); ++ if (!util::fieldeq(uri.c_str(), u, UF_SCHEMA, prev_scheme.c_str()) || ++ host != prev_host || port != prev_port) { ++ if (!requests.empty()) { ++ if (communicate(prev_scheme, prev_host, prev_port, std::move(requests), ++ callbacks) != 0) { ++ ++failures; ++ } ++ requests.clear(); ++ } ++ prev_scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA).str(); ++ prev_host = std::move(host); ++ prev_port = port; ++ } ++ requests.emplace_back(uri, data_fd == -1 ? nullptr : &data_prd, ++ data_stat.st_size, config.weight[next_weight_idx++]); ++ } ++ if (!requests.empty()) { ++ if (communicate(prev_scheme, prev_host, prev_port, std::move(requests), ++ callbacks) != 0) { ++ ++failures; ++ } ++ } ++ return failures; ++} ++} // namespace ++ ++namespace { ++void print_version(std::ostream &out) { ++ out << "nghttp nghttp2/" NGHTTP2_VERSION << std::endl; ++} ++} // namespace ++ ++namespace { ++void print_usage(std::ostream &out) { ++ out << R"(Usage: nghttp [OPTIONS]... ... ++HTTP/2 client)" ++ << std::endl; ++} ++} // namespace ++ ++namespace { ++void print_help(std::ostream &out) { ++ print_usage(out); ++ out << R"( ++ Specify URI to access. ++Options: ++ -v, --verbose ++ Print debug information such as reception and ++ transmission of frames and name/value pairs. Specifying ++ this option multiple times increases verbosity. ++ -n, --null-out ++ Discard downloaded data. ++ -O, --remote-name ++ Save download data in the current directory. The ++ filename is derived from URI. If URI ends with '/', ++ 'index.html' is used as a filename. Not implemented ++ yet. ++ -t, --timeout= ++ Timeout each request after . Set 0 to disable ++ timeout. ++ -w, --window-bits= ++ Sets the stream level initial window size to 2**-1. ++ -W, --connection-window-bits= ++ Sets the connection level initial window size to ++ 2**-1. ++ -a, --get-assets ++ Download assets such as stylesheets, images and script ++ files linked from the downloaded resource. Only links ++ whose origins are the same with the linking resource ++ will be downloaded. nghttp prioritizes resources using ++ HTTP/2 dependency based priority. The priority order, ++ from highest to lowest, is html itself, css, javascript ++ and images. ++ -s, --stat Print statistics. ++ -H, --header=
++ Add a header to the requests. Example: -H':method: PUT' ++ --trailer=
++ Add a trailer header to the requests.
must not ++ include pseudo header field (header field name starting ++ with ':'). To send trailer, one must use -d option to ++ send request body. Example: --trailer 'foo: bar'. ++ --cert= ++ Use the specified client certificate file. The file ++ must be in PEM format. ++ --key= Use the client private key file. The file must be in ++ PEM format. ++ -d, --data= ++ Post FILE to server. If '-' is given, data will be read ++ from stdin. ++ -m, --multiply= ++ Request each URI times. By default, same URI is not ++ requested twice. This option disables it too. ++ -u, --upgrade ++ Perform HTTP Upgrade for HTTP/2. This option is ignored ++ if the request URI has https scheme. If -d is used, the ++ HTTP upgrade request is performed with OPTIONS method. ++ -p, --weight= ++ Sets weight of given URI. This option can be used ++ multiple times, and N-th -p option sets weight of N-th ++ URI in the command line. If the number of -p option is ++ less than the number of URI, the last -p option value is ++ repeated. If there is no -p option, default weight, 16, ++ is assumed. The valid value range is ++ [)" ++ << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT << R"(], inclusive. ++ -M, --peer-max-concurrent-streams= ++ Use as SETTINGS_MAX_CONCURRENT_STREAMS value of ++ remote endpoint as if it is received in SETTINGS frame. ++ Default: 100 ++ -c, --header-table-size= ++ Specify decoder header table size. If this option is ++ used multiple times, and the minimum value among the ++ given values except for last one is strictly less than ++ the last value, that minimum value is set in SETTINGS ++ frame payload before the last value, to simulate ++ multiple header table size change. ++ --encoder-header-table-size= ++ Specify encoder header table size. The decoder (server) ++ specifies the maximum dynamic table size it accepts. ++ Then the negotiated dynamic table size is the minimum of ++ this option value and the value which server specified. ++ -b, --padding= ++ Add at most bytes to a frame payload as padding. ++ Specify 0 to disable padding. ++ -r, --har= ++ Output HTTP transactions in HAR format. If '-' ++ is given, data is written to stdout. ++ --color Force colored log output. ++ --continuation ++ Send large header to test CONTINUATION. ++ --no-content-length ++ Don't send content-length header field. ++ --no-dep Don't send dependency based priority hint to server. ++ --hexdump Display the incoming traffic in hexadecimal (Canonical ++ hex+ASCII display). If SSL/TLS is used, decrypted data ++ are used. ++ --no-push Disable server push. ++ --max-concurrent-streams= ++ The number of concurrent pushed streams this client ++ accepts. ++ --expect-continue ++ Perform an Expect/Continue handshake: wait to send DATA ++ (up to a short timeout) until the server sends a 100 ++ Continue interim response. This option is ignored unless ++ combined with the -d option. ++ -y, --no-verify-peer ++ Suppress warning on server certificate verification ++ failure. ++ --ktls Enable ktls. ++ --no-rfc7540-pri ++ Disable RFC7540 priorities. ++ --version Display version information and exit. ++ -h, --help Display this help and exit. ++ ++-- ++ ++ The argument is an integer and an optional unit (e.g., 10K is ++ 10 * 1024). Units are K, M and G (powers of 1024). ++ ++ The argument is an integer and an optional unit (e.g., 1s ++ is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms ++ (hours, minutes, seconds and milliseconds, respectively). If a unit ++ is omitted, a second is used as unit.)" ++ << std::endl; ++} ++} // namespace ++ ++int main(int argc, char **argv) { ++ tls::libssl_init(); ++ ++ bool color = false; ++ while (1) { ++ static int flag = 0; ++ constexpr static option long_options[] = { ++ {"verbose", no_argument, nullptr, 'v'}, ++ {"null-out", no_argument, nullptr, 'n'}, ++ {"remote-name", no_argument, nullptr, 'O'}, ++ {"timeout", required_argument, nullptr, 't'}, ++ {"window-bits", required_argument, nullptr, 'w'}, ++ {"connection-window-bits", required_argument, nullptr, 'W'}, ++ {"get-assets", no_argument, nullptr, 'a'}, ++ {"stat", no_argument, nullptr, 's'}, ++ {"help", no_argument, nullptr, 'h'}, ++ {"header", required_argument, nullptr, 'H'}, ++ {"data", required_argument, nullptr, 'd'}, ++ {"multiply", required_argument, nullptr, 'm'}, ++ {"upgrade", no_argument, nullptr, 'u'}, ++ {"weight", required_argument, nullptr, 'p'}, ++ {"peer-max-concurrent-streams", required_argument, nullptr, 'M'}, ++ {"header-table-size", required_argument, nullptr, 'c'}, ++ {"padding", required_argument, nullptr, 'b'}, ++ {"har", required_argument, nullptr, 'r'}, ++ {"no-verify-peer", no_argument, nullptr, 'y'}, ++ {"cert", required_argument, &flag, 1}, ++ {"key", required_argument, &flag, 2}, ++ {"color", no_argument, &flag, 3}, ++ {"continuation", no_argument, &flag, 4}, ++ {"version", no_argument, &flag, 5}, ++ {"no-content-length", no_argument, &flag, 6}, ++ {"no-dep", no_argument, &flag, 7}, ++ {"trailer", required_argument, &flag, 9}, ++ {"hexdump", no_argument, &flag, 10}, ++ {"no-push", no_argument, &flag, 11}, ++ {"max-concurrent-streams", required_argument, &flag, 12}, ++ {"expect-continue", no_argument, &flag, 13}, ++ {"encoder-header-table-size", required_argument, &flag, 14}, ++ {"ktls", no_argument, &flag, 15}, ++ {"no-rfc7540-pri", no_argument, &flag, 16}, ++ {nullptr, 0, nullptr, 0}}; ++ int option_index = 0; ++ int c = ++ getopt_long(argc, argv, "M:Oab:c:d:m:np:r:hH:vst:uw:yW:", long_options, ++ &option_index); ++ if (c == -1) { ++ break; ++ } ++ switch (c) { ++ case 'M': { ++ // peer-max-concurrent-streams option ++ auto n = util::parse_uint(optarg); ++ if (n == -1) { ++ std::cerr << "-M: Bad option value: " << optarg << std::endl; ++ exit(EXIT_FAILURE); ++ } ++ config.peer_max_concurrent_streams = n; ++ break; ++ } ++ case 'O': ++ config.remote_name = true; ++ break; ++ case 'h': ++ print_help(std::cout); ++ exit(EXIT_SUCCESS); ++ case 'b': { ++ auto n = util::parse_uint(optarg); ++ if (n == -1) { ++ std::cerr << "-b: Bad option value: " << optarg << std::endl; ++ exit(EXIT_FAILURE); ++ } ++ config.padding = n; ++ break; ++ } ++ case 'n': ++ config.null_out = true; ++ break; ++ case 'p': { ++ auto n = util::parse_uint(optarg); ++ if (n == -1 || NGHTTP2_MIN_WEIGHT > n || n > NGHTTP2_MAX_WEIGHT) { ++ std::cerr << "-p: specify the integer in the range [" ++ << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT ++ << "], inclusive" << std::endl; ++ exit(EXIT_FAILURE); ++ } ++ config.weight.push_back(n); ++ break; ++ } ++ case 'r': ++#ifdef HAVE_JANSSON ++ config.harfile = optarg; ++#else // !HAVE_JANSSON ++ std::cerr << "[WARNING]: -r, --har option is ignored because\n" ++ << "the binary was not compiled with libjansson." << std::endl; ++#endif // !HAVE_JANSSON ++ break; ++ case 'v': ++ ++config.verbose; ++ break; ++ case 't': ++ config.timeout = util::parse_duration_with_unit(optarg); ++ if (config.timeout == std::numeric_limits::infinity()) { ++ std::cerr << "-t: bad timeout value: " << optarg << std::endl; ++ exit(EXIT_FAILURE); ++ } ++ break; ++ case 'u': ++ config.upgrade = true; ++ break; ++ case 'w': ++ case 'W': { ++ auto n = util::parse_uint(optarg); ++ if (n == -1 || n > 30) { ++ std::cerr << "-" << static_cast(c) ++ << ": specify the integer in the range [0, 30], inclusive" ++ << std::endl; ++ exit(EXIT_FAILURE); ++ } ++ if (c == 'w') { ++ config.window_bits = n; ++ } else { ++ config.connection_window_bits = n; ++ } ++ break; ++ } ++ case 'H': { ++ char *header = optarg; ++ // Skip first possible ':' in the header name ++ char *value = strchr(optarg + 1, ':'); ++ if (!value || (header[0] == ':' && header + 1 == value)) { ++ std::cerr << "-H: invalid header: " << optarg << std::endl; ++ exit(EXIT_FAILURE); ++ } ++ *value = 0; ++ value++; ++ while (isspace(*value)) { ++ value++; ++ } ++ if (*value == 0) { ++ // This could also be a valid case for suppressing a header ++ // similar to curl ++ std::cerr << "-H: invalid header - value missing: " << optarg ++ << std::endl; ++ exit(EXIT_FAILURE); ++ } ++ config.headers.emplace_back(header, value, false); ++ util::inp_strlower(config.headers.back().name); ++ break; ++ } ++ case 'a': ++#ifdef HAVE_LIBXML2 ++ config.get_assets = true; ++#else // !HAVE_LIBXML2 ++ std::cerr << "[WARNING]: -a, --get-assets option is ignored because\n" ++ << "the binary was not compiled with libxml2." << std::endl; ++#endif // !HAVE_LIBXML2 ++ break; ++ case 's': ++ config.stat = true; ++ break; ++ case 'd': ++ config.datafile = optarg; ++ break; ++ case 'm': { ++ auto n = util::parse_uint(optarg); ++ if (n == -1) { ++ std::cerr << "-m: Bad option value: " << optarg << std::endl; ++ exit(EXIT_FAILURE); ++ } ++ config.multiply = n; ++ break; ++ } ++ case 'c': { ++ auto n = util::parse_uint_with_unit(optarg); ++ if (n == -1) { ++ std::cerr << "-c: Bad option value: " << optarg << std::endl; ++ exit(EXIT_FAILURE); ++ } ++ if (n > std::numeric_limits::max()) { ++ std::cerr << "-c: Value too large. It should be less than or equal to " ++ << std::numeric_limits::max() << std::endl; ++ exit(EXIT_FAILURE); ++ } ++ config.header_table_size = n; ++ config.min_header_table_size = std::min(config.min_header_table_size, n); ++ break; ++ } ++ case 'y': ++ config.verify_peer = false; ++ break; ++ case '?': ++ util::show_candidates(argv[optind - 1], long_options); ++ exit(EXIT_FAILURE); ++ case 0: ++ switch (flag) { ++ case 1: ++ // cert option ++ config.certfile = optarg; ++ break; ++ case 2: ++ // key option ++ config.keyfile = optarg; ++ break; ++ case 3: ++ // color option ++ color = true; ++ break; ++ case 4: ++ // continuation option ++ config.continuation = true; ++ break; ++ case 5: ++ // version option ++ print_version(std::cout); ++ exit(EXIT_SUCCESS); ++ case 6: ++ // no-content-length option ++ config.no_content_length = true; ++ break; ++ case 7: ++ // no-dep option ++ config.no_dep = true; ++ break; ++ case 9: { ++ // trailer option ++ auto header = optarg; ++ auto value = strchr(optarg, ':'); ++ if (!value) { ++ std::cerr << "--trailer: invalid header: " << optarg << std::endl; ++ exit(EXIT_FAILURE); ++ } ++ *value = 0; ++ value++; ++ while (isspace(*value)) { ++ value++; ++ } ++ if (*value == 0) { ++ // This could also be a valid case for suppressing a header ++ // similar to curl ++ std::cerr << "--trailer: invalid header - value missing: " << optarg ++ << std::endl; ++ exit(EXIT_FAILURE); ++ } ++ config.trailer.emplace_back(header, value, false); ++ util::inp_strlower(config.trailer.back().name); ++ break; ++ } ++ case 10: ++ // hexdump option ++ config.hexdump = true; ++ break; ++ case 11: ++ // no-push option ++ config.no_push = true; ++ break; ++ case 12: { ++ // max-concurrent-streams option ++ auto n = util::parse_uint(optarg); ++ if (n == -1) { ++ std::cerr << "--max-concurrent-streams: Bad option value: " << optarg ++ << std::endl; ++ exit(EXIT_FAILURE); ++ } ++ config.max_concurrent_streams = n; ++ break; ++ } ++ case 13: ++ // expect-continue option ++ config.expect_continue = true; ++ break; ++ case 14: { ++ // encoder-header-table-size option ++ auto n = util::parse_uint_with_unit(optarg); ++ if (n == -1) { ++ std::cerr << "--encoder-header-table-size: Bad option value: " ++ << optarg << std::endl; ++ exit(EXIT_FAILURE); ++ } ++ if (n > std::numeric_limits::max()) { ++ std::cerr << "--encoder-header-table-size: Value too large. It " ++ "should be less than or equal to " ++ << std::numeric_limits::max() << std::endl; ++ exit(EXIT_FAILURE); ++ } ++ config.encoder_header_table_size = n; ++ break; ++ } ++ case 15: ++ // ktls option ++ config.ktls = true; ++ break; ++ case 16: ++ // no-rfc7540-pri option ++ config.no_rfc7540_pri = true; ++ break; ++ } ++ break; ++ default: ++ break; ++ } ++ } ++ ++ int32_t weight_to_fill; ++ if (config.weight.empty()) { ++ weight_to_fill = NGHTTP2_DEFAULT_WEIGHT; ++ } else { ++ weight_to_fill = config.weight.back(); ++ } ++ config.weight.insert(std::end(config.weight), argc - optind, weight_to_fill); ++ ++ // Find scheme overridden by extra header fields. ++ auto scheme_it = ++ std::find_if(std::begin(config.headers), std::end(config.headers), ++ [](const Header &nv) { return nv.name == ":scheme"; }); ++ if (scheme_it != std::end(config.headers)) { ++ config.scheme_override = (*scheme_it).value; ++ } ++ ++ // Find host and port overridden by extra header fields. ++ auto authority_it = ++ std::find_if(std::begin(config.headers), std::end(config.headers), ++ [](const Header &nv) { return nv.name == ":authority"; }); ++ if (authority_it == std::end(config.headers)) { ++ authority_it = ++ std::find_if(std::begin(config.headers), std::end(config.headers), ++ [](const Header &nv) { return nv.name == "host"; }); ++ } ++ ++ if (authority_it != std::end(config.headers)) { ++ // authority_it may looks like "host:port". ++ auto uri = "https://" + (*authority_it).value; ++ http_parser_url u{}; ++ if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) { ++ std::cerr << "[ERROR] Could not parse authority in " ++ << (*authority_it).name << ": " << (*authority_it).value ++ << std::endl; ++ exit(EXIT_FAILURE); ++ } ++ ++ config.host_override = util::get_uri_field(uri.c_str(), u, UF_HOST).str(); ++ if (util::has_uri_field(u, UF_PORT)) { ++ config.port_override = u.port; ++ } ++ } ++ ++ set_color_output(color || isatty(fileno(stdout))); ++ ++ nghttp2_option_set_peer_max_concurrent_streams( ++ config.http2_option, config.peer_max_concurrent_streams); ++ ++ if (config.encoder_header_table_size != -1) { ++ nghttp2_option_set_max_deflate_dynamic_table_size( ++ config.http2_option, config.encoder_header_table_size); ++ } ++ ++ struct sigaction act {}; ++ act.sa_handler = SIG_IGN; ++ sigaction(SIGPIPE, &act, nullptr); ++ reset_timer(); ++ return run(argv + optind, argc - optind); ++} ++ ++} // namespace nghttp2 ++ ++int main(int argc, char **argv) { ++ return nghttp2::run_app(nghttp2::main, argc, argv); ++} +diff --git a/src/nghttp_test.h b/src/nghttp_test.h +new file mode 100644 +index 0000000..7c3b2c4 +--- /dev/null ++++ b/src/nghttp_test.h +@@ -0,0 +1,310 @@ ++ /* ++ * nghttp2 - HTTP/2 C Library ++ * ++ * Copyright (c) 2015 Tatsuhiro Tsujikawa ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ */ ++#ifndef NGHTTP_H ++#define NGHTTP_H ++ ++#include "nghttp2_config.h" ++ ++#include ++#ifdef HAVE_SYS_SOCKET_H ++# include ++#endif // HAVE_SYS_SOCKET_H ++#ifdef HAVE_NETDB_H ++# include ++#endif // HAVE_NETDB_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++ ++#include ++ ++#include "llhttp.h" ++ ++#include "memchunk.h" ++#include "http2.h" ++#include "nghttp2_gzip.h" ++#include "template.h" ++ ++namespace nghttp2 { ++ ++class HtmlParser; ++ ++struct Config { ++ Config(); ++ ~Config(); ++ ++ Headers headers; ++ Headers trailer; ++ std::vector weight; ++ std::string certfile; ++ std::string keyfile; ++ std::string datafile; ++ std::string harfile; ++ std::string scheme_override; ++ std::string host_override; ++ nghttp2_option *http2_option; ++ int64_t header_table_size; ++ int64_t min_header_table_size; ++ int64_t encoder_header_table_size; ++ size_t padding; ++ size_t max_concurrent_streams; ++ ssize_t peer_max_concurrent_streams; ++ int multiply; ++ // milliseconds ++ ev_tstamp timeout; ++ int window_bits; ++ int connection_window_bits; ++ int verbose; ++ uint16_t port_override; ++ bool null_out; ++ bool remote_name; ++ bool get_assets; ++ bool stat; ++ bool upgrade; ++ bool continuation; ++ bool no_content_length; ++ bool no_dep; ++ bool hexdump; ++ bool no_push; ++ bool expect_continue; ++ bool verify_peer; ++ bool ktls; ++ bool no_rfc7540_pri; ++}; ++ ++enum class RequestState { INITIAL, ON_REQUEST, ON_RESPONSE, ON_COMPLETE }; ++ ++struct RequestTiming { ++ // The point in time when request is started to be sent. ++ // Corresponds to requestStart in Resource Timing TR. ++ std::chrono::steady_clock::time_point request_start_time; ++ // The point in time when first byte of response is received. ++ // Corresponds to responseStart in Resource Timing TR. ++ std::chrono::steady_clock::time_point response_start_time; ++ // The point in time when last byte of response is received. ++ // Corresponds to responseEnd in Resource Timing TR. ++ std::chrono::steady_clock::time_point response_end_time; ++ RequestState state; ++ RequestTiming() : state(RequestState::INITIAL) {} ++}; ++ ++struct Request; // forward declaration for ContinueTimer ++ ++struct ContinueTimer { ++ ContinueTimer(struct ev_loop *loop, Request *req); ++ ~ContinueTimer(); ++ ++ void start(); ++ void stop(); ++ ++ // Schedules an immediate run of the continue callback on the loop, if the ++ // callback has not already been run ++ void dispatch_continue(); ++ ++ struct ev_loop *loop; ++ ev_timer timer; ++}; ++ ++struct Request { ++ // For pushed request, |uri| is empty and |u| is zero-cleared. ++ Request(const std::string &uri, const http_parser_url &u, ++ const nghttp2_data_provider *data_prd, int64_t data_length, ++ const nghttp2_priority_spec &pri_spec, int level = 0); ++ ~Request(); ++ ++ void init_inflater(); ++ ++ void init_html_parser(); ++ int update_html_parser(const uint8_t *data, size_t len, int fin); ++ ++ std::string make_reqpath() const; ++ ++ bool is_ipv6_literal_addr() const; ++ ++ Headers::value_type *get_res_header(int32_t token); ++ Headers::value_type *get_req_header(int32_t token); ++ ++ void record_request_start_time(); ++ void record_response_start_time(); ++ void record_response_end_time(); ++ ++ // Returns scheme taking into account overridden scheme. ++ StringRef get_real_scheme() const; ++ // Returns request host, without port, taking into account ++ // overridden host. ++ StringRef get_real_host() const; ++ // Returns request port, taking into account overridden host, port, ++ // and scheme. ++ uint16_t get_real_port() const; ++ ++ Headers res_nva; ++ Headers req_nva; ++ std::string method; ++ // URI without fragment ++ std::string uri; ++ http_parser_url u; ++ nghttp2_priority_spec pri_spec; ++ RequestTiming timing; ++ int64_t data_length; ++ int64_t data_offset; ++ // Number of bytes received from server ++ int64_t response_len; ++ nghttp2_gzip *inflater; ++ std::unique_ptr html_parser; ++ const nghttp2_data_provider *data_prd; ++ size_t header_buffer_size; ++ int32_t stream_id; ++ int status; ++ // Recursion level: 0: first entity, 1: entity linked from first entity ++ int level; ++ http2::HeaderIndex res_hdidx; ++ // used for incoming PUSH_PROMISE ++ http2::HeaderIndex req_hdidx; ++ bool expect_final_response; ++ // only assigned if this request is using Expect/Continue ++ std::unique_ptr continue_timer; ++}; ++ ++struct SessionTiming { ++ // The point in time when operation was started. Corresponds to ++ // startTime in Resource Timing TR, but recorded in system clock time. ++ std::chrono::system_clock::time_point system_start_time; ++ // Same as above, but recorded in steady clock time. ++ std::chrono::steady_clock::time_point start_time; ++ // The point in time when DNS resolution was completed. Corresponds ++ // to domainLookupEnd in Resource Timing TR. ++ std::chrono::steady_clock::time_point domain_lookup_end_time; ++ // The point in time when connection was established or SSL/TLS ++ // handshake was completed. Corresponds to connectEnd in Resource ++ // Timing TR. ++ std::chrono::steady_clock::time_point connect_end_time; ++}; ++ ++enum class ClientState { IDLE, CONNECTED }; ++ ++struct HttpClient { ++ HttpClient(const nghttp2_session_callbacks *callbacks, struct ev_loop *loop, ++ SSL_CTX *ssl_ctx); ++ ~HttpClient(); ++ ++ bool need_upgrade() const; ++ int resolve_host(const std::string &host, uint16_t port); ++ int initiate_connection(); ++ void disconnect(); ++ ++ int noop(); ++ int read_clear(); ++ int write_clear(); ++ int connected(); ++ int tls_handshake(); ++ int read_tls(); ++ int write_tls(); ++ ++ int do_read(); ++ int do_write(); ++ ++ int on_upgrade_connect(); ++ int on_upgrade_read(const uint8_t *data, size_t len); ++ int on_read(const uint8_t *data, size_t len); ++ int on_write(); ++ ++ int connection_made(); ++ void connect_fail(); ++ void request_done(Request *req); ++ ++ void signal_write(); ++ ++ bool all_requests_processed() const; ++ void update_hostport(); ++ bool add_request(const std::string &uri, ++ const nghttp2_data_provider *data_prd, int64_t data_length, ++ const nghttp2_priority_spec &pri_spec, int level = 0); ++ ++ void record_start_time(); ++ void record_domain_lookup_end_time(); ++ void record_connect_end_time(); ++ ++#ifdef HAVE_JANSSON ++ void output_har(FILE *outfile); ++#endif // HAVE_JANSSON ++ ++ MemchunkPool mcpool; ++ DefaultMemchunks wb; ++ std::vector> reqvec; ++ // Insert path already added in reqvec to prevent multiple request ++ // for 1 resource. ++ std::set path_cache; ++ std::string scheme; ++ std::string host; ++ std::string hostport; ++ // Used for parse the HTTP upgrade response from server ++ std::unique_ptr htp; ++ SessionTiming timing; ++ ev_io wev; ++ ev_io rev; ++ ev_timer wt; ++ ev_timer rt; ++ ev_timer settings_timer; ++ std::function readfn, writefn; ++ std::function on_readfn; ++ std::function on_writefn; ++ nghttp2_session *session; ++ const nghttp2_session_callbacks *callbacks; ++ struct ev_loop *loop; ++ SSL_CTX *ssl_ctx; ++ SSL *ssl; ++ addrinfo *addrs; ++ addrinfo *next_addr; ++ addrinfo *cur_addr; ++ // The number of completed requests, including failed ones. ++ size_t complete; ++ // The number of requests that local endpoint received END_STREAM ++ // from peer. ++ size_t success; ++ // The length of settings_payload ++ size_t settings_payloadlen; ++ ClientState state; ++ // The HTTP status code of the response message of HTTP Upgrade. ++ unsigned int upgrade_response_status_code; ++ int fd; ++ // true if the response message of HTTP Upgrade request is fully ++ // received. It is not relevant the upgrade succeeds, or not. ++ bool upgrade_response_complete; ++ // SETTINGS payload sent as token68 in HTTP Upgrade ++ std::array settings_payload; ++ ++ enum { ERR_CONNECT_FAIL = -100 }; ++}; ++ ++} // namespace nghttp2 ++ ++#endif // NGHTTP_H +-- +2.33.0 + diff --git a/nghttp2.spec b/nghttp2.spec index d377189..35ae52f 100644 --- a/nghttp2.spec +++ b/nghttp2.spec @@ -1,11 +1,13 @@ Name: nghttp2 Version: 1.55.1 -Release: 1 +Release: 2 Summary: Contains the HTTP/2 client, server and proxy programs. License: MIT URL: https://nghttp2.org/ Source0: https://github.com/nghttp2/nghttp2/releases/download/v%{version}/%{name}-%{version}.tar.xz +Patch1: Store-a-copy-of-the-nghttp-Client.patch + BuildRequires: CUnit-devel c-ares-devel gcc-c++ libev-devel openssl-devel automake BuildRequires: python3-devel systemd-devel zlib-devel make libxml2-devel @@ -89,6 +91,12 @@ export "LD_LIBRARY_PATH=$RPM_BUILD_ROOT%{_libdir}:$LD_LIBRARY_PATH" %{_mandir}/man1/* %changelog +* Thu Sep 28 2023 dengjie <1171276417@qq.com> - 1.55.1-2 +- Type:requirements +- ID:NA +- SUG:NA +- DESC:update nghttp2 to 1.55.1 + * Tue Jul 25 2023 wangye - 1.55.1-1 - Type:requirements - ID:NA -- Gitee From e8c5acfaac6d62c058f038d3b06c14ecb0ae8c24 Mon Sep 17 00:00:00 2001 From: dengjie <1171276417@qq.com> Date: Thu, 28 Sep 2023 15:44:12 +0800 Subject: [PATCH 2/2] refactor the nghttp.h file based on libev dependency to libverto dependency --- Refactor-src-nghttp-h.patch | 177 ++++++++++++++++++++++++++++++++++++ nghttp2.spec | 11 ++- 2 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 Refactor-src-nghttp-h.patch diff --git a/Refactor-src-nghttp-h.patch b/Refactor-src-nghttp-h.patch new file mode 100644 index 0000000..343f23f --- /dev/null +++ b/Refactor-src-nghttp-h.patch @@ -0,0 +1,177 @@ +From 3a075e4f18a873807ef09398097f882d55154051 Mon Sep 17 00:00:00 2001 +From: dengjie <1171276417@qq.com> +Date: Thu, 28 Sep 2023 15:33:13 +0800 +Subject: [PATCH] refactor(src/nghttp.h): refactor the nghttp.h file based on + libev dependency to libverto dependency + +--- + src/nghttp.h | 94 ++++++++++++++++++++++++++++------------------------ + 1 file changed, 51 insertions(+), 43 deletions(-) + +diff --git a/src/nghttp.h b/src/nghttp.h +index a880414..64d9418 100644 +--- a/src/nghttp.h ++++ b/src/nghttp.h +@@ -44,6 +44,7 @@ + #include + + #include ++#include + + #include + +@@ -80,7 +81,7 @@ struct Config { + ssize_t peer_max_concurrent_streams; + int multiply; + // milliseconds +- ev_tstamp timeout; ++ double verto_timeout; + int window_bits; + int connection_window_bits; + int verbose; +@@ -120,7 +121,7 @@ struct RequestTiming { + struct Request; // forward declaration for ContinueTimer + + struct ContinueTimer { +- ContinueTimer(struct ev_loop *loop, Request *req); ++ ContinueTimer(verto_ctx *verto_loop, Request *req); + ~ContinueTimer(); + + void start(); +@@ -130,8 +131,8 @@ struct ContinueTimer { + // callback has not already been run + void dispatch_continue(); + +- struct ev_loop *loop; +- ev_timer timer; ++ verto_ctx *verto_loop; ++ verto_ev *verto_timer; + }; + + struct Request { +@@ -192,6 +193,8 @@ struct Request { + bool expect_final_response; + // only assigned if this request is using Expect/Continue + std::unique_ptr continue_timer; ++ // Pointer to the current Client ++ HttpClient *client; + }; + + struct SessionTiming { +@@ -212,51 +215,51 @@ struct SessionTiming { + enum class ClientState { IDLE, CONNECTED }; + + struct HttpClient { +- HttpClient(const nghttp2_session_callbacks *callbacks, struct ev_loop *loop, +- SSL_CTX *ssl_ctx); ++ HttpClient(const nghttp2_session_callbacks *callbacks, verto_ctx *verto_loop, SSL_CTX *ssl_ctx); ++ + ~HttpClient(); + +- bool need_upgrade() const; +- int resolve_host(const std::string &host, uint16_t port); +- int initiate_connection(); +- void disconnect(); ++ bool verto_need_upgrade() const; ++ int verto_resolve_host(const std::string &host, uint16_t port); ++ int verto_initiate_connection(); ++ void verto_disconnect(); + +- int noop(); +- int read_clear(); +- int write_clear(); +- int connected(); +- int tls_handshake(); +- int read_tls(); +- int write_tls(); ++ int verto_noop(); ++ int verto_read_clear(); ++ int verto_write_clear(); ++ int verto_connected(); ++ int verto_tls_handshake(); ++ int verto_read_tls(); ++ int verto_write_tls(); + +- int do_read(); +- int do_write(); ++ int verto_do_read(); ++ int verto_do_write(); + +- int on_upgrade_connect(); +- int on_upgrade_read(const uint8_t *data, size_t len); +- int on_read(const uint8_t *data, size_t len); +- int on_write(); ++ int verto_on_upgrade_connect(); ++ int verto_on_upgrade_read(const uint8_t *data, size_t len); ++ int verto_on_read(const uint8_t *data, size_t len); ++ int verto_on_write(); + +- int connection_made(); +- void connect_fail(); +- void request_done(Request *req); ++ int verto_connection_made(); ++ void verto_connect_fail(); ++ void verto_request_done(Request *req); + +- void signal_write(); ++ void verto_signal_write(); + +- bool all_requests_processed() const; +- void update_hostport(); +- bool add_request(const std::string &uri, ++ bool verto_all_requests_processed() const; ++ void verto_update_hostport(); ++ bool verto_add_request(const std::string &uri, + const nghttp2_data_provider *data_prd, int64_t data_length, + const nghttp2_priority_spec &pri_spec, int level = 0); + +- void record_start_time(); +- void record_domain_lookup_end_time(); +- void record_connect_end_time(); ++ void verto_record_start_time(); ++ void verto_record_domain_lookup_end_time(); ++ void verto_record_connect_end_time(); + + #ifdef HAVE_JANSSON + void output_har(FILE *outfile); + #endif // HAVE_JANSSON +- ++ + MemchunkPool mcpool; + DefaultMemchunks wb; + std::vector> reqvec; +@@ -269,17 +272,22 @@ struct HttpClient { + // Used for parse the HTTP upgrade response from server + std::unique_ptr htp; + SessionTiming timing; +- ev_io wev; +- ev_io rev; +- ev_timer wt; +- ev_timer rt; +- ev_timer settings_timer; +- std::function readfn, writefn; +- std::function on_readfn; +- std::function on_writefn; ++ ++ verto_ev *verto_wev; ++ verto_ev *verto_rev; ++ verto_ev *verto_wt; ++ verto_ev *verto_rt; ++ verto_ev *verto_settings_timer; ++ ++ std::function verto_readfn, verto_writefn; ++ std::function verto_on_readfn; ++ std::function verto_on_writefn; ++ + nghttp2_session *session; + const nghttp2_session_callbacks *callbacks; +- struct ev_loop *loop; ++ ++ verto_ctx *verto_loop; ++ + SSL_CTX *ssl_ctx; + SSL *ssl; + addrinfo *addrs; +-- +2.33.0 + diff --git a/nghttp2.spec b/nghttp2.spec index 35ae52f..7632b47 100644 --- a/nghttp2.spec +++ b/nghttp2.spec @@ -1,12 +1,13 @@ Name: nghttp2 Version: 1.55.1 -Release: 2 +Release: 3 Summary: Contains the HTTP/2 client, server and proxy programs. License: MIT URL: https://nghttp2.org/ Source0: https://github.com/nghttp2/nghttp2/releases/download/v%{version}/%{name}-%{version}.tar.xz Patch1: Store-a-copy-of-the-nghttp-Client.patch +Patch2: Refactor-src-nghttp-h.patch BuildRequires: CUnit-devel c-ares-devel gcc-c++ libev-devel openssl-devel automake BuildRequires: python3-devel systemd-devel zlib-devel make libxml2-devel @@ -91,11 +92,17 @@ export "LD_LIBRARY_PATH=$RPM_BUILD_ROOT%{_libdir}:$LD_LIBRARY_PATH" %{_mandir}/man1/* %changelog +* Thu Sep 28 2023 dengjie <1171276417@qq.com> - 1.55.1-3 +- Type:requirements +- ID:NA +- SUG:NA +- DESC:refactor the nghttp.h file based on libev dependency to libverto dependency + * Thu Sep 28 2023 dengjie <1171276417@qq.com> - 1.55.1-2 - Type:requirements - ID:NA - SUG:NA -- DESC:update nghttp2 to 1.55.1 +- DESC:Store a copy of the nghttp Client in nghttp_test.h and nghttp_test.cc files * Tue Jul 25 2023 wangye - 1.55.1-1 - Type:requirements -- Gitee