Select backend based on request host and path by extending -b option
-b option syntax is now <HOST>,<PORT>[;<PATTERN>[:...]]. The optional <PATTERN>s specify the request host and path it is used for. The <PATTERN> can contain path, host + path or host. The matching rule is closely designed to ServeMux in Go programming language.
This commit is contained in:
parent
1940413eb3
commit
3119fc259c
36
src/http2.h
36
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 <typename InputIt>
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
97
src/shrpx.cc
97
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=<HOST,PORT>
|
||||
-b, --backend=<HOST>,<PORT>[;<PATTERN>[:...]]
|
||||
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 <PATTERN>s are given, the backend address
|
||||
is only used if request matches the pattern. If -s, -p,
|
||||
--client or --http2-bridge is used, <PATTERN>s are
|
||||
ignored. The pattern matching is closely designed to
|
||||
ServeMux in net/http package of Go programming language.
|
||||
<PATTERN> 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
|
||||
<PATTERN> 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 <PATTERN>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 <PATTERN> are grouped together
|
||||
forming load balancing group. Since ";" and ":" are
|
||||
used as delimiter, <PATTERN> must not contain these
|
||||
characters.
|
||||
Default: )" << DEFAULT_DOWNSTREAM_HOST << ","
|
||||
<< DEFAULT_DOWNSTREAM_PORT << R"(
|
||||
-f, --frontend=<HOST,PORT>
|
||||
-f, --frontend=<HOST>,<PORT>
|
||||
Set frontend host and port. If <HOST> is '*', it
|
||||
assumes all addresses including both IPv4 and IPv6.
|
||||
UNIX domain socket can be specified by prefixing path
|
||||
|
@ -2118,19 +2150,60 @@ 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<DownstreamAddrGroup>().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 (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;
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "Catch-all pattern is group " << catch_all_group;
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -2163,12 +2236,13 @@ int main(int argc, char **argv) {
|
|||
|
||||
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) {
|
||||
get_config()->backend_ipv4 ? AF_INET : (get_config()->backend_ipv6
|
||||
? AF_INET6
|
||||
: AF_UNSPEC)) == -1) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (get_config()->downstream_http_proxy_host) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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<DownstreamConnection>
|
||||
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<Http2DownstreamConnection>(dconn_pool, http2session_);
|
||||
} else {
|
||||
dconn = make_unique<HttpDownstreamConnection>(dconn_pool, conn_.loop);
|
||||
dconn =
|
||||
make_unique<HttpDownstreamConnection>(dconn_pool, group, conn_.loop);
|
||||
}
|
||||
dconn->set_client_handler(this);
|
||||
return dconn;
|
||||
|
|
|
@ -92,7 +92,8 @@ public:
|
|||
|
||||
void pool_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
|
||||
void remove_downstream_connection(DownstreamConnection *dconn);
|
||||
std::unique_ptr<DownstreamConnection> get_downstream_connection();
|
||||
std::unique_ptr<DownstreamConnection>
|
||||
get_downstream_connection(Downstream *downstream);
|
||||
MemchunkPool *get_mcpool();
|
||||
SSL *get_ssl() const;
|
||||
ConnectBlocker *get_connect_blocker() const;
|
||||
|
|
|
@ -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<uint16_t>::max()) {
|
||||
auto portlen = hostportlen - len - 1;
|
||||
auto d = util::parse_uint(reinterpret_cast<const uint8_t *>(p + 1), portlen);
|
||||
if (1 <= d && d <= std::numeric_limits<uint16_t>::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<char[]> strcopy(const std::string &val) {
|
|||
return strcopy(val.c_str(), val.size());
|
||||
}
|
||||
|
||||
std::vector<char *> parse_config_str_list(const char *s) {
|
||||
std::vector<char *> 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<char *>(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)) {
|
||||
if (util::istartsWith(optarg, SHRPX_UNIX_PATH_PREFIX)) {
|
||||
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)) {
|
||||
auto path = optarg + str_size(SHRPX_UNIX_PATH_PREFIX);
|
||||
addr.host = strcopy(path);
|
||||
addr.host_unix = true;
|
||||
|
||||
mod_config()->downstream_addrs.push_back(std::move(addr));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (split_host_port(host, sizeof(host), &port, optarg) == -1) {
|
||||
} else {
|
||||
if (split_host_port(host, sizeof(host), &port, optarg,
|
||||
pat_delim - optarg) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
DownstreamAddr addr;
|
||||
addr.host = strcopy(host);
|
||||
addr.port = port;
|
||||
}
|
||||
|
||||
mod_config()->downstream_addrs.push_back(std::move(addr));
|
||||
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;
|
||||
}
|
||||
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<DownstreamAddrGroup> &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<DownstreamAddrGroup> &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<DownstreamAddrGroup> &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
|
||||
|
|
|
@ -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<DownstreamAddr> addrs;
|
||||
};
|
||||
|
||||
struct TicketKey {
|
||||
uint8_t name[16];
|
||||
uint8_t aes_key[16];
|
||||
|
@ -225,7 +236,7 @@ struct Config {
|
|||
std::vector<std::pair<std::string, std::string>> add_response_headers;
|
||||
std::vector<unsigned char> alpn_prefs;
|
||||
std::vector<LogFragment> accesslog_format;
|
||||
std::vector<DownstreamAddr> downstream_addrs;
|
||||
std::vector<DownstreamAddrGroup> downstream_addr_groups;
|
||||
std::vector<std::string> 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<char *> parse_config_str_list(const char *s);
|
||||
std::vector<char *> 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<TicketKeys>
|
||||
read_tls_ticket_key_file(const std::vector<std::string> &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<DownstreamAddrGroup> &groups, size_t catch_all);
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
#endif // SHRPX_CONFIG_H
|
||||
|
|
|
@ -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<DownstreamAddrGroup>{
|
||||
{"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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -27,33 +27,42 @@
|
|||
|
||||
namespace shrpx {
|
||||
|
||||
DownstreamConnectionPool::DownstreamConnectionPool() {}
|
||||
DownstreamConnectionPool::DownstreamConnectionPool(size_t num_groups)
|
||||
: gpool_(num_groups) {}
|
||||
|
||||
DownstreamConnectionPool::~DownstreamConnectionPool() {
|
||||
for (auto dconn : pool_) {
|
||||
for (auto &pool : gpool_) {
|
||||
for (auto dconn : pool) {
|
||||
delete dconn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DownstreamConnectionPool::add_downstream_connection(
|
||||
std::unique_ptr<DownstreamConnection> dconn) {
|
||||
pool_.insert(dconn.release());
|
||||
auto group = dconn->get_group();
|
||||
assert(gpool_.size() > group);
|
||||
gpool_[group].insert(dconn.release());
|
||||
}
|
||||
|
||||
std::unique_ptr<DownstreamConnection>
|
||||
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<DownstreamConnection>(*std::begin(pool_));
|
||||
pool_.erase(std::begin(pool_));
|
||||
auto dconn = std::unique_ptr<DownstreamConnection>(*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;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,15 +36,15 @@ class DownstreamConnection;
|
|||
|
||||
class DownstreamConnectionPool {
|
||||
public:
|
||||
DownstreamConnectionPool();
|
||||
DownstreamConnectionPool(size_t num_groups);
|
||||
~DownstreamConnectionPool();
|
||||
|
||||
void add_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
|
||||
std::unique_ptr<DownstreamConnection> pop_downstream_connection();
|
||||
std::unique_ptr<DownstreamConnection> pop_downstream_connection(size_t group);
|
||||
void remove_downstream_connection(DownstreamConnection *dconn);
|
||||
|
||||
private:
|
||||
std::set<DownstreamConnection *> pool_;
|
||||
std::vector<std::set<DownstreamConnection *>> gpool_;
|
||||
};
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -743,7 +743,7 @@ void get_altnames(X509 *cert, std::vector<std::string> &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<std::string> dns_names;
|
||||
std::vector<std::string> 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;
|
||||
|
|
|
@ -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|.
|
||||
|
|
|
@ -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<TicketKeys> &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<ConnectBlocker>(loop_)),
|
||||
graceful_shutdown_(false) {
|
||||
ev_async_init(&w_, eventcb);
|
||||
|
|
|
@ -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<size_t> next_downstream;
|
||||
};
|
||||
|
||||
enum WorkerEventType {
|
||||
|
|
|
@ -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 <typename InputIt> 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.
|
||||
|
|
Loading…
Reference in New Issue