2 * nghttp2 - HTTP/2 C Library
4 * Copyright (c) 2013 Tatsuhiro Tsujikawa
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 #endif // HAVE_UNISTD_H
33 #endif // HAVE_FCNTL_H
34 #ifdef HAVE_NETINET_IN_H
35 #include <netinet/in.h>
36 #endif // HAVE_NETINET_IN_H
37 #include <netinet/tcp.h>
50 #include <openssl/err.h>
51 #include <openssl/conf.h>
55 #endif // HAVE_JANSSON
57 #include "app_helper.h"
58 #include "HtmlParser.h"
70 // The anchor stream nodes when --no-dep is not used. The stream ID =
71 // 1 is excluded since it is used as first stream in upgrade case. We
72 // follows the same dependency anchor nodes as Firefox does.
75 // stream ID this anchor depends on
76 int32_t dep_stream_id;
77 // .. with this weight.
81 // This is index into anchors. Firefox uses ANCHOR_FOLLOWERS for html
92 auto anchors = std::array<Anchor, 5>{{
93 {3, 0, 201}, {5, 0, 101}, {7, 0, 1}, {9, 7, 1}, {11, 3, 1},
98 : output_upper_thres(1024 * 1024), padding(0),
99 peer_max_concurrent_streams(NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS),
100 header_table_size(-1), weight(NGHTTP2_DEFAULT_WEIGHT), multiply(1),
101 timeout(0.), window_bits(-1), connection_window_bits(-1), verbose(0),
102 null_out(false), remote_name(false), get_assets(false), stat(false),
103 upgrade(false), continuation(false), no_content_length(false),
104 no_dep(false), hexdump(false), no_push(false) {
105 nghttp2_option_new(&http2_option);
106 nghttp2_option_set_peer_max_concurrent_streams(http2_option,
107 peer_max_concurrent_streams);
110 Config::~Config() { nghttp2_option_del(http2_option); }
117 void print_protocol_nego_error() {
118 std::cerr << "[ERROR] HTTP/2 protocol was not selected."
119 << " (nghttp2 expects " << NGHTTP2_PROTO_VERSION_ID << ")"
125 std::string strip_fragment(const char *raw_uri) {
127 for (end = raw_uri; *end && *end != '#'; ++end)
129 size_t len = end - raw_uri;
130 return std::string(raw_uri, len);
134 Request::Request(const std::string &uri, const http_parser_url &u,
135 const nghttp2_data_provider *data_prd, int64_t data_length,
136 const nghttp2_priority_spec &pri_spec, int level)
137 : uri(uri), u(u), pri_spec(pri_spec), data_length(data_length),
138 data_offset(0), response_len(0), inflater(nullptr), html_parser(nullptr),
139 data_prd(data_prd), stream_id(-1), status(0), level(level),
140 expect_final_response(false) {
141 http2::init_hdidx(res_hdidx);
142 http2::init_hdidx(req_hdidx);
145 Request::~Request() {
146 nghttp2_gzip_inflate_del(inflater);
150 void Request::init_inflater() {
152 rv = nghttp2_gzip_inflate_new(&inflater);
156 void Request::init_html_parser() { html_parser = new HtmlParser(uri); }
158 int Request::update_html_parser(const uint8_t *data, size_t len, int fin) {
162 return html_parser->parse_chunk(reinterpret_cast<const char *>(data), len,
166 std::string Request::make_reqpath() const {
167 std::string path = util::has_uri_field(u, UF_PATH)
168 ? util::get_uri_field(uri.c_str(), u, UF_PATH)
170 if (util::has_uri_field(u, UF_QUERY)) {
172 path.append(uri.c_str() + u.field_data[UF_QUERY].off,
173 u.field_data[UF_QUERY].len);
179 nghttp2_priority_spec resolve_dep(int res_type) {
180 nghttp2_priority_spec pri_spec;
183 nghttp2_priority_spec_default_init(&pri_spec);
193 anchor_id = anchors[ANCHOR_LEADERS].stream_id;
197 anchor_id = anchors[ANCHOR_UNBLOCKED].stream_id;
201 anchor_id = anchors[ANCHOR_FOLLOWERS].stream_id;
205 anchor_id = anchors[ANCHOR_FOLLOWERS].stream_id;
209 nghttp2_priority_spec_init(&pri_spec, anchor_id, weight, 0);
214 bool Request::is_ipv6_literal_addr() const {
215 if (util::has_uri_field(u, UF_HOST)) {
216 return memchr(uri.c_str() + u.field_data[UF_HOST].off, ':',
217 u.field_data[UF_HOST].len);
223 bool Request::response_pseudo_header_allowed(int16_t token) const {
224 if (!res_nva.empty() && res_nva.back().name.c_str()[0] != ':') {
228 case http2::HD__STATUS:
229 return res_hdidx[token] == -1;
235 bool Request::push_request_pseudo_header_allowed(int16_t token) const {
236 if (!req_nva.empty() && req_nva.back().name.c_str()[0] != ':') {
240 case http2::HD__AUTHORITY:
241 case http2::HD__METHOD:
242 case http2::HD__PATH:
243 case http2::HD__SCHEME:
244 return req_hdidx[token] == -1;
250 Headers::value_type *Request::get_res_header(int16_t token) {
251 auto idx = res_hdidx[token];
255 return &res_nva[idx];
258 Headers::value_type *Request::get_req_header(int16_t token) {
259 auto idx = req_hdidx[token];
263 return &req_nva[idx];
266 void Request::record_request_start_time() {
267 timing.state = RequestState::ON_REQUEST;
268 timing.request_start_time = get_time();
271 void Request::record_response_start_time() {
272 timing.state = RequestState::ON_RESPONSE;
273 timing.response_start_time = get_time();
276 void Request::record_response_end_time() {
277 timing.state = RequestState::ON_COMPLETE;
278 timing.response_end_time = get_time();
282 int htp_msg_begincb(http_parser *htp) {
283 if (config.verbose) {
285 std::cout << " HTTP Upgrade response" << std::endl;
292 int htp_statuscb(http_parser *htp, const char *at, size_t length) {
293 auto client = static_cast<HttpClient *>(htp->data);
294 client->upgrade_response_status_code = htp->status_code;
300 int htp_msg_completecb(http_parser *htp) {
301 auto client = static_cast<HttpClient *>(htp->data);
302 client->upgrade_response_complete = true;
308 http_parser_settings htp_hooks = {
309 htp_msg_begincb, // http_cb on_message_begin;
310 nullptr, // http_data_cb on_url;
311 htp_statuscb, // http_data_cb on_status;
312 nullptr, // http_data_cb on_header_field;
313 nullptr, // http_data_cb on_header_value;
314 nullptr, // http_cb on_headers_complete;
315 nullptr, // http_data_cb on_body;
316 htp_msg_completecb // http_cb on_message_complete;
321 int submit_request(HttpClient *client, const Headers &headers, Request *req) {
322 auto path = req->make_reqpath();
323 auto scheme = util::get_uri_field(req->uri.c_str(), req->u, UF_SCHEMA);
324 auto build_headers = Headers{{":method", req->data_prd ? "POST" : "GET"},
327 {":authority", client->hostport},
329 {"accept-encoding", "gzip, deflate"},
330 {"user-agent", "nghttp2/" NGHTTP2_VERSION}};
331 if (config.continuation) {
332 for (size_t i = 0; i < 6; ++i) {
333 build_headers.emplace_back("continuation-test-" + util::utos(i + 1),
334 std::string(4096, '-'));
337 auto num_initial_headers = build_headers.size();
338 if (!config.no_content_length && req->data_prd) {
339 build_headers.emplace_back("content-length", util::utos(req->data_length));
341 for (auto &kv : headers) {
343 for (i = 0; i < num_initial_headers; ++i) {
344 if (kv.name == build_headers[i].name) {
345 build_headers[i].value = kv.value;
349 if (i < num_initial_headers) {
353 build_headers.emplace_back(kv.name, kv.value, kv.no_index);
356 auto nva = std::vector<nghttp2_nv>();
357 nva.reserve(build_headers.size());
359 for (auto &kv : build_headers) {
360 nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
363 std::string trailer_names;
364 if (!config.trailer.empty()) {
365 trailer_names = config.trailer[0].name;
366 for (size_t i = 1; i < config.trailer.size(); ++i) {
367 trailer_names += ", ";
368 trailer_names += config.trailer[i].name;
370 nva.push_back(http2::make_nv_ls("trailer", trailer_names));
374 nghttp2_submit_request(client->session, &req->pri_spec, nva.data(),
375 nva.size(), req->data_prd, req);
377 std::cerr << "[ERROR] nghttp2_submit_request() returned error: "
378 << nghttp2_strerror(stream_id) << std::endl;
382 req->stream_id = stream_id;
383 client->request_done(req);
385 req->req_nva = std::move(build_headers);
392 void readcb(struct ev_loop *loop, ev_io *w, int revents) {
393 auto client = static_cast<HttpClient *>(w->data);
394 if (client->do_read() != 0) {
395 client->disconnect();
401 void writecb(struct ev_loop *loop, ev_io *w, int revents) {
402 auto client = static_cast<HttpClient *>(w->data);
403 auto rv = client->do_write();
404 if (rv == HttpClient::ERR_CONNECT_FAIL) {
405 client->connect_fail();
409 client->disconnect();
415 void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
416 auto client = static_cast<HttpClient *>(w->data);
417 std::cerr << "[ERROR] Timeout" << std::endl;
418 client->disconnect();
423 void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
424 auto client = static_cast<HttpClient *>(w->data);
425 ev_timer_stop(loop, w);
427 nghttp2_session_terminate_session(client->session, NGHTTP2_SETTINGS_TIMEOUT);
429 client->signal_write();
433 HttpClient::HttpClient(const nghttp2_session_callbacks *callbacks,
434 struct ev_loop *loop, SSL_CTX *ssl_ctx)
435 : session(nullptr), callbacks(callbacks), loop(loop), ssl_ctx(ssl_ctx),
436 ssl(nullptr), addrs(nullptr), next_addr(nullptr), cur_addr(nullptr),
437 complete(0), success(0), settings_payloadlen(0), state(ClientState::IDLE),
438 upgrade_response_status_code(0), fd(-1),
439 upgrade_response_complete(false) {
440 ev_io_init(&wev, writecb, 0, EV_WRITE);
441 ev_io_init(&rev, readcb, 0, EV_READ);
446 ev_timer_init(&wt, timeoutcb, 0., config.timeout);
447 ev_timer_init(&rt, timeoutcb, 0., config.timeout);
452 ev_timer_init(&settings_timer, settings_timeout_cb, 0., 10.);
454 settings_timer.data = this;
457 HttpClient::~HttpClient() {
467 bool HttpClient::need_upgrade() const {
468 return config.upgrade && scheme == "http";
471 int HttpClient::resolve_host(const std::string &host, uint16_t port) {
475 memset(&hints, 0, sizeof(hints));
476 hints.ai_family = AF_UNSPEC;
477 hints.ai_socktype = SOCK_STREAM;
478 hints.ai_protocol = 0;
479 hints.ai_flags = AI_ADDRCONFIG;
480 rv = getaddrinfo(host.c_str(), util::utos(port).c_str(), &hints, &addrs);
482 std::cerr << "[ERROR] getaddrinfo() failed: " << gai_strerror(rv)
486 if (addrs == nullptr) {
487 std::cerr << "[ERROR] No address returned" << std::endl;
494 int HttpClient::initiate_connection() {
499 cur_addr = next_addr;
500 next_addr = next_addr->ai_next;
501 fd = util::create_nonblock_socket(cur_addr->ai_family);
507 // We are establishing TLS connection.
508 ssl = SSL_new(ssl_ctx);
510 std::cerr << "[ERROR] SSL_new() failed: "
511 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
516 SSL_set_connect_state(ssl);
518 // If the user overrode the :authority or host header, use that
519 // value for the SNI extension
520 const char *host_string = nullptr;
521 auto i = std::find_if(std::begin(config.headers),
522 std::end(config.headers), [](const Header &nv) {
523 return ":authority" == nv.name || "host" == nv.name;
525 if (i != std::end(config.headers)) {
526 host_string = (*i).value.c_str();
528 host_string = host.c_str();
531 if (!util::numeric_host(host_string)) {
532 SSL_set_tlsext_host_name(ssl, host_string);
536 rv = connect(fd, cur_addr->ai_addr, cur_addr->ai_addrlen);
538 if (rv != 0 && errno != EINPROGRESS) {
554 writefn = &HttpClient::connected;
556 if (need_upgrade()) {
557 on_readfn = &HttpClient::on_upgrade_read;
558 on_writefn = &HttpClient::on_upgrade_connect;
560 on_readfn = &HttpClient::on_read;
561 on_writefn = &HttpClient::on_write;
564 ev_io_set(&rev, fd, EV_READ);
565 ev_io_set(&wev, fd, EV_WRITE);
567 ev_io_start(loop, &wev);
569 ev_timer_again(loop, &wt);
574 void HttpClient::disconnect() {
575 state = ClientState::IDLE;
577 ev_timer_stop(loop, &settings_timer);
579 ev_timer_stop(loop, &rt);
580 ev_timer_stop(loop, &wt);
582 ev_io_stop(loop, &rev);
583 ev_io_stop(loop, &wev);
585 nghttp2_session_del(session);
589 SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN);
597 shutdown(fd, SHUT_WR);
603 int HttpClient::read_clear() {
604 ev_timer_again(loop, &rt);
606 std::array<uint8_t, 8192> buf;
610 while ((nread = read(fd, buf.data(), buf.size())) == -1 && errno == EINTR)
613 if (errno == EAGAIN || errno == EWOULDBLOCK) {
623 if (on_readfn(*this, buf.data(), nread) != 0) {
631 int HttpClient::write_clear() {
632 ev_timer_again(loop, &rt);
635 if (wb.rleft() > 0) {
637 while ((nwrite = write(fd, wb.pos, wb.rleft())) == -1 && errno == EINTR)
640 if (errno == EAGAIN || errno == EWOULDBLOCK) {
641 ev_io_start(loop, &wev);
642 ev_timer_again(loop, &wt);
651 if (on_writefn(*this) != 0) {
654 if (wb.rleft() == 0) {
659 ev_io_stop(loop, &wev);
660 ev_timer_stop(loop, &wt);
665 int HttpClient::noop() { return 0; }
667 void HttpClient::connect_fail() {
668 if (state == ClientState::IDLE) {
669 std::cerr << "[ERROR] Could not connect to the address "
670 << util::numeric_name(cur_addr->ai_addr, cur_addr->ai_addrlen)
673 auto cur_state = state;
675 if (cur_state == ClientState::IDLE) {
676 if (initiate_connection() == 0) {
677 std::cerr << "Trying next address "
678 << util::numeric_name(cur_addr->ai_addr, cur_addr->ai_addrlen)
684 int HttpClient::connected() {
685 if (!util::check_socket_connected(fd)) {
686 return ERR_CONNECT_FAIL;
689 if (config.verbose) {
691 std::cout << " Connected" << std::endl;
694 state = ClientState::CONNECTED;
696 ev_io_start(loop, &rev);
697 ev_io_stop(loop, &wev);
699 ev_timer_again(loop, &rt);
700 ev_timer_stop(loop, &wt);
703 readfn = &HttpClient::tls_handshake;
704 writefn = &HttpClient::tls_handshake;
709 readfn = &HttpClient::read_clear;
710 writefn = &HttpClient::write_clear;
712 if (need_upgrade()) {
713 htp = make_unique<http_parser>();
714 http_parser_init(htp.get(), HTTP_RESPONSE);
720 if (connection_made() != 0) {
728 size_t populate_settings(nghttp2_settings_entry *iv) {
731 iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
734 iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
735 if (config.window_bits != -1) {
736 iv[1].value = (1 << config.window_bits) - 1;
738 iv[1].value = NGHTTP2_INITIAL_WINDOW_SIZE;
741 if (config.header_table_size >= 0) {
742 iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
743 iv[niv].value = config.header_table_size;
747 if (config.no_push) {
748 iv[niv].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
757 int HttpClient::on_upgrade_connect() {
759 record_connect_end_time();
760 assert(!reqvec.empty());
761 std::array<nghttp2_settings_entry, 16> iv;
762 size_t niv = populate_settings(iv.data());
763 assert(settings_payload.size() >= 8 * niv);
764 rv = nghttp2_pack_settings_payload(settings_payload.data(),
765 settings_payload.size(), iv.data(), niv);
769 settings_payloadlen = rv;
771 base64::encode(std::begin(settings_payload),
772 std::begin(settings_payload) + settings_payloadlen);
773 util::to_token68(token68);
775 if (reqvec[0]->data_prd) {
776 // If the request contains upload data, use OPTIONS * to upgrade
780 req += reqvec[0]->make_reqpath();
783 auto headers = Headers{{"Host", hostport},
784 {"Connection", "Upgrade, HTTP2-Settings"},
785 {"Upgrade", NGHTTP2_CLEARTEXT_PROTO_VERSION_ID},
786 {"HTTP2-Settings", token68},
788 {"User-Agent", "nghttp2/" NGHTTP2_VERSION}};
789 auto initial_headerslen = headers.size();
791 for (auto &kv : config.headers) {
793 for (i = 0; i < initial_headerslen; ++i) {
794 if (util::strieq(kv.name, headers[i].name)) {
795 headers[i].value = kv.value;
799 if (i < initial_headerslen) {
802 if (kv.name.size() != 0 && kv.name[0] != ':') {
803 headers.emplace_back(kv.name, kv.value, kv.no_index);
807 req += " HTTP/1.1\r\n";
809 for (auto &kv : headers) {
817 wb.write(req.c_str(), req.size());
819 if (config.verbose) {
821 std::cout << " HTTP Upgrade request\n" << req << std::endl;
824 // record request time if this is GET request
825 if (!reqvec[0]->data_prd) {
826 reqvec[0]->record_request_start_time();
829 on_writefn = &HttpClient::noop;
836 int HttpClient::on_upgrade_read(const uint8_t *data, size_t len) {
839 auto nread = http_parser_execute(htp.get(), &htp_hooks,
840 reinterpret_cast<const char *>(data), len);
842 if (config.verbose) {
843 std::cout.write(reinterpret_cast<const char *>(data), nread);
846 auto htperr = HTTP_PARSER_ERRNO(htp.get());
848 if (htperr != HPE_OK) {
849 std::cerr << "[ERROR] Failed to parse HTTP Upgrade response header: "
850 << "(" << http_errno_name(htperr) << ") "
851 << http_errno_description(htperr) << std::endl;
855 if (!upgrade_response_complete) {
859 if (config.verbose) {
860 std::cout << std::endl;
863 if (upgrade_response_status_code != 101) {
864 std::cerr << "[ERROR] HTTP Upgrade failed" << std::endl;
869 if (config.verbose) {
871 std::cout << " HTTP Upgrade success" << std::endl;
874 on_readfn = &HttpClient::on_read;
875 on_writefn = &HttpClient::on_write;
877 rv = connection_made();
882 // Read remaining data in the buffer because it is not notified
884 rv = on_readfn(*this, data + nread, len - nread);
892 int HttpClient::do_read() { return readfn(*this); }
893 int HttpClient::do_write() { return writefn(*this); }
895 int HttpClient::connection_made() {
898 if (!need_upgrade()) {
899 record_connect_end_time();
903 // Check NPN or ALPN result
904 const unsigned char *next_proto = nullptr;
905 unsigned int next_proto_len;
906 SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len);
907 for (int i = 0; i < 2; ++i) {
909 if (config.verbose) {
910 std::cout << "The negotiated protocol: ";
911 std::cout.write(reinterpret_cast<const char *>(next_proto),
913 std::cout << std::endl;
915 if (!util::check_h2_is_selected(next_proto, next_proto_len)) {
916 next_proto = nullptr;
920 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
921 SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
922 #else // OPENSSL_VERSION_NUMBER < 0x10002000L
924 #endif // OPENSSL_VERSION_NUMBER < 0x10002000L
927 print_protocol_nego_error();
932 rv = nghttp2_session_client_new2(&session, callbacks, this,
933 config.http2_option);
938 if (need_upgrade()) {
939 // Adjust stream user-data depending on the existence of upload
941 Request *stream_user_data = nullptr;
942 if (!reqvec[0]->data_prd) {
943 stream_user_data = reqvec[0].get();
945 rv = nghttp2_session_upgrade(session, settings_payload.data(),
946 settings_payloadlen, stream_user_data);
948 std::cerr << "[ERROR] nghttp2_session_upgrade() returned error: "
949 << nghttp2_strerror(rv) << std::endl;
952 if (stream_user_data) {
953 stream_user_data->stream_id = 1;
954 request_done(stream_user_data);
957 // If upgrade succeeds, the SETTINGS value sent with
958 // HTTP2-Settings header field has already been submitted to
960 if (!need_upgrade()) {
961 std::array<nghttp2_settings_entry, 16> iv;
962 auto niv = populate_settings(iv.data());
963 rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv.data(), niv);
968 if (!config.no_dep) {
969 // Create anchor stream nodes
970 nghttp2_priority_spec pri_spec;
972 for (auto &anchor : anchors) {
973 nghttp2_priority_spec_init(&pri_spec, anchor.dep_stream_id, anchor.weight,
975 rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, anchor.stream_id,
982 rv = nghttp2_session_set_next_stream_id(
983 session, anchors[ANCHOR_FOLLOWERS].stream_id + 2);
988 if (need_upgrade()) {
989 // Amend the priority because we cannot send priority in
991 auto &anchor = anchors[ANCHOR_FOLLOWERS];
992 nghttp2_priority_spec_init(&pri_spec, anchor.stream_id, config.weight, 0);
994 rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec);
999 } else if (need_upgrade() && config.weight != NGHTTP2_DEFAULT_WEIGHT) {
1000 // Amend the priority because we cannot send priority in
1001 // HTTP/1.1 Upgrade.
1002 nghttp2_priority_spec pri_spec;
1004 nghttp2_priority_spec_init(&pri_spec, 0, config.weight, 0);
1006 rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec);
1012 ev_timer_again(loop, &settings_timer);
1014 if (config.connection_window_bits != -1) {
1015 int32_t wininc = (1 << config.connection_window_bits) - 1 -
1016 NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
1017 rv = nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, wininc);
1022 // Adjust first request depending on the existence of the upload
1024 for (auto i = std::begin(reqvec) + (need_upgrade() && !reqvec[0]->data_prd);
1025 i != std::end(reqvec); ++i) {
1026 if (submit_request(this, config.headers, (*i).get()) != 0) {
1036 int HttpClient::on_read(const uint8_t *data, size_t len) {
1037 if (config.hexdump) {
1038 util::hexdump(stdout, data, len);
1041 auto rv = nghttp2_session_mem_recv(session, data, len);
1043 std::cerr << "[ERROR] nghttp2_session_mem_recv() returned error: "
1044 << nghttp2_strerror(rv) << std::endl;
1048 assert(static_cast<size_t>(rv) == len);
1050 if (nghttp2_session_want_read(session) == 0 &&
1051 nghttp2_session_want_write(session) == 0 && wb.rleft() == 0) {
1060 int HttpClient::on_write() {
1061 auto rv = nghttp2_session_send(session);
1063 std::cerr << "[ERROR] nghttp2_session_send() returned error: "
1064 << nghttp2_strerror(rv) << std::endl;
1068 if (nghttp2_session_want_read(session) == 0 &&
1069 nghttp2_session_want_write(session) == 0 && wb.rleft() == 0) {
1076 int HttpClient::tls_handshake() {
1077 ev_timer_again(loop, &rt);
1081 auto rv = SSL_do_handshake(ssl);
1088 auto err = SSL_get_error(ssl, rv);
1090 case SSL_ERROR_WANT_READ:
1091 ev_io_stop(loop, &wev);
1092 ev_timer_stop(loop, &wt);
1094 case SSL_ERROR_WANT_WRITE:
1095 ev_io_start(loop, &wev);
1096 ev_timer_again(loop, &wt);
1103 ev_io_stop(loop, &wev);
1104 ev_timer_stop(loop, &wt);
1106 readfn = &HttpClient::read_tls;
1107 writefn = &HttpClient::write_tls;
1109 if (connection_made() != 0) {
1116 int HttpClient::read_tls() {
1117 ev_timer_again(loop, &rt);
1121 std::array<uint8_t, 8192> buf;
1123 auto rv = SSL_read(ssl, buf.data(), buf.size());
1130 auto err = SSL_get_error(ssl, rv);
1132 case SSL_ERROR_WANT_READ:
1134 case SSL_ERROR_WANT_WRITE:
1135 // renegotiation started
1142 if (on_readfn(*this, buf.data(), rv) != 0) {
1148 int HttpClient::write_tls() {
1149 ev_timer_again(loop, &rt);
1154 if (wb.rleft() > 0) {
1155 auto rv = SSL_write(ssl, wb.pos, wb.rleft());
1162 auto err = SSL_get_error(ssl, rv);
1164 case SSL_ERROR_WANT_READ:
1165 // renegotiation started
1167 case SSL_ERROR_WANT_WRITE:
1168 ev_io_start(loop, &wev);
1169 ev_timer_again(loop, &wt);
1181 if (on_writefn(*this) != 0) {
1184 if (wb.rleft() == 0) {
1189 ev_io_stop(loop, &wev);
1190 ev_timer_stop(loop, &wt);
1195 void HttpClient::signal_write() { ev_io_start(loop, &wev); }
1197 bool HttpClient::all_requests_processed() const {
1198 return complete == reqvec.size();
1201 void HttpClient::update_hostport() {
1202 if (reqvec.empty()) {
1205 scheme = util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_SCHEMA);
1206 std::stringstream ss;
1207 if (reqvec[0]->is_ipv6_literal_addr()) {
1209 util::write_uri_field(ss, reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST);
1212 util::write_uri_field(ss, reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST);
1214 if (util::has_uri_field(reqvec[0]->u, UF_PORT) &&
1215 reqvec[0]->u.port !=
1216 util::get_default_port(reqvec[0]->uri.c_str(), reqvec[0]->u)) {
1217 ss << ":" << reqvec[0]->u.port;
1219 hostport = ss.str();
1222 bool HttpClient::add_request(const std::string &uri,
1223 const nghttp2_data_provider *data_prd,
1224 int64_t data_length,
1225 const nghttp2_priority_spec &pri_spec, int level) {
1227 memset(&u, 0, sizeof(u));
1228 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
1231 if (path_cache.count(uri)) {
1235 if (config.multiply == 1) {
1236 path_cache.insert(uri);
1240 make_unique<Request>(uri, u, data_prd, data_length, pri_spec, level));
1244 void HttpClient::record_start_time() {
1245 timing.system_start_time = std::chrono::system_clock::now();
1246 timing.start_time = get_time();
1249 void HttpClient::record_domain_lookup_end_time() {
1250 timing.domain_lookup_end_time = get_time();
1253 void HttpClient::record_connect_end_time() {
1254 timing.connect_end_time = get_time();
1257 void HttpClient::request_done(Request *req) {
1258 if (req->stream_id % 2 == 0) {
1264 void HttpClient::output_har(FILE *outfile) {
1265 static auto PAGE_ID = "page_0";
1267 auto root = json_object();
1268 auto log = json_object();
1269 json_object_set_new(root, "log", log);
1270 json_object_set_new(log, "version", json_string("1.2"));
1272 auto creator = json_object();
1273 json_object_set_new(log, "creator", creator);
1275 json_object_set_new(creator, "name", json_string("nghttp"));
1276 json_object_set_new(creator, "version", json_string(NGHTTP2_VERSION));
1278 auto pages = json_array();
1279 json_object_set_new(log, "pages", pages);
1281 auto page = json_object();
1282 json_array_append_new(pages, page);
1284 json_object_set_new(
1285 page, "startedDateTime",
1286 json_string(util::format_iso8601(timing.system_start_time).c_str()));
1287 json_object_set_new(page, "id", json_string(PAGE_ID));
1288 json_object_set_new(page, "title", json_string(""));
1290 json_object_set_new(page, "pageTimings", json_object());
1292 auto entries = json_array();
1293 json_object_set_new(log, "entries", entries);
1296 std::chrono::duration_cast<std::chrono::microseconds>(
1297 timing.domain_lookup_end_time - timing.start_time).count() /
1299 auto connect_delta =
1300 std::chrono::duration_cast<std::chrono::microseconds>(
1301 timing.connect_end_time - timing.domain_lookup_end_time).count() /
1304 for (size_t i = 0; i < reqvec.size(); ++i) {
1305 auto &req = reqvec[i];
1307 if (req->timing.state != RequestState::ON_COMPLETE) {
1311 auto entry = json_object();
1312 json_array_append_new(entries, entry);
1314 auto &req_timing = req->timing;
1316 (i == 0) ? timing.system_start_time
1317 : timing.system_start_time +
1318 std::chrono::duration_cast<
1319 std::chrono::system_clock::duration>(
1320 req_timing.request_start_time - timing.start_time);
1322 auto wait_delta = std::chrono::duration_cast<std::chrono::microseconds>(
1323 req_timing.response_start_time -
1324 req_timing.request_start_time).count() /
1326 auto receive_delta = std::chrono::duration_cast<std::chrono::microseconds>(
1327 req_timing.response_end_time -
1328 req_timing.response_start_time).count() /
1332 std::chrono::duration_cast<std::chrono::microseconds>(
1333 (i == 0) ? (req_timing.response_end_time - timing.start_time)
1334 : (req_timing.response_end_time -
1335 req_timing.request_start_time)).count() /
1338 json_object_set_new(
1339 entry, "startedDateTime",
1340 json_string(util::format_iso8601(request_time).c_str()));
1341 json_object_set_new(entry, "time", json_real(time_sum));
1343 auto request = json_object();
1344 json_object_set_new(entry, "request", request);
1346 auto method_ptr = http2::get_header(req->req_nva, ":method");
1348 const char *method = "GET";
1350 method = (*method_ptr).value.c_str();
1353 auto req_headers = json_array();
1354 json_object_set_new(request, "headers", req_headers);
1356 for (auto &nv : req->req_nva) {
1357 auto hd = json_object();
1358 json_array_append_new(req_headers, hd);
1360 json_object_set_new(hd, "name", json_string(nv.name.c_str()));
1361 json_object_set_new(hd, "value", json_string(nv.value.c_str()));
1364 json_object_set_new(request, "method", json_string(method));
1365 json_object_set_new(request, "url", json_string(req->uri.c_str()));
1366 json_object_set_new(request, "httpVersion", json_string("HTTP/2.0"));
1367 json_object_set_new(request, "cookies", json_array());
1368 json_object_set_new(request, "queryString", json_array());
1369 json_object_set_new(request, "headersSize", json_integer(-1));
1370 json_object_set_new(request, "bodySize", json_integer(-1));
1372 auto response = json_object();
1373 json_object_set_new(entry, "response", response);
1375 auto res_headers = json_array();
1376 json_object_set_new(response, "headers", res_headers);
1378 for (auto &nv : req->res_nva) {
1379 auto hd = json_object();
1380 json_array_append_new(res_headers, hd);
1382 json_object_set_new(hd, "name", json_string(nv.name.c_str()));
1383 json_object_set_new(hd, "value", json_string(nv.value.c_str()));
1386 json_object_set_new(response, "status", json_integer(req->status));
1387 json_object_set_new(response, "statusText", json_string(""));
1388 json_object_set_new(response, "httpVersion", json_string("HTTP/2.0"));
1389 json_object_set_new(response, "cookies", json_array());
1391 auto content = json_object();
1392 json_object_set_new(response, "content", content);
1394 json_object_set_new(content, "size", json_integer(req->response_len));
1396 auto content_type_ptr = http2::get_header(req->res_nva, "content-type");
1398 const char *content_type = "";
1399 if (content_type_ptr) {
1400 content_type = content_type_ptr->value.c_str();
1403 json_object_set_new(content, "mimeType", json_string(content_type));
1405 json_object_set_new(response, "redirectURL", json_string(""));
1406 json_object_set_new(response, "headersSize", json_integer(-1));
1407 json_object_set_new(response, "bodySize", json_integer(-1));
1409 json_object_set_new(entry, "cache", json_object());
1411 auto timings = json_object();
1412 json_object_set_new(entry, "timings", timings);
1414 auto dns_timing = (i == 0) ? dns_delta : 0;
1415 auto connect_timing = (i == 0) ? connect_delta : 0;
1417 json_object_set_new(timings, "dns", json_real(dns_timing));
1418 json_object_set_new(timings, "connect", json_real(connect_timing));
1420 json_object_set_new(timings, "blocked", json_real(0.0));
1421 json_object_set_new(timings, "send", json_real(0.0));
1422 json_object_set_new(timings, "wait", json_real(wait_delta));
1423 json_object_set_new(timings, "receive", json_real(receive_delta));
1425 json_object_set_new(entry, "pageref", json_string(PAGE_ID));
1428 json_dumpf(root, outfile, JSON_PRESERVE_ORDER | JSON_INDENT(2));
1431 #endif // HAVE_JANSSON
1434 void update_html_parser(HttpClient *client, Request *req, const uint8_t *data,
1435 size_t len, int fin) {
1436 if (!req->html_parser) {
1439 req->update_html_parser(data, len, fin);
1441 for (auto &p : req->html_parser->get_links()) {
1442 auto uri = strip_fragment(p.first.c_str());
1443 auto res_type = p.second;
1446 memset(&u, 0, sizeof(u));
1447 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) == 0 &&
1448 util::fieldeq(uri.c_str(), u, req->uri.c_str(), req->u, UF_SCHEMA) &&
1449 util::fieldeq(uri.c_str(), u, req->uri.c_str(), req->u, UF_HOST) &&
1450 util::porteq(uri.c_str(), u, req->uri.c_str(), req->u)) {
1451 // No POST data for assets
1452 auto pri_spec = resolve_dep(res_type);
1454 if (client->add_request(uri, nullptr, 0, pri_spec, req->level + 1)) {
1456 submit_request(client, config.headers, client->reqvec.back().get());
1460 req->html_parser->clear_links();
1465 HttpClient *get_client(void *user_data) {
1466 return static_cast<HttpClient *>(user_data);
1471 int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
1472 int32_t stream_id, const uint8_t *data,
1473 size_t len, void *user_data) {
1474 auto client = get_client(user_data);
1475 auto req = static_cast<Request *>(
1476 nghttp2_session_get_stream_user_data(session, stream_id));
1482 if (config.verbose >= 2) {
1483 verbose_on_data_chunk_recv_callback(session, flags, stream_id, data, len,
1487 req->response_len += len;
1489 if (req->inflater) {
1491 const size_t MAX_OUTLEN = 4096;
1492 std::array<uint8_t, MAX_OUTLEN> out;
1493 size_t outlen = MAX_OUTLEN;
1496 nghttp2_gzip_inflate(req->inflater, out.data(), &outlen, data, &tlen);
1498 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
1499 NGHTTP2_INTERNAL_ERROR);
1503 if (!config.null_out) {
1504 std::cout.write(reinterpret_cast<const char *>(out.data()), outlen);
1507 update_html_parser(client, req, out.data(), outlen, 0);
1515 if (!config.null_out) {
1516 std::cout.write(reinterpret_cast<const char *>(data), len);
1519 update_html_parser(client, req, data, len, 0);
1526 ssize_t select_padding_callback(nghttp2_session *session,
1527 const nghttp2_frame *frame, size_t max_payload,
1529 return std::min(max_payload, frame->hd.length + config.padding);
1534 void check_response_header(nghttp2_session *session, Request *req) {
1537 req->expect_final_response = false;
1539 auto status_hd = req->get_res_header(http2::HD__STATUS);
1542 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
1543 NGHTTP2_PROTOCOL_ERROR);
1547 auto status = http2::parse_http_status_code(status_hd->value);
1549 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
1550 NGHTTP2_PROTOCOL_ERROR);
1554 req->status = status;
1556 for (auto &nv : req->res_nva) {
1557 if ("content-encoding" == nv.name) {
1558 gzip = util::strieq_l("gzip", nv.value) ||
1559 util::strieq_l("deflate", nv.value);
1564 if (req->status / 100 == 1) {
1565 req->expect_final_response = true;
1567 req->res_nva.clear();
1568 http2::init_hdidx(req->res_hdidx);
1573 if (!req->inflater) {
1574 req->init_inflater();
1577 if (config.get_assets && req->level == 0) {
1578 if (!req->html_parser) {
1579 req->init_html_parser();
1586 int on_begin_headers_callback(nghttp2_session *session,
1587 const nghttp2_frame *frame, void *user_data) {
1588 auto client = get_client(user_data);
1589 switch (frame->hd.type) {
1590 case NGHTTP2_HEADERS: {
1591 auto req = static_cast<Request *>(
1592 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1597 switch (frame->headers.cat) {
1598 case NGHTTP2_HCAT_RESPONSE:
1599 case NGHTTP2_HCAT_PUSH_RESPONSE:
1600 req->record_response_start_time();
1608 case NGHTTP2_PUSH_PROMISE: {
1609 auto stream_id = frame->push_promise.promised_stream_id;
1611 memset(&u, 0, sizeof(u));
1612 // TODO Set pri and level
1613 nghttp2_priority_spec pri_spec;
1615 nghttp2_priority_spec_default_init(&pri_spec);
1617 auto req = make_unique<Request>("", u, nullptr, 0, pri_spec);
1618 req->stream_id = stream_id;
1620 nghttp2_session_set_stream_user_data(session, stream_id, req.get());
1622 client->request_done(req.get());
1623 req->record_request_start_time();
1624 client->reqvec.push_back(std::move(req));
1634 int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
1635 const uint8_t *name, size_t namelen,
1636 const uint8_t *value, size_t valuelen, uint8_t flags,
1638 if (config.verbose) {
1639 verbose_on_header_callback(session, frame, name, namelen, value, valuelen,
1643 switch (frame->hd.type) {
1644 case NGHTTP2_HEADERS: {
1645 auto req = static_cast<Request *>(
1646 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1652 /* ignore trailer header */
1653 if (frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
1654 !req->expect_final_response) {
1658 auto token = http2::lookup_token(name, namelen);
1660 http2::index_header(req->res_hdidx, token, req->res_nva.size());
1661 http2::add_header(req->res_nva, name, namelen, value, valuelen,
1662 flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
1665 case NGHTTP2_PUSH_PROMISE: {
1666 auto req = static_cast<Request *>(nghttp2_session_get_stream_user_data(
1667 session, frame->push_promise.promised_stream_id));
1673 auto token = http2::lookup_token(name, namelen);
1675 http2::index_header(req->req_hdidx, token, req->req_nva.size());
1676 http2::add_header(req->req_nva, name, namelen, value, valuelen,
1677 flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
1686 int on_frame_recv_callback2(nghttp2_session *session,
1687 const nghttp2_frame *frame, void *user_data) {
1690 if (config.verbose) {
1691 verbose_on_frame_recv_callback(session, frame, user_data);
1694 auto client = get_client(user_data);
1695 switch (frame->hd.type) {
1696 case NGHTTP2_DATA: {
1697 auto req = static_cast<Request *>(
1698 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1704 if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
1705 req->record_response_end_time();
1711 case NGHTTP2_HEADERS: {
1712 auto req = static_cast<Request *>(
1713 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1714 // If this is the HTTP Upgrade with OPTIONS method to avoid POST,
1721 switch (frame->headers.cat) {
1722 case NGHTTP2_HCAT_RESPONSE:
1723 case NGHTTP2_HCAT_PUSH_RESPONSE:
1724 check_response_header(session, req);
1726 case NGHTTP2_HCAT_HEADERS:
1727 if (req->expect_final_response) {
1728 check_response_header(session, req);
1731 if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
1732 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
1733 frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
1741 if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
1742 req->record_response_end_time();
1748 case NGHTTP2_SETTINGS:
1749 if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
1752 ev_timer_stop(client->loop, &client->settings_timer);
1754 case NGHTTP2_PUSH_PROMISE: {
1755 auto req = static_cast<Request *>(nghttp2_session_get_stream_user_data(
1756 session, frame->push_promise.promised_stream_id));
1760 auto scheme = req->get_req_header(http2::HD__SCHEME);
1761 auto authority = req->get_req_header(http2::HD__AUTHORITY);
1762 auto path = req->get_req_header(http2::HD__PATH);
1765 authority = req->get_req_header(http2::HD_HOST);
1768 // libnghttp2 guarantees :scheme, :method, :path and (:authority |
1769 // host) exist and non-empty.
1770 if (path->value[0] != '/') {
1771 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
1772 frame->push_promise.promised_stream_id,
1773 NGHTTP2_PROTOCOL_ERROR);
1776 std::string uri = scheme->value;
1778 uri += authority->value;
1781 memset(&u, 0, sizeof(u));
1782 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
1783 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
1784 frame->push_promise.promised_stream_id,
1785 NGHTTP2_PROTOCOL_ERROR);
1791 if (client->path_cache.count(uri)) {
1792 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
1793 frame->push_promise.promised_stream_id,
1798 if (config.multiply == 1) {
1799 client->path_cache.insert(uri);
1810 int before_frame_send_callback(nghttp2_session *session,
1811 const nghttp2_frame *frame, void *user_data) {
1812 if (frame->hd.type != NGHTTP2_HEADERS ||
1813 frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
1816 auto req = static_cast<Request *>(
1817 nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
1819 req->record_request_start_time();
1826 int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
1827 uint32_t error_code, void *user_data) {
1828 auto client = get_client(user_data);
1829 auto req = static_cast<Request *>(
1830 nghttp2_session_get_stream_user_data(session, stream_id));
1836 update_html_parser(client, req, nullptr, 0, 1);
1839 if (client->all_requests_processed()) {
1840 nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
1847 struct RequestResult {
1848 std::chrono::microseconds time;
1852 void print_stats(const HttpClient &client) {
1853 std::cout << "***** Statistics *****" << std::endl;
1855 std::vector<Request *> reqs;
1856 reqs.reserve(client.reqvec.size());
1857 for (const auto &req : client.reqvec) {
1858 if (req->timing.state == RequestState::ON_COMPLETE) {
1859 reqs.push_back(req.get());
1863 std::sort(std::begin(reqs), std::end(reqs),
1864 [](const Request *lhs, const Request *rhs) {
1865 const auto <iming = lhs->timing;
1866 const auto &rtiming = rhs->timing;
1867 return ltiming.response_end_time < rtiming.response_end_time ||
1868 (ltiming.response_end_time == rtiming.response_end_time &&
1869 ltiming.request_start_time < rtiming.request_start_time);
1874 responseEnd: the time when last byte of response was received
1875 relative to connectEnd
1876 requestStart: the time just before first byte of request was sent
1877 relative to connectEnd. If '*' is shown, this was
1879 process: responseEnd - requestStart
1880 code: HTTP status code
1881 size: number of bytes received as response body without
1885 see http://www.w3.org/TR/resource-timing/#processing-model
1887 sorted by 'complete'
1889 id responseEnd requestStart process code size request path)" << std::endl;
1891 const auto &base = client.timing.connect_end_time;
1892 for (const auto &req : reqs) {
1893 auto response_end = std::chrono::duration_cast<std::chrono::microseconds>(
1894 req->timing.response_end_time - base);
1895 auto request_start = std::chrono::duration_cast<std::chrono::microseconds>(
1896 req->timing.request_start_time - base);
1897 auto total = std::chrono::duration_cast<std::chrono::microseconds>(
1898 req->timing.response_end_time - req->timing.request_start_time);
1899 auto pushed = req->stream_id % 2 == 0;
1901 std::cout << std::setw(3) << req->stream_id << " " << std::setw(11)
1902 << ("+" + util::format_duration(response_end)) << " "
1903 << (pushed ? "*" : " ") << std::setw(11)
1904 << ("+" + util::format_duration(request_start)) << " "
1905 << std::setw(8) << util::format_duration(total) << " "
1906 << std::setw(4) << req->status << " " << std::setw(4)
1907 << util::utos_with_unit(req->response_len) << " "
1908 << req->make_reqpath() << std::endl;
1914 int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
1915 unsigned char *outlen, const unsigned char *in,
1916 unsigned int inlen, void *arg) {
1917 if (config.verbose) {
1919 std::cout << "[NPN] server offers:" << std::endl;
1921 for (unsigned int i = 0; i < inlen; i += in [i] + 1) {
1922 if (config.verbose) {
1924 std::cout.write(reinterpret_cast<const char *>(&in[i + 1]), in[i]);
1925 std::cout << std::endl;
1928 if (!util::select_h2(const_cast<const unsigned char **>(out), outlen, in,
1930 print_protocol_nego_error();
1931 return SSL_TLSEXT_ERR_NOACK;
1933 return SSL_TLSEXT_ERR_OK;
1938 // Recommended general purpose "Intermediate compatibility" cipher by
1941 // https://wiki.mozilla.org/Security/Server_Side_TLS
1942 const char *const CIPHER_LIST =
1943 "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-"
1944 "AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:"
1945 "DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-"
1946 "AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-"
1947 "AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-"
1948 "AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:"
1949 "DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-"
1950 "SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-"
1951 "SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!"
1952 "aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA";
1957 const std::string &scheme, const std::string &host, uint16_t port,
1958 std::vector<std::tuple<std::string, nghttp2_data_provider *, int64_t>>
1959 requests, const nghttp2_session_callbacks *callbacks) {
1961 auto loop = EV_DEFAULT;
1962 SSL_CTX *ssl_ctx = nullptr;
1963 if (scheme == "https") {
1964 ssl_ctx = SSL_CTX_new(SSLv23_client_method());
1966 std::cerr << "[ERROR] Failed to create SSL_CTX: "
1967 << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
1971 SSL_CTX_set_options(ssl_ctx,
1972 SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
1973 SSL_OP_NO_COMPRESSION |
1974 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
1975 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
1976 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
1977 if (SSL_CTX_set_cipher_list(ssl_ctx, CIPHER_LIST) == 0) {
1978 std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
1983 if (!config.keyfile.empty()) {
1984 if (SSL_CTX_use_PrivateKey_file(ssl_ctx, config.keyfile.c_str(),
1985 SSL_FILETYPE_PEM) != 1) {
1986 std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
1992 if (!config.certfile.empty()) {
1993 if (SSL_CTX_use_certificate_chain_file(ssl_ctx,
1994 config.certfile.c_str()) != 1) {
1995 std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
2001 SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb,
2004 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
2005 auto proto_list = util::get_default_alpn();
2007 SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
2008 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
2011 HttpClient client{callbacks, loop, ssl_ctx};
2013 nghttp2_priority_spec pri_spec;
2014 int32_t dep_stream_id = 0;
2016 if (!config.no_dep) {
2017 dep_stream_id = anchors[ANCHOR_FOLLOWERS].stream_id;
2020 nghttp2_priority_spec_init(&pri_spec, dep_stream_id, config.weight, 0);
2022 for (auto req : requests) {
2023 for (int i = 0; i < config.multiply; ++i) {
2024 client.add_request(std::get<0>(req), std::get<1>(req), std::get<2>(req),
2028 client.update_hostport();
2030 client.record_start_time();
2032 if (client.resolve_host(host, port) != 0) {
2036 client.record_domain_lookup_end_time();
2038 if (client.initiate_connection() != 0) {
2044 if (!config.harfile.empty()) {
2046 if (config.harfile == "-") {
2049 outfile = fopen(config.harfile.c_str(), "wb");
2053 client.output_har(outfile);
2055 if (outfile != stdout) {
2059 std::cerr << "Cannot open file " << config.harfile << ". "
2060 << "har file could not be created." << std::endl;
2063 #endif // HAVE_JANSSON
2065 if (client.success != client.reqvec.size()) {
2066 std::cerr << "Some requests were not processed. total="
2067 << client.reqvec.size() << ", processed=" << client.success
2071 print_stats(client);
2076 SSL_CTX_free(ssl_ctx);
2083 ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
2084 uint8_t *buf, size_t length, uint32_t *data_flags,
2085 nghttp2_data_source *source, void *user_data) {
2087 auto req = static_cast<Request *>(
2088 nghttp2_session_get_stream_user_data(session, stream_id));
2090 int fd = source->fd;
2093 while ((nread = pread(fd, buf, length, req->data_offset)) == -1 &&
2098 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
2102 *data_flags |= NGHTTP2_DATA_FLAG_EOF;
2103 if (!config.trailer.empty()) {
2104 std::vector<nghttp2_nv> nva;
2105 nva.reserve(config.trailer.size());
2106 for (auto &kv : config.trailer) {
2107 nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
2109 rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size());
2111 if (nghttp2_is_fatal(rv)) {
2112 return NGHTTP2_ERR_CALLBACK_FAILURE;
2115 *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
2119 req->data_offset += nread;
2127 ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
2128 size_t length, int flags, void *user_data) {
2129 auto client = static_cast<HttpClient *>(user_data);
2130 auto &wb = client->wb;
2132 if (wb.wleft() == 0) {
2133 return NGHTTP2_ERR_WOULDBLOCK;
2136 return wb.write(data, length);
2141 int run(char **uris, int n) {
2142 nghttp2_session_callbacks *callbacks;
2144 nghttp2_session_callbacks_new(&callbacks);
2145 auto cbsdel = defer(nghttp2_session_callbacks_del, callbacks);
2147 nghttp2_session_callbacks_set_on_stream_close_callback(
2148 callbacks, on_stream_close_callback);
2150 nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
2151 on_frame_recv_callback2);
2153 if (config.verbose) {
2154 nghttp2_session_callbacks_set_on_frame_send_callback(
2155 callbacks, verbose_on_frame_send_callback);
2157 nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
2158 callbacks, verbose_on_invalid_frame_recv_callback);
2161 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
2162 callbacks, on_data_chunk_recv_callback);
2164 nghttp2_session_callbacks_set_on_begin_headers_callback(
2165 callbacks, on_begin_headers_callback);
2167 nghttp2_session_callbacks_set_on_header_callback(callbacks,
2168 on_header_callback);
2170 nghttp2_session_callbacks_set_before_frame_send_callback(
2171 callbacks, before_frame_send_callback);
2173 nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
2175 if (config.padding) {
2176 nghttp2_session_callbacks_set_select_padding_callback(
2177 callbacks, select_padding_callback);
2180 std::string prev_scheme;
2181 std::string prev_host;
2182 uint16_t prev_port = 0;
2185 nghttp2_data_provider data_prd;
2186 struct stat data_stat;
2188 if (!config.datafile.empty()) {
2189 if (config.datafile == "-") {
2190 if (fstat(0, &data_stat) == 0 &&
2191 (data_stat.st_mode & S_IFMT) == S_IFREG) {
2192 // use STDIN if it is a regular file
2195 // copy the contents of STDIN to a temporary file
2196 char tempfn[] = "/tmp/nghttp.temp.XXXXXX";
2197 data_fd = mkstemp(tempfn);
2198 if (data_fd == -1) {
2199 std::cerr << "[ERROR] Could not create a temporary file in /tmp"
2203 if (unlink(tempfn) != 0) {
2204 std::cerr << "[WARNING] failed to unlink temporary file:" << tempfn
2208 std::array<char, 1024> buf;
2210 while ((rret = read(0, buf.data(), buf.size())) == -1 &&
2216 std::cerr << "[ERROR] I/O error while reading from STDIN"
2220 while ((wret = write(data_fd, buf.data(), rret)) == -1 &&
2224 std::cerr << "[ERROR] I/O error while writing to temporary file"
2229 if (fstat(data_fd, &data_stat) == -1) {
2231 std::cerr << "[ERROR] Could not stat temporary file" << std::endl;
2236 data_fd = open(config.datafile.c_str(), O_RDONLY | O_BINARY);
2237 if (data_fd == -1) {
2238 std::cerr << "[ERROR] Could not open file " << config.datafile
2242 if (fstat(data_fd, &data_stat) == -1) {
2244 std::cerr << "[ERROR] Could not stat file " << config.datafile
2249 data_prd.source.fd = data_fd;
2250 data_prd.read_callback = file_read_callback;
2252 std::vector<std::tuple<std::string, nghttp2_data_provider *, int64_t>>
2254 for (int i = 0; i < n; ++i) {
2256 memset(&u, 0, sizeof(u));
2257 auto uri = strip_fragment(uris[i]);
2258 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) == 0 &&
2259 util::has_uri_field(u, UF_SCHEMA)) {
2260 uint16_t port = util::has_uri_field(u, UF_PORT)
2262 : util::get_default_port(uri.c_str(), u);
2263 if (!util::fieldeq(uri.c_str(), u, UF_SCHEMA, prev_scheme.c_str()) ||
2264 !util::fieldeq(uri.c_str(), u, UF_HOST, prev_host.c_str()) ||
2265 port != prev_port) {
2266 if (!requests.empty()) {
2267 if (communicate(prev_scheme, prev_host, prev_port,
2268 std::move(requests), callbacks) != 0) {
2273 prev_scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA);
2274 prev_host = util::get_uri_field(uri.c_str(), u, UF_HOST);
2277 requests.emplace_back(uri, data_fd == -1 ? nullptr : &data_prd,
2281 if (!requests.empty()) {
2282 if (communicate(prev_scheme, prev_host, prev_port, std::move(requests),
2292 void print_version(std::ostream &out) {
2293 out << "nghttp nghttp2/" NGHTTP2_VERSION << std::endl;
2298 void print_usage(std::ostream &out) {
2299 out << R"(Usage: nghttp [OPTIONS]... <URI>...
2300 HTTP/2 experimental client)" << std::endl;
2305 void print_help(std::ostream &out) {
2308 <URI> Specify URI to access.
2311 Print debug information such as reception and
2312 transmission of frames and name/value pairs. Specifying
2313 this option multiple times increases verbosity.
2315 Discard downloaded data.
2317 Save download data in the current directory. The
2318 filename is dereived from URI. If URI ends with '/',
2319 'index.html' is used as a filename. Not implemented
2321 -t, --timeout=<DURATION>
2322 Timeout each request after <DURATION>. Set 0 to disable
2324 -w, --window-bits=<N>
2325 Sets the stream level initial window size to 2**<N>-1.
2326 -W, --connection-window-bits=<N>
2327 Sets the connection level initial window size to
2330 Download assets such as stylesheets, images and script
2331 files linked from the downloaded resource. Only links
2332 whose origins are the same with the linking resource
2333 will be downloaded. nghttp prioritizes resources using
2334 HTTP/2 dependency based priority. The priority order,
2335 from highest to lowest, is html itself, css, javascript
2337 -s, --stat Print statistics.
2338 -H, --header=<HEADER>
2339 Add a header to the requests. Example: -H':method: PUT'
2341 Add a trailer header to the requests. <HEADER> must not
2342 include pseudo header field (header field name starting
2343 with ':'). To send trailer, one must use -d option to
2344 send request body. Example: --trailer 'foo: bar'.
2346 Use the specified client certificate file. The file
2347 must be in PEM format.
2348 --key=<KEY> Use the client private key file. The file must be in
2351 Post FILE to server. If '-' is given, data will be read
2354 Request each URI <N> times. By default, same URI is not
2355 requested twice. This option disables it too.
2357 Perform HTTP Upgrade for HTTP/2. This option is ignored
2358 if the request URI has https scheme. If -d is used, the
2359 HTTP upgrade request is performed with OPTIONS method.
2360 -p, --weight=<WEIGHT>
2361 Sets priority group weight. The valid value range is
2362 [)" << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT
2364 Default: )" << NGHTTP2_DEFAULT_WEIGHT << R"(
2365 -M, --peer-max-concurrent-streams=<N>
2366 Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value of
2367 remote endpoint as if it is received in SETTINGS frame.
2368 The default is large enough as it is seen as unlimited.
2369 -c, --header-table-size=<SIZE>
2370 Specify decoder header table size.
2372 Add at most <N> bytes to a frame payload as padding.
2373 Specify 0 to disable padding.
2375 Output HTTP transactions <FILE> in HAR format. If '-'
2376 is given, data is written to stdout.
2377 --color Force colored log output.
2379 Send large header to test CONTINUATION.
2381 Don't send content-length header field.
2382 --no-dep Don't send dependency based priority hint to server.
2383 --hexdump Display the incoming traffic in hexadecimal (Canonical
2384 hex+ASCII display). If SSL/TLS is used, decrypted data
2386 --no-push Disable server push.
2387 --version Display version information and exit.
2388 -h, --help Display this help and exit.
2392 The <SIZE> argument is an integer and an optional unit (e.g., 10K is
2393 10 * 1024). Units are K, M and G (powers of 1024).
2395 The <DURATION> argument is an integer and an optional unit (e.g., 1s
2396 is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
2397 (hours, minutes, seconds and milliseconds, respectively). If a unit
2398 is omitted, a second is used as unit.)" << std::endl;
2402 int main(int argc, char **argv) {
2405 static int flag = 0;
2406 static option long_options[] = {
2407 {"verbose", no_argument, nullptr, 'v'},
2408 {"null-out", no_argument, nullptr, 'n'},
2409 {"remote-name", no_argument, nullptr, 'O'},
2410 {"timeout", required_argument, nullptr, 't'},
2411 {"window-bits", required_argument, nullptr, 'w'},
2412 {"connection-window-bits", required_argument, nullptr, 'W'},
2413 {"get-assets", no_argument, nullptr, 'a'},
2414 {"stat", no_argument, nullptr, 's'},
2415 {"help", no_argument, nullptr, 'h'},
2416 {"header", required_argument, nullptr, 'H'},
2417 {"data", required_argument, nullptr, 'd'},
2418 {"multiply", required_argument, nullptr, 'm'},
2419 {"upgrade", no_argument, nullptr, 'u'},
2420 {"weight", required_argument, nullptr, 'p'},
2421 {"peer-max-concurrent-streams", required_argument, nullptr, 'M'},
2422 {"header-table-size", required_argument, nullptr, 'c'},
2423 {"padding", required_argument, nullptr, 'b'},
2424 {"har", required_argument, nullptr, 'r'},
2425 {"cert", required_argument, &flag, 1},
2426 {"key", required_argument, &flag, 2},
2427 {"color", no_argument, &flag, 3},
2428 {"continuation", no_argument, &flag, 4},
2429 {"version", no_argument, &flag, 5},
2430 {"no-content-length", no_argument, &flag, 6},
2431 {"no-dep", no_argument, &flag, 7},
2432 {"trailer", required_argument, &flag, 9},
2433 {"hexdump", no_argument, &flag, 10},
2434 {"no-push", no_argument, &flag, 11},
2435 {nullptr, 0, nullptr, 0}};
2436 int option_index = 0;
2437 int c = getopt_long(argc, argv, "M:Oab:c:d:gm:np:r:hH:vst:uw:W:",
2438 long_options, &option_index);
2444 // peer-max-concurrent-streams option
2445 config.peer_max_concurrent_streams = strtoul(optarg, nullptr, 10);
2448 config.remote_name = true;
2451 print_help(std::cout);
2454 config.padding = strtol(optarg, nullptr, 10);
2457 config.null_out = true;
2461 auto n = strtoul(optarg, nullptr, 10);
2462 if (errno == 0 && NGHTTP2_MIN_WEIGHT <= n && n <= NGHTTP2_MAX_WEIGHT) {
2465 std::cerr << "-p: specify the integer in the range ["
2466 << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT
2467 << "], inclusive" << std::endl;
2474 config.harfile = optarg;
2475 #else // !HAVE_JANSSON
2476 std::cerr << "[WARNING]: -r, --har option is ignored because\n"
2477 << "the binary was not compiled with libjansson." << std::endl;
2478 #endif // !HAVE_JANSSON
2484 config.timeout = util::parse_duration_with_unit(optarg);
2485 if (config.timeout == std::numeric_limits<double>::infinity()) {
2486 std::cerr << "-t: bad timeout value: " << optarg << std::endl;
2491 config.upgrade = true;
2496 char *endptr = nullptr;
2497 unsigned long int n = strtoul(optarg, &endptr, 10);
2498 if (errno == 0 && *endptr == '\0' && n < 31) {
2500 config.window_bits = n;
2502 config.connection_window_bits = n;
2505 std::cerr << "-" << static_cast<char>(c)
2506 << ": specify the integer in the range [0, 30], inclusive"
2513 char *header = optarg;
2514 // Skip first possible ':' in the header name
2515 char *value = strchr(optarg + 1, ':');
2516 if (!value || (header[0] == ':' && header + 1 == value)) {
2517 std::cerr << "-H: invalid header: " << optarg << std::endl;
2522 while (isspace(*value)) {
2526 // This could also be a valid case for suppressing a header
2528 std::cerr << "-H: invalid header - value missing: " << optarg
2532 config.headers.emplace_back(header, value, false);
2533 util::inp_strlower(config.headers.back().name);
2538 config.get_assets = true;
2539 #else // !HAVE_LIBXML2
2540 std::cerr << "[WARNING]: -a, --get-assets option is ignored because\n"
2541 << "the binary was not compiled with libxml2." << std::endl;
2542 #endif // !HAVE_LIBXML2
2548 config.datafile = optarg;
2551 config.multiply = strtoul(optarg, nullptr, 10);
2555 config.header_table_size = util::parse_uint_with_unit(optarg);
2556 if (config.header_table_size == -1) {
2557 std::cerr << "-c: Bad option value: " << optarg << std::endl;
2562 util::show_candidates(argv[optind - 1], long_options);
2568 config.certfile = optarg;
2572 config.keyfile = optarg;
2579 // continuation option
2580 config.continuation = true;
2584 print_version(std::cout);
2587 // no-content-length option
2588 config.no_content_length = true;
2592 config.no_dep = true;
2596 auto header = optarg;
2597 auto value = strchr(optarg, ':');
2599 std::cerr << "--trailer: invalid header: " << optarg << std::endl;
2604 while (isspace(*value)) {
2608 // This could also be a valid case for suppressing a header
2610 std::cerr << "--trailer: invalid header - value missing: " << optarg
2614 config.trailer.emplace_back(header, value, false);
2615 util::inp_strlower(config.trailer.back().name);
2620 config.hexdump = true;
2624 config.no_push = true;
2633 set_color_output(color || isatty(fileno(stdout)));
2635 nghttp2_option_set_peer_max_concurrent_streams(
2636 config.http2_option, config.peer_max_concurrent_streams);
2638 struct sigaction act;
2639 memset(&act, 0, sizeof(struct sigaction));
2640 act.sa_handler = SIG_IGN;
2641 sigaction(SIGPIPE, &act, nullptr);
2642 OPENSSL_config(nullptr);
2643 OpenSSL_add_all_algorithms();
2644 SSL_load_error_strings();
2647 return run(argv + optind, argc - optind);
2650 } // namespace nghttp2
2652 int main(int argc, char **argv) { return nghttp2::main(argc, argv); }