From ec47dfb9b8649a712f5819f8f74863beba62eaf8 Mon Sep 17 00:00:00 2001 From: Lucas Pardue Date: Mon, 14 Sep 2015 13:33:48 +0000 Subject: [PATCH] Initial HTTP/1.1 capability. Add npn-list option to h2load. Make NPN/ALPN more runtime dependent --- src/Makefile.am | 3 +- src/h2load.cc | 188 ++++++++++++++++++++++++++---------- src/h2load.h | 16 ++- src/h2load_http1_session.cc | 181 ++++++++++++++++++++++++++++++++++ src/h2load_http1_session.h | 58 +++++++++++ src/shrpx-unittest.cc | 4 +- src/shrpx.cc | 4 +- src/shrpx_config.cc | 39 +------- src/shrpx_config.h | 14 --- src/shrpx_config_test.cc | 28 ------ src/shrpx_config_test.h | 1 - src/util.cc | 52 +++++++++- src/util.h | 23 +++++ src/util_test.cc | 28 ++++++ src/util_test.h | 1 + 15 files changed, 503 insertions(+), 137 deletions(-) create mode 100644 src/h2load_http1_session.cc create mode 100644 src/h2load_http1_session.h diff --git a/src/Makefile.am b/src/Makefile.am index e3575669..8ee76239 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -86,7 +86,8 @@ h2load_SOURCES = util.cc util.h \ timegm.c timegm.h \ ssl.cc ssl.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 h2load_SOURCES += h2load_spdy_session.cc h2load_spdy_session.h diff --git a/src/h2load.cc b/src/h2load.cc index 9ec18d5d..63e587e6 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -54,6 +54,7 @@ #include "http-parser/http_parser.h" +#include "h2load_http1_session.h" #include "h2load_http2_session.h" #ifdef HAVE_SPDYLAY #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()); } 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) {} Stats::Stats(size_t req_todo) @@ -452,12 +428,19 @@ void Client::report_tls_info() { if (worker->id == 0 && !worker->tls_info_report_done) { worker->tls_info_report_done = true; 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; 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::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, RequestStat *req_stat) { 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)) { session = make_unique(this); break; + } else if (util::streq_l(NGHTTP2_H1_1, next_proto, next_proto_len)) { + session = make_unique(this); + break; } #ifdef HAVE_SPDYLAY else { @@ -559,20 +566,59 @@ int Client::connection_made() { #if OPENSSL_VERSION_NUMBER >= 0x10002000L SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len); + auto proto = std::string(reinterpret_cast(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 break; #endif // OPENSSL_VERSION_NUMBER < 0x10002000L } if (!next_proto) { - debug_nextproto_error(); - fail(); - return -1; + for (const auto &proto : config.npn_list) { + 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(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; + } } } else { switch (config.no_tls_proto) { case Config::PROTO_HTTP2: session = make_unique(this); + selected_proto = NGHTTP2_CLEARTEXT_PROTO_VERSION_ID; + report_app_info(); + break; + case Config::PROTO_HTTP1_1: + session = make_unique(this); + selected_proto = NGHTTP2_H1_1; + report_app_info(); break; #ifdef HAVE_SPDYLAY 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, size_t rate, 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), - rate(rate) { + id(id), tls_info_report_done(false), app_info_report_done(false), + nconns_made(0), nclients(nclients), rate(rate) { stats.req_todo = req_todo; progress_interval = std::max(static_cast(1), req_todo / 10); auto nreqs_per_client = req_todo / nclients; @@ -1047,16 +1093,15 @@ namespace { int client_select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) { - if (util::select_h2(const_cast(out), outlen, in, - inlen)) { + if (util::select_protocol(const_cast(out), outlen, in, + inlen, config.npn_list)) { 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; - } -#endif - return SSL_TLSEXT_ERR_NOACK; + return SSL_TLSEXT_ERR_OK; } } // namespace @@ -1186,6 +1231,14 @@ benchmarking tool for HTTP/2 and SPDY server)" << std::endl; } } // 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 { void print_help(std::ostream &out) { print_usage(out); @@ -1245,12 +1298,13 @@ Options: #ifdef HAVE_SPDYLAY out << R"( - Available protocols: spdy/2, spdy/3, spdy/3.1 and )"; + Available protocols: spdy/2, spdy/3, spdy/3.1, )"; #else // !HAVE_SPDYLAY out << R"( - Available protocol: )"; + Available protocols: )"; #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"( -d, --data= Post FILE to server. The request method is changed to @@ -1310,6 +1364,14 @@ Options: used for all requests. The base URI overrides all values defined either at the command line or inside input files. + --npn-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 Output debug information. --version Display version information and exit. @@ -1351,6 +1413,7 @@ int main(int argc, char **argv) { {"connection-inactivity-timeout", required_argument, nullptr, 'N'}, {"timing-script-file", required_argument, &flag, 3}, {"base-uri", required_argument, nullptr, 'B'}, + {"npn-list", required_argument, &flag, 4}, {nullptr, 0, nullptr, 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:", @@ -1435,6 +1498,8 @@ int main(int argc, char **argv) { case 'p': if (util::strieq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, optarg)) { 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 } else if (util::strieq("spdy/2", optarg)) { config.no_tls_proto = Config::PROTO_SPDY2; @@ -1507,9 +1572,14 @@ int main(int argc, char **argv) { config.ciphers = optarg; break; case 3: + // timing-script option config.ifile = optarg; config.timing_script = true; break; + case 4: + // npn-list option + config.npn_list = util::parse_config_str_list(optarg); + break; } break; default: @@ -1536,6 +1606,15 @@ int main(int argc, char **argv) { 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(proto.size())); + } + std::vector reqlines; if (config.ifile.empty()) { @@ -1704,12 +1783,11 @@ int main(int argc, char **argv) { nullptr); #if OPENSSL_VERSION_NUMBER >= 0x10002000L - auto proto_list = util::get_default_alpn(); -#ifdef HAVE_SPDYLAY - static const char spdy_proto_list[] = "\x8spdy/3.1\x6spdy/3\x6spdy/2"; - std::copy_n(spdy_proto_list, sizeof(spdy_proto_list) - 1, - std::back_inserter(proto_list)); -#endif // HAVE_SPDYLAY + std::vector proto_list; + for (const auto &proto : config.npn_list) { + std::copy_n(proto.c_str(), proto.size(), std::back_inserter(proto_list)); + } + SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size()); #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; shared_nva.emplace_back(":scheme", config.scheme); 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(":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 auto override_hdrs = @@ -1789,6 +1868,17 @@ int main(int argc, char **argv) { } 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 std::vector nva; diff --git a/src/h2load.h b/src/h2load.h index 13363a32..e9225c37 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -62,6 +62,7 @@ struct Worker; struct Config { std::vector> nva; std::vector> nv; + std::vector h1reqs; std::vector timings; nghttp2::Headers custom_headers; std::string scheme; @@ -86,7 +87,13 @@ struct Config { ssize_t conn_active_timeout; // amount of time to wait after the last request is made on a connection 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 int data_fd; uint16_t port; @@ -94,6 +101,9 @@ struct Config { bool verbose; bool timing_script; std::string base_uri; + // list of supported NPN/ALPN protocol strings in the order of + // preference. + std::vector npn_list; Config(); ~Config(); @@ -187,6 +197,7 @@ struct Worker { size_t progress_interval; uint32_t id; bool tls_info_report_done; + bool app_info_report_done; size_t nconns_made; size_t nclients; size_t rate; @@ -227,6 +238,7 @@ struct Client { Buffer<64_k> wb; ev_timer conn_active_watcher; ev_timer conn_inactivity_watcher; + std::string selected_proto; enum { ERR_CONNECT_FAIL = -100 }; @@ -242,6 +254,7 @@ struct Client { void process_abandoned_streams(); void report_progress(); void report_tls_info(); + void report_app_info(); void terminate_session(); int do_read(); @@ -263,6 +276,7 @@ struct Client { void on_request(int32_t stream_id); void on_header(int32_t stream_id, const uint8_t *name, size_t namelen, 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 record_request_time(RequestStat *req_stat); diff --git a/src/h2load_http1_session.cc b/src/h2load_http1_session.cc new file mode 100644 index 00000000..965d3ca3 --- /dev/null +++ b/src/h2load_http1_session.cc @@ -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 +#include + +#include "h2load.h" +#include "util.h" +#include "template.h" + +#include +#include + +#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(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(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(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(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(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(data), len); + + if (client_->worker->config->verbose) { + std::cout.write(reinterpret_cast(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 diff --git a/src/h2load_http1_session.h b/src/h2load_http1_session.h new file mode 100644 index 00000000..bb0600eb --- /dev/null +++ b/src/h2load_http1_session.h @@ -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 + +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 req_stats_; + +private: + Client *client_; + http_parser htp_; + bool complete_; +}; + +} // namespace h2load + +#endif // H2LOAD_HTTP1_SESSION_H diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc index 03219a65..45009089 100644 --- a/src/shrpx-unittest.cc +++ b/src/shrpx-unittest.cc @@ -118,8 +118,6 @@ int main(int argc, char *argv[]) { shrpx::test_downstream_assemble_request_cookie) || !CU_add_test(pSuite, "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", shrpx::test_shrpx_config_parse_header) || !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", shrpx::test_util_localtime_date) || !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, "buffer_write", nghttp2::test_buffer_write) || !CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) || diff --git a/src/shrpx.cc b/src/shrpx.cc index 554410a2..635e78e6 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -2437,11 +2437,11 @@ int main(int argc, char **argv) { } 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()) { 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 = diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index fb2d34ef..affcab36 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -260,37 +260,6 @@ std::string read_passwd_from_file(const char *filename) { return line; } -std::vector> 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>(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 parse_config_str_list(const char *s, char delim) { - auto ranges = split_config_str_list(s, delim); - auto res = std::vector(); - res.reserve(ranges.size()); - for (const auto &range : ranges) { - res.emplace_back(range.first, range.second); - } - return res; -} std::pair parse_header(const char *optarg) { // We skip possible ":" at the start of optarg. @@ -590,7 +559,7 @@ namespace { void parse_mapping(const DownstreamAddr &addr, const char *src) { // This returns at least 1 element (it could be empty string). We // 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()); for (const auto &raw_pattern : mapping) { auto done = false; @@ -1684,11 +1653,11 @@ int parse_config(const char *opt, const char *optarg, LOG(WARN) << opt << ": not implemented yet"; return parse_uint_with_unit(&mod_config()->worker_write_burst, opt, optarg); 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; 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; case SHRPX_OPTID_VERIFY_CLIENT: @@ -1726,7 +1695,7 @@ int parse_config(const char *opt, const char *optarg, case SHRPX_OPTID_PADDING: return parse_uint(&mod_config()->padding, opt, optarg); case SHRPX_OPTID_ALTSVC: { - auto tokens = parse_config_str_list(optarg); + auto tokens = util::parse_config_str_list(optarg); if (tokens.size() < 2) { // Requires at least protocol_id and port diff --git a/src/shrpx_config.h b/src/shrpx_config.h index 3da46dee..15107575 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -433,20 +433,6 @@ int load_config(const char *filename, std::set &include_set); // Read passwd from |filename| std::string read_passwd_from_file(const char *filename); -template using Range = std::pair; - -// 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 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> split_config_str_list(const char *s, - char delim); - // Parses header field in |optarg|. We expect header field is formed // like "NAME: VALUE". We require that NAME is non empty string. ":" // is allowed at the start of the NAME, but NAME == ":" is not diff --git a/src/shrpx_config_test.cc b/src/shrpx_config_test.cc index e03c8c0c..4cec6615 100644 --- a/src/shrpx_config_test.cc +++ b/src/shrpx_config_test.cc @@ -36,34 +36,6 @@ 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) { auto p = parse_header("a: b"); CU_ASSERT("a" == p.first); diff --git a/src/shrpx_config_test.h b/src/shrpx_config_test.h index 5db97392..3e86a4be 100644 --- a/src/shrpx_config_test.h +++ b/src/shrpx_config_test.h @@ -31,7 +31,6 @@ namespace shrpx { -void test_shrpx_config_parse_config_str_list(void); void test_shrpx_config_parse_header(void); void test_shrpx_config_parse_log_format(void); void test_shrpx_config_read_tls_ticket_key_file(void); diff --git a/src/util.cc b/src/util.cc index d3699aab..c02e2839 100644 --- a/src/util.cc +++ b/src/util.cc @@ -770,7 +770,7 @@ bool check_h2_is_selected(const unsigned char *proto, size_t len) { } 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, unsigned int keylen) { 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, 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)) || - 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)) || - 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)); } +bool select_protocol(const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, std::vector proto_list) { + for (const auto &proto : proto_list) { + if (select_proto(out, outlen, in, inlen, proto.c_str(), static_cast(proto.size()))) { + return true; + } + } + + return false; +} + std::vector get_default_alpn() { auto res = std::vector(str_size(NGHTTP2_PROTO_ALPN) + str_size(NGHTTP2_H2_16_ALPN) + @@ -807,6 +818,39 @@ std::vector get_default_alpn() { return res; } + +std::vector> 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>(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 parse_config_str_list(const char *s, char delim) { + auto ranges = split_config_str_list(s, delim); + auto res = std::vector(); + 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 flags; int rv; diff --git a/src/util.h b/src/util.h index 877ddc22..7998b4c0 100644 --- a/src/util.h +++ b/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[] = "h2-14"; +constexpr const char NGHTTP2_H1_1_ALPN[] = "\x8http/1.1"; +constexpr const char NGHTTP2_H1_1[] = "http/1.1"; + namespace util { 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, 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 proto_list); + // Returns default ALPN protocol list, which only contains supported // HTTP/2 protocol identifier. std::vector get_default_alpn(); +template using Range = std::pair; + +// 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 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> split_config_str_list(const char *s, + char delim); + // Returns given time |tp| in Common Log format (e.g., // 03/Jul/2014:00:19:38 +0900) // Expected type of |tp| is std::chrono::timepoint diff --git a/src/util_test.cc b/src/util_test.cc index f424a0f6..da2485a9 100644 --- a/src/util_test.cc +++ b/src/util_test.cc @@ -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 diff --git a/src/util_test.h b/src/util_test.h index 80933bff..352fec4c 100644 --- a/src/util_test.h +++ b/src/util_test.h @@ -56,6 +56,7 @@ void test_util_ends_with(void); void test_util_parse_http_date(void); void test_util_localtime_date(void); void test_util_get_uint64(void); +void test_util_parse_config_str_list(void); } // namespace shrpx