From 3119fc259cd29513c9aeef1371bdd6807a51b850 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Fri, 10 Jul 2015 02:52:11 +0900 Subject: [PATCH] Select backend based on request host and path by extending -b option -b option syntax is now ,[;[:...]]. The optional s specify the request host and path it is used for. The can contain path, host + path or host. The matching rule is closely designed to ServeMux in Go programming language. --- src/http2.h | 36 ++++ src/http2_test.cc | 27 +++ src/http2_test.h | 1 + src/shrpx-unittest.cc | 4 + src/shrpx.cc | 151 +++++++++++---- src/shrpx_client_handler.cc | 45 ++++- src/shrpx_client_handler.h | 3 +- src/shrpx_config.cc | 226 +++++++++++++++++++++-- src/shrpx_config.h | 38 +++- src/shrpx_config_test.cc | 66 ++++++- src/shrpx_config_test.h | 1 + src/shrpx_downstream_connection.h | 1 + src/shrpx_downstream_connection_pool.cc | 27 ++- src/shrpx_downstream_connection_pool.h | 6 +- src/shrpx_http2_downstream_connection.cc | 2 +- src/shrpx_http2_downstream_connection.h | 2 + src/shrpx_http2_session.cc | 22 ++- src/shrpx_http2_upstream.cc | 4 +- src/shrpx_http_downstream_connection.cc | 32 ++-- src/shrpx_http_downstream_connection.h | 4 +- src/shrpx_https_upstream.cc | 4 +- src/shrpx_spdy_upstream.cc | 4 +- src/shrpx_ssl.cc | 6 +- src/shrpx_ssl.h | 3 +- src/shrpx_worker.cc | 7 +- src/shrpx_worker.h | 8 +- src/util.h | 6 +- 27 files changed, 613 insertions(+), 123 deletions(-) diff --git a/src/http2.h b/src/http2.h index 9b7bf94b..be79caca 100644 --- a/src/http2.h +++ b/src/http2.h @@ -37,6 +37,8 @@ #include "http-parser/http_parser.h" +#include "util.h" + namespace nghttp2 { struct Header { @@ -298,6 +300,40 @@ int lookup_method_token(const std::string &name); const char *to_method_string(int method_token); +template +std::string normalize_path(InputIt first, InputIt last) { + // First, decode %XX for unreserved characters, then do + // http2::join_path + std::string result; + // We won't find %XX if length is less than 3. + if (last - first < 3) { + result.assign(first, last); + } else { + for (; first < last - 2;) { + if (*first == '%') { + if (util::isHexDigit(*(first + 1)) && util::isHexDigit(*(first + 2))) { + auto c = (util::hex_to_uint(*(first + 1)) << 4) + + util::hex_to_uint(*(first + 2)); + if (util::inRFC3986UnreservedChars(c)) { + result += c; + first += 3; + continue; + } + result += '%'; + result += util::upcase(*(first + 1)); + result += util::upcase(*(first + 2)); + first += 3; + continue; + } + } + result += *first++; + } + result.append(first, last); + } + return path_join(nullptr, 0, nullptr, 0, result.c_str(), result.size(), + nullptr, 0); +} + } // namespace http2 } // namespace nghttp2 diff --git a/src/http2_test.cc b/src/http2_test.cc index a11df674..1937bfaf 100644 --- a/src/http2_test.cc +++ b/src/http2_test.cc @@ -829,4 +829,31 @@ void test_http2_path_join(void) { } } +void test_http2_normalize_path(void) { + std::string src; + + src = "/alpha/bravo/../charlie"; + CU_ASSERT("/alpha/charlie" == + http2::normalize_path(std::begin(src), std::end(src))); + + src = "/a%6c%70%68%61"; + CU_ASSERT("/alpha" == http2::normalize_path(std::begin(src), std::end(src))); + + src = "/alpha%2f%3a"; + CU_ASSERT("/alpha%2F%3A" == + http2::normalize_path(std::begin(src), std::end(src))); + + src = "%2f"; + CU_ASSERT("/%2F" == http2::normalize_path(std::begin(src), std::end(src))); + + src = "%f"; + CU_ASSERT("/%f" == http2::normalize_path(std::begin(src), std::end(src))); + + src = "%"; + CU_ASSERT("/%" == http2::normalize_path(std::begin(src), std::end(src))); + + src = ""; + CU_ASSERT("/" == http2::normalize_path(std::begin(src), std::end(src))); +} + } // namespace shrpx diff --git a/src/http2_test.h b/src/http2_test.h index 36c5a59a..193f23e3 100644 --- a/src/http2_test.h +++ b/src/http2_test.h @@ -45,6 +45,7 @@ void test_http2_http2_header_allowed(void); void test_http2_mandatory_request_headers_presence(void); void test_http2_parse_link_header(void); void test_http2_path_join(void); +void test_http2_normalize_path(void); } // namespace shrpx diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc index d4e332ae..7030aa3d 100644 --- a/src/shrpx-unittest.cc +++ b/src/shrpx-unittest.cc @@ -96,6 +96,8 @@ int main(int argc, char *argv[]) { !CU_add_test(pSuite, "http2_parse_link_header", shrpx::test_http2_parse_link_header) || !CU_add_test(pSuite, "http2_path_join", shrpx::test_http2_path_join) || + !CU_add_test(pSuite, "http2_normalize_path", + shrpx::test_http2_normalize_path) || !CU_add_test(pSuite, "downstream_index_request_headers", shrpx::test_downstream_index_request_headers) || !CU_add_test(pSuite, "downstream_index_response_headers", @@ -118,6 +120,8 @@ int main(int argc, char *argv[]) { shrpx::test_shrpx_config_parse_log_format) || !CU_add_test(pSuite, "config_read_tls_ticket_key_file", shrpx::test_shrpx_config_read_tls_ticket_key_file) || + !CU_add_test(pSuite, "config_match_downstream_addr_group", + shrpx::test_shrpx_config_match_downstream_addr_group) || !CU_add_test(pSuite, "util_streq", shrpx::test_util_streq) || !CU_add_test(pSuite, "util_strieq", shrpx::test_util_strieq) || !CU_add_test(pSuite, "util_inp_strlower", diff --git a/src/shrpx.cc b/src/shrpx.cc index 92046aed..8bfadbc3 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -966,6 +966,7 @@ void fill_default_config() { mod_config()->no_ocsp = false; mod_config()->header_field_buffer = 64_k; mod_config()->max_header_fields = 100; + mod_config()->downstream_addr_group_catch_all = 0; } } // namespace @@ -997,14 +998,45 @@ Options: The options are categorized into several groups. Connections: - -b, --backend= + -b, --backend=,[;[:...]] Set backend host and port. The multiple backend addresses are accepted by repeating this option. UNIX domain socket can be specified by prefixing path name - with "unix:" (e.g., unix:/var/run/backend.sock) + with "unix:" (e.g., unix:/var/run/backend.sock). + Optionally, if s are given, the backend address + is only used if request matches the pattern. If -s, -p, + --client or --http2-bridge is used, s are + ignored. The pattern matching is closely designed to + ServeMux in net/http package of Go programming language. + consists of path, host + path or host. The + path must starts with "/". If it ends with "/", it + matches to the request path whose prefix is the path. + If it does not end with "/", it performs exact match + against the request path. If host is given, it performs + exact match against the request host. If host alone is + given, "/" is appended to it, so that it matches all + paths under the host (e.g., specifying "nghttp2.org" + equals to "nghttp2.org/"). Longer patterns take + precedence over shorter ones, breaking a tie by the + order of the appearance in the configuration. If + is omitted, "/" is used as pattern, which + matches all paths (catch-all pattern). For example, + -b'127.0.0.1,8080;nghttp2.org/httpbin/' matches the + request host "nghttp2.org" and the request path + "/httpbin/get", but does not match the request host + "nghttp2.org" and the request path "/index.html". The + multiple s can be specified, delimiting them by + ":". Specifying + -b'127.0.0.1,8080;nghttp2.org:www.nghttp2.org' has the + same effect to specify -b'127.0.0.1,8080;nghttp2.org' + and -b'127.0.0.1,8080:www.nghttp2.org'. The backend + addresses sharing same are grouped together + forming load balancing group. Since ";" and ":" are + used as delimiter, must not contain these + characters. Default: )" << DEFAULT_DOWNSTREAM_HOST << "," << DEFAULT_DOWNSTREAM_PORT << R"( - -f, --frontend= + -f, --frontend=, Set frontend host and port. If is '*', it assumes all addresses including both IPv4 and IPv6. UNIX domain socket can be specified by prefixing path @@ -2118,55 +2150,97 @@ int main(int argc, char **argv) { } } - if (get_config()->downstream_addrs.empty()) { + if (get_config()->downstream_addr_groups.empty()) { DownstreamAddr addr; addr.host = strcopy(DEFAULT_DOWNSTREAM_HOST); addr.port = DEFAULT_DOWNSTREAM_PORT; - mod_config()->downstream_addrs.push_back(std::move(addr)); + DownstreamAddrGroup g("/"); + g.addrs.push_back(std::move(addr)); + mod_config()->downstream_addr_groups.push_back(std::move(g)); + } else if (get_config()->downstream_proto == PROTO_HTTP2 || + get_config()->http2_proxy || get_config()->client_proxy) { + // We don't support host mapping in these cases. Move all + // non-catch-all patterns to catch-all pattern for HTTP/2 backend + DownstreamAddrGroup catch_all("/"); + for (auto &g : mod_config()->downstream_addr_groups) { + std::move(std::begin(g.addrs), std::end(g.addrs), + std::back_inserter(catch_all.addrs)); + } + std::vector().swap( + mod_config()->downstream_addr_groups); + mod_config()->downstream_addr_groups.push_back(std::move(catch_all)); } if (LOG_ENABLED(INFO)) { LOG(INFO) << "Resolving backend address"; } - for (auto &addr : mod_config()->downstream_addrs) { + ssize_t catch_all_group = -1; + for (size_t i = 0; i < mod_config()->downstream_addr_groups.size(); ++i) { + auto &g = mod_config()->downstream_addr_groups[i]; + if (g.pattern == "/") { + catch_all_group = i; + } + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Host-path pattern: group " << i << ": '" << g.pattern + << "'"; + for (auto &addr : g.addrs) { + LOG(INFO) << "group " << i << " -> " << addr.host.get() + << (addr.host_unix ? "" : ":" + util::utos(addr.port)); + } + } + } - if (addr.host_unix) { - // for AF_UNIX socket, we use "localhost" as host for backend - // hostport. This is used as Host header field to backend and - // not going to be passed to any syscalls. - addr.hostport = - strcopy(util::make_hostport("localhost", get_config()->port)); + if (catch_all_group == -1) { + LOG(FATAL) << "-b: No catch-all backend address is configured"; + exit(EXIT_FAILURE); + } + mod_config()->downstream_addr_group_catch_all = catch_all_group; - auto path = addr.host.get(); - auto pathlen = strlen(path); + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Catch-all pattern is group " << catch_all_group; + } - if (pathlen + 1 > sizeof(addr.addr.un.sun_path)) { - LOG(FATAL) << "UNIX domain socket path " << path << " is too long > " - << sizeof(addr.addr.un.sun_path); - exit(EXIT_FAILURE); + for (auto &g : mod_config()->downstream_addr_groups) { + for (auto &addr : g.addrs) { + + if (addr.host_unix) { + // for AF_UNIX socket, we use "localhost" as host for backend + // hostport. This is used as Host header field to backend and + // not going to be passed to any syscalls. + addr.hostport = + strcopy(util::make_hostport("localhost", get_config()->port)); + + auto path = addr.host.get(); + auto pathlen = strlen(path); + + if (pathlen + 1 > sizeof(addr.addr.un.sun_path)) { + LOG(FATAL) << "UNIX domain socket path " << path << " is too long > " + << sizeof(addr.addr.un.sun_path); + exit(EXIT_FAILURE); + } + + LOG(INFO) << "Use UNIX domain socket path " << path + << " for backend connection"; + + addr.addr.un.sun_family = AF_UNIX; + // copy path including terminal NULL + std::copy_n(path, pathlen + 1, addr.addr.un.sun_path); + addr.addrlen = sizeof(addr.addr.un); + + continue; } - LOG(INFO) << "Use UNIX domain socket path " << path - << " for backend connection"; + addr.hostport = strcopy(util::make_hostport(addr.host.get(), addr.port)); - addr.addr.un.sun_family = AF_UNIX; - // copy path including terminal NULL - std::copy_n(path, pathlen + 1, addr.addr.un.sun_path); - addr.addrlen = sizeof(addr.addr.un); - - continue; - } - - addr.hostport = strcopy(util::make_hostport(addr.host.get(), addr.port)); - - if (resolve_hostname( - &addr.addr, &addr.addrlen, addr.host.get(), addr.port, - get_config()->backend_ipv4 - ? AF_INET - : (get_config()->backend_ipv6 ? AF_INET6 : AF_UNSPEC)) == -1) { - exit(EXIT_FAILURE); + if (resolve_hostname( + &addr.addr, &addr.addrlen, addr.host.get(), addr.port, + get_config()->backend_ipv4 ? AF_INET : (get_config()->backend_ipv6 + ? AF_INET6 + : AF_UNSPEC)) == -1) { + exit(EXIT_FAILURE); + } } } @@ -2183,9 +2257,10 @@ int main(int argc, char **argv) { } } - if (get_config()->http2_downstream_connections_per_worker == 0) { + if (get_config()->downstream_proto == PROTO_HTTP2 && + get_config()->http2_downstream_connections_per_worker == 0) { mod_config()->http2_downstream_connections_per_worker = - get_config()->downstream_addrs.size(); + get_config()->downstream_addr_groups[0].addrs.size(); } if (get_config()->rlimit_nofile) { diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc index d727fe39..644a8cdc 100644 --- a/src/shrpx_client_handler.cc +++ b/src/shrpx_client_handler.cc @@ -588,7 +588,8 @@ void ClientHandler::pool_downstream_connection( return; } if (LOG_ENABLED(INFO)) { - CLOG(INFO, this) << "Pooling downstream connection DCONN:" << dconn.get(); + CLOG(INFO, this) << "Pooling downstream connection DCONN:" << dconn.get() + << " in group " << dconn->get_group(); } dconn->set_client_handler(nullptr); auto dconn_pool = worker_->get_dconn_pool(); @@ -605,9 +606,44 @@ void ClientHandler::remove_downstream_connection(DownstreamConnection *dconn) { } std::unique_ptr -ClientHandler::get_downstream_connection() { +ClientHandler::get_downstream_connection(Downstream *downstream) { + size_t group; + auto &groups = get_config()->downstream_addr_groups; + auto catch_all = get_config()->downstream_addr_group_catch_all; + + // Fast path. If we have one group, it must be catch-all group. + // HTTP/2 and client proxy modes fall in this case. Currently, + // HTTP/2 backend does not perform host-path mapping. + if (groups.size() == 1 || get_config()->downstream_proto == PROTO_HTTP2) { + group = 0; + } else if (downstream->get_request_method() == HTTP_CONNECT) { + // We don't know how to treat CONNECT request in host-path + // mapping. It most likely appears in proxy scenario. Since we + // have dealt with proxy case already, just use catch-all group. + group = catch_all; + } else { + if (!downstream->get_request_http2_authority().empty()) { + group = match_downstream_addr_group( + downstream->get_request_http2_authority(), + downstream->get_request_path(), groups, catch_all); + } else { + auto h = downstream->get_request_header(http2::HD_HOST); + if (h) { + group = match_downstream_addr_group( + h->value, downstream->get_request_path(), groups, catch_all); + } else { + group = match_downstream_addr_group("", downstream->get_request_path(), + groups, catch_all); + } + } + } + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "Downstream address group: " << group; + } + auto dconn_pool = worker_->get_dconn_pool(); - auto dconn = dconn_pool->pop_downstream_connection(); + auto dconn = dconn_pool->pop_downstream_connection(group); if (!dconn) { if (LOG_ENABLED(INFO)) { @@ -620,7 +656,8 @@ ClientHandler::get_downstream_connection() { if (http2session_) { dconn = make_unique(dconn_pool, http2session_); } else { - dconn = make_unique(dconn_pool, conn_.loop); + dconn = + make_unique(dconn_pool, group, conn_.loop); } dconn->set_client_handler(this); return dconn; diff --git a/src/shrpx_client_handler.h b/src/shrpx_client_handler.h index a2df98eb..4f21f513 100644 --- a/src/shrpx_client_handler.h +++ b/src/shrpx_client_handler.h @@ -92,7 +92,8 @@ public: void pool_downstream_connection(std::unique_ptr dconn); void remove_downstream_connection(DownstreamConnection *dconn); - std::unique_ptr get_downstream_connection(); + std::unique_ptr + get_downstream_connection(Downstream *downstream); MemchunkPool *get_mcpool(); SSL *get_ssl() const; ConnectBlocker *get_connect_blocker() const; diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index d59a1d0d..3fdbe425 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -57,6 +57,7 @@ #include "http2.h" #include "util.h" #include "template.h" +#include "base64.h" using namespace nghttp2; @@ -79,9 +80,29 @@ TicketKeys::~TicketKeys() { } } +DownstreamAddr::DownstreamAddr(const DownstreamAddr &other) + : addr(other.addr), host(other.host ? strcopy(other.host.get()) : nullptr), + hostport(other.hostport ? strcopy(other.hostport.get()) : nullptr), + addrlen(other.addrlen), port(other.port), host_unix(other.host_unix) {} + +DownstreamAddr &DownstreamAddr::operator=(const DownstreamAddr &other) { + if (this == &other) { + return *this; + } + + addr = other.addr; + host = (other.host ? strcopy(other.host.get()) : nullptr); + hostport = (other.hostport ? strcopy(other.hostport.get()) : nullptr); + addrlen = other.addrlen; + port = other.port; + host_unix = other.host_unix; + + return *this; +} + namespace { int split_host_port(char *host, size_t hostlen, uint16_t *port_ptr, - const char *hostport) { + const char *hostport, size_t hostportlen) { // host and port in |hostport| is separated by single ','. const char *p = strchr(hostport, ','); if (!p) { @@ -97,12 +118,13 @@ int split_host_port(char *host, size_t hostlen, uint16_t *port_ptr, host[len] = '\0'; errno = 0; - unsigned long d = strtoul(p + 1, nullptr, 10); - if (errno == 0 && 1 <= d && d <= std::numeric_limits::max()) { + auto portlen = hostportlen - len - 1; + auto d = util::parse_uint(reinterpret_cast(p + 1), portlen); + if (1 <= d && d <= std::numeric_limits::max()) { *port_ptr = d; return 0; } else { - LOG(ERROR) << "Port is invalid: " << p + 1; + LOG(ERROR) << "Port is invalid: " << std::string(p + 1, portlen); return -1; } } @@ -220,16 +242,16 @@ std::unique_ptr strcopy(const std::string &val) { return strcopy(val.c_str(), val.size()); } -std::vector parse_config_str_list(const char *s) { +std::vector parse_config_str_list(const char *s, char delim) { size_t len = 1; - for (const char *first = s, *p = nullptr; (p = strchr(first, ',')); + for (const char *first = s, *p = nullptr; (p = strchr(first, delim)); ++len, first = p + 1) ; auto list = std::vector(len); auto first = strdup(s); len = 0; for (;;) { - auto p = strchr(first, ','); + auto p = strchr(first, delim); if (p == nullptr) { break; } @@ -440,30 +462,82 @@ int parse_duration(ev_tstamp *dest, const char *opt, const char *optarg) { } } // namespace +namespace { +// Parses host-path mapping patterns in |src|, and stores mappings in +// config. We will store each host-path pattern found in |src| with +// |addr|. |addr| will be copied accordingly. Also we make a group +// based on the pattern. The "/" pattern is considered as catch-all. +void parse_mapping(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 = parse_config_str_list(src, ':'); + assert(!mapping.empty()); + for (auto raw_pattern : mapping) { + auto done = false; + std::string pattern; + auto slash = strchr(raw_pattern, '/'); + if (slash == nullptr) { + // This effectively makes empty pattern to "/". + pattern = raw_pattern; + util::inp_strlower(pattern); + pattern += "/"; + } else { + pattern.assign(raw_pattern, slash); + util::inp_strlower(pattern); + pattern += + http2::normalize_path(slash, raw_pattern + strlen(raw_pattern)); + } + for (auto &g : mod_config()->downstream_addr_groups) { + if (g.pattern == pattern) { + g.addrs.push_back(addr); + done = true; + break; + } + } + if (done) { + continue; + } + DownstreamAddrGroup g(pattern); + g.addrs.push_back(addr); + mod_config()->downstream_addr_groups.push_back(std::move(g)); + } + clear_config_str_list(mapping); +} +} // namespace + int parse_config(const char *opt, const char *optarg) { char host[NI_MAXHOST]; uint16_t port; + if (util::strieq(opt, SHRPX_OPT_BACKEND)) { + auto optarglen = strlen(optarg); + auto pat_delim = strchr(optarg, ';'); + if (!pat_delim) { + pat_delim = optarg + optarglen; + } + DownstreamAddr addr; if (util::istartsWith(optarg, SHRPX_UNIX_PATH_PREFIX)) { - DownstreamAddr addr; auto path = optarg + str_size(SHRPX_UNIX_PATH_PREFIX); addr.host = strcopy(path); addr.host_unix = true; + } else { + if (split_host_port(host, sizeof(host), &port, optarg, + pat_delim - optarg) == -1) { + return -1; + } - mod_config()->downstream_addrs.push_back(std::move(addr)); - - return 0; + addr.host = strcopy(host); + addr.port = port; } - if (split_host_port(host, sizeof(host), &port, optarg) == -1) { + auto mapping = pat_delim < optarg + optarglen ? pat_delim + 1 : pat_delim; + // We may introduce new parameter after additional ';', so don't + // allow extra ';' in pattern for now. + if (strchr(mapping, ';') != nullptr) { + LOG(ERROR) << opt << ": ';' must not be used in pattern"; return -1; } - - DownstreamAddr addr; - addr.host = strcopy(host); - addr.port = port; - - mod_config()->downstream_addrs.push_back(std::move(addr)); + parse_mapping(std::move(addr), mapping); return 0; } @@ -478,7 +552,8 @@ int parse_config(const char *opt, const char *optarg) { return 0; } - if (split_host_port(host, sizeof(host), &port, optarg) == -1) { + if (split_host_port(host, sizeof(host), &port, optarg, strlen(optarg)) == + -1) { return -1; } @@ -1322,4 +1397,117 @@ int int_syslog_facility(const char *strfacility) { return -1; } +namespace { +bool path_match(const std::string &pattern, const std::string &path) { + if (pattern.back() != '/') { + return pattern == path; + } + return util::startsWith(path, pattern); +} +} // namespace + +namespace { +ssize_t match(const std::string &path, + const std::vector &groups) { + ssize_t res = -1; + size_t best = 0; + for (size_t i = 0; i < groups.size(); ++i) { + auto &g = groups[i]; + auto &pattern = g.pattern; + if (!path_match(pattern, path)) { + continue; + } + if (res == -1 || best < pattern.size()) { + best = pattern.size(); + res = i; + } + } + return res; +} +} // namespace + +namespace { +size_t match_downstream_addr_group_host( + const std::string &host, const std::string &raw_path, + const std::vector &groups, size_t catch_all) { + if (raw_path == "*") { + auto group = match(host + "/", groups); + if (group != -1) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Found pattern with query " << host + << ", matched pattern=" << groups[group].pattern; + } + return group; + } + return catch_all; + } + + // probably, not necessary most of the case, but just in case. + auto fragment = std::find(std::begin(raw_path), std::end(raw_path), '#'); + auto query = std::find(std::begin(raw_path), fragment, '?'); + auto path = http2::normalize_path(std::begin(raw_path), query); + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Perform mapping selection, using host=" << host + << ", path=" << path; + } + + auto group = match(host + path, groups); + if (group != -1) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Found pattern with query " << host + path + << ", matched pattern=" << groups[group].pattern; + } + return group; + } + + group = match(path, groups); + if (group != -1) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Found pattern with query " << path + << ", matched pattern=" << groups[group].pattern; + } + return group; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "None match. Use catch-all pattern"; + } + return catch_all; +} +} // namespace + +size_t match_downstream_addr_group( + const std::string &hostport, const std::string &raw_path, + const std::vector &groups, size_t catch_all) { + if (hostport.empty() || + std::find(std::begin(hostport), std::end(hostport), '/') != + std::end(hostport)) { + // We use '/' specially, and if '/' is included in host, it breaks + // our code. Select catch-all case. + return catch_all; + } + std::string host; + if (hostport[0] == '[') { + // assume this is IPv6 numeric address + auto p = std::find(std::begin(hostport), std::end(hostport), ']'); + if (p == std::end(hostport)) { + return catch_all; + } + if (p + 1 < std::end(hostport) && *(p + 1) != ':') { + return catch_all; + } + host.assign(std::begin(hostport), p + 1); + } else { + auto p = std::find(std::begin(hostport), std::end(hostport), ':'); + if (p == std::begin(hostport)) { + return catch_all; + } + host.assign(std::begin(hostport), p); + } + + util::inp_strlower(host); + return match_downstream_addr_group_host(host, raw_path, groups, catch_all); +} + } // namespace shrpx diff --git a/src/shrpx_config.h b/src/shrpx_config.h index c50ab64b..3e5c869e 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -194,6 +194,11 @@ struct AltSvc { struct DownstreamAddr { DownstreamAddr() : addr{{0}}, addrlen(0), port(0), host_unix(false) {} + DownstreamAddr(const DownstreamAddr &other); + DownstreamAddr(DownstreamAddr &&) = default; + DownstreamAddr &operator=(const DownstreamAddr &other); + DownstreamAddr &operator=(DownstreamAddr &&other) = default; + sockaddr_union addr; // backend address. If |host_unix| is true, this is UNIX domain // socket path. @@ -206,6 +211,12 @@ struct DownstreamAddr { bool host_unix; }; +struct DownstreamAddrGroup { + DownstreamAddrGroup(std::string pattern) : pattern(std::move(pattern)) {} + std::string pattern; + std::vector addrs; +}; + struct TicketKey { uint8_t name[16]; uint8_t aes_key[16]; @@ -225,7 +236,7 @@ struct Config { std::vector> add_response_headers; std::vector alpn_prefs; std::vector accesslog_format; - std::vector downstream_addrs; + std::vector downstream_addr_groups; std::vector tls_ticket_key_files; // binary form of http proxy host and port sockaddr_union downstream_http_proxy_addr; @@ -311,6 +322,8 @@ struct Config { size_t downstream_response_buffer_size; size_t header_field_buffer; size_t max_header_fields; + // The index of catch-all group in downstream_addr_groups. + size_t downstream_addr_group_catch_all; // Bit mask to disable SSL/TLS protocol versions. This will be // passed to SSL_CTX_set_options(). long int tls_proto_mask; @@ -376,15 +389,14 @@ int load_config(const char *filename); // Read passwd from |filename| std::string read_passwd_from_file(const char *filename); -// Parses comma delimited strings in |s| and returns the array of -// pointers, each element points to the each substring in |s|. The -// |s| must be comma delimited list of strings. The strings must be -// delimited by a single comma and any white spaces around it are -// treated as a part of protocol strings. This function may modify -// |s| and the caller must leave it as is after this call. This +// Parses delimited strings in |s| and returns the array of pointers, +// each element points to the each substring in |s|. The delimiter is +// given by |delim. The |s| must be comma delimited list of strings. +// The strings must be delimited by a single comma and any white +// spaces around it are treated as a part of protocol strings. This // function copies |s| and first element in the return value points to // it. It is caller's responsibility to deallocate its memory. -std::vector parse_config_str_list(const char *s); +std::vector parse_config_str_list(const char *s, char delim = ','); // Clears all elements of |list|, which is returned by // parse_config_str_list(). If list is not empty, list[0] is freed by @@ -423,6 +435,16 @@ FILE *open_file_for_write(const char *filename); std::unique_ptr read_tls_ticket_key_file(const std::vector &files); +// Selects group based on request's |hostport| and |path|. |hostport| +// is the value taken from :authority or host header field, and may +// contain port. The |path| may contain query part. We require the +// catch-all pattern in place, so this function always selects one +// group. The catch-all group index is given in |catch_all|. All +// patterns are given in |groups|. +size_t match_downstream_addr_group( + const std::string &hostport, const std::string &path, + const std::vector &groups, size_t catch_all); + } // namespace shrpx #endif // SHRPX_CONFIG_H diff --git a/src/shrpx_config_test.cc b/src/shrpx_config_test.cc index ad570bbc..4f21fc48 100644 --- a/src/shrpx_config_test.cc +++ b/src/shrpx_config_test.cc @@ -48,7 +48,7 @@ void test_shrpx_config_parse_config_str_list(void) { CU_ASSERT(0 == strcmp("", res[1])); clear_config_str_list(res); - res = parse_config_str_list(",a,,"); + res = parse_config_str_list(":a::", ':'); CU_ASSERT(4 == res.size()); CU_ASSERT(0 == strcmp("", res[0])); CU_ASSERT(0 == strcmp("a", res[1])); @@ -172,4 +172,68 @@ void test_shrpx_config_read_tls_ticket_key_file(void) { memcmp("a..............b", key->hmac_key, sizeof(key->hmac_key))); } +void test_shrpx_config_match_downstream_addr_group(void) { + auto groups = std::vector{ + {"nghttp2.org/"}, + {"nghttp2.org/alpha/bravo/"}, + {"nghttp2.org/alpha/charlie"}, + {"nghttp2.org/delta%3A"}, + {"www.nghttp2.org/"}, + {"[::1]/"}, + }; + + CU_ASSERT(0 == match_downstream_addr_group("nghttp2.org", "/", groups, 255)); + + // port is removed + CU_ASSERT(0 == + match_downstream_addr_group("nghttp2.org:8080", "/", groups, 255)); + + // host is case-insensitive + CU_ASSERT(4 == match_downstream_addr_group("WWW.nghttp2.org", "/alpha", + groups, 255)); + + // path part is case-sensitive + CU_ASSERT(0 == match_downstream_addr_group("nghttp2.org", "/Alpha/bravo", + groups, 255)); + + // unreserved characters are decoded before matching + CU_ASSERT(1 == match_downstream_addr_group("nghttp2.org", "/alpha/%62ravo/", + groups, 255)); + + CU_ASSERT(1 == match_downstream_addr_group( + "nghttp2.org", "/alpha/%62ravo/charlie", groups, 255)); + + CU_ASSERT(2 == match_downstream_addr_group("nghttp2.org", "/alpha/charlie", + groups, 255)); + + // pattern which does not end with '/' must match its entirely. So + // this matches to group 0, not group 2. + CU_ASSERT(0 == match_downstream_addr_group("nghttp2.org", "/alpha/charlie/", + groups, 255)); + + // percent-encoding is normalized to upper case hex digits. + CU_ASSERT(3 == match_downstream_addr_group("nghttp2.org", "/delta%3a", groups, + 255)); + + // path component is normalized before mathcing + CU_ASSERT(1 == match_downstream_addr_group( + "nghttp2.org", "/alpha/charlie/%2e././bravo/delta/..", + groups, 255)); + + CU_ASSERT(255 == + match_downstream_addr_group("example.org", "/", groups, 255)); + + CU_ASSERT(255 == match_downstream_addr_group("", "/", groups, 255)); + + CU_ASSERT(255 == match_downstream_addr_group("foo/bar", "/", groups, 255)); + + // If path is "*", only match with host + "/". + CU_ASSERT(0 == match_downstream_addr_group("nghttp2.org", "*", groups, 255)); + + CU_ASSERT(5 == match_downstream_addr_group("[::1]", "/", groups, 255)); + CU_ASSERT(5 == match_downstream_addr_group("[::1]:8080", "/", groups, 255)); + CU_ASSERT(255 == match_downstream_addr_group("[::1", "/", groups, 255)); + CU_ASSERT(255 == match_downstream_addr_group("[::1]8000", "/", groups, 255)); +} + } // namespace shrpx diff --git a/src/shrpx_config_test.h b/src/shrpx_config_test.h index 6d6d0353..5f9c170a 100644 --- a/src/shrpx_config_test.h +++ b/src/shrpx_config_test.h @@ -35,6 +35,7 @@ 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); +void test_shrpx_config_match_downstream_addr_group(void); } // namespace shrpx diff --git a/src/shrpx_downstream_connection.h b/src/shrpx_downstream_connection.h index 49ca1beb..8d409f61 100644 --- a/src/shrpx_downstream_connection.h +++ b/src/shrpx_downstream_connection.h @@ -57,6 +57,7 @@ public: virtual void on_upstream_change(Upstream *uptream) = 0; virtual int on_priority_change(int32_t pri) = 0; + virtual size_t get_group() const = 0; // true if this object is poolable. virtual bool poolable() const = 0; diff --git a/src/shrpx_downstream_connection_pool.cc b/src/shrpx_downstream_connection_pool.cc index d762b770..eb9b1c8c 100644 --- a/src/shrpx_downstream_connection_pool.cc +++ b/src/shrpx_downstream_connection_pool.cc @@ -27,33 +27,42 @@ namespace shrpx { -DownstreamConnectionPool::DownstreamConnectionPool() {} +DownstreamConnectionPool::DownstreamConnectionPool(size_t num_groups) + : gpool_(num_groups) {} DownstreamConnectionPool::~DownstreamConnectionPool() { - for (auto dconn : pool_) { - delete dconn; + for (auto &pool : gpool_) { + for (auto dconn : pool) { + delete dconn; + } } } void DownstreamConnectionPool::add_downstream_connection( std::unique_ptr dconn) { - pool_.insert(dconn.release()); + auto group = dconn->get_group(); + assert(gpool_.size() > group); + gpool_[group].insert(dconn.release()); } std::unique_ptr -DownstreamConnectionPool::pop_downstream_connection() { - if (pool_.empty()) { +DownstreamConnectionPool::pop_downstream_connection(size_t group) { + assert(gpool_.size() > group); + auto &pool = gpool_[group]; + if (pool.empty()) { return nullptr; } - auto dconn = std::unique_ptr(*std::begin(pool_)); - pool_.erase(std::begin(pool_)); + auto dconn = std::unique_ptr(*std::begin(pool)); + pool.erase(std::begin(pool)); return dconn; } void DownstreamConnectionPool::remove_downstream_connection( DownstreamConnection *dconn) { - pool_.erase(dconn); + auto group = dconn->get_group(); + assert(gpool_.size() > group); + gpool_[group].erase(dconn); delete dconn; } diff --git a/src/shrpx_downstream_connection_pool.h b/src/shrpx_downstream_connection_pool.h index c2edce45..1fb889bb 100644 --- a/src/shrpx_downstream_connection_pool.h +++ b/src/shrpx_downstream_connection_pool.h @@ -36,15 +36,15 @@ class DownstreamConnection; class DownstreamConnectionPool { public: - DownstreamConnectionPool(); + DownstreamConnectionPool(size_t num_groups); ~DownstreamConnectionPool(); void add_downstream_connection(std::unique_ptr dconn); - std::unique_ptr pop_downstream_connection(); + std::unique_ptr pop_downstream_connection(size_t group); void remove_downstream_connection(DownstreamConnection *dconn); private: - std::set pool_; + std::vector> gpool_; }; } // namespace shrpx diff --git a/src/shrpx_http2_downstream_connection.cc b/src/shrpx_http2_downstream_connection.cc index 8a83f012..f82b5230 100644 --- a/src/shrpx_http2_downstream_connection.cc +++ b/src/shrpx_http2_downstream_connection.cc @@ -264,7 +264,7 @@ int Http2DownstreamConnection::push_request_headers() { // addr_idx here. auto addr_idx = http2session_->get_addr_idx(); auto downstream_hostport = - get_config()->downstream_addrs[addr_idx].hostport.get(); + get_config()->downstream_addr_groups[0].addrs[addr_idx].hostport.get(); const char *authority = nullptr, *host = nullptr; if (!no_host_rewrite) { diff --git a/src/shrpx_http2_downstream_connection.h b/src/shrpx_http2_downstream_connection.h index a2a78a34..736f546f 100644 --- a/src/shrpx_http2_downstream_connection.h +++ b/src/shrpx_http2_downstream_connection.h @@ -61,6 +61,8 @@ public: virtual void on_upstream_change(Upstream *upstream) {} virtual int on_priority_change(int32_t pri); + // Currently, HTTP/2 backend does not perform host-path mapping. + virtual size_t get_group() const { return 0; } // This object is not poolable because we dont' have facility to // migrate to another Http2Session object. diff --git a/src/shrpx_http2_session.cc b/src/shrpx_http2_session.cc index c071a722..5ed2f3c3 100644 --- a/src/shrpx_http2_session.cc +++ b/src/shrpx_http2_session.cc @@ -233,11 +233,16 @@ int Http2Session::disconnect(bool hard) { return 0; } -int Http2Session::check_cert() { return ssl::check_cert(conn_.tls.ssl); } +int Http2Session::check_cert() { + return ssl::check_cert( + conn_.tls.ssl, &get_config()->downstream_addr_groups[0].addrs[addr_idx_]); +} int Http2Session::initiate_connection() { int rv = 0; + auto &addrs = get_config()->downstream_addr_groups[0].addrs; + if (state_ == DISCONNECTED) { if (connect_blocker_->blocked()) { if (LOG_ENABLED(INFO)) { @@ -248,17 +253,19 @@ int Http2Session::initiate_connection() { } auto worker_stat = worker_->get_worker_stat(); - addr_idx_ = worker_stat->next_downstream; - ++worker_stat->next_downstream; - worker_stat->next_downstream %= get_config()->downstream_addrs.size(); + auto &next_downstream = worker_stat->next_downstream[0]; + addr_idx_ = next_downstream; + if (++next_downstream >= addrs.size()) { + next_downstream = 0; + } if (LOG_ENABLED(INFO)) { SSLOG(INFO, this) << "Using downstream address idx=" << addr_idx_ - << " out of " << get_config()->downstream_addrs.size(); + << " out of " << addrs.size(); } } - auto &downstream_addr = get_config()->downstream_addrs[addr_idx_]; + auto &downstream_addr = addrs[addr_idx_]; if (get_config()->downstream_http_proxy_host && state_ == DISCONNECTED) { if (LOG_ENABLED(INFO)) { @@ -503,7 +510,8 @@ int Http2Session::downstream_connect_proxy() { if (LOG_ENABLED(INFO)) { SSLOG(INFO, this) << "Connected to the proxy"; } - auto &downstream_addr = get_config()->downstream_addrs[addr_idx_]; + auto &downstream_addr = + get_config()->downstream_addr_groups[0].addrs[addr_idx_]; std::string req = "CONNECT "; req += downstream_addr.hostport.get(); diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index d8016ca6..59553292 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -334,7 +334,7 @@ void Http2Upstream::initiate_downstream(Downstream *downstream) { int rv; rv = downstream->attach_downstream_connection( - handler_->get_downstream_connection()); + handler_->get_downstream_connection(downstream)); if (rv != 0) { // downstream connection fails, send error page if (error_reply(downstream, 503) != 0) { @@ -1476,7 +1476,7 @@ int Http2Upstream::on_downstream_reset(bool no_retry) { // downstream connection. rv = downstream->attach_downstream_connection( - handler_->get_downstream_connection()); + handler_->get_downstream_connection(downstream)); if (rv != 0) { goto fail; } diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc index 34f06b56..d0181b7e 100644 --- a/src/shrpx_http_downstream_connection.cc +++ b/src/shrpx_http_downstream_connection.cc @@ -109,12 +109,12 @@ void connectcb(struct ev_loop *loop, ev_io *w, int revents) { } // namespace HttpDownstreamConnection::HttpDownstreamConnection( - DownstreamConnectionPool *dconn_pool, struct ev_loop *loop) + DownstreamConnectionPool *dconn_pool, size_t group, struct ev_loop *loop) : DownstreamConnection(dconn_pool), conn_(loop, -1, nullptr, get_config()->downstream_write_timeout, get_config()->downstream_read_timeout, 0, 0, 0, 0, connectcb, readcb, timeoutcb, this), - ioctrl_(&conn_.rlimit), response_htp_{0}, addr_idx_(0), + ioctrl_(&conn_.rlimit), response_htp_{0}, group_(group), addr_idx_(0), connected_(false) {} HttpDownstreamConnection::~HttpDownstreamConnection() { @@ -143,14 +143,17 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) { auto worker = client_handler_->get_worker(); auto worker_stat = worker->get_worker_stat(); - auto end = worker_stat->next_downstream; + auto &next_downstream = worker_stat->next_downstream[group_]; + auto end = next_downstream; + auto &addrs = get_config()->downstream_addr_groups[group_].addrs; for (;;) { - auto i = worker_stat->next_downstream; - ++worker_stat->next_downstream; - worker_stat->next_downstream %= get_config()->downstream_addrs.size(); + auto &addr = addrs[next_downstream]; + auto i = next_downstream; + if (++next_downstream >= addrs.size()) { + next_downstream = 0; + } - conn_.fd = util::create_nonblock_socket( - get_config()->downstream_addrs[i].addr.storage.ss_family); + conn_.fd = util::create_nonblock_socket(addr.addr.storage.ss_family); if (conn_.fd == -1) { auto error = errno; @@ -162,8 +165,7 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) { } int rv; - rv = connect(conn_.fd, &get_config()->downstream_addrs[i].addr.sa, - get_config()->downstream_addrs[i].addrlen); + rv = connect(conn_.fd, &addr.addr.sa, addr.addrlen); if (rv != 0 && errno != EINPROGRESS) { auto error = errno; DCLOG(WARN, this) << "connect() failed; errno=" << error; @@ -172,7 +174,7 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) { close(conn_.fd); conn_.fd = -1; - if (end == worker_stat->next_downstream) { + if (end == next_downstream) { return SHRPX_ERR_NETWORK; } @@ -212,8 +214,10 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) { int HttpDownstreamConnection::push_request_headers() { const char *authority = nullptr, *host = nullptr; - auto downstream_hostport = - get_config()->downstream_addrs[addr_idx_].hostport.get(); + auto downstream_hostport = get_config() + ->downstream_addr_groups[group_] + .addrs[addr_idx_] + .hostport.get(); auto connect_method = downstream_->get_request_method() == HTTP_CONNECT; if (!get_config()->no_host_rewrite && !get_config()->http2_proxy && @@ -877,4 +881,6 @@ void HttpDownstreamConnection::on_upstream_change(Upstream *upstream) {} void HttpDownstreamConnection::signal_write() { conn_.wlimit.startw(); } +size_t HttpDownstreamConnection::get_group() const { return group_; } + } // namespace shrpx diff --git a/src/shrpx_http_downstream_connection.h b/src/shrpx_http_downstream_connection.h index 02480d5b..430a5edb 100644 --- a/src/shrpx_http_downstream_connection.h +++ b/src/shrpx_http_downstream_connection.h @@ -39,7 +39,7 @@ class DownstreamConnectionPool; class HttpDownstreamConnection : public DownstreamConnection { public: - HttpDownstreamConnection(DownstreamConnectionPool *dconn_pool, + HttpDownstreamConnection(DownstreamConnectionPool *dconn_pool, size_t group, struct ev_loop *loop); virtual ~HttpDownstreamConnection(); virtual int attach_downstream(Downstream *downstream); @@ -58,6 +58,7 @@ public: virtual void on_upstream_change(Upstream *upstream); virtual int on_priority_change(int32_t pri) { return 0; } + virtual size_t get_group() const; virtual bool poolable() const { return true; } @@ -68,6 +69,7 @@ private: Connection conn_; IOControl ioctrl_; http_parser response_htp_; + size_t group_; // index of get_config()->downstream_addrs this object is using size_t addr_idx_; bool connected_; diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index 261d0c38..0fef7e6f 100644 --- a/src/shrpx_https_upstream.cc +++ b/src/shrpx_https_upstream.cc @@ -297,7 +297,7 @@ int htp_hdrs_completecb(http_parser *htp) { } rv = downstream->attach_downstream_connection( - upstream->get_client_handler()->get_downstream_connection()); + upstream->get_client_handler()->get_downstream_connection(downstream)); if (rv != 0) { downstream->set_request_state(Downstream::CONNECT_FAIL); @@ -993,7 +993,7 @@ int HttpsUpstream::on_downstream_reset(bool no_retry) { } rv = downstream_->attach_downstream_connection( - handler_->get_downstream_connection()); + handler_->get_downstream_connection(downstream_.get())); if (rv != 0) { goto fail; } diff --git a/src/shrpx_spdy_upstream.cc b/src/shrpx_spdy_upstream.cc index 535909e6..eb23e107 100644 --- a/src/shrpx_spdy_upstream.cc +++ b/src/shrpx_spdy_upstream.cc @@ -271,7 +271,7 @@ void SpdyUpstream::start_downstream(Downstream *downstream) { void SpdyUpstream::initiate_downstream(Downstream *downstream) { int rv = downstream->attach_downstream_connection( - handler_->get_downstream_connection()); + handler_->get_downstream_connection(downstream)); if (rv != 0) { // If downstream connection fails, issue RST_STREAM. rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); @@ -1104,7 +1104,7 @@ int SpdyUpstream::on_downstream_reset(bool no_retry) { // downstream connection. rv = downstream->attach_downstream_connection( - handler_->get_downstream_connection()); + handler_->get_downstream_connection(downstream)); if (rv != 0) { goto fail; } diff --git a/src/shrpx_ssl.cc b/src/shrpx_ssl.cc index 1c04fe6c..60526653 100644 --- a/src/shrpx_ssl.cc +++ b/src/shrpx_ssl.cc @@ -743,7 +743,7 @@ void get_altnames(X509 *cert, std::vector &dns_names, } } -int check_cert(SSL *ssl) { +int check_cert(SSL *ssl, const DownstreamAddr *addr) { auto cert = SSL_get_peer_certificate(ssl); if (!cert) { LOG(ERROR) << "No certificate found"; @@ -760,9 +760,7 @@ int check_cert(SSL *ssl) { std::vector dns_names; std::vector ip_addrs; get_altnames(cert, dns_names, ip_addrs, common_name); - if (verify_hostname(get_config()->downstream_addrs[0].host.get(), - &get_config()->downstream_addrs[0].addr, - get_config()->downstream_addrs[0].addrlen, dns_names, + if (verify_hostname(addr->host.get(), &addr->addr, addr->addrlen, dns_names, ip_addrs, common_name) != 0) { LOG(ERROR) << "Certificate verification failed: hostname does not match"; return -1; diff --git a/src/shrpx_ssl.h b/src/shrpx_ssl.h index ae428eaa..168b5f95 100644 --- a/src/shrpx_ssl.h +++ b/src/shrpx_ssl.h @@ -40,6 +40,7 @@ namespace shrpx { class ClientHandler; class Worker; class DownstreamConnectionPool; +struct DownstreamAddr; namespace ssl { @@ -68,7 +69,7 @@ ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr, // Check peer's certificate against first downstream address in // Config::downstream_addrs. We only consider first downstream since // we use this function for HTTP/2 downstream link only. -int check_cert(SSL *ssl); +int check_cert(SSL *ssl, const DownstreamAddr *addr); // Retrieves DNS and IP address in subjectAltNames and commonName from // the |cert|. diff --git a/src/shrpx_worker.cc b/src/shrpx_worker.cc index 9074b91e..a756e7c0 100644 --- a/src/shrpx_worker.cc +++ b/src/shrpx_worker.cc @@ -61,8 +61,11 @@ void mcpool_clear_cb(struct ev_loop *loop, ev_timer *w, int revents) { Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx, ssl::CertLookupTree *cert_tree, const std::shared_ptr &ticket_keys) - : next_http2session_(0), loop_(loop), sv_ssl_ctx_(sv_ssl_ctx), - cl_ssl_ctx_(cl_ssl_ctx), cert_tree_(cert_tree), ticket_keys_(ticket_keys), + : next_http2session_(0), + dconn_pool_(get_config()->downstream_addr_groups.size()), + worker_stat_(get_config()->downstream_addr_groups.size()), loop_(loop), + sv_ssl_ctx_(sv_ssl_ctx), cl_ssl_ctx_(cl_ssl_ctx), cert_tree_(cert_tree), + ticket_keys_(ticket_keys), connect_blocker_(make_unique(loop_)), graceful_shutdown_(false) { ev_async_init(&w_, eventcb); diff --git a/src/shrpx_worker.h b/src/shrpx_worker.h index 7a7a4e9e..f9924647 100644 --- a/src/shrpx_worker.h +++ b/src/shrpx_worker.h @@ -55,13 +55,13 @@ class CertLookupTree; } // namespace ssl struct WorkerStat { - WorkerStat() : num_connections(0), next_downstream(0) {} + WorkerStat(size_t num_groups) + : num_connections(0), next_downstream(num_groups) {} size_t num_connections; - // Next downstream index in Config::downstream_addrs. For HTTP/2 - // downstream connections, this is always 0. For HTTP/1, this is + // Next downstream index in Config::downstream_addr_groups. This is // used as load balancing. - size_t next_downstream; + std::vector next_downstream; }; enum WorkerEventType { diff --git a/src/util.h b/src/util.h index 9ee2b115..d43d3c76 100644 --- a/src/util.h +++ b/src/util.h @@ -386,9 +386,13 @@ bool streq_l(const char (&a)[N], InputIt b, size_t blen) { bool strifind(const char *a, const char *b); +template void inp_strlower(InputIt first, InputIt last) { + std::transform(first, last, first, lowcase); +} + // Lowercase |s| in place. inline void inp_strlower(std::string &s) { - std::transform(std::begin(s), std::end(s), std::begin(s), lowcase); + inp_strlower(std::begin(s), std::end(s)); } // Returns string representation of |n| with 2 fractional digits.