Initial HTTP/1.1 capability. Add npn-list option to h2load. Make NPN/ALPN more runtime dependent

This commit is contained in:
Lucas Pardue 2015-09-14 13:33:48 +00:00
parent d22573086f
commit ec47dfb9b8
15 changed files with 503 additions and 137 deletions

View File

@ -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

View File

@ -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<Http2Session>(this);
break;
} else if (util::streq_l(NGHTTP2_H1_1, next_proto, next_proto_len)) {
session = make_unique<Http1Session>(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<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
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<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;
}
}
} else {
switch (config.no_tls_proto) {
case Config::PROTO_HTTP2:
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;
#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<size_t>(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<const unsigned char **>(out), outlen, in,
inlen)) {
if (util::select_protocol(const_cast<const unsigned char **>(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=<PATH>
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=<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<unsigned char>(proto.size()));
}
std::vector<std::string> 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<unsigned char> 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<nghttp2_nv> nva;

View File

@ -62,6 +62,7 @@ struct Worker;
struct Config {
std::vector<std::vector<nghttp2_nv>> nva;
std::vector<std::vector<const char *>> nv;
std::vector<std::string> h1reqs;
std::vector<ev_tstamp> 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<std::string> 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);

181
src/h2load_http1_session.cc Normal file
View File

@ -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

View File

@ -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

View File

@ -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) ||

View File

@ -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 =

View File

@ -260,37 +260,6 @@ std::string read_passwd_from_file(const char *filename) {
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) {
// 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

View File

@ -433,20 +433,6 @@ int load_config(const char *filename, std::set<std::string> &include_set);
// Read passwd from |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
// like "NAME: VALUE". We require that NAME is non empty string. ":"
// is allowed at the start of the NAME, but NAME == ":" is not

View File

@ -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);

View File

@ -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);

View File

@ -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<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() {
auto res = std::vector<unsigned char>(str_size(NGHTTP2_PROTO_ALPN) +
str_size(NGHTTP2_H2_16_ALPN) +
@ -807,6 +818,39 @@ std::vector<unsigned char> get_default_alpn() {
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 flags;
int rv;

View File

@ -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<std::string> proto_list);
// Returns default ALPN protocol list, which only contains supported
// HTTP/2 protocol identifier.
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.,
// 03/Jul/2014:00:19:38 +0900)
// Expected type of |tp| is std::chrono::timepoint

View File

@ -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

View File

@ -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