nghttp2/src/nghttp.cc

2717 lines
77 KiB
C++
Raw Normal View History

2013-07-21 16:49:12 +02:00
/*
2014-03-30 12:09:21 +02:00
* nghttp2 - HTTP/2 C Library
2013-07-21 16:49:12 +02:00
*
* 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 "nghttp2_config.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <signal.h>
#include <getopt.h>
#include <cassert>
#include <cstdio>
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iostream>
#include <string>
#include <set>
#include <iomanip>
#include <fstream>
#include <map>
#include <vector>
#include <sstream>
#include <tuple>
#include <chrono>
2013-07-21 16:49:12 +02:00
#include <openssl/ssl.h>
#include <openssl/err.h>
2014-08-02 03:11:45 +02:00
#include <openssl/conf.h>
2013-07-21 16:49:12 +02:00
#include <ev.h>
2013-07-21 16:49:12 +02:00
#include <nghttp2/nghttp2.h>
#ifdef HAVE_JANSSON
#include <jansson.h>
#endif // HAVE_JANSSON
2013-07-21 16:49:12 +02:00
#include "http-parser/http_parser.h"
2013-07-22 15:12:54 +02:00
#include "app_helper.h"
2013-07-21 16:49:12 +02:00
#include "HtmlParser.h"
#include "util.h"
#include "base64.h"
2013-12-05 13:54:36 +01:00
#include "http2.h"
#include "nghttp2_gzip.h"
#include "ssl.h"
#include "ringbuf.h"
2013-07-21 16:49:12 +02:00
#ifndef O_BINARY
2014-11-27 15:39:04 +01:00
#define O_BINARY (0)
2013-07-21 16:49:12 +02:00
#endif // O_BINARY
namespace nghttp2 {
// stream ID of anchor stream node when --dep-idle is enabled. These
// * portion of ANCHOR_ID_* matches RequestPriority in HtmlParser.h.
// The stream ID = 1 is excluded since it is used as first stream in
// upgrade case.
enum {
ANCHOR_ID_HIGH = 3,
ANCHOR_ID_MEDIUM = 5,
ANCHOR_ID_LOW = 7,
ANCHOR_ID_LOWEST = 9,
};
2013-09-16 09:54:11 +02:00
namespace {
2013-07-21 16:49:12 +02:00
struct Config {
Headers headers;
2013-12-06 15:17:38 +01:00
std::string certfile;
std::string keyfile;
std::string datafile;
std::string harfile;
nghttp2_option *http2_option;
2013-12-06 15:17:38 +01:00
size_t output_upper_thres;
size_t padding;
2013-12-06 15:17:38 +01:00
ssize_t peer_max_concurrent_streams;
ssize_t header_table_size;
2014-03-25 18:04:24 +01:00
int32_t weight;
2013-12-06 15:17:38 +01:00
int multiply;
// milliseconds
ev_tstamp timeout;
2013-12-06 15:17:38 +01:00
int window_bits;
int connection_window_bits;
int verbose;
2013-07-21 16:49:12 +02:00
bool null_out;
bool remote_name;
bool get_assets;
bool stat;
bool upgrade;
bool continuation;
2014-07-03 15:48:43 +02:00
bool no_content_length;
bool no_dep;
bool dep_idle;
2014-06-07 09:04:43 +02:00
Config()
2014-11-27 15:39:04 +01:00
: output_upper_thres(1024 * 1024), padding(0),
peer_max_concurrent_streams(NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS),
header_table_size(-1), weight(NGHTTP2_DEFAULT_WEIGHT), multiply(1),
timeout(0.), window_bits(-1), connection_window_bits(-1), verbose(0),
2014-11-27 15:39:04 +01:00
null_out(false), remote_name(false), get_assets(false), stat(false),
upgrade(false), continuation(false), no_content_length(false),
no_dep(false), dep_idle(false) {
nghttp2_option_new(&http2_option);
2014-11-27 15:39:04 +01:00
nghttp2_option_set_peer_max_concurrent_streams(http2_option,
peer_max_concurrent_streams);
}
2014-11-27 15:39:04 +01:00
~Config() { nghttp2_option_del(http2_option); }
2013-07-21 16:49:12 +02:00
};
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
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
enum StatStage {
STAT_INITIAL,
STAT_ON_REQUEST,
STAT_ON_RESPONSE,
STAT_ON_COMPLETE
2013-07-21 16:49:12 +02:00
};
2013-09-16 09:54:11 +02:00
namespace {
struct RequestStat {
std::chrono::steady_clock::time_point on_request_time;
std::chrono::steady_clock::time_point on_response_time;
std::chrono::steady_clock::time_point on_complete_time;
StatStage stage;
2014-11-27 15:39:04 +01:00
RequestStat() : stage(STAT_INITIAL) {}
};
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
2013-09-16 09:54:11 +02:00
namespace {
2014-11-27 15:39:04 +01:00
std::string strip_fragment(const char *raw_uri) {
2013-07-21 16:49:12 +02:00
const char *end;
2014-11-27 15:39:04 +01:00
for (end = raw_uri; *end && *end != '#'; ++end)
;
size_t len = end - raw_uri;
2013-07-21 16:49:12 +02:00
return std::string(raw_uri, len);
}
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
2014-12-16 16:26:41 +01:00
namespace {
// Returns numeric address string of |addr|. If getnameinfo() is
// failed, "unknown" is returned.
std::string numeric_name(addrinfo *addr) {
char host[NI_MAXHOST];
auto rv = getnameinfo(addr->ai_addr, addr->ai_addrlen, host, sizeof(host),
nullptr, 0, NI_NUMERICHOST);
if (rv != 0) {
return "unknown";
}
return host;
}
} // namespace
2014-03-25 18:04:24 +01:00
namespace {
struct Request;
} // namespace
namespace {
struct Dependency {
2014-11-27 15:39:04 +01:00
std::vector<std::vector<Request *>> deps;
2014-03-25 18:04:24 +01:00
};
} // namespace
2013-09-16 09:54:11 +02:00
namespace {
2013-07-21 16:49:12 +02:00
struct Request {
Headers res_nva;
Headers req_nva;
2013-07-21 16:49:12 +02:00
// URI without fragment
std::string uri;
http_parser_url u;
2014-03-25 18:04:24 +01:00
std::shared_ptr<Dependency> dep;
nghttp2_priority_spec pri_spec;
2013-12-06 15:17:38 +01:00
RequestStat stat;
int64_t data_length;
int64_t data_offset;
// Number of bytes received from server
int64_t response_len;
2013-07-21 16:49:12 +02:00
nghttp2_gzip *inflater;
HtmlParser *html_parser;
const nghttp2_data_provider *data_prd;
2014-03-25 18:04:24 +01:00
int32_t stream_id;
int status;
2013-07-21 16:49:12 +02:00
// Recursion level: 0: first entity, 1: entity linked from first entity
int level;
2014-03-25 18:04:24 +01:00
// RequestPriority value defined in HtmlParser.h
int pri;
int res_hdidx[http2::HD_MAXIDX];
// used for incoming PUSH_PROMISE
int req_hdidx[http2::HD_MAXIDX];
bool expect_final_response;
2014-03-25 18:04:24 +01:00
// For pushed request, |uri| is empty and |u| is zero-cleared.
2014-11-27 15:39:04 +01:00
Request(const std::string &uri, const http_parser_url &u,
2013-07-21 16:49:12 +02:00
const nghttp2_data_provider *data_prd, int64_t data_length,
2014-11-27 15:39:04 +01:00
const nghttp2_priority_spec &pri_spec,
std::shared_ptr<Dependency> dep, int pri = 0, int level = 0)
2014-11-27 15:39:04 +01:00
: uri(uri), u(u), dep(std::move(dep)), pri_spec(pri_spec),
data_length(data_length), data_offset(0), response_len(0),
inflater(nullptr), html_parser(nullptr), data_prd(data_prd),
stream_id(-1), status(0), level(level), pri(pri),
expect_final_response(false) {
http2::init_hdidx(res_hdidx);
http2::init_hdidx(req_hdidx);
}
2014-11-27 15:39:04 +01:00
~Request() {
2013-07-21 16:49:12 +02:00
nghttp2_gzip_inflate_del(inflater);
delete html_parser;
}
2014-11-27 15:39:04 +01:00
void init_inflater() {
2013-07-21 16:49:12 +02:00
int rv;
rv = nghttp2_gzip_inflate_new(&inflater);
assert(rv == 0);
}
2014-11-27 15:39:04 +01:00
void init_html_parser() { html_parser = new HtmlParser(uri); }
2013-07-21 16:49:12 +02:00
2014-11-27 15:39:04 +01:00
int update_html_parser(const uint8_t *data, size_t len, int fin) {
if (!html_parser) {
2013-07-21 16:49:12 +02:00
return 0;
}
int rv;
2014-11-27 15:39:04 +01:00
rv = html_parser->parse_chunk(reinterpret_cast<const char *>(data), len,
2013-07-21 16:49:12 +02:00
fin);
return rv;
}
2014-11-27 15:39:04 +01:00
std::string make_reqpath() const {
std::string path = util::has_uri_field(u, UF_PATH)
? util::get_uri_field(uri.c_str(), u, UF_PATH)
: "/";
if (util::has_uri_field(u, UF_QUERY)) {
2013-07-21 16:49:12 +02:00
path += "?";
2014-11-27 15:39:04 +01:00
path.append(uri.c_str() + u.field_data[UF_QUERY].off,
2013-07-21 16:49:12 +02:00
u.field_data[UF_QUERY].len);
}
return path;
}
2014-11-27 15:39:04 +01:00
int32_t find_dep_stream_id(int start) {
for (auto i = start; i >= 0; --i) {
for (auto req : dep->deps[i]) {
return req->stream_id;
2014-03-25 18:04:24 +01:00
}
}
return -1;
}
2014-11-27 15:39:04 +01:00
nghttp2_priority_spec resolve_dep(int32_t pri) {
nghttp2_priority_spec pri_spec;
2014-03-25 18:04:24 +01:00
int exclusive = 0;
int32_t stream_id = -1;
2014-04-25 14:23:47 +02:00
nghttp2_priority_spec_default_init(&pri_spec);
if (config.no_dep) {
2014-03-25 18:04:24 +01:00
return pri_spec;
}
if (config.dep_idle) {
int32_t anchor_id = 0;
switch (pri) {
case REQ_PRI_HIGH:
anchor_id = ANCHOR_ID_HIGH;
break;
case REQ_PRI_MEDIUM:
anchor_id = ANCHOR_ID_MEDIUM;
break;
case REQ_PRI_LOW:
anchor_id = ANCHOR_ID_LOW;
break;
case REQ_PRI_LOWEST:
anchor_id = ANCHOR_ID_LOWEST;
break;
}
nghttp2_priority_spec_init(&pri_spec, anchor_id, NGHTTP2_DEFAULT_WEIGHT,
0);
return pri_spec;
}
if (pri == 0) {
return pri_spec;
}
2014-03-25 18:04:24 +01:00
auto start = std::min(pri, (int)dep->deps.size() - 1);
2014-11-27 15:39:04 +01:00
for (auto i = start; i >= 0; --i) {
if (dep->deps[i][0]->pri < pri) {
2014-03-25 18:04:24 +01:00
stream_id = find_dep_stream_id(i);
2014-11-27 15:39:04 +01:00
if (i != (int)dep->deps.size() - 1) {
2014-03-25 18:04:24 +01:00
exclusive = 1;
}
break;
2014-11-27 15:39:04 +01:00
} else if (dep->deps[i][0]->pri == pri) {
2014-03-25 18:04:24 +01:00
stream_id = find_dep_stream_id(i - 1);
break;
}
}
2014-11-27 15:39:04 +01:00
if (stream_id == -1) {
2014-03-25 18:04:24 +01:00
return pri_spec;
}
nghttp2_priority_spec_init(&pri_spec, stream_id, NGHTTP2_DEFAULT_WEIGHT,
exclusive);
2014-03-25 18:04:24 +01:00
return pri_spec;
}
2014-11-27 15:39:04 +01:00
bool is_ipv6_literal_addr() const {
if (util::has_uri_field(u, UF_HOST)) {
return memchr(uri.c_str() + u.field_data[UF_HOST].off, ':',
2013-07-21 16:49:12 +02:00
u.field_data[UF_HOST].len);
} else {
return false;
}
}
bool response_pseudo_header_allowed(int token) const {
if (!res_nva.empty() && res_nva.back().name.c_str()[0] != ':') {
return false;
}
switch (token) {
case http2::HD__STATUS:
return res_hdidx[token] == -1;
default:
return false;
}
}
bool push_request_pseudo_header_allowed(int token) const {
if (!req_nva.empty() && req_nva.back().name.c_str()[0] != ':') {
return false;
}
switch (token) {
case http2::HD__AUTHORITY:
case http2::HD__METHOD:
case http2::HD__PATH:
case http2::HD__SCHEME:
return req_hdidx[token] == -1;
default:
return false;
}
}
Headers::value_type *get_res_header(int token) {
auto idx = res_hdidx[token];
if (idx == -1) {
return nullptr;
}
return &res_nva[idx];
}
Headers::value_type *get_req_header(int token) {
auto idx = req_hdidx[token];
if (idx == -1) {
return nullptr;
}
return &req_nva[idx];
}
2014-11-27 15:39:04 +01:00
void record_request_time() {
stat.stage = STAT_ON_REQUEST;
stat.on_request_time = get_time();
2013-07-21 16:49:12 +02:00
}
2014-11-27 15:39:04 +01:00
void record_response_time() {
stat.stage = STAT_ON_RESPONSE;
stat.on_response_time = get_time();
2013-07-21 16:49:12 +02:00
}
2014-11-27 15:39:04 +01:00
void record_complete_time() {
stat.stage = STAT_ON_COMPLETE;
stat.on_complete_time = get_time();
2013-07-21 16:49:12 +02:00
}
};
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
2013-09-16 09:54:11 +02:00
namespace {
2013-07-21 16:49:12 +02:00
struct SessionStat {
// The point in time when download was started.
std::chrono::system_clock::time_point started_system_time;
// The point of time when download was started.
std::chrono::steady_clock::time_point on_started_time;
// The point of time when DNS resolution was completed.
std::chrono::steady_clock::time_point on_dns_complete_time;
// The point of time when connection was established or SSL/TLS
// handshake was completed.
std::chrono::steady_clock::time_point on_connect_time;
// The point of time when HTTP/2 commnucation was started.
std::chrono::steady_clock::time_point on_handshake_time;
2013-07-21 16:49:12 +02:00
};
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
namespace {
2014-11-27 15:39:04 +01:00
size_t populate_settings(nghttp2_settings_entry *iv) {
2014-06-07 09:04:43 +02:00
size_t niv = 2;
iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
iv[0].value = 100;
iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
2014-11-27 15:39:04 +01:00
if (config.window_bits != -1) {
iv[1].value = (1 << config.window_bits) - 1;
} else {
iv[1].value = NGHTTP2_INITIAL_WINDOW_SIZE;
}
2014-11-27 15:39:04 +01:00
if (config.header_table_size >= 0) {
2013-10-29 13:34:22 +01:00
iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
iv[niv].value = config.header_table_size;
++niv;
}
return niv;
}
} // namespace
namespace {
extern http_parser_settings htp_hooks;
} // namespace
namespace {
void readcb(struct ev_loop *loop, ev_io *w, int revents);
} // namespace
2013-07-21 16:49:12 +02:00
namespace {
void writecb(struct ev_loop *loop, ev_io *w, int revents);
2013-07-21 16:49:12 +02:00
} // namespace
namespace {
void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents);
2013-07-21 16:49:12 +02:00
} // namespace
2013-09-16 09:54:11 +02:00
namespace {
2013-07-21 16:49:12 +02:00
struct HttpClient;
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
namespace {
2014-11-27 15:39:04 +01:00
int submit_request(HttpClient *client, const Headers &headers, Request *req);
2013-07-21 16:49:12 +02:00
} // namespace
namespace {
void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents);
} // namespace
2014-11-27 15:39:04 +01:00
enum client_state { STATE_IDLE, STATE_CONNECTED };
2013-07-21 16:49:12 +02:00
2013-09-16 09:54:11 +02:00
namespace {
2013-07-21 16:49:12 +02:00
struct HttpClient {
std::vector<std::unique_ptr<Request>> reqvec;
// Insert path already added in reqvec to prevent multiple request
// for 1 resource.
std::set<std::string> path_cache;
std::string scheme;
std::string host;
2013-07-21 16:49:12 +02:00
std::string hostport;
// Used for parse the HTTP upgrade response from server
2013-09-03 16:09:56 +02:00
std::unique_ptr<http_parser> htp;
2013-12-06 15:17:38 +01:00
SessionStat stat;
ev_io wev;
ev_io rev;
ev_timer wt;
ev_timer rt;
ev_timer settings_timer;
std::function<int(HttpClient &)> readfn, writefn;
std::function<int(HttpClient &, const uint8_t *, size_t)> on_readfn;
std::function<int(HttpClient &)> on_writefn;
2013-12-06 15:17:38 +01:00
nghttp2_session *session;
const nghttp2_session_callbacks *callbacks;
struct ev_loop *loop;
2013-12-06 15:17:38 +01:00
SSL_CTX *ssl_ctx;
SSL *ssl;
addrinfo *addrs;
addrinfo *next_addr;
2014-12-16 16:26:41 +01:00
addrinfo *cur_addr;
2013-12-06 15:17:38 +01:00
// The number of completed requests, including failed ones.
size_t complete;
// The length of settings_payload
size_t settings_payloadlen;
client_state 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;
RingBuf<65536> wb;
// SETTINGS payload sent as token68 in HTTP Upgrade
uint8_t settings_payload[128];
enum { ERR_CONNECT_FAIL = -100 };
HttpClient(const nghttp2_session_callbacks *callbacks, struct ev_loop *loop,
2014-11-27 15:39:04 +01:00
SSL_CTX *ssl_ctx)
: session(nullptr), callbacks(callbacks), loop(loop), ssl_ctx(ssl_ctx),
ssl(nullptr), addrs(nullptr), next_addr(nullptr), cur_addr(nullptr),
complete(0), settings_payloadlen(0), state(STATE_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;
}
2014-11-27 15:39:04 +01:00
~HttpClient() {
2013-07-21 16:49:12 +02:00
disconnect();
2014-11-27 15:39:04 +01:00
if (addrs) {
2014-01-27 17:40:00 +01:00
freeaddrinfo(addrs);
addrs = nullptr;
next_addr = nullptr;
}
2013-07-21 16:49:12 +02:00
}
2014-11-27 15:39:04 +01:00
bool need_upgrade() const { return config.upgrade && scheme == "http"; }
2014-11-27 15:39:04 +01:00
int resolve_host(const std::string &host, uint16_t port) {
2013-07-21 16:49:12 +02:00
int rv;
2014-01-27 17:40:00 +01:00
addrinfo hints;
this->host = host;
2014-01-27 17:40:00 +01:00
memset(&hints, 0, sizeof(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);
2014-11-27 15:39:04 +01:00
if (rv != 0) {
std::cerr << "[ERROR] getaddrinfo() failed: " << gai_strerror(rv)
<< std::endl;
return -1;
}
2014-11-27 15:39:04 +01:00
if (addrs == nullptr) {
2014-08-08 17:03:33 +02:00
std::cerr << "[ERROR] No address returned" << std::endl;
return -1;
}
next_addr = addrs;
return 0;
}
2014-11-27 15:39:04 +01:00
int initiate_connection() {
int rv;
2014-12-16 16:26:41 +01:00
cur_addr = nullptr;
2014-11-27 15:39:04 +01:00
while (next_addr) {
2014-12-16 16:26:41 +01:00
cur_addr = next_addr;
2014-01-27 17:40:00 +01:00
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_fd(ssl, fd);
SSL_set_connect_state(ssl);
// If the user overrode the host header, use that value for
// the SNI extension
const char *host_string = nullptr;
auto i =
std::find_if(std::begin(config.headers), std::end(config.headers),
[](const Header &nv) { return "host" == nv.name; });
if (i != std::end(config.headers)) {
host_string = (*i).value.c_str();
} else {
host_string = host.c_str();
}
if (!util::numeric_host(host_string)) {
SSL_set_tlsext_host_name(ssl, host_string);
}
}
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) {
2013-07-21 16:49:12 +02:00
return -1;
}
writefn = &HttpClient::connected;
2014-11-27 15:39:04 +01:00
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;
2013-07-21 16:49:12 +02:00
}
ev_io_set(&rev, fd, EV_READ);
ev_io_set(&wev, fd, EV_WRITE);
ev_io_start(loop, &wev);
ev_timer_again(loop, &wt);
2013-07-21 16:49:12 +02:00
return 0;
}
2014-11-27 15:39:04 +01:00
void disconnect() {
2013-07-21 16:49:12 +02:00
state = STATE_IDLE;
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);
2013-07-21 16:49:12 +02:00
nghttp2_session_del(session);
session = nullptr;
2014-11-27 15:39:04 +01:00
if (ssl) {
SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN);
ERR_clear_error();
2013-07-21 16:49:12 +02:00
SSL_shutdown(ssl);
SSL_free(ssl);
ssl = nullptr;
}
2014-11-27 15:39:04 +01:00
if (fd != -1) {
2013-12-22 11:23:01 +01:00
shutdown(fd, SHUT_WR);
close(fd);
fd = -1;
}
}
int read_clear() {
ev_timer_again(loop, &rt);
uint8_t buf[8192];
for (;;) {
ssize_t nread;
while ((nread = read(fd, buf, sizeof(buf))) == -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, nread) != 0) {
return -1;
}
}
return 0;
}
int write_clear() {
ev_timer_again(loop, &rt);
for (;;) {
if (wb.rleft() > 0) {
struct iovec iov[2];
auto iovcnt = wb.riovec(iov);
ssize_t nwrite;
while ((nwrite = writev(fd, iov, 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);
continue;
}
if (on_writefn(*this) != 0) {
return -1;
}
if (wb.rleft() == 0) {
wb.reset();
break;
}
}
ev_io_stop(loop, &wev);
ev_timer_stop(loop, &wt);
return 0;
}
int noop() { return 0; }
void on_connect_fail() {
if (state == STATE_IDLE) {
std::cerr << "[ERROR] Could not connect to the address "
<< numeric_name(cur_addr) << std::endl;
}
auto cur_state = state;
disconnect();
if (cur_state == STATE_IDLE) {
if (initiate_connection() == 0) {
std::cerr << "Trying next address " << numeric_name(cur_addr)
<< std::endl;
}
2013-12-22 11:23:01 +01:00
}
2013-07-21 16:49:12 +02:00
}
int connected() {
if (!util::check_socket_connected(fd)) {
return ERR_CONNECT_FAIL;
}
if (config.verbose) {
print_timer();
std::cout << " Connected" << std::endl;
}
record_connect_time();
state = STATE_CONNECTED;
ev_io_start(loop, &rev);
ev_io_stop(loop, &wev);
ev_timer_again(loop, &rt);
ev_timer_stop(loop, &wt);
if (ssl) {
readfn = &HttpClient::tls_handshake;
writefn = &HttpClient::tls_handshake;
return do_write();
}
readfn = &HttpClient::read_clear;
writefn = &HttpClient::write_clear;
if (need_upgrade()) {
htp = util::make_unique<http_parser>();
http_parser_init(htp.get(), HTTP_RESPONSE);
htp->data = this;
return do_write();
}
if (on_connect() != 0) {
return -1;
}
return 0;
}
2014-11-27 15:39:04 +01:00
int on_upgrade_connect() {
2013-09-07 09:38:21 +02:00
ssize_t rv;
record_handshake_time();
assert(!reqvec.empty());
nghttp2_settings_entry iv[32];
size_t niv = populate_settings(iv);
2014-11-27 15:39:04 +01:00
assert(sizeof(settings_payload) >= 8 * niv);
rv = nghttp2_pack_settings_payload(settings_payload,
sizeof(settings_payload), iv, niv);
2014-11-27 15:39:04 +01:00
if (rv < 0) {
2013-09-07 09:38:21 +02:00
return -1;
}
settings_payloadlen = rv;
auto token68 = base64::encode(&settings_payload[0],
&settings_payload[settings_payloadlen]);
util::to_token68(token68);
std::string req;
2014-11-27 15:39:04 +01:00
if (reqvec[0]->data_prd) {
// If the request contains upload data, use OPTIONS * to upgrade
req = "OPTIONS *";
} else {
req = "GET ";
req += reqvec[0]->make_reqpath();
}
req += " HTTP/1.1\r\n"
2014-11-27 15:39:04 +01:00
"Host: ";
req += hostport;
2013-09-03 16:09:56 +02:00
req += "\r\n"
2014-11-27 15:39:04 +01:00
"Connection: Upgrade, HTTP2-Settings\r\n"
"Upgrade: " NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "\r\n"
"HTTP2-Settings: ";
req += token68;
2013-09-03 16:09:56 +02:00
req += "\r\n"
2014-11-27 15:39:04 +01:00
"Accept: */*\r\n"
"User-Agent: nghttp2/" NGHTTP2_VERSION "\r\n"
"\r\n";
wb.write(req.c_str(), req.size());
2014-11-27 15:39:04 +01:00
if (config.verbose) {
print_timer();
2014-11-27 15:39:04 +01:00
std::cout << " HTTP Upgrade request\n" << req << std::endl;
}
on_writefn = &HttpClient::noop;
signal_write();
return 0;
}
int on_upgrade_read(const uint8_t *data, size_t len) {
int rv;
auto nread = http_parser_execute(htp.get(), &htp_hooks,
reinterpret_cast<const char *>(data), len);
if (config.verbose) {
std::cout.write(reinterpret_cast<const char *>(data), nread);
}
auto htperr = HTTP_PARSER_ERRNO(htp.get());
if (htperr != HPE_OK) {
std::cerr << "[ERROR] Failed to parse HTTP Upgrade response header: "
<< "(" << http_errno_name(htperr) << ") "
<< http_errno_description(htperr) << 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 = on_connect();
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 do_read() { return readfn(*this); }
int do_write() { return writefn(*this); }
int on_connect() {
int rv;
if (ssl) {
// Check NPN or ALPN result
const unsigned char *next_proto = nullptr;
unsigned int next_proto_len;
SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len);
for (int i = 0; i < 2; ++i) {
if (next_proto) {
if (config.verbose) {
std::cout << "The negotiated protocol: ";
std::cout.write(reinterpret_cast<const char *>(next_proto),
next_proto_len);
std::cout << std::endl;
}
if (!util::check_h2_is_selected(next_proto, next_proto_len)) {
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;
}
}
2014-11-27 15:39:04 +01:00
if (!need_upgrade()) {
record_handshake_time();
}
rv = nghttp2_session_client_new2(&session, callbacks, this,
config.http2_option);
2014-11-27 15:39:04 +01:00
if (rv != 0) {
return -1;
}
2014-11-27 15:39:04 +01:00
if (need_upgrade()) {
// Adjust stream user-data depending on the existence of upload
// data
Request *stream_user_data = nullptr;
2014-11-27 15:39:04 +01:00
if (!reqvec[0]->data_prd) {
stream_user_data = reqvec[0].get();
}
rv = nghttp2_session_upgrade(session, settings_payload,
settings_payloadlen, stream_user_data);
2014-11-27 15:39:04 +01:00
if (rv != 0) {
2014-08-08 17:03:33 +02:00
std::cerr << "[ERROR] nghttp2_session_upgrade() returned error: "
<< nghttp2_strerror(rv) << std::endl;
return -1;
}
2014-11-27 15:39:04 +01:00
if (stream_user_data) {
stream_user_data->stream_id = 1;
on_request(stream_user_data);
}
}
// Send connection header here
wb.write(NGHTTP2_CLIENT_CONNECTION_PREFACE,
NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
// If upgrade succeeds, the SETTINGS value sent with
// HTTP2-Settings header field has already been submitted to
// session object.
2014-11-27 15:39:04 +01:00
if (!need_upgrade()) {
nghttp2_settings_entry iv[16];
auto niv = populate_settings(iv);
rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, niv);
2014-11-27 15:39:04 +01:00
if (rv != 0) {
return -1;
}
2013-07-21 16:49:12 +02:00
}
if (!config.no_dep && config.dep_idle) {
// Create anchor stream nodes
nghttp2_priority_spec pri_spec;
int32_t dep_stream_id = 0;
for (auto stream_id : {ANCHOR_ID_HIGH, ANCHOR_ID_MEDIUM, ANCHOR_ID_LOW,
ANCHOR_ID_LOWEST}) {
nghttp2_priority_spec_init(&pri_spec, dep_stream_id,
NGHTTP2_DEFAULT_WEIGHT, 0);
rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, stream_id,
&pri_spec);
if (rv != 0) {
return -1;
}
dep_stream_id = stream_id;
}
rv = nghttp2_session_set_next_stream_id(session, ANCHOR_ID_LOWEST + 2);
if (rv != 0) {
return -1;
}
2014-12-18 12:43:59 +01:00
if (need_upgrade()) {
// Amend the priority because we cannot send priority in
// HTTP/1.1 Upgrade.
nghttp2_priority_spec_init(&pri_spec, ANCHOR_ID_HIGH, config.weight, 0);
rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec);
if (rv != 0) {
return -1;
}
}
} else if (need_upgrade() && config.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, config.weight, 0);
rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec);
if (rv != 0) {
return -1;
}
}
ev_timer_again(loop, &settings_timer);
2014-11-27 15:39:04 +01:00
if (config.connection_window_bits != -1) {
int32_t wininc = (1 << config.connection_window_bits) - 1 -
NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
rv = nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, wininc);
if (rv != 0) {
return -1;
}
}
// Adjust first request depending on the existence of the upload
// data
2014-11-27 15:39:04 +01:00
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;
}
2013-07-21 16:49:12 +02:00
}
signal_write();
return 0;
2013-07-21 16:49:12 +02:00
}
int on_read(const uint8_t *data, size_t 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;
}
2013-07-21 16:49:12 +02:00
assert(static_cast<size_t>(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 on_write() {
auto rv = nghttp2_session_send(session);
if (rv != 0) {
std::cerr << "[ERROR] nghttp2_session_send() returned error: "
<< nghttp2_strerror(rv) << std::endl;
return -1;
}
if (nghttp2_session_want_read(session) == 0 &&
nghttp2_session_want_write(session) == 0 && wb.rleft() == 0) {
return -1;
}
return 0;
}
int tls_handshake() {
ev_timer_again(loop, &rt);
ERR_clear_error();
auto rv = SSL_do_handshake(ssl);
if (rv == 0) {
return -1;
}
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 (on_connect() != 0) {
return -1;
}
return 0;
}
int read_tls() {
ev_timer_again(loop, &rt);
ERR_clear_error();
uint8_t buf[8192];
for (;;) {
auto rv = SSL_read(ssl, buf, sizeof(buf));
if (rv == 0) {
return -1;
}
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, rv) != 0) {
return -1;
}
}
2013-07-21 16:49:12 +02:00
}
int write_tls() {
ev_timer_again(loop, &rt);
ERR_clear_error();
2014-11-27 15:39:04 +01:00
for (;;) {
if (wb.rleft() > 0) {
const void *p;
size_t len;
std::tie(p, len) = wb.get();
2013-07-21 16:49:12 +02:00
auto rv = SSL_write(ssl, p, len);
if (rv == 0) {
return -1;
}
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);
continue;
}
if (on_writefn(*this) != 0) {
return -1;
}
if (wb.rleft() == 0) {
break;
}
2013-07-21 16:49:12 +02:00
}
ev_io_stop(loop, &wev);
ev_timer_stop(loop, &wt);
return 0;
2013-07-21 16:49:12 +02:00
}
void signal_write() { ev_io_start(loop, &wev); }
2014-11-27 15:39:04 +01:00
bool all_requests_processed() const { return complete == reqvec.size(); }
void update_hostport() {
if (reqvec.empty()) {
2013-07-21 16:49:12 +02:00
return;
}
2014-11-27 15:39:04 +01:00
scheme =
util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_SCHEMA);
2013-07-21 16:49:12 +02:00
std::stringstream ss;
2014-11-27 15:39:04 +01:00
if (reqvec[0]->is_ipv6_literal_addr()) {
2013-07-21 16:49:12 +02:00
ss << "[";
util::write_uri_field(ss, reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST);
2013-07-21 16:49:12 +02:00
ss << "]";
} else {
util::write_uri_field(ss, reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST);
2013-07-21 16:49:12 +02:00
}
2014-11-27 15:39:04 +01:00
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)) {
2013-07-21 16:49:12 +02:00
ss << ":" << reqvec[0]->u.port;
}
hostport = ss.str();
}
2014-11-27 15:39:04 +01:00
bool add_request(const std::string &uri,
const nghttp2_data_provider *data_prd, int64_t data_length,
const nghttp2_priority_spec &pri_spec,
std::shared_ptr<Dependency> dep, int pri = 0,
int level = 0) {
2013-07-21 16:49:12 +02:00
http_parser_url u;
2014-02-27 14:11:31 +01:00
memset(&u, 0, sizeof(u));
2014-11-27 15:39:04 +01:00
if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
2013-09-07 09:38:21 +02:00
return false;
}
2014-11-27 15:39:04 +01:00
if (path_cache.count(uri)) {
2013-07-21 16:49:12 +02:00
return false;
2014-06-12 15:48:40 +02:00
}
2014-03-25 18:04:24 +01:00
2014-11-27 15:39:04 +01:00
if (config.multiply == 1) {
2014-06-12 15:48:40 +02:00
path_cache.insert(uri);
2013-07-21 16:49:12 +02:00
}
2014-06-12 15:48:40 +02:00
2014-11-27 15:39:04 +01:00
reqvec.push_back(util::make_unique<Request>(
uri, u, data_prd, data_length, pri_spec, std::move(dep), pri, level));
2014-06-12 15:48:40 +02:00
return true;
2013-07-21 16:49:12 +02:00
}
2014-11-27 15:39:04 +01:00
void record_handshake_time() { stat.on_handshake_time = get_time(); }
2014-11-27 15:39:04 +01:00
void record_started_time() {
stat.started_system_time = std::chrono::system_clock::now();
stat.on_started_time = get_time();
}
2014-11-27 15:39:04 +01:00
void record_dns_complete_time() { stat.on_dns_complete_time = get_time(); }
2014-11-27 15:39:04 +01:00
void record_connect_time() { stat.on_connect_time = get_time(); }
2014-11-27 15:39:04 +01:00
void on_request(Request *req) {
req->record_request_time();
2014-11-27 15:39:04 +01:00
if (req->pri == 0 && req->dep) {
assert(req->dep->deps.empty());
2014-11-27 15:39:04 +01:00
req->dep->deps.push_back(std::vector<Request *>{req});
return;
}
2014-11-27 15:39:04 +01:00
if (req->stream_id % 2 == 0) {
return;
}
auto itr = std::begin(req->dep->deps);
2014-11-27 15:39:04 +01:00
for (; itr != std::end(req->dep->deps); ++itr) {
if ((*itr)[0]->pri == req->pri) {
(*itr).push_back(req);
break;
}
2014-11-27 15:39:04 +01:00
if ((*itr)[0]->pri > req->pri) {
auto v = std::vector<Request *>{req};
req->dep->deps.insert(itr, std::move(v));
break;
}
}
2014-11-27 15:39:04 +01:00
if (itr == std::end(req->dep->deps)) {
req->dep->deps.push_back(std::vector<Request *>{req});
}
}
#ifdef HAVE_JANSSON
2014-11-27 15:39:04 +01:00
void 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);
2014-11-27 15:39:04 +01:00
json_object_set_new(
page, "startedDateTime",
json_string(util::format_iso8601(stat.started_system_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);
2014-11-27 15:39:04 +01:00
auto dns_delta =
std::chrono::duration_cast<std::chrono::microseconds>(
stat.on_dns_complete_time - stat.on_started_time).count() /
1000.0;
auto connect_delta =
std::chrono::duration_cast<std::chrono::microseconds>(
stat.on_connect_time - stat.on_dns_complete_time).count() /
1000.0;
2014-11-27 15:39:04 +01:00
for (size_t i = 0; i < reqvec.size(); ++i) {
auto &req = reqvec[i];
2014-11-27 15:39:04 +01:00
if (req->stat.stage != STAT_ON_COMPLETE) {
continue;
}
auto entry = json_object();
json_array_append_new(entries, entry);
2014-11-27 15:39:04 +01:00
auto &req_stat = req->stat;
auto request_time =
(i == 0) ? stat.started_system_time
: stat.started_system_time +
std::chrono::duration_cast<
std::chrono::system_clock::duration>(
req_stat.on_request_time - stat.on_started_time);
auto wait_delta =
std::chrono::duration_cast<std::chrono::microseconds>(
req_stat.on_response_time - req_stat.on_request_time).count() /
1000.0;
auto receive_delta =
std::chrono::duration_cast<std::chrono::microseconds>(
req_stat.on_complete_time - req_stat.on_response_time).count() /
1000.0;
auto time_sum =
std::chrono::duration_cast<std::chrono::microseconds>(
(i == 0) ? (req_stat.on_complete_time - stat.on_started_time)
: (req_stat.on_complete_time - req_stat.on_request_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 request = json_object();
json_object_set_new(entry, "request", request);
auto method_ptr = http2::get_header(req->req_nva, ":method");
const char *method = "GET";
2014-11-27 15:39:04 +01:00
if (method_ptr) {
method = (*method_ptr).value.c_str();
}
auto req_headers = json_array();
json_object_set_new(request, "headers", req_headers);
2014-11-27 15:39:04 +01:00
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(method));
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);
2014-11-27 15:39:04 +01:00
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 = "";
2014-11-27 15:39:04 +01:00
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_dumpf(root, outfile, JSON_PRESERVE_ORDER | JSON_INDENT(2));
json_decref(root);
}
#endif // HAVE_JANSSON
2013-07-21 16:49:12 +02:00
};
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
namespace {
2014-11-27 15:39:04 +01:00
int htp_msg_begincb(http_parser *htp) {
if (config.verbose) {
print_timer();
std::cout << " HTTP Upgrade response" << std::endl;
}
return 0;
}
} // namespace
namespace {
2014-11-27 15:39:04 +01:00
int htp_statuscb(http_parser *htp, const char *at, size_t length) {
auto client = static_cast<HttpClient *>(htp->data);
client->upgrade_response_status_code = htp->status_code;
return 0;
}
} // namespace
namespace {
2014-11-27 15:39:04 +01:00
int htp_msg_completecb(http_parser *htp) {
auto client = static_cast<HttpClient *>(htp->data);
client->upgrade_response_complete = true;
return 0;
}
} // namespace
namespace {
http_parser_settings htp_hooks = {
2014-11-27 15:39:04 +01:00
htp_msg_begincb, // http_cb on_message_begin;
nullptr, // http_data_cb on_url;
htp_statuscb, // http_data_cb on_status;
nullptr, // http_data_cb on_header_field;
nullptr, // http_data_cb on_header_value;
nullptr, // http_cb on_headers_complete;
nullptr, // http_data_cb on_body;
htp_msg_completecb // http_cb on_message_complete;
};
} // namespace
2013-07-21 16:49:12 +02:00
namespace {
2014-11-27 15:39:04 +01:00
int submit_request(HttpClient *client, const Headers &headers, Request *req) {
2013-07-21 16:49:12 +02:00
auto path = req->make_reqpath();
auto scheme = util::get_uri_field(req->uri.c_str(), req->u, UF_SCHEMA);
2014-11-27 15:39:04 +01:00
auto build_headers = Headers{{":method", req->data_prd ? "POST" : "GET"},
{":path", path},
{":scheme", scheme},
{":authority", client->hostport},
{"accept", "*/*"},
{"accept-encoding", "gzip, deflate"},
{"user-agent", "nghttp2/" NGHTTP2_VERSION}};
if (config.continuation) {
for (size_t i = 0; i < 6; ++i) {
build_headers.emplace_back("continuation-test-" + util::utos(i + 1),
std::string(4096, '-'));
}
}
auto num_initial_headers = build_headers.size();
2014-11-27 15:39:04 +01:00
if (!config.no_content_length && req->data_prd) {
build_headers.emplace_back("content-length", util::utos(req->data_length));
2013-07-21 16:49:12 +02:00
}
2014-11-27 15:39:04 +01:00
for (auto &kv : headers) {
size_t i;
2014-11-27 15:39:04 +01:00
for (i = 0; i < num_initial_headers; ++i) {
if (kv.name == build_headers[i].name) {
build_headers[i].value = kv.value;
break;
}
2013-07-21 16:49:12 +02:00
}
2014-11-27 15:39:04 +01:00
if (i < num_initial_headers) {
continue;
}
build_headers.emplace_back(kv.name, kv.value, kv.no_index);
}
auto nva = std::vector<nghttp2_nv>();
nva.reserve(build_headers.size());
2014-11-27 15:39:04 +01:00
for (auto &kv : build_headers) {
nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
}
2014-03-25 18:04:24 +01:00
2014-11-27 15:39:04 +01:00
auto stream_id =
nghttp2_submit_request(client->session, &req->pri_spec, nva.data(),
nva.size(), req->data_prd, req);
if (stream_id < 0) {
2014-08-08 17:03:33 +02:00
std::cerr << "[ERROR] nghttp2_submit_request() returned error: "
<< nghttp2_strerror(stream_id) << std::endl;
return -1;
}
req->stream_id = stream_id;
client->on_request(req);
req->req_nva = std::move(build_headers);
return 0;
2013-07-21 16:49:12 +02:00
}
} // namespace
2013-09-16 09:54:11 +02:00
namespace {
2014-11-27 15:39:04 +01:00
void update_html_parser(HttpClient *client, Request *req, const uint8_t *data,
size_t len, int fin) {
if (!req->html_parser) {
2013-07-21 16:49:12 +02:00
return;
}
req->update_html_parser(data, len, fin);
2014-11-27 15:39:04 +01:00
for (auto &p : req->html_parser->get_links()) {
auto uri = strip_fragment(p.first.c_str());
auto pri = p.second;
2013-07-21 16:49:12 +02:00
http_parser_url u;
2014-02-27 14:11:31 +01:00
memset(&u, 0, sizeof(u));
2014-11-27 15:39:04 +01:00
if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) == 0 &&
util::fieldeq(uri.c_str(), u, req->uri.c_str(), req->u, UF_SCHEMA) &&
util::fieldeq(uri.c_str(), u, req->uri.c_str(), req->u, UF_HOST) &&
util::porteq(uri.c_str(), u, req->uri.c_str(), req->u)) {
2013-07-21 16:49:12 +02:00
// No POST data for assets
auto pri_spec = req->resolve_dep(pri);
2014-03-25 18:04:24 +01:00
2014-11-27 15:39:04 +01:00
if (client->add_request(uri, nullptr, 0, pri_spec, req->dep, pri,
req->level + 1)) {
2014-03-25 18:04:24 +01:00
2014-11-27 15:39:04 +01:00
submit_request(client, config.headers, client->reqvec.back().get());
2013-07-21 16:49:12 +02:00
}
}
}
req->html_parser->clear_links();
}
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
2013-09-16 09:54:11 +02:00
namespace {
2014-11-27 15:39:04 +01:00
HttpClient *get_session(void *user_data) {
return static_cast<HttpClient *>(user_data);
2013-07-21 16:49:12 +02:00
}
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
2013-09-16 09:54:11 +02:00
namespace {
2014-11-27 15:39:04 +01:00
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) {
2013-09-03 16:09:56 +02:00
auto client = get_session(user_data);
2014-11-27 15:39:04 +01:00
auto req = static_cast<Request *>(
nghttp2_session_get_stream_user_data(session, stream_id));
2014-11-27 15:39:04 +01:00
if (!req) {
2014-05-07 17:29:46 +02:00
return 0;
}
2014-11-27 15:39:04 +01:00
if (config.verbose >= 2) {
verbose_on_data_chunk_recv_callback(session, flags, stream_id, data, len,
user_data);
}
2014-11-27 15:39:04 +01:00
if (req->status == 0) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
NGHTTP2_PROTOCOL_ERROR);
return 0;
}
2014-11-27 15:39:04 +01:00
if (req->inflater) {
while (len > 0) {
const size_t MAX_OUTLEN = 4096;
uint8_t out[MAX_OUTLEN];
2014-05-07 17:29:46 +02:00
size_t outlen = MAX_OUTLEN;
size_t tlen = len;
int rv = nghttp2_gzip_inflate(req->inflater, out, &outlen, data, &tlen);
2014-11-27 15:39:04 +01:00
if (rv != 0) {
2014-05-07 17:29:46 +02:00
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
NGHTTP2_INTERNAL_ERROR);
break;
}
req->response_len += outlen;
2014-11-27 15:39:04 +01:00
if (!config.null_out) {
std::cout.write(reinterpret_cast<const char *>(out), outlen);
2014-05-07 17:29:46 +02:00
}
2014-05-07 17:29:46 +02:00
update_html_parser(client, req, out, outlen, 0);
data += tlen;
len -= tlen;
}
2014-06-07 09:04:43 +02:00
return 0;
2013-07-21 16:49:12 +02:00
}
req->response_len += len;
2014-11-27 15:39:04 +01:00
if (!config.null_out) {
std::cout.write(reinterpret_cast<const char *>(data), len);
}
2014-06-07 09:04:43 +02:00
update_html_parser(client, req, data, len, 0);
return 0;
2013-07-21 16:49:12 +02:00
}
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
namespace {
void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
auto client = static_cast<HttpClient *>(w->data);
ev_timer_stop(loop, w);
nghttp2_session_terminate_session(client->session, NGHTTP2_SETTINGS_TIMEOUT);
client->signal_write();
}
} // namespace
namespace {
2014-11-27 15:39:04 +01:00
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
2013-09-16 09:54:11 +02:00
namespace {
2014-11-27 15:39:04 +01:00
void check_response_header(nghttp2_session *session, Request *req) {
2013-07-21 16:49:12 +02:00
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;
2013-07-21 16:49:12 +02:00
}
auto status = http2::parse_http_status_code(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("gzip", nv.value) || util::strieq("deflate", nv.value);
continue;
}
}
2014-11-27 15:39:04 +01:00
if (req->status / 100 == 1) {
req->expect_final_response = true;
req->status = 0;
req->res_nva.clear();
http2::init_hdidx(req->res_hdidx);
return;
}
2014-11-27 15:39:04 +01:00
if (gzip) {
if (!req->inflater) {
2013-07-21 16:49:12 +02:00
req->init_inflater();
}
}
2014-11-27 15:39:04 +01:00
if (config.get_assets && req->level == 0) {
if (!req->html_parser) {
2013-07-21 16:49:12 +02:00
req->init_html_parser();
}
}
}
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
namespace {
int on_begin_headers_callback(nghttp2_session *session,
2014-11-27 15:39:04 +01:00
const nghttp2_frame *frame, void *user_data) {
auto client = get_session(user_data);
2014-11-27 15:39:04 +01:00
switch (frame->hd.type) {
case NGHTTP2_PUSH_PROMISE: {
auto stream_id = frame->push_promise.promised_stream_id;
http_parser_url u;
memset(&u, 0, sizeof(u));
// TODO Set pri and level
nghttp2_priority_spec pri_spec;
nghttp2_priority_spec_default_init(&pri_spec);
2014-03-25 18:04:24 +01:00
2014-11-27 15:39:04 +01:00
auto req = util::make_unique<Request>("", u, nullptr, 0, pri_spec, nullptr);
req->stream_id = stream_id;
nghttp2_session_set_stream_user_data(session, stream_id, req.get());
client->on_request(req.get());
client->reqvec.push_back(std::move(req));
break;
}
}
return 0;
}
2014-11-27 15:39:04 +01:00
} // namespace
namespace {
2014-11-27 15:39:04 +01:00
int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
const uint8_t *name, size_t namelen,
2014-11-27 15:39:04 +01:00
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);
}
2014-11-27 15:39:04 +01:00
if (!http2::check_nv(name, namelen, value, valuelen)) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
2014-11-27 15:39:04 +01:00
switch (frame->hd.type) {
case NGHTTP2_HEADERS: {
2014-11-27 15:39:04 +01:00
auto req = static_cast<Request *>(
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
2014-11-27 15:39:04 +01:00
if (!req) {
break;
}
2014-11-27 15:39:04 +01:00
if (frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
frame->headers.cat != NGHTTP2_HCAT_PUSH_RESPONSE &&
(frame->headers.cat != NGHTTP2_HCAT_HEADERS ||
!req->expect_final_response)) {
break;
}
auto token = http2::lookup_token(name, namelen);
if (name[0] == ':') {
if (!req->response_pseudo_header_allowed(token)) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
2014-11-27 15:39:04 +01:00
frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
}
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);
break;
}
case NGHTTP2_PUSH_PROMISE: {
2014-11-27 15:39:04 +01:00
auto req = static_cast<Request *>(nghttp2_session_get_stream_user_data(
session, frame->push_promise.promised_stream_id));
2014-11-27 15:39:04 +01:00
if (!req) {
break;
}
auto token = http2::lookup_token(name, namelen);
if (name[0] == ':') {
if (!req->push_request_pseudo_header_allowed(token)) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
frame->push_promise.promised_stream_id,
NGHTTP2_PROTOCOL_ERROR);
break;
}
}
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);
break;
}
}
return 0;
}
} // namespace
2013-09-16 09:54:11 +02:00
namespace {
2014-11-27 15:39:04 +01:00
int on_frame_recv_callback2(nghttp2_session *session,
const nghttp2_frame *frame, void *user_data) {
int rv = 0;
2014-11-27 15:39:04 +01:00
if (config.verbose) {
verbose_on_frame_recv_callback(session, frame, user_data);
}
auto client = get_session(user_data);
2014-11-27 15:39:04 +01:00
switch (frame->hd.type) {
case NGHTTP2_HEADERS: {
2014-11-27 15:39:04 +01:00
auto req = static_cast<Request *>(
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.
2014-11-27 15:39:04 +01:00
if (!req) {
break;
}
2014-11-27 15:39:04 +01:00
if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE ||
frame->headers.cat == NGHTTP2_HCAT_PUSH_RESPONSE) {
req->record_response_time();
check_response_header(session, req);
break;
}
2014-11-27 15:39:04 +01:00
if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) {
if (req->expect_final_response) {
check_response_header(session, req);
} else {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
break;
}
}
2014-11-27 15:39:04 +01:00
if (req->status == 0 && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR);
break;
}
break;
}
case NGHTTP2_SETTINGS:
2014-11-27 15:39:04 +01:00
if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
break;
}
ev_timer_stop(client->loop, &client->settings_timer);
break;
case NGHTTP2_PUSH_PROMISE: {
2014-11-27 15:39:04 +01:00
auto req = static_cast<Request *>(nghttp2_session_get_stream_user_data(
session, frame->push_promise.promised_stream_id));
if (!req) {
break;
}
auto scheme = req->get_req_header(http2::HD__SCHEME);
auto authority = req->get_req_header(http2::HD__AUTHORITY);
auto method = req->get_req_header(http2::HD__METHOD);
auto path = req->get_req_header(http2::HD__PATH);
if (!authority) {
authority = req->get_req_header(http2::HD_HOST);
}
if (!scheme || !authority || !method || !path || scheme->value.empty() ||
authority->value.empty() || method->value.empty() ||
path->value.empty() || 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;
2014-02-27 14:11:31 +01:00
memset(&u, 0, sizeof(u));
2014-11-27 15:39:04 +01:00
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;
break;
}
}
return rv;
2013-07-21 16:49:12 +02:00
}
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
2013-09-16 09:54:11 +02:00
namespace {
2014-11-27 15:39:04 +01:00
int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
uint32_t error_code, void *user_data) {
2013-09-03 16:09:56 +02:00
auto client = get_session(user_data);
2014-11-27 15:39:04 +01:00
auto req = static_cast<Request *>(
nghttp2_session_get_stream_user_data(session, stream_id));
2014-05-07 17:29:46 +02:00
2014-11-27 15:39:04 +01:00
if (!req) {
2014-05-07 17:29:46 +02:00
return 0;
}
2014-05-07 17:29:46 +02:00
update_html_parser(client, req, nullptr, 0, 1);
req->record_complete_time();
++client->complete;
2014-11-27 15:39:04 +01:00
if (client->all_requests_processed()) {
nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
2013-07-21 16:49:12 +02:00
}
2014-05-07 17:29:46 +02:00
return 0;
2013-07-21 16:49:12 +02:00
}
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
2013-09-16 09:54:11 +02:00
namespace {
2014-11-27 15:39:04 +01:00
void print_stats(const HttpClient &client) {
2013-07-21 16:49:12 +02:00
std::cout << "***** Statistics *****" << std::endl;
int i = 0;
2014-11-27 15:39:04 +01:00
for (auto &req : client.reqvec) {
2013-07-21 16:49:12 +02:00
std::cout << "#" << ++i << ": " << req->uri << std::endl;
std::cout << " Status: " << req->status << std::endl;
2014-11-27 15:39:04 +01:00
std::cout << " Delta (ms) from handshake(HEADERS):" << std::endl;
if (req->stat.stage >= STAT_ON_RESPONSE) {
std::cout << " response HEADERS: "
<< time_delta(req->stat.on_response_time,
2014-11-27 15:39:04 +01:00
client.stat.on_handshake_time).count() << "("
<< time_delta(req->stat.on_response_time,
2014-11-27 15:39:04 +01:00
req->stat.on_request_time).count() << ")"
2013-07-21 16:49:12 +02:00
<< std::endl;
}
2014-11-27 15:39:04 +01:00
if (req->stat.stage >= STAT_ON_COMPLETE) {
2013-07-21 16:49:12 +02:00
std::cout << " Completed: "
<< time_delta(req->stat.on_complete_time,
2014-11-27 15:39:04 +01:00
client.stat.on_handshake_time).count() << "("
2013-07-21 16:49:12 +02:00
<< time_delta(req->stat.on_complete_time,
2014-11-27 15:39:04 +01:00
req->stat.on_request_time).count() << ")"
2013-07-21 16:49:12 +02:00
<< std::endl;
}
std::cout << std::endl;
}
}
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
namespace {
2014-11-27 15:39:04 +01:00
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) {
2013-07-21 16:49:12 +02:00
print_timer();
std::cout << "[NPN] server offers:" << std::endl;
2013-07-21 16:49:12 +02:00
}
2014-11-27 15:39:04 +01:00
for (unsigned int i = 0; i < inlen; i += in [i] + 1) {
if (config.verbose) {
2013-07-21 16:49:12 +02:00
std::cout << " * ";
2014-11-27 15:39:04 +01:00
std::cout.write(reinterpret_cast<const char *>(&in[i + 1]), in[i]);
2013-07-21 16:49:12 +02:00
std::cout << std::endl;
}
}
if (!util::select_h2(const_cast<const unsigned char **>(out), outlen, in,
inlen)) {
print_protocol_nego_error();
return SSL_TLSEXT_ERR_NOACK;
2013-07-21 16:49:12 +02:00
}
return SSL_TLSEXT_ERR_OK;
}
} // namespace
namespace {
void readcb(struct ev_loop *loop, ev_io *w, int revents) {
auto client = static_cast<HttpClient *>(w->data);
if (client->do_read() != 0) {
2013-07-21 16:49:12 +02:00
client->disconnect();
}
}
} // namespace
namespace {
void writecb(struct ev_loop *loop, ev_io *w, int revents) {
auto client = static_cast<HttpClient *>(w->data);
auto rv = client->do_write();
if (rv == HttpClient::ERR_CONNECT_FAIL) {
client->on_connect_fail();
2013-07-21 16:49:12 +02:00
return;
}
2014-11-27 15:39:04 +01:00
if (rv != 0) {
2013-07-21 16:49:12 +02:00
client->disconnect();
}
}
} // namespace
namespace {
void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
auto client = static_cast<HttpClient *>(w->data);
std::cerr << "[ERROR] Timeout" << std::endl;
client->disconnect();
2013-07-21 16:49:12 +02:00
}
} // namespace
2013-09-16 09:54:11 +02:00
namespace {
2014-11-27 15:39:04 +01:00
int communicate(
const std::string &scheme, const std::string &host, uint16_t port,
std::vector<std::tuple<std::string, nghttp2_data_provider *, int64_t>>
requests, const nghttp2_session_callbacks *callbacks) {
2013-07-21 16:49:12 +02:00
int result = 0;
auto loop = EV_DEFAULT;
2013-07-21 16:49:12 +02:00
SSL_CTX *ssl_ctx = nullptr;
2014-11-27 15:39:04 +01:00
if (scheme == "https") {
2013-07-21 16:49:12 +02:00
ssl_ctx = SSL_CTX_new(SSLv23_client_method());
2014-11-27 15:39:04 +01:00
if (!ssl_ctx) {
2014-08-08 17:03:33 +02:00
std::cerr << "[ERROR] Failed to create SSL_CTX: "
2013-07-21 16:49:12 +02:00
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
result = -1;
goto fin;
}
SSL_CTX_set_options(ssl_ctx,
2014-10-22 16:14:07 +02:00
SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
2014-11-27 15:39:04 +01:00
SSL_OP_NO_COMPRESSION |
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
2013-07-21 16:49:12 +02:00
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
if (SSL_CTX_set_cipher_list(ssl_ctx, ssl::DEFAULT_CIPHER_LIST) == 0) {
std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
<< std::endl;
result = -1;
goto fin;
}
2014-11-27 15:39:04 +01:00
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;
2013-07-21 16:49:12 +02:00
result = -1;
goto fin;
}
}
2014-11-27 15:39:04 +01:00
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;
2013-07-21 16:49:12 +02:00
result = -1;
goto fin;
}
}
2014-11-27 15:39:04 +01:00
SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb,
nullptr);
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
2014-11-14 15:14:39 +01:00
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
2013-07-21 16:49:12 +02:00
}
{
HttpClient client{callbacks, loop, ssl_ctx};
2014-03-25 18:04:24 +01:00
nghttp2_priority_spec pri_spec;
int32_t dep_stream_id = 0;
2014-03-25 18:04:24 +01:00
if (!config.no_dep && config.dep_idle) {
dep_stream_id = ANCHOR_ID_HIGH;
}
2014-12-18 12:43:59 +01:00
nghttp2_priority_spec_init(&pri_spec, dep_stream_id, config.weight, 0);
2014-03-25 18:04:24 +01:00
2014-11-27 15:39:04 +01:00
for (auto req : requests) {
for (int i = 0; i < config.multiply; ++i) {
2014-03-25 18:04:24 +01:00
auto dep = std::make_shared<Dependency>();
2014-11-27 15:39:04 +01:00
client.add_request(std::get<0>(req), std::get<1>(req), std::get<2>(req),
pri_spec, std::move(dep));
2013-07-21 16:49:12 +02:00
}
}
client.update_hostport();
client.record_started_time();
2014-11-27 15:39:04 +01:00
if (client.resolve_host(host, port) != 0) {
goto fin;
}
client.record_dns_complete_time();
2014-11-27 15:39:04 +01:00
if (client.initiate_connection() != 0) {
2013-07-21 16:49:12 +02:00
goto fin;
}
ev_run(loop, 0);
2013-07-21 16:49:12 +02:00
#ifdef HAVE_JANSSON
2014-11-27 15:39:04 +01:00
if (!config.harfile.empty()) {
FILE *outfile;
2014-11-27 15:39:04 +01:00
if (config.harfile == "-") {
outfile = stdout;
} else {
outfile = fopen(config.harfile.c_str(), "wb");
}
2014-11-27 15:39:04 +01:00
if (outfile) {
client.output_har(outfile);
2014-11-27 15:39:04 +01:00
if (outfile != stdout) {
fclose(outfile);
}
} else {
std::cerr << "Cannot open file " << config.harfile << ". "
2014-11-27 15:39:04 +01:00
<< "har file could not be created." << std::endl;
}
}
#endif // HAVE_JANSSON
2014-11-27 15:39:04 +01:00
if (!client.all_requests_processed()) {
2013-07-21 16:49:12 +02:00
std::cerr << "Some requests were not processed. total="
2014-11-27 15:39:04 +01:00
<< client.reqvec.size() << ", processed=" << client.complete
<< std::endl;
2013-07-21 16:49:12 +02:00
}
2014-11-27 15:39:04 +01:00
if (config.stat) {
2013-07-21 16:49:12 +02:00
print_stats(client);
}
}
2014-11-27 15:39:04 +01:00
fin:
if (ssl_ctx) {
2013-07-21 16:49:12 +02:00
SSL_CTX_free(ssl_ctx);
}
return result;
}
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
2013-09-16 09:54:11 +02:00
namespace {
2014-11-27 15:39:04 +01:00
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) {
auto req = static_cast<Request *>(
nghttp2_session_get_stream_user_data(session, stream_id));
2013-09-07 09:38:21 +02:00
assert(req);
2013-07-21 16:49:12 +02:00
int fd = source->fd;
ssize_t nread;
2014-11-27 15:39:04 +01:00
while ((nread = pread(fd, buf, length, req->data_offset)) == -1 &&
errno == EINTR)
;
2014-11-27 15:39:04 +01:00
if (nread == -1) {
2014-06-07 09:04:43 +02:00
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
2013-07-21 16:49:12 +02:00
}
2014-11-27 15:39:04 +01:00
if (nread == 0) {
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
} else {
req->data_offset += nread;
}
return nread;
2013-07-21 16:49:12 +02:00
}
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
namespace {
ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
size_t length, int flags, void *user_data) {
auto client = static_cast<HttpClient *>(user_data);
auto &wb = client->wb;
if (wb.wleft() == 0) {
return NGHTTP2_ERR_WOULDBLOCK;
}
return wb.write(data, length);
}
} // namespace
2013-09-16 09:54:11 +02:00
namespace {
2014-11-27 15:39:04 +01:00
int run(char **uris, int n) {
nghttp2_session_callbacks *callbacks;
nghttp2_session_callbacks_new(&callbacks);
2014-09-16 16:39:38 +02:00
auto cbsdel = util::defer(callbacks, nghttp2_session_callbacks_del);
2014-11-27 15:39:04 +01:00
nghttp2_session_callbacks_set_on_stream_close_callback(
callbacks, on_stream_close_callback);
2014-11-27 15:39:04 +01:00
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
on_frame_recv_callback2);
2014-11-27 15:39:04 +01:00
if (config.verbose) {
nghttp2_session_callbacks_set_on_frame_send_callback(
callbacks, verbose_on_frame_send_callback);
2014-11-27 15:39:04 +01:00
nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
callbacks, verbose_on_invalid_frame_recv_callback);
}
2014-11-27 15:39:04 +01:00
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
callbacks, on_data_chunk_recv_callback);
2014-11-27 15:39:04 +01:00
nghttp2_session_callbacks_set_on_begin_headers_callback(
callbacks, on_begin_headers_callback);
2014-11-27 15:39:04 +01:00
nghttp2_session_callbacks_set_on_header_callback(callbacks,
on_header_callback);
nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
2014-11-27 15:39:04 +01:00
if (config.padding) {
nghttp2_session_callbacks_set_select_padding_callback(
callbacks, select_padding_callback);
}
std::string prev_scheme;
2013-07-21 16:49:12 +02:00
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;
2014-11-27 15:39:04 +01:00
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) {
char buf[1024];
ssize_t rret, wret;
while ((rret = read(0, buf, sizeof(buf))) == -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, 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;
}
2013-07-21 16:49:12 +02:00
}
data_prd.source.fd = data_fd;
data_prd.read_callback = file_read_callback;
}
2014-11-27 15:39:04 +01:00
std::vector<std::tuple<std::string, nghttp2_data_provider *, int64_t>>
requests;
for (int i = 0; i < n; ++i) {
2013-07-21 16:49:12 +02:00
http_parser_url u;
2014-02-27 14:11:31 +01:00
memset(&u, 0, sizeof(u));
2013-09-03 16:09:56 +02:00
auto uri = strip_fragment(uris[i]);
2014-11-27 15:39:04 +01:00
if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) == 0 &&
util::has_uri_field(u, UF_SCHEMA)) {
uint16_t port = util::has_uri_field(u, UF_PORT)
? u.port
: util::get_default_port(uri.c_str(), u);
if (!util::fieldeq(uri.c_str(), u, UF_SCHEMA, prev_scheme.c_str()) ||
!util::fieldeq(uri.c_str(), u, UF_HOST, prev_host.c_str()) ||
port != prev_port) {
if (!requests.empty()) {
if (communicate(prev_scheme, prev_host, prev_port,
std::move(requests), callbacks) != 0) {
2013-07-21 16:49:12 +02:00
++failures;
}
requests.clear();
}
prev_scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA);
prev_host = util::get_uri_field(uri.c_str(), u, UF_HOST);
2013-07-21 16:49:12 +02:00
prev_port = port;
}
requests.emplace_back(uri, data_fd == -1 ? nullptr : &data_prd,
data_stat.st_size);
}
}
2014-11-27 15:39:04 +01:00
if (!requests.empty()) {
if (communicate(prev_scheme, prev_host, prev_port, std::move(requests),
callbacks) != 0) {
2013-07-21 16:49:12 +02:00
++failures;
}
}
return failures;
}
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
namespace {
2014-11-27 15:39:04 +01:00
void print_version(std::ostream &out) {
out << "nghttp nghttp2/" NGHTTP2_VERSION << std::endl;
}
} // namespace
2013-09-16 09:54:11 +02:00
namespace {
2014-11-27 15:39:04 +01:00
void print_usage(std::ostream &out) {
out << R"(Usage: nghttp [OPTIONS]... <URI>...
HTTP/2 experimental client)" << std::endl;
2013-07-21 16:49:12 +02:00
}
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
2013-09-16 09:54:11 +02:00
namespace {
2014-11-27 15:39:04 +01:00
void print_help(std::ostream &out) {
2013-07-21 16:49:12 +02:00
print_usage(out);
out << R"(
<URI> 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 dereived from URI. If URI ends with
'/', 'index.html' is used as a filename. Not
implemented yet.
-t, --timeout=<N> Timeout each request after <N> seconds.
-w, --window-bits=<N>
Sets the stream level initial window size to
2**<N>-1.
-W, --connection-window-bits=<N>
Sets the connection level initial window size to
2**<N>-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=<HEADER>
Add a header to the requests. Example:
-H':method: PUT'
--cert=<CERT> Use the specified client certificate file. The
file must be in PEM format.
--key=<KEY> Use the client private key file. The file must
be in PEM format.
-d, --data=<FILE> Post FILE to server. If '-' is given, data will
be read from stdin.
-m, --multiply=<N> Request each URI <N> times. By default, same URI
is not requested twice. This option disables it
too.
2014-03-30 12:09:21 +02:00
-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.
2014-03-25 18:04:24 +01:00
-p, --weight=<WEIGHT>
Sets priority group weight. The valid value
2014-11-27 15:39:04 +01:00
range is [)" << NGHTTP2_MIN_WEIGHT << ", "
<< NGHTTP2_MAX_WEIGHT << R"(], inclusive.
Default: )" << NGHTTP2_DEFAULT_WEIGHT << R"(
-M, --peer-max-concurrent-streams=<N>
Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value
of remote endpoint as if it is received in
SETTINGS frame. The default is large enough as
it is seen as unlimited.
-c, --header-table-size=<N>
Specify decoder header table size.
-b, --padding=<N> Add at most <N> bytes to a frame payload as
padding. Specify 0 to disable padding.
-r, --har=<FILE> Output HTTP transactions <FILE> in HAR format.
If '-' is given, data is written to stdout.
--color Force colored log output.
--continuation Send large header to test CONTINUATION.
2014-07-03 15:48:43 +02:00
--no-content-length
Don't send content-length header field.
--no-dep Don't send dependency based priority hint to
server.
--dep-idle Use idle streams as anchor nodes to express
priority.
--version Display version information and exit.
2014-11-27 15:39:04 +01:00
-h, --help Display this help and exit.)" << std::endl;
2013-07-21 16:49:12 +02:00
}
2013-09-16 09:54:11 +02:00
} // namespace
2013-07-21 16:49:12 +02:00
2014-11-27 15:39:04 +01:00
int main(int argc, char **argv) {
bool color = false;
2014-11-27 15:39:04 +01:00
while (1) {
static int flag = 0;
2013-07-21 16:49:12 +02:00
static option long_options[] = {
2014-11-27 15:39:04 +01:00
{"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'},
{"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},
{"dep-idle", no_argument, &flag, 8},
2014-11-27 15:39:04 +01:00
{nullptr, 0, nullptr, 0}};
2013-07-21 16:49:12 +02:00
int option_index = 0;
int c = getopt_long(argc, argv, "M:Oab:c:d:gm:np:r:hH:vst:uw:W:",
long_options, &option_index);
2013-10-29 13:34:22 +01:00
char *end;
2014-11-27 15:39:04 +01:00
if (c == -1) {
2013-07-21 16:49:12 +02:00
break;
}
2014-11-27 15:39:04 +01:00
switch (c) {
case 'M':
// peer-max-concurrent-streams option
config.peer_max_concurrent_streams = strtoul(optarg, nullptr, 10);
break;
2013-07-21 16:49:12 +02:00
case 'O':
config.remote_name = true;
break;
case 'h':
print_help(std::cout);
exit(EXIT_SUCCESS);
2014-02-07 15:22:17 +01:00
case 'b':
config.padding = strtol(optarg, nullptr, 10);
2014-02-07 15:22:17 +01:00
break;
2013-07-21 16:49:12 +02:00
case 'n':
config.null_out = true;
break;
case 'p': {
errno = 0;
auto n = strtoul(optarg, nullptr, 10);
2014-11-27 15:39:04 +01:00
if (errno == 0 && NGHTTP2_MIN_WEIGHT <= n && n <= NGHTTP2_MAX_WEIGHT) {
2014-03-25 18:04:24 +01:00
config.weight = n;
} else {
std::cerr << "-p: specify the integer in the range ["
2014-11-27 15:39:04 +01:00
<< NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT
<< "], inclusive" << std::endl;
exit(EXIT_FAILURE);
}
break;
}
case 'r':
#ifdef HAVE_JANSSON
config.harfile = optarg;
2014-11-27 15:39:04 +01:00
#else // !HAVE_JANSSON
std::cerr << "[WARNING]: -r, --har option is ignored because\n"
2014-11-27 15:39:04 +01:00
<< "the binary was not compiled with libjansson." << std::endl;
#endif // !HAVE_JANSSON
break;
2013-07-21 16:49:12 +02:00
case 'v':
++config.verbose;
2013-07-21 16:49:12 +02:00
break;
case 't':
config.timeout = atoi(optarg);
2013-07-21 16:49:12 +02:00
break;
case 'u':
config.upgrade = true;
break;
case 'w':
case 'W': {
2013-07-21 16:49:12 +02:00
errno = 0;
char *endptr = nullptr;
unsigned long int n = strtoul(optarg, &endptr, 10);
2014-11-27 15:39:04 +01:00
if (errno == 0 && *endptr == '\0' && n < 31) {
if (c == 'w') {
config.window_bits = n;
} else {
config.connection_window_bits = n;
}
2013-07-21 16:49:12 +02:00
} else {
std::cerr << "-" << static_cast<char>(c)
<< ": specify the integer in the range [0, 30], inclusive"
2013-07-21 16:49:12 +02:00
<< std::endl;
exit(EXIT_FAILURE);
}
break;
}
case 'H': {
char *header = optarg;
// Skip first possible ':' in the header name
2014-11-27 15:39:04 +01:00
char *value = strchr(optarg + 1, ':');
if (!value || (header[0] == ':' && header + 1 == value)) {
std::cerr << "-H: invalid header: " << optarg << std::endl;
2013-07-21 16:49:12 +02:00
exit(EXIT_FAILURE);
}
*value = 0;
value++;
2014-11-27 15:39:04 +01:00
while (isspace(*value)) {
value++;
}
if (*value == 0) {
2013-07-21 16:49:12 +02:00
// 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);
}
// To test "never index" repr, don't index authorization header
// field unconditionally.
auto no_index = util::strieq("authorization", header);
config.headers.emplace_back(header, value, no_index);
util::inp_strlower(config.headers.back().name);
2013-07-21 16:49:12 +02:00
break;
}
case 'a':
#ifdef HAVE_LIBXML2
config.get_assets = true;
2014-11-27 15:39:04 +01:00
#else // !HAVE_LIBXML2
2014-11-15 15:34:37 +01:00
std::cerr << "[WARNING]: -a, --get-assets option is ignored because\n"
2014-11-27 15:39:04 +01:00
<< "the binary was not compiled with libxml2." << std::endl;
2013-07-21 16:49:12 +02:00
#endif // !HAVE_LIBXML2
break;
case 's':
config.stat = true;
break;
case 'd':
config.datafile = optarg;
2013-07-21 16:49:12 +02:00
break;
case 'm':
2013-09-03 16:09:56 +02:00
config.multiply = strtoul(optarg, nullptr, 10);
2013-07-21 16:49:12 +02:00
break;
2013-10-29 13:34:22 +01:00
case 'c':
errno = 0;
2013-10-29 13:34:22 +01:00
config.header_table_size = strtol(optarg, &end, 10);
2014-11-27 15:39:04 +01:00
if (errno == ERANGE || *end != '\0') {
2013-10-29 13:34:22 +01:00
std::cerr << "-c: Bad option value: " << optarg << std::endl;
exit(EXIT_FAILURE);
}
break;
2013-07-21 16:49:12 +02:00
case '?':
2014-01-08 17:27:56 +01:00
util::show_candidates(argv[optind - 1], long_options);
2013-07-21 16:49:12 +02:00
exit(EXIT_FAILURE);
case 0:
2014-11-27 15:39:04 +01:00
switch (flag) {
2013-07-21 16:49:12 +02:00
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);
2014-07-03 15:48:43 +02:00
case 6:
// no-content-length option
config.no_content_length = true;
break;
case 7:
// no-dep option
config.no_dep = true;
break;
case 8:
// dep-idle option
config.dep_idle = true;
break;
2013-07-21 16:49:12 +02:00
}
break;
default:
break;
}
}
set_color_output(color || isatty(fileno(stdout)));
2013-07-21 16:49:12 +02:00
2014-11-27 15:39:04 +01:00
nghttp2_option_set_peer_max_concurrent_streams(
config.http2_option, config.peer_max_concurrent_streams);
2013-07-21 16:49:12 +02:00
struct sigaction act;
memset(&act, 0, sizeof(struct sigaction));
act.sa_handler = SIG_IGN;
2013-09-03 16:09:56 +02:00
sigaction(SIGPIPE, &act, nullptr);
2014-08-02 03:11:45 +02:00
OPENSSL_config(nullptr);
OpenSSL_add_all_algorithms();
2013-07-21 16:49:12 +02:00
SSL_load_error_strings();
SSL_library_init();
reset_timer();
2014-11-27 15:39:04 +01:00
return run(argv + optind, argc - optind);
2013-07-21 16:49:12 +02:00
}
} // namespace nghttp2
2014-11-27 15:39:04 +01:00
int main(int argc, char **argv) { return nghttp2::main(argc, argv); }