Commit 03b32d92 authored by Tatsuhiro Tsujikawa's avatar Tatsuhiro Tsujikawa

Merge branch 'LPardue-h2load-http1_a'

parents 1c2fcc2a 604732b6
......@@ -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
......
This diff is collapsed.
......@@ -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);
......
/*
* 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
/*
* 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);
......
......@@ -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;
......
......@@ -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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment