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 \
|
||||
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
|
||||
|
|
188
src/h2load.cc
188
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<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;
|
||||
|
||||
|
|
16
src/h2load.h
16
src/h2load.h
|
@ -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);
|
||||
|
|
|
@ -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) ||
|
||||
!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) ||
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
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 {
|
||||
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;
|
||||
|
|
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[] = "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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue