Kylin/HttpProxy/UrlRouterPrivate.cpp
2023-12-30 00:03:40 +08:00

324 lines
12 KiB
C++

#include "UrlRouterPrivate.h"
#include "BoostLog.h"
#include "TemplateSegmentRule.h"
#include <boost/url/decode_view.hpp>
#include <boost/url/rfc/detail/path_rules.hpp>
UrlRouterPrivate::UrlRouterPrivate() {
m_nodes.push_back(SegementNode{});
}
void UrlRouterPrivate::insertImpl(boost::core::string_view pattern, const std::shared_ptr<AnyResource> &resource) {
if (pattern.starts_with("/")) pattern.remove_prefix(1);
auto segements = boost::urls::grammar::parse(pattern, templatePathRule);
if (!segements) {
segements.value();
}
auto iterator = segements->begin();
auto end = segements->end();
auto currentNode = &m_nodes.front();
int level = 0;
while (iterator != end) {
boost::core::string_view segement = (*iterator).string();
if (segement == ".") {
++iterator;
continue;
} else if (segement == "..") {
if (currentNode == &m_nodes.front()) {
--level;
++iterator;
continue;
}
std::size_t parentIndex = currentNode->parentIndex;
if (currentNode == &m_nodes.back() && !currentNode->resource && currentNode->childIndexes.empty()) {
auto p = &m_nodes[parentIndex];
std::size_t currentIndex = currentNode - m_nodes.data();
p->childIndexes.erase(std::remove(p->childIndexes.begin(), p->childIndexes.end(), currentIndex));
m_nodes.pop_back();
}
currentNode = &m_nodes[parentIndex];
++iterator;
continue;
}
if (level < 0) {
++level;
++iterator;
continue;
}
auto cit = std::find_if(currentNode->childIndexes.begin(), currentNode->childIndexes.end(),
[this, &iterator](std::size_t ci) -> bool { return m_nodes[ci].segment == *iterator; });
if (cit != currentNode->childIndexes.end()) {
currentNode = &m_nodes[*cit];
} else {
SegementNode child;
child.segment = *iterator;
std::size_t currentIndex = currentNode - m_nodes.data();
child.parentIndex = currentIndex;
m_nodes.push_back(std::move(child));
m_nodes[currentIndex].childIndexes.push_back(m_nodes.size() - 1);
if (m_nodes[currentIndex].childIndexes.size() > 1) {
// keep nodes sorted
auto &cs = m_nodes[currentIndex].childIndexes;
std::size_t n = cs.size() - 1;
while (n) {
if (m_nodes[cs.begin()[n]].segment < m_nodes[cs.begin()[n - 1]].segment)
std::swap(cs.begin()[n], cs.begin()[n - 1]);
else
break;
--n;
}
}
currentNode = &m_nodes.back();
}
++iterator;
}
if (level != 0) {
boost::urls::detail::throw_invalid_argument();
}
currentNode->resource = resource;
currentNode->templatePath = pattern;
}
const AnyResource *UrlRouterPrivate::findImpl(boost::urls::segments_encoded_view path,
boost::core::string_view *&matches,
boost::core::string_view *&ids) const noexcept {
if (path.empty()) path = boost::urls::segments_encoded_view("./");
const SegementNode *p = tryMatch(path.begin(), path.end(), &m_nodes.front(), 0, matches, ids);
if (p) return p->resource.get();
return nullptr;
}
const SegementNode *UrlRouterPrivate::tryMatch(boost::urls::segments_encoded_base::const_iterator it,
boost::urls::segments_encoded_base::const_iterator end,
const SegementNode *current, int level,
boost::core::string_view *&matches,
boost::core::string_view *&ids) const {
while (it != end) {
boost::urls::pct_string_view s = *it;
if (*s == ".") {
// ignore segment
++it;
continue;
} else if (*s == "..") { // move back to the parent node
++it;
if (level <= 0 && current != &m_nodes.front()) {
if (!current->segment.isLiteral()) {
--matches;
--ids;
}
current = &m_nodes[current->parentIndex];
} else
// there's no parent, so we
// discount that from the implicit
// tree beyond terminals
--level;
continue;
}
if (level < 0) {
++level;
++it;
continue;
}
bool branch = false;
if (current->childIndexes.size() > 1) {
int branches_lb = 0;
for (auto i : current->childIndexes) {
auto &c = m_nodes[i];
if (c.segment.isLiteral() || !c.segment.hasModifier()) {
// a literal path counts only
// if it matches
branches_lb += c.segment.match(s);
} else {
// everything not matching
// a single path counts as
// more than one path already
branches_lb = 2;
}
if (branches_lb > 1) {
// already know we need to
// branch
branch = true;
break;
}
}
}
const SegementNode *r = nullptr;
bool matchAny = false;
for (auto index : current->childIndexes) {
auto &child = m_nodes[index];
if (!child.segment.match(s)) continue;
if (child.segment.isLiteral()) {
if (branch) {
r = tryMatch(std::next(it), end, &child, level, matches, ids);
if (r) break;
} else {
current = &child;
matchAny = true;
break;
}
} else if (!child.segment.hasModifier()) {
if (branch) {
auto matches0 = matches;
auto ids0 = ids;
*matches++ = *it;
*ids++ = child.segment.id();
r = tryMatch(std::next(it), end, &child, level, matches, ids);
if (r) {
break;
} else {
// rewind
matches = matches0;
ids = ids0;
}
} else {
// only path possible
*matches++ = *it;
*ids++ = child.segment.id();
current = &child;
matchAny = true;
break;
}
} else if (child.segment.isOptional()) {
auto matches0 = matches;
auto ids0 = ids;
*matches++ = *it;
*ids++ = child.segment.id();
r = tryMatch(std::next(it), end, &child, level, matches, ids);
if (r) break;
matches = matches0;
ids = ids0;
*matches++ = {};
*ids++ = child.segment.id();
r = tryMatch(it, end, &child, level, matches, ids);
if (r) break;
matches = matches0;
ids = ids0;
} else {
auto first = it;
std::size_t ndotdot = 0;
std::size_t nnondot = 0;
auto it1 = it;
while (it1 != end) {
if (*it1 == "..") {
++ndotdot;
if (ndotdot >= (nnondot + child.segment.isStar())) break;
} else if (*it1 != ".") {
++nnondot;
}
++it1;
}
if (it1 != end) break;
auto matches0 = matches;
auto ids0 = ids;
*matches++ = *it;
*ids++ = child.segment.id();
if (child.segment.isPlus()) {
++first;
}
auto start = end;
while (start != first) {
r = tryMatch(start, end, &child, level, matches, ids);
if (r) {
boost::core::string_view prev = *std::prev(start);
*matches0 = {matches0->data(), prev.data() + prev.size()};
break;
}
matches = matches0 + 1;
ids = ids0 + 1;
--start;
}
if (r) {
break;
}
matches = matches0 + 1;
ids = ids0 + 1;
r = tryMatch(start, end, &child, level, matches, ids);
if (r) {
if (!child.segment.isPlus()) *matches0 = {};
break;
}
}
}
if (r) return r;
if (!matchAny) ++level;
++it;
}
if (level != 0) { // the path ended below or above an existing node
return nullptr;
}
if (!current->resource) {
return findOptionalResource(current, m_nodes, matches, ids);
}
return current;
}
const SegementNode *UrlRouterPrivate::findOptionalResource(const SegementNode *root,
const std::vector<SegementNode> &ns,
boost::core::string_view *&matches,
boost::core::string_view *&ids) {
BOOST_ASSERT(root);
if (root->resource) return root;
BOOST_ASSERT(!root->childIndexes.empty());
for (auto index : root->childIndexes) {
auto &child = ns[index];
if (!child.segment.isOptional() && !child.segment.isStar()) continue;
// Child nodes are also potentially optional.
auto matches0 = matches;
auto ids0 = ids;
*matches++ = {};
*ids++ = child.segment.id();
auto n = findOptionalResource(&child, ns, matches, ids);
if (n) return n;
matches = matches0;
ids = ids0;
}
return nullptr;
}
bool ChildIndexVector::empty() const {
return m_size == 0;
}
size_t ChildIndexVector::size() const {
return m_size;
}
size_t *ChildIndexVector::begin() {
if (m_childIndexes) return m_childIndexes;
return m_staticChildIndexes;
}
const size_t *ChildIndexVector::begin() const {
return const_cast<ChildIndexVector *>(this)->begin();
}
size_t *ChildIndexVector::end() {
return begin() + m_size;
}
const size_t *ChildIndexVector::end() const {
return const_cast<ChildIndexVector *>(this)->end();
}
void ChildIndexVector::erase(size_t *it) {
BOOST_ASSERT(it - begin() >= 0);
std::memmove(it - 1, it, end() - it);
--m_size;
}
void ChildIndexVector::push_back(size_t v) {
if (m_size == N && !m_childIndexes) {
m_childIndexes = new std::size_t[N * 2];
m_capcity = N * 2;
std::memcpy(m_childIndexes, m_staticChildIndexes, N * sizeof(std::size_t));
} else if (m_childIndexes && m_size == m_capcity) {
auto *tmp = new std::size_t[m_capcity * 2];
std::memcpy(tmp, m_childIndexes, m_capcity * sizeof(std::size_t));
delete[] m_childIndexes;
m_childIndexes = tmp;
m_capcity = m_capcity * 2;
}
begin()[m_size++] = v;
}