nghttpx: Trie based routing

This commit is contained in:
Tatsuhiro Tsujikawa 2015-09-26 02:38:45 +09:00
parent 566b0476d7
commit 8acf9a2802
9 changed files with 550 additions and 136 deletions

View File

@ -128,6 +128,7 @@ NGHTTPX_SRCS = \
shrpx_worker_process.cc shrpx_worker_process.h \ shrpx_worker_process.cc shrpx_worker_process.h \
shrpx_process.h \ shrpx_process.h \
shrpx_signal.cc shrpx_signal.h \ shrpx_signal.cc shrpx_signal.h \
shrpx_router.cc shrpx_router.h \
buffer.h memchunk.h template.h buffer.h memchunk.h template.h
if HAVE_SPDYLAY if HAVE_SPDYLAY

View File

@ -2370,6 +2370,8 @@ int main(int argc, char **argv) {
DownstreamAddrGroup g("/"); DownstreamAddrGroup g("/");
g.addrs.push_back(std::move(addr)); g.addrs.push_back(std::move(addr));
mod_config()->router.add_route(g.pattern.get(), 1,
get_config()->downstream_addr_groups.size());
mod_config()->downstream_addr_groups.push_back(std::move(g)); mod_config()->downstream_addr_groups.push_back(std::move(g));
} else if (get_config()->http2_proxy || get_config()->client_proxy) { } else if (get_config()->http2_proxy || get_config()->client_proxy) {
// We don't support host mapping in these cases. Move all // We don't support host mapping in these cases. Move all
@ -2381,6 +2383,9 @@ int main(int argc, char **argv) {
} }
std::vector<DownstreamAddrGroup>().swap( std::vector<DownstreamAddrGroup>().swap(
mod_config()->downstream_addr_groups); mod_config()->downstream_addr_groups);
// maybe not necessary?
mod_config()->router.add_route(catch_all.pattern.get(), 1,
get_config()->downstream_addr_groups.size());
mod_config()->downstream_addr_groups.push_back(std::move(catch_all)); mod_config()->downstream_addr_groups.push_back(std::move(catch_all));
} }
@ -2391,11 +2396,11 @@ int main(int argc, char **argv) {
ssize_t catch_all_group = -1; ssize_t catch_all_group = -1;
for (size_t i = 0; i < mod_config()->downstream_addr_groups.size(); ++i) { for (size_t i = 0; i < mod_config()->downstream_addr_groups.size(); ++i) {
auto &g = mod_config()->downstream_addr_groups[i]; auto &g = mod_config()->downstream_addr_groups[i];
if (g.pattern == "/") { if (util::streq(g.pattern.get(), "/")) {
catch_all_group = i; catch_all_group = i;
} }
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Host-path pattern: group " << i << ": '" << g.pattern LOG(INFO) << "Host-path pattern: group " << i << ": '" << g.pattern.get()
<< "'"; << "'";
for (auto &addr : g.addrs) { for (auto &addr : g.addrs) {
LOG(INFO) << "group " << i << " -> " << addr.host.get() LOG(INFO) << "group " << i << " -> " << addr.host.get()

View File

@ -635,18 +635,20 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
// have dealt with proxy case already, just use catch-all group. // have dealt with proxy case already, just use catch-all group.
group = catch_all; group = catch_all;
} else { } else {
auto &router = get_config()->router;
if (!downstream->get_request_http2_authority().empty()) { if (!downstream->get_request_http2_authority().empty()) {
group = match_downstream_addr_group( group = match_downstream_addr_group(
downstream->get_request_http2_authority(), router, downstream->get_request_http2_authority(),
downstream->get_request_path(), groups, catch_all); downstream->get_request_path(), groups, catch_all);
} else { } else {
auto h = downstream->get_request_header(http2::HD_HOST); auto h = downstream->get_request_header(http2::HD_HOST);
if (h) { if (h) {
group = match_downstream_addr_group( group = match_downstream_addr_group(router, h->value,
h->value, downstream->get_request_path(), groups, catch_all); downstream->get_request_path(),
} else {
group = match_downstream_addr_group("", downstream->get_request_path(),
groups, catch_all); groups, catch_all);
} else {
group = match_downstream_addr_group(
router, "", downstream->get_request_path(), groups, catch_all);
} }
} }
} }

View File

@ -79,9 +79,9 @@ TicketKeys::~TicketKeys() {
} }
DownstreamAddr::DownstreamAddr(const DownstreamAddr &other) DownstreamAddr::DownstreamAddr(const DownstreamAddr &other)
: addr(other.addr), host(other.host ? strcopy(other.host.get()) : nullptr), : addr(other.addr), host(strcopy(other.host)),
hostport(other.hostport ? strcopy(other.hostport.get()) : nullptr), hostport(strcopy(other.hostport)), port(other.port),
port(other.port), host_unix(other.host_unix) {} host_unix(other.host_unix) {}
DownstreamAddr &DownstreamAddr::operator=(const DownstreamAddr &other) { DownstreamAddr &DownstreamAddr::operator=(const DownstreamAddr &other) {
if (this == &other) { if (this == &other) {
@ -89,14 +89,29 @@ DownstreamAddr &DownstreamAddr::operator=(const DownstreamAddr &other) {
} }
addr = other.addr; addr = other.addr;
host = (other.host ? strcopy(other.host.get()) : nullptr); host = strcopy(other.host);
hostport = (other.hostport ? strcopy(other.hostport.get()) : nullptr); hostport = strcopy(other.hostport);
port = other.port; port = other.port;
host_unix = other.host_unix; host_unix = other.host_unix;
return *this; return *this;
} }
DownstreamAddrGroup::DownstreamAddrGroup(const DownstreamAddrGroup &other)
: pattern(strcopy(other.pattern)), addrs(other.addrs) {}
DownstreamAddrGroup &DownstreamAddrGroup::
operator=(const DownstreamAddrGroup &other) {
if (this == &other) {
return *this;
}
pattern = strcopy(other.pattern);
addrs = other.addrs;
return *this;
}
namespace { namespace {
int split_host_port(char *host, size_t hostlen, uint16_t *port_ptr, int split_host_port(char *host, size_t hostlen, uint16_t *port_ptr,
const char *hostport, size_t hostportlen) { const char *hostport, size_t hostportlen) {
@ -260,7 +275,6 @@ std::string read_passwd_from_file(const char *filename) {
return line; return line;
} }
std::pair<std::string, std::string> parse_header(const char *optarg) { std::pair<std::string, std::string> parse_header(const char *optarg) {
// We skip possible ":" at the start of optarg. // We skip possible ":" at the start of optarg.
const auto *colon = strchr(optarg + 1, ':'); const auto *colon = strchr(optarg + 1, ':');
@ -576,7 +590,7 @@ void parse_mapping(const DownstreamAddr &addr, const char *src) {
pattern += http2::normalize_path(slash, raw_pattern.second); pattern += http2::normalize_path(slash, raw_pattern.second);
} }
for (auto &g : mod_config()->downstream_addr_groups) { for (auto &g : mod_config()->downstream_addr_groups) {
if (g.pattern == pattern) { if (g.pattern.get() == pattern) {
g.addrs.push_back(addr); g.addrs.push_back(addr);
done = true; done = true;
break; break;
@ -587,6 +601,10 @@ void parse_mapping(const DownstreamAddr &addr, const char *src) {
} }
DownstreamAddrGroup g(pattern); DownstreamAddrGroup g(pattern);
g.addrs.push_back(addr); g.addrs.push_back(addr);
mod_config()->router.add_route(g.pattern.get(), strlen(g.pattern.get()),
get_config()->downstream_addr_groups.size());
mod_config()->downstream_addr_groups.push_back(std::move(g)); mod_config()->downstream_addr_groups.push_back(std::move(g));
} }
} }
@ -2128,67 +2146,17 @@ int int_syslog_facility(const char *strfacility) {
} }
namespace { namespace {
template <typename InputIt> size_t
bool path_match(const std::string &pattern, const std::string &host, match_downstream_addr_group_host(const Router &router, const std::string &host,
InputIt path_first, InputIt path_last) { const char *path, size_t pathlen,
if (pattern.back() != '/') { const std::vector<DownstreamAddrGroup> &groups,
return pattern.size() == host.size() + (path_last - path_first) && size_t catch_all) {
std::equal(std::begin(host), std::end(host), std::begin(pattern)) && if (pathlen == 0 || *path != '/') {
std::equal(path_first, path_last, std::begin(pattern) + host.size()); auto group = router.match(host, "/", 1);
}
if (pattern.size() >= host.size() &&
std::equal(std::begin(host), std::end(host), std::begin(pattern)) &&
util::startsWith(path_first, path_last, std::begin(pattern) + host.size(),
std::end(pattern))) {
return true;
}
// If pattern ends with '/', and pattern and path matches without
// that slash, we consider they match to deal with request to the
// directory without trailing slash. That is if pattern is "/foo/"
// and path is "/foo", we consider they match.
assert(!pattern.empty());
return pattern.size() - 1 == host.size() + (path_last - path_first) &&
std::equal(std::begin(host), std::end(host), std::begin(pattern)) &&
std::equal(path_first, path_last, std::begin(pattern) + host.size());
}
} // namespace
namespace {
template <typename InputIt>
ssize_t match(const std::string &host, InputIt path_first, InputIt path_last,
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, host, path_first, path_last)) {
continue;
}
if (res == -1 || best < pattern.size()) {
best = pattern.size();
res = i;
}
}
return res;
}
} // namespace
namespace {
template <typename InputIt>
size_t match_downstream_addr_group_host(
const std::string &host, InputIt path_first, InputIt path_last,
const std::vector<DownstreamAddrGroup> &groups, size_t catch_all) {
if (path_first == path_last || *path_first != '/') {
constexpr const char P[] = "/";
auto group = match(host, P, P + 1, groups);
if (group != -1) { if (group != -1) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Found pattern with query " << host LOG(INFO) << "Found pattern with query " << host
<< ", matched pattern=" << groups[group].pattern; << ", matched pattern=" << groups[group].pattern.get();
} }
return group; return group;
} }
@ -2197,25 +2165,24 @@ size_t match_downstream_addr_group_host(
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Perform mapping selection, using host=" << host LOG(INFO) << "Perform mapping selection, using host=" << host
<< ", path=" << std::string(path_first, path_last); << ", path=" << std::string(path, pathlen);
} }
auto group = match(host, path_first, path_last, groups); auto group = router.match(host, path, pathlen);
if (group != -1) { if (group != -1) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Found pattern with query " << host LOG(INFO) << "Found pattern with query " << host
<< std::string(path_first, path_last) << std::string(path, pathlen)
<< ", matched pattern=" << groups[group].pattern; << ", matched pattern=" << groups[group].pattern.get();
} }
return group; return group;
} }
group = match("", path_first, path_last, groups); group = router.match("", path, pathlen);
if (group != -1) { if (group != -1) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Found pattern with query " LOG(INFO) << "Found pattern with query " << std::string(path, pathlen)
<< std::string(path_first, path_last) << ", matched pattern=" << groups[group].pattern.get();
<< ", matched pattern=" << groups[group].pattern;
} }
return group; return group;
} }
@ -2227,9 +2194,11 @@ size_t match_downstream_addr_group_host(
} }
} // namespace } // namespace
size_t match_downstream_addr_group( size_t
const std::string &hostport, const std::string &raw_path, match_downstream_addr_group(const Router &router, const std::string &hostport,
const std::vector<DownstreamAddrGroup> &groups, size_t catch_all) { const std::string &raw_path,
const std::vector<DownstreamAddrGroup> &groups,
size_t catch_all) {
if (std::find(std::begin(hostport), std::end(hostport), '/') != if (std::find(std::begin(hostport), std::end(hostport), '/') !=
std::end(hostport)) { std::end(hostport)) {
// We use '/' specially, and if '/' is included in host, it breaks // We use '/' specially, and if '/' is included in host, it breaks
@ -2239,11 +2208,11 @@ size_t match_downstream_addr_group(
auto fragment = std::find(std::begin(raw_path), std::end(raw_path), '#'); auto fragment = std::find(std::begin(raw_path), std::end(raw_path), '#');
auto query = std::find(std::begin(raw_path), fragment, '?'); auto query = std::find(std::begin(raw_path), fragment, '?');
auto path_first = std::begin(raw_path); auto path = raw_path.c_str();
auto path_last = query; auto pathlen = query - std::begin(raw_path);
if (hostport.empty()) { if (hostport.empty()) {
return match_downstream_addr_group_host(hostport, path_first, path_last, return match_downstream_addr_group_host(router, hostport, path, pathlen,
groups, catch_all); groups, catch_all);
} }
@ -2267,7 +2236,7 @@ size_t match_downstream_addr_group(
} }
util::inp_strlower(host); util::inp_strlower(host);
return match_downstream_addr_group_host(host, path_first, path_last, groups, return match_downstream_addr_group_host(router, host, path, pathlen, groups,
catch_all); catch_all);
} }

View File

@ -50,6 +50,7 @@
#include <nghttp2/nghttp2.h> #include <nghttp2/nghttp2.h>
#include "shrpx_router.h"
#include "template.h" #include "template.h"
using namespace nghttp2; using namespace nghttp2;
@ -229,8 +230,13 @@ struct DownstreamAddr {
}; };
struct DownstreamAddrGroup { struct DownstreamAddrGroup {
DownstreamAddrGroup(std::string pattern) : pattern(std::move(pattern)) {} DownstreamAddrGroup(const std::string &pattern) : pattern(strcopy(pattern)) {}
std::string pattern; DownstreamAddrGroup(const DownstreamAddrGroup &other);
DownstreamAddrGroup(DownstreamAddrGroup &&) = default;
DownstreamAddrGroup &operator=(const DownstreamAddrGroup &other);
DownstreamAddrGroup &operator=(DownstreamAddrGroup &&) = default;
std::unique_ptr<char[]> pattern;
std::vector<DownstreamAddr> addrs; std::vector<DownstreamAddr> addrs;
}; };
@ -272,6 +278,7 @@ struct Config {
Address downstream_http_proxy_addr; Address downstream_http_proxy_addr;
Address session_cache_memcached_addr; Address session_cache_memcached_addr;
Address tls_ticket_key_memcached_addr; Address tls_ticket_key_memcached_addr;
Router router;
std::chrono::seconds tls_session_timeout; std::chrono::seconds tls_session_timeout;
ev_tstamp http2_upstream_read_timeout; ev_tstamp http2_upstream_read_timeout;
ev_tstamp upstream_read_timeout; ev_tstamp upstream_read_timeout;
@ -442,24 +449,6 @@ std::pair<std::string, std::string> parse_header(const char *optarg);
std::vector<LogFragment> parse_log_format(const char *optarg); std::vector<LogFragment> parse_log_format(const char *optarg);
// Returns a copy of NULL-terminated string [first, last).
template <typename InputIt>
std::unique_ptr<char[]> strcopy(InputIt first, InputIt last) {
auto res = make_unique<char[]>(last - first + 1);
*std::copy(first, last, res.get()) = '\0';
return res;
}
// Returns a copy of NULL-terminated string |val|.
inline std::unique_ptr<char[]> strcopy(const char *val) {
return strcopy(val, val + strlen(val));
}
// Returns a copy of val.c_str().
inline std::unique_ptr<char[]> strcopy(const std::string &val) {
return strcopy(std::begin(val), std::end(val));
}
// Returns string for syslog |facility|. // Returns string for syslog |facility|.
const char *str_syslog_facility(int facility); const char *str_syslog_facility(int facility);
@ -483,7 +472,7 @@ read_tls_ticket_key_file(const std::vector<std::string> &files,
// group. The catch-all group index is given in |catch_all|. All // group. The catch-all group index is given in |catch_all|. All
// patterns are given in |groups|. // patterns are given in |groups|.
size_t match_downstream_addr_group( size_t match_downstream_addr_group(
const std::string &hostport, const std::string &path, const Router &router, const std::string &hostport, const std::string &path,
const std::vector<DownstreamAddrGroup> &groups, size_t catch_all); const std::vector<DownstreamAddrGroup> &groups, size_t catch_all);
} // namespace shrpx } // namespace shrpx

View File

@ -235,56 +235,112 @@ void test_shrpx_config_match_downstream_addr_group(void) {
{"nghttp2.org/delta%3A"}, {"nghttp2.org/delta%3A"},
{"www.nghttp2.org/"}, {"www.nghttp2.org/"},
{"[::1]/"}, {"[::1]/"},
{"nghttp2.org/alpha/bravo/delta"},
// Check that match is done in the single node
{"example.com/alpha/bravo"},
{"192.168.0.1/alpha/"},
}; };
CU_ASSERT(0 == match_downstream_addr_group("nghttp2.org", "/", groups, 255)); Router router;
for (size_t i = 0; i < groups.size(); ++i) {
auto &g = groups[i];
router.add_route(g.pattern.get(), strlen(g.pattern.get()), i);
}
CU_ASSERT(0 == match_downstream_addr_group(router, "nghttp2.org", "/", groups,
255));
// port is removed // port is removed
CU_ASSERT(0 == CU_ASSERT(0 == match_downstream_addr_group(router, "nghttp2.org:8080", "/",
match_downstream_addr_group("nghttp2.org:8080", "/", groups, 255)); groups, 255));
// host is case-insensitive // host is case-insensitive
CU_ASSERT(4 == match_downstream_addr_group("WWW.nghttp2.org", "/alpha", CU_ASSERT(4 == match_downstream_addr_group(router, "WWW.nghttp2.org",
groups, 255)); "/alpha", groups, 255));
CU_ASSERT(1 == match_downstream_addr_group("nghttp2.org", "/alpha/bravo/", CU_ASSERT(1 == match_downstream_addr_group(router, "nghttp2.org",
groups, 255)); "/alpha/bravo/", groups, 255));
// /alpha/bravo also matches /alpha/bravo/ // /alpha/bravo also matches /alpha/bravo/
CU_ASSERT(1 == match_downstream_addr_group("nghttp2.org", "/alpha/bravo", CU_ASSERT(1 == match_downstream_addr_group(router, "nghttp2.org",
groups, 255)); "/alpha/bravo", groups, 255));
// path part is case-sensitive // path part is case-sensitive
CU_ASSERT(0 == match_downstream_addr_group("nghttp2.org", "/Alpha/bravo", CU_ASSERT(0 == match_downstream_addr_group(router, "nghttp2.org",
groups, 255)); "/Alpha/bravo", groups, 255));
CU_ASSERT(1 == match_downstream_addr_group( CU_ASSERT(1 == match_downstream_addr_group(router, "nghttp2.org",
"nghttp2.org", "/alpha/bravo/charlie", groups, 255)); "/alpha/bravo/charlie", groups,
255));
CU_ASSERT(2 == match_downstream_addr_group("nghttp2.org", "/alpha/charlie", CU_ASSERT(2 == match_downstream_addr_group(router, "nghttp2.org",
groups, 255)); "/alpha/charlie", groups, 255));
// pattern which does not end with '/' must match its entirely. So // pattern which does not end with '/' must match its entirely. So
// this matches to group 0, not group 2. // this matches to group 0, not group 2.
CU_ASSERT(0 == match_downstream_addr_group("nghttp2.org", "/alpha/charlie/", CU_ASSERT(0 == match_downstream_addr_group(router, "nghttp2.org",
groups, 255)); "/alpha/charlie/", groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(router, "example.org", "/",
groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(router, "", "/", groups, 255));
CU_ASSERT(255 == CU_ASSERT(255 ==
match_downstream_addr_group("example.org", "/", groups, 255)); match_downstream_addr_group(router, "", "alpha", groups, 255));
CU_ASSERT(255 == match_downstream_addr_group("", "/", groups, 255)); CU_ASSERT(255 ==
match_downstream_addr_group(router, "foo/bar", "/", groups, 255));
CU_ASSERT(255 == match_downstream_addr_group("", "alpha", groups, 255));
CU_ASSERT(255 == match_downstream_addr_group("foo/bar", "/", groups, 255));
// If path is "*", only match with host + "/". // If path is "*", only match with host + "/".
CU_ASSERT(0 == match_downstream_addr_group("nghttp2.org", "*", groups, 255)); CU_ASSERT(0 == match_downstream_addr_group(router, "nghttp2.org", "*", groups,
255));
CU_ASSERT(5 == match_downstream_addr_group("[::1]", "/", groups, 255)); CU_ASSERT(5 ==
CU_ASSERT(5 == match_downstream_addr_group("[::1]:8080", "/", groups, 255)); match_downstream_addr_group(router, "[::1]", "/", groups, 255));
CU_ASSERT(255 == match_downstream_addr_group("[::1", "/", groups, 255)); CU_ASSERT(
CU_ASSERT(255 == match_downstream_addr_group("[::1]8000", "/", groups, 255)); 5 == match_downstream_addr_group(router, "[::1]:8080", "/", groups, 255));
CU_ASSERT(255 ==
match_downstream_addr_group(router, "[::1", "/", groups, 255));
CU_ASSERT(255 ==
match_downstream_addr_group(router, "[::1]8000", "/", groups, 255));
// Check the case where adding route extends tree
CU_ASSERT(6 == match_downstream_addr_group(
router, "nghttp2.org", "/alpha/bravo/delta", groups, 255));
CU_ASSERT(1 == match_downstream_addr_group(router, "nghttp2.org",
"/alpha/bravo/delta/", groups,
255));
// Check the case where query is done in a single node
CU_ASSERT(7 == match_downstream_addr_group(router, "example.com",
"/alpha/bravo", groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(router, "example.com",
"/alpha/bravo/", groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(router, "example.com", "/alpha",
groups, 255));
// Check the case where quey is done in a single node
CU_ASSERT(8 == match_downstream_addr_group(router, "192.168.0.1", "/alpha",
groups, 255));
CU_ASSERT(8 == match_downstream_addr_group(router, "192.168.0.1", "/alpha/",
groups, 255));
CU_ASSERT(8 == match_downstream_addr_group(router, "192.168.0.1",
"/alpha/bravo", groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(router, "192.168.0.1", "/alph",
groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(router, "192.168.0.1", "/",
groups, 255));
router.dump();
} }
} // namespace shrpx } // namespace shrpx

291
src/shrpx_router.cc Normal file
View File

@ -0,0 +1,291 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "shrpx_router.h"
#include "shrpx_config.h"
namespace shrpx {
RNode::RNode() : s(nullptr), len(0), index(-1) {}
RNode::RNode(const char *s, size_t len, size_t index)
: s(s), len(len), index(index) {}
Router::Router() : root_{} {}
namespace {
RNode *find_next_node(const RNode *node, char c) {
auto itr = std::lower_bound(std::begin(node->next), std::end(node->next), c,
[](const std::unique_ptr<RNode> &lhs,
const char c) { return lhs->s[0] < c; });
if (itr == std::end(node->next) || (*itr)->s[0] != c) {
return nullptr;
}
return (*itr).get();
}
} // namespace
namespace {
void add_next_node(RNode *node, std::unique_ptr<RNode> new_node) {
auto itr = std::lower_bound(std::begin(node->next), std::end(node->next),
new_node->s[0],
[](const std::unique_ptr<RNode> &lhs,
const char c) { return lhs->s[0] < c; });
node->next.insert(itr, std::move(new_node));
}
} // namespace
void Router::add_node(RNode *node, const char *pattern, size_t patlen,
size_t index) {
auto new_node = make_unique<RNode>(pattern, patlen, index);
add_next_node(node, std::move(new_node));
}
bool Router::add_route(const char *pattern, size_t patlen, size_t index) {
auto node = &root_;
size_t i = 0;
for (;;) {
auto next_node = find_next_node(node, pattern[i]);
if (next_node == nullptr) {
add_node(node, pattern + i, patlen - i, index);
return true;
}
node = next_node;
auto slen = patlen - i;
auto s = pattern + i;
auto n = std::min(node->len, slen);
size_t j;
for (j = 0; j < n && node->s[j] == s[j]; ++j)
;
if (j == n) {
// The common prefix was matched
if (slen == node->len) {
// Complete match
if (node->index != -1) {
// Don't allow duplicate
return false;
}
node->index = index;
return true;
}
if (slen > node->len) {
// We still have pattern to add
i += j;
continue;
}
}
if (node->len > j) {
// node must be split into 2 nodes. new_node is now the child
// of node.
auto new_node =
make_unique<RNode>(&node->s[j], node->len - j, node->index);
std::swap(node->next, new_node->next);
node->len = j;
node->index = -1;
add_next_node(node, std::move(new_node));
if (slen == j) {
node->index = index;
return true;
}
}
i += j;
assert(patlen > i);
add_node(node, pattern + i, patlen - i, index);
return true;
}
}
namespace {
const RNode *match_complete(size_t *offset, const RNode *node,
const char *first, const char *last) {
*offset = 0;
if (first == last) {
return node;
}
auto p = first;
for (;;) {
auto next_node = find_next_node(node, *p);
if (next_node == nullptr) {
return nullptr;
}
node = next_node;
auto n = std::min(node->len, static_cast<size_t>(last - p));
if (memcmp(node->s, p, n) != 0) {
return nullptr;
}
p += n;
if (p == last) {
*offset = n;
return node;
}
}
}
} // namespace
namespace {
const RNode *match_partial(const RNode *node, size_t offset, const char *first,
const char *last) {
if (first == last) {
if (node->len == offset) {
return node;
}
return nullptr;
}
auto p = first;
const RNode *found_node = nullptr;
if (offset > 0) {
auto n = std::min(node->len - offset, static_cast<size_t>(last - first));
if (memcmp(node->s + offset, first, n) != 0) {
return nullptr;
}
p += n;
if (p == last) {
if (node->len == offset + n) {
if (node->index != -1) {
return node;
}
return nullptr;
}
if (node->index != -1 && offset + n + 1 == node->len &&
node->s[node->len - 1] == '/') {
return node;
}
return nullptr;
}
if (node->index != -1 && node->s[node->len - 1] == '/') {
found_node = node;
}
assert(node->len == offset + n);
}
for (;;) {
auto next_node = find_next_node(node, *p);
if (next_node == nullptr) {
return found_node;
}
node = next_node;
auto n = std::min(node->len, static_cast<size_t>(last - p));
if (memcmp(node->s, p, n) != 0) {
return found_node;
}
p += n;
if (p == last) {
if (node->len == n) {
// Complete match with this node
if (node->index != -1) {
return node;
}
return found_node;
}
// We allow match without trailing "/" at the end of pattern.
// So, if pattern ends with '/', and pattern and path matches
// without that slash, we consider they match to deal with
// request to the directory without trailing slash. That is if
// pattern is "/foo/" and path is "/foo", we consider they
// match.
if (node->index != -1 && n + 1 == node->len && node->s[n] == '/') {
return node;
}
return found_node;
}
// This is the case when pattern which ends with "/" is included
// in query.
if (node->index != -1 && node->s[node->len - 1] == '/') {
found_node = node;
}
assert(node->len == n);
}
}
} // namespace
ssize_t Router::match(const std::string &host, const char *path,
size_t pathlen) const {
const RNode *node;
size_t offset;
node =
match_complete(&offset, &root_, host.c_str(), host.c_str() + host.size());
if (node == nullptr) {
return -1;
}
node = match_partial(node, offset, path, path + pathlen);
if (node == nullptr || node == &root_) {
return -1;
}
return node->index;
}
namespace {
void dump_node(const RNode *node, int depth) {
fprintf(stderr, "%*ss='%.*s', len=%zu, index=%zd\n", depth, "",
(int)node->len, node->s, node->len, node->index);
for (auto &nd : node->next) {
dump_node(nd.get(), depth + 4);
}
}
} // namespace
void Router::dump() const { dump_node(&root_, 0); }
} // namespace shrpx

76
src/shrpx_router.h Normal file
View File

@ -0,0 +1,76 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef SHRPX_ROUTER_H
#define SHRPX_ROUTER_H
#include "shrpx.h"
#include <vector>
#include <memory>
namespace shrpx {
struct RNode {
RNode();
RNode(const char *s, size_t len, size_t index);
RNode(RNode &&) = default;
RNode(const RNode &) = delete;
RNode &operator=(RNode &&) = default;
RNode &operator=(const RNode &) = delete;
// Next RNode, sorted by s[0].
std::vector<std::unique_ptr<RNode>> next;
// Stores pointer to the string this node represents. Not
// NULL-terminated.
const char *s;
// Length of |s|
size_t len;
// Index of pattern if match ends in this node. Note that we don't
// store duplicated pattern.
ssize_t index;
};
class Router {
public:
Router();
// Adds route |pattern| of size |patlen| with its |index|.
bool add_route(const char *pattern, size_t patlen, size_t index);
// Returns the matched index of pattern. -1 if there is no match.
ssize_t match(const std::string &host, const char *path,
size_t pathlen) const;
void add_node(RNode *node, const char *pattern, size_t patlen, size_t index);
void dump() const;
private:
// The root node of Patricia tree. This is special node and its s
// field is nulptr, and len field is 0.
RNode root_;
};
} // namespace shrpx
#endif // SHRPX_ROUTER_H

View File

@ -170,6 +170,31 @@ constexpr double operator"" _h(unsigned long long h) { return h * 60 * 60; }
constexpr double operator"" _min(unsigned long long min) { return min * 60; } constexpr double operator"" _min(unsigned long long min) { return min * 60; }
// Returns a copy of NULL-terminated string [first, last).
template <typename InputIt>
std::unique_ptr<char[]> strcopy(InputIt first, InputIt last) {
auto res = make_unique<char[]>(last - first + 1);
*std::copy(first, last, res.get()) = '\0';
return res;
}
// Returns a copy of NULL-terminated string |val|.
inline std::unique_ptr<char[]> strcopy(const char *val) {
return strcopy(val, val + strlen(val));
}
// Returns a copy of val.c_str().
inline std::unique_ptr<char[]> strcopy(const std::string &val) {
return strcopy(std::begin(val), std::end(val));
}
inline std::unique_ptr<char[]> strcopy(const std::unique_ptr<char[]> &val) {
if (!val) {
return nullptr;
}
return strcopy(val.get());
}
} // namespace nghttp2 } // namespace nghttp2
#endif // TEMPLATE_H #endif // TEMPLATE_H