Initial HTTP/1.1 capability. Add npn-list option to h2load. Make NPN/ALPN more runtime dependent
This commit is contained in:
parent
d22573086f
commit
ec47dfb9b8
|
@ -86,7 +86,8 @@ h2load_SOURCES = util.cc util.h \
|
||||||
timegm.c timegm.h \
|
timegm.c timegm.h \
|
||||||
ssl.cc ssl.h \
|
ssl.cc ssl.h \
|
||||||
h2load_session.h \
|
h2load_session.h \
|
||||||
h2load_http2_session.cc h2load_http2_session.h
|
h2load_http2_session.cc h2load_http2_session.h \
|
||||||
|
h2load_http1_session.cc h2load_http1_session.h
|
||||||
|
|
||||||
if HAVE_SPDYLAY
|
if HAVE_SPDYLAY
|
||||||
h2load_SOURCES += h2load_spdy_session.cc h2load_spdy_session.h
|
h2load_SOURCES += h2load_spdy_session.cc h2load_spdy_session.h
|
||||||
|
|
184
src/h2load.cc
184
src/h2load.cc
|
@ -54,6 +54,7 @@
|
||||||
|
|
||||||
#include "http-parser/http_parser.h"
|
#include "http-parser/http_parser.h"
|
||||||
|
|
||||||
|
#include "h2load_http1_session.h"
|
||||||
#include "h2load_http2_session.h"
|
#include "h2load_http2_session.h"
|
||||||
#ifdef HAVE_SPDYLAY
|
#ifdef HAVE_SPDYLAY
|
||||||
#include "h2load_spdy_session.h"
|
#include "h2load_spdy_session.h"
|
||||||
|
@ -90,31 +91,6 @@ bool Config::is_rate_mode() const { return (this->rate != 0); }
|
||||||
bool Config::has_base_uri() const { return (!this->base_uri.empty()); }
|
bool Config::has_base_uri() const { return (!this->base_uri.empty()); }
|
||||||
Config config;
|
Config config;
|
||||||
|
|
||||||
namespace {
|
|
||||||
void debug(const char *format, ...) {
|
|
||||||
if (config.verbose) {
|
|
||||||
fprintf(stderr, "[DEBUG] ");
|
|
||||||
va_list ap;
|
|
||||||
va_start(ap, format);
|
|
||||||
vfprintf(stderr, format, ap);
|
|
||||||
va_end(ap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
void debug_nextproto_error() {
|
|
||||||
#ifdef HAVE_SPDYLAY
|
|
||||||
debug("no supported protocol was negotiated, expected: %s, "
|
|
||||||
"spdy/2, spdy/3, spdy/3.1\n",
|
|
||||||
NGHTTP2_PROTO_VERSION_ID);
|
|
||||||
#else // !HAVE_SPDYLAY
|
|
||||||
debug("no supported protocol was negotiated, expected: %s\n",
|
|
||||||
NGHTTP2_PROTO_VERSION_ID);
|
|
||||||
#endif // !HAVE_SPDYLAY
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
RequestStat::RequestStat() : data_offset(0), completed(false) {}
|
RequestStat::RequestStat() : data_offset(0), completed(false) {}
|
||||||
|
|
||||||
Stats::Stats(size_t req_todo)
|
Stats::Stats(size_t req_todo)
|
||||||
|
@ -452,12 +428,19 @@ void Client::report_tls_info() {
|
||||||
if (worker->id == 0 && !worker->tls_info_report_done) {
|
if (worker->id == 0 && !worker->tls_info_report_done) {
|
||||||
worker->tls_info_report_done = true;
|
worker->tls_info_report_done = true;
|
||||||
auto cipher = SSL_get_current_cipher(ssl);
|
auto cipher = SSL_get_current_cipher(ssl);
|
||||||
std::cout << "Protocol: " << ssl::get_tls_protocol(ssl) << "\n"
|
std::cout << "TLS Protocol: " << ssl::get_tls_protocol(ssl) << "\n"
|
||||||
<< "Cipher: " << SSL_CIPHER_get_name(cipher) << std::endl;
|
<< "Cipher: " << SSL_CIPHER_get_name(cipher) << std::endl;
|
||||||
print_server_tmp_key(ssl);
|
print_server_tmp_key(ssl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Client::report_app_info() {
|
||||||
|
if (worker->id == 0 && !worker->app_info_report_done) {
|
||||||
|
worker->app_info_report_done = true;
|
||||||
|
std::cout << "Application protocol: " << selected_proto << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Client::terminate_session() { session->terminate(); }
|
void Client::terminate_session() { session->terminate(); }
|
||||||
|
|
||||||
void Client::on_request(int32_t stream_id) { streams[stream_id] = Stream(); }
|
void Client::on_request(int32_t stream_id) { streams[stream_id] = Stream(); }
|
||||||
|
@ -500,6 +483,27 @@ void Client::on_header(int32_t stream_id, const uint8_t *name, size_t namelen,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Client::on_status_code(int32_t stream_id, uint16_t status) {
|
||||||
|
auto itr = streams.find(stream_id);
|
||||||
|
if (itr == std::end(streams)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto &stream = (*itr).second;
|
||||||
|
|
||||||
|
if (status >= 200 && status < 300) {
|
||||||
|
++worker->stats.status[2];
|
||||||
|
stream.status_success = 1;
|
||||||
|
} else if (status < 400) {
|
||||||
|
++worker->stats.status[3];
|
||||||
|
stream.status_success = 1;
|
||||||
|
} else if (status < 600) {
|
||||||
|
++worker->stats.status[status / 100];
|
||||||
|
stream.status_success = 0;
|
||||||
|
} else {
|
||||||
|
stream.status_success = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Client::on_stream_close(int32_t stream_id, bool success,
|
void Client::on_stream_close(int32_t stream_id, bool success,
|
||||||
RequestStat *req_stat) {
|
RequestStat *req_stat) {
|
||||||
req_stat->stream_close_time = std::chrono::steady_clock::now();
|
req_stat->stream_close_time = std::chrono::steady_clock::now();
|
||||||
|
@ -541,6 +545,9 @@ int Client::connection_made() {
|
||||||
if (util::check_h2_is_selected(next_proto, next_proto_len)) {
|
if (util::check_h2_is_selected(next_proto, next_proto_len)) {
|
||||||
session = make_unique<Http2Session>(this);
|
session = make_unique<Http2Session>(this);
|
||||||
break;
|
break;
|
||||||
|
} else if (util::streq_l(NGHTTP2_H1_1, next_proto, next_proto_len)) {
|
||||||
|
session = make_unique<Http1Session>(this);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
#ifdef HAVE_SPDYLAY
|
#ifdef HAVE_SPDYLAY
|
||||||
else {
|
else {
|
||||||
|
@ -559,20 +566,59 @@ int Client::connection_made() {
|
||||||
|
|
||||||
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||||
SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
|
SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
|
||||||
|
auto proto = std::string(reinterpret_cast<const char *>(next_proto),
|
||||||
|
next_proto_len);
|
||||||
|
|
||||||
|
if (proto.empty()) {
|
||||||
|
std::cout
|
||||||
|
<< "No protocol negotiated. Fallback behaviour may be activated"
|
||||||
|
<< std::endl;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
selected_proto = proto;
|
||||||
|
report_app_info();
|
||||||
|
}
|
||||||
#else // OPENSSL_VERSION_NUMBER < 0x10002000L
|
#else // OPENSSL_VERSION_NUMBER < 0x10002000L
|
||||||
break;
|
break;
|
||||||
#endif // OPENSSL_VERSION_NUMBER < 0x10002000L
|
#endif // OPENSSL_VERSION_NUMBER < 0x10002000L
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!next_proto) {
|
if (!next_proto) {
|
||||||
debug_nextproto_error();
|
for (const auto &proto : config.npn_list) {
|
||||||
fail();
|
if (std::equal(NGHTTP2_H1_1_ALPN,
|
||||||
|
NGHTTP2_H1_1_ALPN + str_size(NGHTTP2_H1_1_ALPN),
|
||||||
|
proto.c_str())) {
|
||||||
|
std::cout
|
||||||
|
<< "Server does not support NPN/ALPN. Falling back to HTTP/1.1."
|
||||||
|
<< std::endl;
|
||||||
|
session = make_unique<Http1Session>(this);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
std::cout
|
||||||
|
<< "No supported protocol was negotiated. Supported protocols were:"
|
||||||
|
<< std::endl;
|
||||||
|
for (const auto &proto : config.npn_list) {
|
||||||
|
std::cout << proto.substr(1) << std::endl;
|
||||||
|
}
|
||||||
|
disconnect();
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (config.no_tls_proto) {
|
switch (config.no_tls_proto) {
|
||||||
case Config::PROTO_HTTP2:
|
case Config::PROTO_HTTP2:
|
||||||
session = make_unique<Http2Session>(this);
|
session = make_unique<Http2Session>(this);
|
||||||
|
selected_proto = NGHTTP2_CLEARTEXT_PROTO_VERSION_ID;
|
||||||
|
report_app_info();
|
||||||
|
break;
|
||||||
|
case Config::PROTO_HTTP1_1:
|
||||||
|
session = make_unique<Http1Session>(this);
|
||||||
|
selected_proto = NGHTTP2_H1_1;
|
||||||
|
report_app_info();
|
||||||
break;
|
break;
|
||||||
#ifdef HAVE_SPDYLAY
|
#ifdef HAVE_SPDYLAY
|
||||||
case Config::PROTO_SPDY2:
|
case Config::PROTO_SPDY2:
|
||||||
|
@ -853,8 +899,8 @@ void Client::signal_write() { ev_io_start(worker->loop, &wev); }
|
||||||
Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
|
Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
|
||||||
size_t rate, Config *config)
|
size_t rate, Config *config)
|
||||||
: stats(req_todo), loop(ev_loop_new(0)), ssl_ctx(ssl_ctx), config(config),
|
: stats(req_todo), loop(ev_loop_new(0)), ssl_ctx(ssl_ctx), config(config),
|
||||||
id(id), tls_info_report_done(false), nconns_made(0), nclients(nclients),
|
id(id), tls_info_report_done(false), app_info_report_done(false),
|
||||||
rate(rate) {
|
nconns_made(0), nclients(nclients), rate(rate) {
|
||||||
stats.req_todo = req_todo;
|
stats.req_todo = req_todo;
|
||||||
progress_interval = std::max(static_cast<size_t>(1), req_todo / 10);
|
progress_interval = std::max(static_cast<size_t>(1), req_todo / 10);
|
||||||
auto nreqs_per_client = req_todo / nclients;
|
auto nreqs_per_client = req_todo / nclients;
|
||||||
|
@ -1047,16 +1093,15 @@ namespace {
|
||||||
int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
|
int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
|
||||||
unsigned char *outlen, const unsigned char *in,
|
unsigned char *outlen, const unsigned char *in,
|
||||||
unsigned int inlen, void *arg) {
|
unsigned int inlen, void *arg) {
|
||||||
if (util::select_h2(const_cast<const unsigned char **>(out), outlen, in,
|
if (util::select_protocol(const_cast<const unsigned char **>(out), outlen, in,
|
||||||
inlen)) {
|
inlen, config.npn_list)) {
|
||||||
return SSL_TLSEXT_ERR_OK;
|
return SSL_TLSEXT_ERR_OK;
|
||||||
|
} else if (inlen == 0) {
|
||||||
|
std::cout
|
||||||
|
<< "Server does not support NPN. Fallback behaviour may be activated."
|
||||||
|
<< std::endl;
|
||||||
}
|
}
|
||||||
#ifdef HAVE_SPDYLAY
|
|
||||||
if (spdylay_select_next_protocol(out, outlen, in, inlen) > 0) {
|
|
||||||
return SSL_TLSEXT_ERR_OK;
|
return SSL_TLSEXT_ERR_OK;
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return SSL_TLSEXT_ERR_NOACK;
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -1186,6 +1231,14 @@ benchmarking tool for HTTP/2 and SPDY server)" << std::endl;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr char DEFAULT_NPN_LIST[] = "h2,h2-16,h2-14,"
|
||||||
|
#ifdef HAVE_SPDYLAY
|
||||||
|
"spdy/3.1,spdy/3,spdy/2"
|
||||||
|
#endif // HAVE_SPDYLAY
|
||||||
|
"http/1.1";
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void print_help(std::ostream &out) {
|
void print_help(std::ostream &out) {
|
||||||
print_usage(out);
|
print_usage(out);
|
||||||
|
@ -1245,12 +1298,13 @@ Options:
|
||||||
|
|
||||||
#ifdef HAVE_SPDYLAY
|
#ifdef HAVE_SPDYLAY
|
||||||
out << R"(
|
out << R"(
|
||||||
Available protocols: spdy/2, spdy/3, spdy/3.1 and )";
|
Available protocols: spdy/2, spdy/3, spdy/3.1, )";
|
||||||
#else // !HAVE_SPDYLAY
|
#else // !HAVE_SPDYLAY
|
||||||
out << R"(
|
out << R"(
|
||||||
Available protocol: )";
|
Available protocols: )";
|
||||||
#endif // !HAVE_SPDYLAY
|
#endif // !HAVE_SPDYLAY
|
||||||
out << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
|
out << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"( and )" << NGHTTP2_H1_1
|
||||||
|
<< R"(
|
||||||
Default: )" << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
|
Default: )" << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
|
||||||
-d, --data=<PATH>
|
-d, --data=<PATH>
|
||||||
Post FILE to server. The request method is changed to
|
Post FILE to server. The request method is changed to
|
||||||
|
@ -1310,6 +1364,14 @@ Options:
|
||||||
used for all requests. The base URI overrides all
|
used for all requests. The base URI overrides all
|
||||||
values defined either at the command line or inside
|
values defined either at the command line or inside
|
||||||
input files.
|
input files.
|
||||||
|
--npn-list=<LIST>
|
||||||
|
Comma delimited list of ALPN protocol identifier sorted
|
||||||
|
in the order of preference. That means most desirable
|
||||||
|
protocol comes first. This is used in both ALPN and
|
||||||
|
NPN. The parameter must be delimited by a single comma
|
||||||
|
only and any white spaces are treated as a part of
|
||||||
|
protocol string.
|
||||||
|
Default: )" << DEFAULT_NPN_LIST << R"(
|
||||||
-v, --verbose
|
-v, --verbose
|
||||||
Output debug information.
|
Output debug information.
|
||||||
--version Display version information and exit.
|
--version Display version information and exit.
|
||||||
|
@ -1351,6 +1413,7 @@ int main(int argc, char **argv) {
|
||||||
{"connection-inactivity-timeout", required_argument, nullptr, 'N'},
|
{"connection-inactivity-timeout", required_argument, nullptr, 'N'},
|
||||||
{"timing-script-file", required_argument, &flag, 3},
|
{"timing-script-file", required_argument, &flag, 3},
|
||||||
{"base-uri", required_argument, nullptr, 'B'},
|
{"base-uri", required_argument, nullptr, 'B'},
|
||||||
|
{"npn-list", required_argument, &flag, 4},
|
||||||
{nullptr, 0, nullptr, 0}};
|
{nullptr, 0, nullptr, 0}};
|
||||||
int option_index = 0;
|
int option_index = 0;
|
||||||
auto c = getopt_long(argc, argv, "hvW:c:d:m:n:p:t:w:H:i:r:C:T:N:B:",
|
auto c = getopt_long(argc, argv, "hvW:c:d:m:n:p:t:w:H:i:r:C:T:N:B:",
|
||||||
|
@ -1435,6 +1498,8 @@ int main(int argc, char **argv) {
|
||||||
case 'p':
|
case 'p':
|
||||||
if (util::strieq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, optarg)) {
|
if (util::strieq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, optarg)) {
|
||||||
config.no_tls_proto = Config::PROTO_HTTP2;
|
config.no_tls_proto = Config::PROTO_HTTP2;
|
||||||
|
} else if (util::strieq(NGHTTP2_H1_1, optarg)) {
|
||||||
|
config.no_tls_proto = Config::PROTO_HTTP1_1;
|
||||||
#ifdef HAVE_SPDYLAY
|
#ifdef HAVE_SPDYLAY
|
||||||
} else if (util::strieq("spdy/2", optarg)) {
|
} else if (util::strieq("spdy/2", optarg)) {
|
||||||
config.no_tls_proto = Config::PROTO_SPDY2;
|
config.no_tls_proto = Config::PROTO_SPDY2;
|
||||||
|
@ -1507,9 +1572,14 @@ int main(int argc, char **argv) {
|
||||||
config.ciphers = optarg;
|
config.ciphers = optarg;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
|
// timing-script option
|
||||||
config.ifile = optarg;
|
config.ifile = optarg;
|
||||||
config.timing_script = true;
|
config.timing_script = true;
|
||||||
break;
|
break;
|
||||||
|
case 4:
|
||||||
|
// npn-list option
|
||||||
|
config.npn_list = util::parse_config_str_list(optarg);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -1536,6 +1606,15 @@ int main(int argc, char **argv) {
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.npn_list.empty()) {
|
||||||
|
config.npn_list = util::parse_config_str_list(DEFAULT_NPN_LIST);
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialize the APLN tokens
|
||||||
|
for (auto &proto : config.npn_list) {
|
||||||
|
proto.insert(proto.begin(), static_cast<unsigned char>(proto.size()));
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::string> reqlines;
|
std::vector<std::string> reqlines;
|
||||||
|
|
||||||
if (config.ifile.empty()) {
|
if (config.ifile.empty()) {
|
||||||
|
@ -1704,12 +1783,11 @@ int main(int argc, char **argv) {
|
||||||
nullptr);
|
nullptr);
|
||||||
|
|
||||||
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||||
auto proto_list = util::get_default_alpn();
|
std::vector<unsigned char> proto_list;
|
||||||
#ifdef HAVE_SPDYLAY
|
for (const auto &proto : config.npn_list) {
|
||||||
static const char spdy_proto_list[] = "\x8spdy/3.1\x6spdy/3\x6spdy/2";
|
std::copy_n(proto.c_str(), proto.size(), std::back_inserter(proto_list));
|
||||||
std::copy_n(spdy_proto_list, sizeof(spdy_proto_list) - 1,
|
}
|
||||||
std::back_inserter(proto_list));
|
|
||||||
#endif // HAVE_SPDYLAY
|
|
||||||
SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
|
SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
|
||||||
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||||
|
|
||||||
|
@ -1757,6 +1835,7 @@ int main(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string user_agent = "h2load nghttp2/" NGHTTP2_VERSION;
|
||||||
Headers shared_nva;
|
Headers shared_nva;
|
||||||
shared_nva.emplace_back(":scheme", config.scheme);
|
shared_nva.emplace_back(":scheme", config.scheme);
|
||||||
if (config.port != config.default_port) {
|
if (config.port != config.default_port) {
|
||||||
|
@ -1766,7 +1845,7 @@ int main(int argc, char **argv) {
|
||||||
shared_nva.emplace_back(":authority", config.host);
|
shared_nva.emplace_back(":authority", config.host);
|
||||||
}
|
}
|
||||||
shared_nva.emplace_back(":method", config.data_fd == -1 ? "GET" : "POST");
|
shared_nva.emplace_back(":method", config.data_fd == -1 ? "GET" : "POST");
|
||||||
shared_nva.emplace_back("user-agent", "h2load nghttp2/" NGHTTP2_VERSION);
|
shared_nva.emplace_back("user-agent", user_agent);
|
||||||
|
|
||||||
// list overridalbe headers
|
// list overridalbe headers
|
||||||
auto override_hdrs =
|
auto override_hdrs =
|
||||||
|
@ -1789,6 +1868,17 @@ int main(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &req : reqlines) {
|
for (auto &req : reqlines) {
|
||||||
|
// For HTTP/1.1
|
||||||
|
std::string h1req;
|
||||||
|
h1req = config.data_fd == -1 ? "GET" : "POST";
|
||||||
|
h1req += " " + req;
|
||||||
|
h1req += " HTTP/1.1\r\n";
|
||||||
|
h1req += "Host: " + config.host + "\r\n";
|
||||||
|
h1req += "User-Agent: " + user_agent + "\r\n";
|
||||||
|
h1req += "Accept: */*\r\n";
|
||||||
|
h1req += "\r\n";
|
||||||
|
config.h1reqs.push_back(h1req);
|
||||||
|
|
||||||
// For nghttp2
|
// For nghttp2
|
||||||
std::vector<nghttp2_nv> nva;
|
std::vector<nghttp2_nv> nva;
|
||||||
|
|
||||||
|
|
16
src/h2load.h
16
src/h2load.h
|
@ -62,6 +62,7 @@ struct Worker;
|
||||||
struct Config {
|
struct Config {
|
||||||
std::vector<std::vector<nghttp2_nv>> nva;
|
std::vector<std::vector<nghttp2_nv>> nva;
|
||||||
std::vector<std::vector<const char *>> nv;
|
std::vector<std::vector<const char *>> nv;
|
||||||
|
std::vector<std::string> h1reqs;
|
||||||
std::vector<ev_tstamp> timings;
|
std::vector<ev_tstamp> timings;
|
||||||
nghttp2::Headers custom_headers;
|
nghttp2::Headers custom_headers;
|
||||||
std::string scheme;
|
std::string scheme;
|
||||||
|
@ -86,7 +87,13 @@ struct Config {
|
||||||
ssize_t conn_active_timeout;
|
ssize_t conn_active_timeout;
|
||||||
// amount of time to wait after the last request is made on a connection
|
// amount of time to wait after the last request is made on a connection
|
||||||
ssize_t conn_inactivity_timeout;
|
ssize_t conn_inactivity_timeout;
|
||||||
enum { PROTO_HTTP2, PROTO_SPDY2, PROTO_SPDY3, PROTO_SPDY3_1 } no_tls_proto;
|
enum {
|
||||||
|
PROTO_HTTP2,
|
||||||
|
PROTO_SPDY2,
|
||||||
|
PROTO_SPDY3,
|
||||||
|
PROTO_SPDY3_1,
|
||||||
|
PROTO_HTTP1_1
|
||||||
|
} no_tls_proto;
|
||||||
// file descriptor for upload data
|
// file descriptor for upload data
|
||||||
int data_fd;
|
int data_fd;
|
||||||
uint16_t port;
|
uint16_t port;
|
||||||
|
@ -94,6 +101,9 @@ struct Config {
|
||||||
bool verbose;
|
bool verbose;
|
||||||
bool timing_script;
|
bool timing_script;
|
||||||
std::string base_uri;
|
std::string base_uri;
|
||||||
|
// list of supported NPN/ALPN protocol strings in the order of
|
||||||
|
// preference.
|
||||||
|
std::vector<std::string> npn_list;
|
||||||
|
|
||||||
Config();
|
Config();
|
||||||
~Config();
|
~Config();
|
||||||
|
@ -187,6 +197,7 @@ struct Worker {
|
||||||
size_t progress_interval;
|
size_t progress_interval;
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
bool tls_info_report_done;
|
bool tls_info_report_done;
|
||||||
|
bool app_info_report_done;
|
||||||
size_t nconns_made;
|
size_t nconns_made;
|
||||||
size_t nclients;
|
size_t nclients;
|
||||||
size_t rate;
|
size_t rate;
|
||||||
|
@ -227,6 +238,7 @@ struct Client {
|
||||||
Buffer<64_k> wb;
|
Buffer<64_k> wb;
|
||||||
ev_timer conn_active_watcher;
|
ev_timer conn_active_watcher;
|
||||||
ev_timer conn_inactivity_watcher;
|
ev_timer conn_inactivity_watcher;
|
||||||
|
std::string selected_proto;
|
||||||
|
|
||||||
enum { ERR_CONNECT_FAIL = -100 };
|
enum { ERR_CONNECT_FAIL = -100 };
|
||||||
|
|
||||||
|
@ -242,6 +254,7 @@ struct Client {
|
||||||
void process_abandoned_streams();
|
void process_abandoned_streams();
|
||||||
void report_progress();
|
void report_progress();
|
||||||
void report_tls_info();
|
void report_tls_info();
|
||||||
|
void report_app_info();
|
||||||
void terminate_session();
|
void terminate_session();
|
||||||
|
|
||||||
int do_read();
|
int do_read();
|
||||||
|
@ -263,6 +276,7 @@ struct Client {
|
||||||
void on_request(int32_t stream_id);
|
void on_request(int32_t stream_id);
|
||||||
void on_header(int32_t stream_id, const uint8_t *name, size_t namelen,
|
void on_header(int32_t stream_id, const uint8_t *name, size_t namelen,
|
||||||
const uint8_t *value, size_t valuelen);
|
const uint8_t *value, size_t valuelen);
|
||||||
|
void on_status_code(int32_t stream_id, uint16_t status);
|
||||||
void on_stream_close(int32_t stream_id, bool success, RequestStat *req_stat);
|
void on_stream_close(int32_t stream_id, bool success, RequestStat *req_stat);
|
||||||
|
|
||||||
void record_request_time(RequestStat *req_stat);
|
void record_request_time(RequestStat *req_stat);
|
||||||
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015 British Broadcasting Corporation
|
||||||
|
*
|
||||||
|
* 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 "h2load_http1_session.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cerrno>
|
||||||
|
|
||||||
|
#include "h2load.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "template.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include "http-parser/http_parser.h"
|
||||||
|
|
||||||
|
using namespace nghttp2;
|
||||||
|
|
||||||
|
namespace h2load {
|
||||||
|
|
||||||
|
Http1Session::Http1Session(Client *client)
|
||||||
|
: stream_req_counter_(1), stream_resp_counter_(1), client_(client), htp_(),
|
||||||
|
complete_(false) {
|
||||||
|
http_parser_init(&htp_, HTTP_RESPONSE);
|
||||||
|
htp_.data = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Http1Session::~Http1Session() {}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// HTTP response message begin
|
||||||
|
int htp_msg_begincb(http_parser *htp) { return 0; }
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// HTTP response status code
|
||||||
|
int htp_statuscb(http_parser *htp, const char *at, size_t length) {
|
||||||
|
auto session = static_cast<Http1Session *>(htp->data);
|
||||||
|
auto client = session->get_client();
|
||||||
|
client->on_status_code(session->stream_resp_counter_, htp->status_code);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// HTTP response message complete
|
||||||
|
int htp_msg_completecb(http_parser *htp) {
|
||||||
|
auto session = static_cast<Http1Session *>(htp->data);
|
||||||
|
auto client = session->get_client();
|
||||||
|
|
||||||
|
client->on_stream_close(session->stream_resp_counter_, true,
|
||||||
|
session->req_stats_[session->stream_resp_counter_]);
|
||||||
|
|
||||||
|
session->stream_resp_counter_ += 2;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
|
||||||
|
auto session = static_cast<Http1Session *>(htp->data);
|
||||||
|
auto client = session->get_client();
|
||||||
|
|
||||||
|
client->worker->stats.bytes_head += len;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
|
||||||
|
auto session = static_cast<Http1Session *>(htp->data);
|
||||||
|
auto client = session->get_client();
|
||||||
|
|
||||||
|
client->worker->stats.bytes_head += len;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int htp_body_cb(http_parser *htp, const char *data, size_t len) {
|
||||||
|
auto session = static_cast<Http1Session *>(htp->data);
|
||||||
|
auto client = session->get_client();
|
||||||
|
|
||||||
|
client->record_ttfb();
|
||||||
|
client->worker->stats.bytes_body += len;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
http_parser_settings htp_hooks = {
|
||||||
|
htp_msg_begincb, // http_cb on_message_begin;
|
||||||
|
nullptr, // http_data_cb on_url;
|
||||||
|
htp_statuscb, // http_data_cb on_status;
|
||||||
|
htp_hdr_keycb, // http_data_cb on_header_field;
|
||||||
|
htp_hdr_valcb, // http_data_cb on_header_value;
|
||||||
|
nullptr, // http_cb on_headers_complete;
|
||||||
|
htp_body_cb, // http_data_cb on_body;
|
||||||
|
htp_msg_completecb // http_cb on_message_complete;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void Http1Session::on_connect() { client_->signal_write(); }
|
||||||
|
|
||||||
|
void Http1Session::submit_request(RequestStat *req_stat) {
|
||||||
|
auto config = client_->worker->config;
|
||||||
|
auto req = config->h1reqs[client_->reqidx];
|
||||||
|
client_->reqidx++;
|
||||||
|
|
||||||
|
if (client_->reqidx == config->h1reqs.size()) {
|
||||||
|
client_->reqidx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(req_stat);
|
||||||
|
client_->record_request_time(req_stat);
|
||||||
|
client_->wb.write(req.c_str(), req.size());
|
||||||
|
|
||||||
|
client_->on_request(stream_req_counter_);
|
||||||
|
req_stats_[stream_req_counter_] = req_stat;
|
||||||
|
|
||||||
|
// increment for next request
|
||||||
|
stream_req_counter_ += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Http1Session::on_read(const uint8_t *data, size_t len) {
|
||||||
|
auto nread = http_parser_execute(&htp_, &htp_hooks,
|
||||||
|
reinterpret_cast<const char *>(data), len);
|
||||||
|
|
||||||
|
if (client_->worker->config->verbose) {
|
||||||
|
std::cout.write(reinterpret_cast<const char *>(data), nread);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto htperr = HTTP_PARSER_ERRNO(&htp_);
|
||||||
|
|
||||||
|
if (htperr != HPE_OK) {
|
||||||
|
std::cerr << "[ERROR] HTTP parse error: "
|
||||||
|
<< "(" << http_errno_name(htperr) << ") "
|
||||||
|
<< http_errno_description(htperr) << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Http1Session::on_write() {
|
||||||
|
if (complete_) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Http1Session::terminate() { complete_ = true; }
|
||||||
|
|
||||||
|
Client *Http1Session::get_client() { return client_; }
|
||||||
|
|
||||||
|
} // namespace h2load
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015 British Broadcasting Corporation
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef H2LOAD_HTTP1_SESSION_H
|
||||||
|
#define H2LOAD_HTTP1_SESSION_H
|
||||||
|
|
||||||
|
#include "h2load_session.h"
|
||||||
|
|
||||||
|
#include <nghttp2/nghttp2.h>
|
||||||
|
|
||||||
|
namespace h2load {
|
||||||
|
|
||||||
|
struct Client;
|
||||||
|
|
||||||
|
class Http1Session : public Session {
|
||||||
|
public:
|
||||||
|
Http1Session(Client *client);
|
||||||
|
virtual ~Http1Session();
|
||||||
|
virtual void on_connect();
|
||||||
|
virtual void submit_request(RequestStat *req_stat);
|
||||||
|
virtual int on_read(const uint8_t *data, size_t len);
|
||||||
|
virtual int on_write();
|
||||||
|
virtual void terminate();
|
||||||
|
Client *get_client();
|
||||||
|
int32_t stream_req_counter_;
|
||||||
|
int32_t stream_resp_counter_;
|
||||||
|
std::unordered_map<int32_t, RequestStat *> req_stats_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Client *client_;
|
||||||
|
http_parser htp_;
|
||||||
|
bool complete_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace h2load
|
||||||
|
|
||||||
|
#endif // H2LOAD_HTTP1_SESSION_H
|
|
@ -118,8 +118,6 @@ int main(int argc, char *argv[]) {
|
||||||
shrpx::test_downstream_assemble_request_cookie) ||
|
shrpx::test_downstream_assemble_request_cookie) ||
|
||||||
!CU_add_test(pSuite, "downstream_rewrite_location_response_header",
|
!CU_add_test(pSuite, "downstream_rewrite_location_response_header",
|
||||||
shrpx::test_downstream_rewrite_location_response_header) ||
|
shrpx::test_downstream_rewrite_location_response_header) ||
|
||||||
!CU_add_test(pSuite, "config_parse_config_str_list",
|
|
||||||
shrpx::test_shrpx_config_parse_config_str_list) ||
|
|
||||||
!CU_add_test(pSuite, "config_parse_header",
|
!CU_add_test(pSuite, "config_parse_header",
|
||||||
shrpx::test_shrpx_config_parse_header) ||
|
shrpx::test_shrpx_config_parse_header) ||
|
||||||
!CU_add_test(pSuite, "config_parse_log_format",
|
!CU_add_test(pSuite, "config_parse_log_format",
|
||||||
|
@ -169,6 +167,8 @@ int main(int argc, char *argv[]) {
|
||||||
!CU_add_test(pSuite, "util_localtime_date",
|
!CU_add_test(pSuite, "util_localtime_date",
|
||||||
shrpx::test_util_localtime_date) ||
|
shrpx::test_util_localtime_date) ||
|
||||||
!CU_add_test(pSuite, "util_get_uint64", shrpx::test_util_get_uint64) ||
|
!CU_add_test(pSuite, "util_get_uint64", shrpx::test_util_get_uint64) ||
|
||||||
|
!CU_add_test(pSuite, "util_parse_config_str_list",
|
||||||
|
shrpx::test_util_parse_config_str_list) ||
|
||||||
!CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) ||
|
!CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) ||
|
||||||
!CU_add_test(pSuite, "buffer_write", nghttp2::test_buffer_write) ||
|
!CU_add_test(pSuite, "buffer_write", nghttp2::test_buffer_write) ||
|
||||||
!CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) ||
|
!CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) ||
|
||||||
|
|
|
@ -2437,11 +2437,11 @@ int main(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_config()->npn_list.empty()) {
|
if (get_config()->npn_list.empty()) {
|
||||||
mod_config()->npn_list = parse_config_str_list(DEFAULT_NPN_LIST);
|
mod_config()->npn_list = util::parse_config_str_list(DEFAULT_NPN_LIST);
|
||||||
}
|
}
|
||||||
if (get_config()->tls_proto_list.empty()) {
|
if (get_config()->tls_proto_list.empty()) {
|
||||||
mod_config()->tls_proto_list =
|
mod_config()->tls_proto_list =
|
||||||
parse_config_str_list(DEFAULT_TLS_PROTO_LIST);
|
util::parse_config_str_list(DEFAULT_TLS_PROTO_LIST);
|
||||||
}
|
}
|
||||||
|
|
||||||
mod_config()->tls_proto_mask =
|
mod_config()->tls_proto_mask =
|
||||||
|
|
|
@ -260,37 +260,6 @@ std::string read_passwd_from_file(const char *filename) {
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Range<const char *>> split_config_str_list(const char *s,
|
|
||||||
char delim) {
|
|
||||||
size_t len = 1;
|
|
||||||
auto last = s + strlen(s);
|
|
||||||
for (const char *first = s, *d = nullptr;
|
|
||||||
(d = std::find(first, last, delim)) != last; ++len, first = d + 1)
|
|
||||||
;
|
|
||||||
|
|
||||||
auto list = std::vector<Range<const char *>>(len);
|
|
||||||
|
|
||||||
len = 0;
|
|
||||||
for (auto first = s;; ++len) {
|
|
||||||
auto stop = std::find(first, last, delim);
|
|
||||||
list[len] = {first, stop};
|
|
||||||
if (stop == last) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
first = stop + 1;
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> parse_config_str_list(const char *s, char delim) {
|
|
||||||
auto ranges = split_config_str_list(s, delim);
|
|
||||||
auto res = std::vector<std::string>();
|
|
||||||
res.reserve(ranges.size());
|
|
||||||
for (const auto &range : ranges) {
|
|
||||||
res.emplace_back(range.first, range.second);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<std::string, std::string> parse_header(const char *optarg) {
|
std::pair<std::string, std::string> parse_header(const char *optarg) {
|
||||||
// We skip possible ":" at the start of optarg.
|
// We skip possible ":" at the start of optarg.
|
||||||
|
@ -590,7 +559,7 @@ namespace {
|
||||||
void parse_mapping(const DownstreamAddr &addr, const char *src) {
|
void parse_mapping(const DownstreamAddr &addr, const char *src) {
|
||||||
// This returns at least 1 element (it could be empty string). We
|
// This returns at least 1 element (it could be empty string). We
|
||||||
// will append '/' to all patterns, so it becomes catch-all pattern.
|
// will append '/' to all patterns, so it becomes catch-all pattern.
|
||||||
auto mapping = split_config_str_list(src, ':');
|
auto mapping = util::split_config_str_list(src, ':');
|
||||||
assert(!mapping.empty());
|
assert(!mapping.empty());
|
||||||
for (const auto &raw_pattern : mapping) {
|
for (const auto &raw_pattern : mapping) {
|
||||||
auto done = false;
|
auto done = false;
|
||||||
|
@ -1684,11 +1653,11 @@ int parse_config(const char *opt, const char *optarg,
|
||||||
LOG(WARN) << opt << ": not implemented yet";
|
LOG(WARN) << opt << ": not implemented yet";
|
||||||
return parse_uint_with_unit(&mod_config()->worker_write_burst, opt, optarg);
|
return parse_uint_with_unit(&mod_config()->worker_write_burst, opt, optarg);
|
||||||
case SHRPX_OPTID_NPN_LIST:
|
case SHRPX_OPTID_NPN_LIST:
|
||||||
mod_config()->npn_list = parse_config_str_list(optarg);
|
mod_config()->npn_list = util::parse_config_str_list(optarg);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
case SHRPX_OPTID_TLS_PROTO_LIST:
|
case SHRPX_OPTID_TLS_PROTO_LIST:
|
||||||
mod_config()->tls_proto_list = parse_config_str_list(optarg);
|
mod_config()->tls_proto_list = util::parse_config_str_list(optarg);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
case SHRPX_OPTID_VERIFY_CLIENT:
|
case SHRPX_OPTID_VERIFY_CLIENT:
|
||||||
|
@ -1726,7 +1695,7 @@ int parse_config(const char *opt, const char *optarg,
|
||||||
case SHRPX_OPTID_PADDING:
|
case SHRPX_OPTID_PADDING:
|
||||||
return parse_uint(&mod_config()->padding, opt, optarg);
|
return parse_uint(&mod_config()->padding, opt, optarg);
|
||||||
case SHRPX_OPTID_ALTSVC: {
|
case SHRPX_OPTID_ALTSVC: {
|
||||||
auto tokens = parse_config_str_list(optarg);
|
auto tokens = util::parse_config_str_list(optarg);
|
||||||
|
|
||||||
if (tokens.size() < 2) {
|
if (tokens.size() < 2) {
|
||||||
// Requires at least protocol_id and port
|
// Requires at least protocol_id and port
|
||||||
|
|
|
@ -433,20 +433,6 @@ int load_config(const char *filename, std::set<std::string> &include_set);
|
||||||
// Read passwd from |filename|
|
// Read passwd from |filename|
|
||||||
std::string read_passwd_from_file(const char *filename);
|
std::string read_passwd_from_file(const char *filename);
|
||||||
|
|
||||||
template <typename T> using Range = std::pair<T, T>;
|
|
||||||
|
|
||||||
// Parses delimited strings in |s| and returns the array of substring,
|
|
||||||
// delimited by |delim|. The any white spaces around substring are
|
|
||||||
// treated as a part of substring.
|
|
||||||
std::vector<std::string> parse_config_str_list(const char *s, char delim = ',');
|
|
||||||
|
|
||||||
// Parses delimited strings in |s| and returns the array of pointers,
|
|
||||||
// each element points to the beginning and one beyond last of
|
|
||||||
// substring in |s|. The delimiter is given by |delim|. The any
|
|
||||||
// white spaces around substring are treated as a part of substring.
|
|
||||||
std::vector<Range<const char *>> split_config_str_list(const char *s,
|
|
||||||
char delim);
|
|
||||||
|
|
||||||
// Parses header field in |optarg|. We expect header field is formed
|
// Parses header field in |optarg|. We expect header field is formed
|
||||||
// like "NAME: VALUE". We require that NAME is non empty string. ":"
|
// like "NAME: VALUE". We require that NAME is non empty string. ":"
|
||||||
// is allowed at the start of the NAME, but NAME == ":" is not
|
// is allowed at the start of the NAME, but NAME == ":" is not
|
||||||
|
|
|
@ -36,34 +36,6 @@
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
void test_shrpx_config_parse_config_str_list(void) {
|
|
||||||
auto res = parse_config_str_list("a");
|
|
||||||
CU_ASSERT(1 == res.size());
|
|
||||||
CU_ASSERT("a" == res[0]);
|
|
||||||
|
|
||||||
res = parse_config_str_list("a,");
|
|
||||||
CU_ASSERT(2 == res.size());
|
|
||||||
CU_ASSERT("a" == res[0]);
|
|
||||||
CU_ASSERT("" == res[1]);
|
|
||||||
|
|
||||||
res = parse_config_str_list(":a::", ':');
|
|
||||||
CU_ASSERT(4 == res.size());
|
|
||||||
CU_ASSERT("" == res[0]);
|
|
||||||
CU_ASSERT("a" == res[1]);
|
|
||||||
CU_ASSERT("" == res[2]);
|
|
||||||
CU_ASSERT("" == res[3]);
|
|
||||||
|
|
||||||
res = parse_config_str_list("");
|
|
||||||
CU_ASSERT(1 == res.size());
|
|
||||||
CU_ASSERT("" == res[0]);
|
|
||||||
|
|
||||||
res = parse_config_str_list("alpha,bravo,charlie");
|
|
||||||
CU_ASSERT(3 == res.size());
|
|
||||||
CU_ASSERT("alpha" == res[0]);
|
|
||||||
CU_ASSERT("bravo" == res[1]);
|
|
||||||
CU_ASSERT("charlie" == res[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_shrpx_config_parse_header(void) {
|
void test_shrpx_config_parse_header(void) {
|
||||||
auto p = parse_header("a: b");
|
auto p = parse_header("a: b");
|
||||||
CU_ASSERT("a" == p.first);
|
CU_ASSERT("a" == p.first);
|
||||||
|
|
|
@ -31,7 +31,6 @@
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
void test_shrpx_config_parse_config_str_list(void);
|
|
||||||
void test_shrpx_config_parse_header(void);
|
void test_shrpx_config_parse_header(void);
|
||||||
void test_shrpx_config_parse_log_format(void);
|
void test_shrpx_config_parse_log_format(void);
|
||||||
void test_shrpx_config_read_tls_ticket_key_file(void);
|
void test_shrpx_config_read_tls_ticket_key_file(void);
|
||||||
|
|
52
src/util.cc
52
src/util.cc
|
@ -770,7 +770,7 @@ bool check_h2_is_selected(const unsigned char *proto, size_t len) {
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
bool select_h2(const unsigned char **out, unsigned char *outlen,
|
bool select_proto(const unsigned char **out, unsigned char *outlen,
|
||||||
const unsigned char *in, unsigned int inlen, const char *key,
|
const unsigned char *in, unsigned int inlen, const char *key,
|
||||||
unsigned int keylen) {
|
unsigned int keylen) {
|
||||||
for (auto p = in, end = in + inlen; p + keylen <= end; p += *p + 1) {
|
for (auto p = in, end = in + inlen; p + keylen <= end; p += *p + 1) {
|
||||||
|
@ -786,14 +786,25 @@ bool select_h2(const unsigned char **out, unsigned char *outlen,
|
||||||
|
|
||||||
bool select_h2(const unsigned char **out, unsigned char *outlen,
|
bool select_h2(const unsigned char **out, unsigned char *outlen,
|
||||||
const unsigned char *in, unsigned int inlen) {
|
const unsigned char *in, unsigned int inlen) {
|
||||||
return select_h2(out, outlen, in, inlen, NGHTTP2_PROTO_ALPN,
|
return select_proto(out, outlen, in, inlen, NGHTTP2_PROTO_ALPN,
|
||||||
str_size(NGHTTP2_PROTO_ALPN)) ||
|
str_size(NGHTTP2_PROTO_ALPN)) ||
|
||||||
select_h2(out, outlen, in, inlen, NGHTTP2_H2_16_ALPN,
|
select_proto(out, outlen, in, inlen, NGHTTP2_H2_16_ALPN,
|
||||||
str_size(NGHTTP2_H2_16_ALPN)) ||
|
str_size(NGHTTP2_H2_16_ALPN)) ||
|
||||||
select_h2(out, outlen, in, inlen, NGHTTP2_H2_14_ALPN,
|
select_proto(out, outlen, in, inlen, NGHTTP2_H2_14_ALPN,
|
||||||
str_size(NGHTTP2_H2_14_ALPN));
|
str_size(NGHTTP2_H2_14_ALPN));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool select_protocol(const unsigned char **out, unsigned char *outlen,
|
||||||
|
const unsigned char *in, unsigned int inlen, std::vector<std::string> proto_list) {
|
||||||
|
for (const auto &proto : proto_list) {
|
||||||
|
if (select_proto(out, outlen, in, inlen, proto.c_str(), static_cast<unsigned int>(proto.size()))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<unsigned char> get_default_alpn() {
|
std::vector<unsigned char> get_default_alpn() {
|
||||||
auto res = std::vector<unsigned char>(str_size(NGHTTP2_PROTO_ALPN) +
|
auto res = std::vector<unsigned char>(str_size(NGHTTP2_PROTO_ALPN) +
|
||||||
str_size(NGHTTP2_H2_16_ALPN) +
|
str_size(NGHTTP2_H2_16_ALPN) +
|
||||||
|
@ -807,6 +818,39 @@ std::vector<unsigned char> get_default_alpn() {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<Range<const char *>> split_config_str_list(const char *s,
|
||||||
|
char delim) {
|
||||||
|
size_t len = 1;
|
||||||
|
auto last = s + strlen(s);
|
||||||
|
for (const char *first = s, *d = nullptr;
|
||||||
|
(d = std::find(first, last, delim)) != last; ++len, first = d + 1)
|
||||||
|
;
|
||||||
|
|
||||||
|
auto list = std::vector<Range<const char *>>(len);
|
||||||
|
|
||||||
|
len = 0;
|
||||||
|
for (auto first = s;; ++len) {
|
||||||
|
auto stop = std::find(first, last, delim);
|
||||||
|
list[len] = {first, stop};
|
||||||
|
if (stop == last) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
first = stop + 1;
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> parse_config_str_list(const char *s, char delim) {
|
||||||
|
auto ranges = split_config_str_list(s, delim);
|
||||||
|
auto res = std::vector<std::string>();
|
||||||
|
res.reserve(ranges.size());
|
||||||
|
for (const auto &range : ranges) {
|
||||||
|
res.emplace_back(range.first, range.second);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
int make_socket_closeonexec(int fd) {
|
int make_socket_closeonexec(int fd) {
|
||||||
int flags;
|
int flags;
|
||||||
int rv;
|
int rv;
|
||||||
|
|
23
src/util.h
23
src/util.h
|
@ -58,6 +58,9 @@ constexpr const char NGHTTP2_H2_16[] = "h2-16";
|
||||||
constexpr const char NGHTTP2_H2_14_ALPN[] = "\x5h2-14";
|
constexpr const char NGHTTP2_H2_14_ALPN[] = "\x5h2-14";
|
||||||
constexpr const char NGHTTP2_H2_14[] = "h2-14";
|
constexpr const char NGHTTP2_H2_14[] = "h2-14";
|
||||||
|
|
||||||
|
constexpr const char NGHTTP2_H1_1_ALPN[] = "\x8http/1.1";
|
||||||
|
constexpr const char NGHTTP2_H1_1[] = "http/1.1";
|
||||||
|
|
||||||
namespace util {
|
namespace util {
|
||||||
|
|
||||||
extern const char DEFAULT_STRIP_CHARSET[];
|
extern const char DEFAULT_STRIP_CHARSET[];
|
||||||
|
@ -572,10 +575,30 @@ bool check_h2_is_selected(const unsigned char *alpn, size_t len);
|
||||||
bool select_h2(const unsigned char **out, unsigned char *outlen,
|
bool select_h2(const unsigned char **out, unsigned char *outlen,
|
||||||
const unsigned char *in, unsigned int inlen);
|
const unsigned char *in, unsigned int inlen);
|
||||||
|
|
||||||
|
// Selects protocol ALPN ID if one of identifiers contained in |protolist| is
|
||||||
|
// present in |in| of length inlen. Returns true if identifier is
|
||||||
|
// selected.
|
||||||
|
bool select_protocol(const unsigned char **out, unsigned char *outlen,
|
||||||
|
const unsigned char *in, unsigned int inlen, std::vector<std::string> proto_list);
|
||||||
|
|
||||||
// Returns default ALPN protocol list, which only contains supported
|
// Returns default ALPN protocol list, which only contains supported
|
||||||
// HTTP/2 protocol identifier.
|
// HTTP/2 protocol identifier.
|
||||||
std::vector<unsigned char> get_default_alpn();
|
std::vector<unsigned char> get_default_alpn();
|
||||||
|
|
||||||
|
template <typename T> using Range = std::pair<T, T>;
|
||||||
|
|
||||||
|
// Parses delimited strings in |s| and returns the array of substring,
|
||||||
|
// delimited by |delim|. The any white spaces around substring are
|
||||||
|
// treated as a part of substring.
|
||||||
|
std::vector<std::string> parse_config_str_list(const char *s, char delim = ',');
|
||||||
|
|
||||||
|
// Parses delimited strings in |s| and returns the array of pointers,
|
||||||
|
// each element points to the beginning and one beyond last of
|
||||||
|
// substring in |s|. The delimiter is given by |delim|. The any
|
||||||
|
// white spaces around substring are treated as a part of substring.
|
||||||
|
std::vector<Range<const char *>> split_config_str_list(const char *s,
|
||||||
|
char delim);
|
||||||
|
|
||||||
// Returns given time |tp| in Common Log format (e.g.,
|
// Returns given time |tp| in Common Log format (e.g.,
|
||||||
// 03/Jul/2014:00:19:38 +0900)
|
// 03/Jul/2014:00:19:38 +0900)
|
||||||
// Expected type of |tp| is std::chrono::timepoint
|
// Expected type of |tp| is std::chrono::timepoint
|
||||||
|
|
|
@ -412,4 +412,32 @@ void test_util_get_uint64(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_util_parse_config_str_list(void) {
|
||||||
|
auto res = util::parse_config_str_list("a");
|
||||||
|
CU_ASSERT(1 == res.size());
|
||||||
|
CU_ASSERT("a" == res[0]);
|
||||||
|
|
||||||
|
res = util::parse_config_str_list("a,");
|
||||||
|
CU_ASSERT(2 == res.size());
|
||||||
|
CU_ASSERT("a" == res[0]);
|
||||||
|
CU_ASSERT("" == res[1]);
|
||||||
|
|
||||||
|
res = util::parse_config_str_list(":a::", ':');
|
||||||
|
CU_ASSERT(4 == res.size());
|
||||||
|
CU_ASSERT("" == res[0]);
|
||||||
|
CU_ASSERT("a" == res[1]);
|
||||||
|
CU_ASSERT("" == res[2]);
|
||||||
|
CU_ASSERT("" == res[3]);
|
||||||
|
|
||||||
|
res = util::parse_config_str_list("");
|
||||||
|
CU_ASSERT(1 == res.size());
|
||||||
|
CU_ASSERT("" == res[0]);
|
||||||
|
|
||||||
|
res = util::parse_config_str_list("alpha,bravo,charlie");
|
||||||
|
CU_ASSERT(3 == res.size());
|
||||||
|
CU_ASSERT("alpha" == res[0]);
|
||||||
|
CU_ASSERT("bravo" == res[1]);
|
||||||
|
CU_ASSERT("charlie" == res[2]);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -56,6 +56,7 @@ void test_util_ends_with(void);
|
||||||
void test_util_parse_http_date(void);
|
void test_util_parse_http_date(void);
|
||||||
void test_util_localtime_date(void);
|
void test_util_localtime_date(void);
|
||||||
void test_util_get_uint64(void);
|
void test_util_get_uint64(void);
|
||||||
|
void test_util_parse_config_str_list(void);
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue