use original code of router.
This commit is contained in:
parent
c1b636acee
commit
0edab61c09
@ -1,3 +1,7 @@
|
|||||||
|
project(HttpProxy
|
||||||
|
DESCRIPTION "router api is copy of boost_1_84_0/libs/url/example/router"
|
||||||
|
)
|
||||||
|
|
||||||
find_package(Boost COMPONENTS url REQUIRED)
|
find_package(Boost COMPONENTS url REQUIRED)
|
||||||
|
|
||||||
add_library(HttpProxy
|
add_library(HttpProxy
|
||||||
@ -5,10 +9,9 @@ add_library(HttpProxy
|
|||||||
ProxyHttpSession.h ProxyHttpSession.cpp
|
ProxyHttpSession.h ProxyHttpSession.cpp
|
||||||
ProxyListener.h ProxyListener.cpp
|
ProxyListener.h ProxyListener.cpp
|
||||||
ProxyTcpSession.h ProxyTcpSession.cpp
|
ProxyTcpSession.h ProxyTcpSession.cpp
|
||||||
TemplateMatchs.h TemplateMatchs.cpp
|
|
||||||
TemplateSegmentRule.h TemplateSegmentRule.cpp
|
detail/impl/router.cpp
|
||||||
UrlRouter.h UrlRouter.cpp
|
impl/matches.cpp
|
||||||
UrlRouterPrivate.h UrlRouterPrivate.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(HttpProxy
|
target_include_directories(HttpProxy
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
#include "TemplateMatchs.h"
|
|
||||||
|
|
||||||
TemplateMatchStorageBase::const_reference TemplateMatchStorageBase::at(boost::core::string_view id) const {
|
|
||||||
for (std::size_t i = 0; i < size(); ++i) {
|
|
||||||
if (ids()[i] == id) return matches()[i];
|
|
||||||
}
|
|
||||||
boost::throw_exception(std::out_of_range(""));
|
|
||||||
}
|
|
||||||
|
|
||||||
TemplateMatchStorageBase::const_reference TemplateMatchStorageBase::operator[](boost::core::string_view id) const {
|
|
||||||
return at(id);
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
#ifndef __TEMPLATEMATCHS_H__
|
|
||||||
#define __TEMPLATEMATCHS_H__
|
|
||||||
|
|
||||||
#include <boost/url/string_view.hpp>
|
|
||||||
|
|
||||||
class TemplateMatchStorageBase {
|
|
||||||
public:
|
|
||||||
using const_reference = boost::core::string_view const &;
|
|
||||||
|
|
||||||
virtual boost::core::string_view *matches() = 0;
|
|
||||||
virtual const boost::core::string_view *matches() const = 0;
|
|
||||||
|
|
||||||
virtual boost::core::string_view *ids() = 0;
|
|
||||||
virtual const boost::core::string_view *ids() const = 0;
|
|
||||||
virtual std::size_t size() const = 0;
|
|
||||||
virtual void resize(std::size_t) = 0;
|
|
||||||
|
|
||||||
const_reference at(boost::core::string_view id) const;
|
|
||||||
|
|
||||||
const_reference operator[](boost::core::string_view id) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <std::size_t N = 20>
|
|
||||||
class TemplateMatchStorage : public TemplateMatchStorageBase {
|
|
||||||
public:
|
|
||||||
boost::core::string_view *matches() final {
|
|
||||||
return m_matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual const boost::core::string_view *matches() const final {
|
|
||||||
return m_matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::core::string_view *ids() final {
|
|
||||||
return m_ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
const boost::core::string_view *ids() const final {
|
|
||||||
return m_ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t size() const final {
|
|
||||||
return m_size;
|
|
||||||
}
|
|
||||||
void resize(std::size_t n) final {
|
|
||||||
m_size = n;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
boost::core::string_view m_matches[N];
|
|
||||||
boost::core::string_view m_ids[N];
|
|
||||||
std::size_t m_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
using TemplateMatches = TemplateMatchStorage<20>;
|
|
||||||
|
|
||||||
#endif // __TEMPLATEMATCHS_H__
|
|
@ -1,96 +0,0 @@
|
|||||||
#include "TemplateSegmentRule.h"
|
|
||||||
#include "BoostLog.h"
|
|
||||||
#include <boost/url/decode_view.hpp>
|
|
||||||
#include <boost/url/detail/replacement_field_rule.hpp>
|
|
||||||
#include <boost/url/error.hpp>
|
|
||||||
#include <boost/url/grammar.hpp>
|
|
||||||
#include <boost/url/rfc/detail/path_rules.hpp>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
boost::system::result<TemplateSegmentRule::value_type> TemplateSegmentRule::parse(const char *&iterator,
|
|
||||||
const char *end) const noexcept {
|
|
||||||
TemplateSegmentRule::value_type ret;
|
|
||||||
bool isTemplate = false;
|
|
||||||
if (iterator != end && *iterator == '{') {
|
|
||||||
auto it0 = iterator;
|
|
||||||
++iterator;
|
|
||||||
auto rightBraces = boost::urls::grammar::find_if(iterator, end, boost::urls::grammar::lut_chars('}'));
|
|
||||||
if (rightBraces != end) {
|
|
||||||
boost::core::string_view segment(iterator, rightBraces);
|
|
||||||
static constexpr auto modifiers_cs = boost::urls::grammar::lut_chars("?*+");
|
|
||||||
static constexpr auto id_rule = boost::urls::grammar::tuple_rule(
|
|
||||||
boost::urls::grammar::optional_rule(boost::urls::detail::arg_id_rule),
|
|
||||||
boost::urls::grammar::optional_rule(boost::urls::grammar::delim_rule(modifiers_cs)));
|
|
||||||
if (segment.empty() || boost::urls::grammar::parse(segment, id_rule)) {
|
|
||||||
isTemplate = true;
|
|
||||||
iterator = rightBraces + 1;
|
|
||||||
ret.m_string = boost::core::string_view(it0, rightBraces + 1);
|
|
||||||
ret.m_isLiteral = false;
|
|
||||||
if (segment.ends_with('?'))
|
|
||||||
ret.modifier = TemplateSegment::Modifier::Optional;
|
|
||||||
else if (segment.ends_with('*'))
|
|
||||||
ret.modifier = TemplateSegment::Modifier::Star;
|
|
||||||
else if (segment.ends_with('+'))
|
|
||||||
ret.modifier = TemplateSegment::Modifier::Plus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isTemplate) iterator = it0;
|
|
||||||
}
|
|
||||||
if (!isTemplate) {
|
|
||||||
auto rv = boost::urls::grammar::parse(iterator, end, boost::urls::detail::segment_rule);
|
|
||||||
BOOST_ASSERT(rv);
|
|
||||||
rv->decode({}, boost::urls::string_token::assign_to(ret.m_string));
|
|
||||||
ret.m_isLiteral = true;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TemplateSegment::isLiteral() const {
|
|
||||||
return m_isLiteral;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TemplateSegment::isStar() const {
|
|
||||||
return modifier == Modifier::Star;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TemplateSegment::isPlus() const {
|
|
||||||
return modifier == Modifier::Plus;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TemplateSegment::isOptional() const {
|
|
||||||
return modifier == Modifier::Optional;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TemplateSegment::hasModifier() const {
|
|
||||||
return !m_isLiteral && modifier != Modifier::None;
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::core::string_view TemplateSegment::id() const {
|
|
||||||
BOOST_ASSERT(!isLiteral());
|
|
||||||
boost::core::string_view r = {m_string};
|
|
||||||
r.remove_prefix(1);
|
|
||||||
r.remove_suffix(1);
|
|
||||||
if (r.ends_with('?') || r.ends_with('+') || r.ends_with('*')) r.remove_suffix(1);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::core::string_view TemplateSegment::string() const {
|
|
||||||
return m_string;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TemplateSegment::match(boost::urls::pct_string_view segement) const {
|
|
||||||
if (m_isLiteral) return *segement == m_string;
|
|
||||||
return true; // other nodes match any string
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TemplateSegment::operator==(const TemplateSegment &other) const {
|
|
||||||
if (m_isLiteral != other.m_isLiteral) return false;
|
|
||||||
if (m_isLiteral) return m_string == other.m_string;
|
|
||||||
return modifier == other.modifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TemplateSegment::operator<(const TemplateSegment &other) const {
|
|
||||||
if (other.m_isLiteral) return false;
|
|
||||||
if (m_isLiteral) return !other.m_isLiteral;
|
|
||||||
return modifier < other.modifier;
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
#ifndef __TEMPLATESEGMENTRULE_H__
|
|
||||||
#define __TEMPLATESEGMENTRULE_H__
|
|
||||||
|
|
||||||
#include <boost/url/error_types.hpp>
|
|
||||||
#include <boost/url/grammar/delim_rule.hpp>
|
|
||||||
#include <boost/url/grammar/optional_rule.hpp>
|
|
||||||
#include <boost/url/grammar/range_rule.hpp>
|
|
||||||
#include <boost/url/grammar/tuple_rule.hpp>
|
|
||||||
#include <boost/url/pct_string_view.hpp>
|
|
||||||
#include <boost/url/string_view.hpp>
|
|
||||||
|
|
||||||
class TemplateSegment {
|
|
||||||
friend class TemplateSegmentRule;
|
|
||||||
|
|
||||||
public:
|
|
||||||
enum class Modifier {
|
|
||||||
None,
|
|
||||||
Optional, // {id?}
|
|
||||||
Star, // {id*}
|
|
||||||
Plus // {id+}
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
bool isLiteral() const;
|
|
||||||
bool isStar() const;
|
|
||||||
bool isPlus() const;
|
|
||||||
bool isOptional() const;
|
|
||||||
bool hasModifier() const;
|
|
||||||
boost::core::string_view id() const;
|
|
||||||
boost::core::string_view string() const;
|
|
||||||
bool match(boost::urls::pct_string_view segement) const;
|
|
||||||
bool operator==(const TemplateSegment &other) const;
|
|
||||||
|
|
||||||
// segments have precedence:
|
|
||||||
// - literal
|
|
||||||
// - unique
|
|
||||||
// - optional
|
|
||||||
// - plus
|
|
||||||
// - star
|
|
||||||
bool operator<(const TemplateSegment &other) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Modifier modifier = Modifier::None;
|
|
||||||
std::string m_string;
|
|
||||||
bool m_isLiteral = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
class TemplateSegmentRule {
|
|
||||||
public:
|
|
||||||
using value_type = TemplateSegment;
|
|
||||||
boost::system::result<value_type> parse(char const *&iterator, char const *end) const noexcept;
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr auto templateSegmentRule = TemplateSegmentRule{};
|
|
||||||
|
|
||||||
constexpr auto templatePathRule = boost::urls::grammar::tuple_rule(
|
|
||||||
boost::urls::grammar::squelch(boost::urls::grammar::optional_rule(boost::urls::grammar::delim_rule('/'))),
|
|
||||||
boost::urls::grammar::range_rule(
|
|
||||||
templateSegmentRule,
|
|
||||||
boost::urls::grammar::tuple_rule(boost::urls::grammar::squelch(boost::urls::grammar::delim_rule('/')),
|
|
||||||
templateSegmentRule)));
|
|
||||||
|
|
||||||
#endif // __TEMPLATESEGMENTRULE_H__
|
|
@ -1 +0,0 @@
|
|||||||
#include "UrlRouter.h"
|
|
@ -1,47 +0,0 @@
|
|||||||
#ifndef __URLROUTER_H__
|
|
||||||
#define __URLROUTER_H__
|
|
||||||
|
|
||||||
#include "TemplateMatchs.h"
|
|
||||||
#include "UrlRouterPrivate.h"
|
|
||||||
#include <boost/url/segments_encoded_view.hpp>
|
|
||||||
|
|
||||||
template <typename Resource>
|
|
||||||
class UrlRouter : public UrlRouterPrivate {
|
|
||||||
public:
|
|
||||||
template <class U>
|
|
||||||
void insert(std::string_view pattern, U &&v) {
|
|
||||||
BOOST_STATIC_ASSERT(std::is_same<Resource, U>::value || std::is_convertible<U, Resource>::value ||
|
|
||||||
std::is_base_of<Resource, U>::value);
|
|
||||||
using ResourceType =
|
|
||||||
typename std::decay_t<typename std::conditional_t<std::is_base_of_v<Resource, U>, U, Resource>>;
|
|
||||||
|
|
||||||
class Implementation : public AnyResource {
|
|
||||||
public:
|
|
||||||
explicit Implementation(U &&u_) : resource(std::forward<U>(u_)) {
|
|
||||||
}
|
|
||||||
void const *get() const noexcept override {
|
|
||||||
return static_cast<Resource const *>(&resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
ResourceType resource;
|
|
||||||
};
|
|
||||||
auto resource = std::make_shared<Implementation>(std::forward<U>(v));
|
|
||||||
insertImpl(pattern, std::dynamic_pointer_cast<AnyResource>(resource));
|
|
||||||
}
|
|
||||||
|
|
||||||
const Resource *find(boost::urls::segments_encoded_view path, TemplateMatchStorageBase &matches) const noexcept {
|
|
||||||
boost::core::string_view *matches_it = matches.matches();
|
|
||||||
boost::core::string_view *ids_it = matches.ids();
|
|
||||||
AnyResource const *p = findImpl(path, matches_it, ids_it);
|
|
||||||
if (p) {
|
|
||||||
BOOST_ASSERT(matches_it >= matches.matches());
|
|
||||||
matches.resize(static_cast<std::size_t>(matches_it - matches.matches()));
|
|
||||||
return reinterpret_cast<Resource const *>(p->get());
|
|
||||||
}
|
|
||||||
matches.resize(0);
|
|
||||||
return nullptr;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // __URLROUTER_H__
|
|
@ -1,323 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
#ifndef __URLROUTERPRIVATE_H__
|
|
||||||
#define __URLROUTERPRIVATE_H__
|
|
||||||
|
|
||||||
#include "TemplateSegmentRule.h"
|
|
||||||
#include <boost/url/segments_encoded_view.hpp>
|
|
||||||
#include <boost/url/string_view.hpp>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class AnyResource {
|
|
||||||
public:
|
|
||||||
virtual ~AnyResource() = default;
|
|
||||||
virtual void const *get() const noexcept = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ChildIndexVector {
|
|
||||||
static constexpr std::size_t N = 5;
|
|
||||||
|
|
||||||
public:
|
|
||||||
bool empty() const;
|
|
||||||
std::size_t size() const;
|
|
||||||
std::size_t *begin();
|
|
||||||
const std::size_t *begin() const;
|
|
||||||
std::size_t *end();
|
|
||||||
const std::size_t *end() const;
|
|
||||||
void erase(std::size_t *it);
|
|
||||||
void push_back(std::size_t v);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::size_t m_capcity{0};
|
|
||||||
std::size_t m_size{0};
|
|
||||||
std::size_t *m_childIndexes{nullptr};
|
|
||||||
std::size_t m_staticChildIndexes[N]{};
|
|
||||||
};
|
|
||||||
|
|
||||||
class SegementNode {
|
|
||||||
public:
|
|
||||||
static constexpr std::size_t npos{std::size_t(-1)};
|
|
||||||
std::size_t parentIndex{npos};
|
|
||||||
ChildIndexVector childIndexes;
|
|
||||||
TemplateSegment segment;
|
|
||||||
std::string templatePath;
|
|
||||||
std::shared_ptr<AnyResource> resource;
|
|
||||||
};
|
|
||||||
|
|
||||||
class UrlRouterPrivate {
|
|
||||||
public:
|
|
||||||
UrlRouterPrivate();
|
|
||||||
void insertImpl(boost::core::string_view pattern, const std::shared_ptr<AnyResource> &resource);
|
|
||||||
const AnyResource *findImpl(boost::urls::segments_encoded_view path, boost::core::string_view *&matches,
|
|
||||||
boost::core::string_view *&ids) const noexcept;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
SegementNode const *tryMatch(boost::urls::segments_encoded_view::const_iterator it,
|
|
||||||
boost::urls::segments_encoded_view::const_iterator end, const SegementNode *current,
|
|
||||||
int level, boost::core::string_view *&matches, boost::core::string_view *&ids) const;
|
|
||||||
static SegementNode const *findOptionalResource(const SegementNode *root, std::vector<SegementNode> const &ns,
|
|
||||||
boost::core::string_view *&matches, boost::core::string_view *&ids);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<SegementNode> m_nodes;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // __URLROUTERPRIVATE_H__
|
|
949
HttpProxy/detail/impl/router.cpp
Normal file
949
HttpProxy/detail/impl/router.cpp
Normal file
@ -0,0 +1,949 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2023 Alan de Freitas (alandefreitas@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||||
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||||
|
//
|
||||||
|
// Official repository: https://github.com/boostorg/url
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef BOOST_URL_DETAIL_ROUTER_IPP
|
||||||
|
#define BOOST_URL_DETAIL_ROUTER_IPP
|
||||||
|
|
||||||
|
#include "../router.hpp"
|
||||||
|
#include <boost/url/decode_view.hpp>
|
||||||
|
#include <boost/url/grammar/alnum_chars.hpp>
|
||||||
|
#include <boost/url/grammar/alpha_chars.hpp>
|
||||||
|
#include <boost/url/grammar/lut_chars.hpp>
|
||||||
|
#include <boost/url/grammar/token_rule.hpp>
|
||||||
|
#include <boost/url/grammar/variant_rule.hpp>
|
||||||
|
#include <boost/url/rfc/detail/path_rules.hpp>
|
||||||
|
#include <boost/url/detail/replacement_field_rule.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace boost {
|
||||||
|
namespace urls {
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
// A path segment template
|
||||||
|
class segment_template
|
||||||
|
{
|
||||||
|
enum class modifier : unsigned char
|
||||||
|
{
|
||||||
|
none,
|
||||||
|
// {id?}
|
||||||
|
optional,
|
||||||
|
// {id*}
|
||||||
|
star,
|
||||||
|
// {id+}
|
||||||
|
plus
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string str_;
|
||||||
|
bool is_literal_ = true;
|
||||||
|
modifier modifier_ = modifier::none;
|
||||||
|
|
||||||
|
friend struct segment_template_rule_t;
|
||||||
|
public:
|
||||||
|
segment_template() = default;
|
||||||
|
|
||||||
|
bool
|
||||||
|
match(pct_string_view seg) const;
|
||||||
|
|
||||||
|
core::string_view
|
||||||
|
string() const
|
||||||
|
{
|
||||||
|
return str_;
|
||||||
|
}
|
||||||
|
|
||||||
|
core::string_view
|
||||||
|
id() const;
|
||||||
|
|
||||||
|
bool
|
||||||
|
empty() const
|
||||||
|
{
|
||||||
|
return str_.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
is_literal() const
|
||||||
|
{
|
||||||
|
return is_literal_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
has_modifier() const
|
||||||
|
{
|
||||||
|
return !is_literal() &&
|
||||||
|
modifier_ != modifier::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
is_optional() const
|
||||||
|
{
|
||||||
|
return modifier_ == modifier::optional;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
is_star() const
|
||||||
|
{
|
||||||
|
return modifier_ == modifier::star;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
is_plus() const
|
||||||
|
{
|
||||||
|
return modifier_ == modifier::plus;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend
|
||||||
|
bool operator==(
|
||||||
|
segment_template const& a,
|
||||||
|
segment_template const& b)
|
||||||
|
{
|
||||||
|
if (a.is_literal_ != b.is_literal_)
|
||||||
|
return false;
|
||||||
|
if (a.is_literal_)
|
||||||
|
return a.str_ == b.str_;
|
||||||
|
return a.modifier_ == b.modifier_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// segments have precedence:
|
||||||
|
// - literal
|
||||||
|
// - unique
|
||||||
|
// - optional
|
||||||
|
// - plus
|
||||||
|
// - star
|
||||||
|
friend
|
||||||
|
bool operator<(
|
||||||
|
segment_template const& a,
|
||||||
|
segment_template const& b)
|
||||||
|
{
|
||||||
|
if (b.is_literal())
|
||||||
|
return false;
|
||||||
|
if (a.is_literal())
|
||||||
|
return !b.is_literal();
|
||||||
|
return a.modifier_ < b.modifier_;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// A segment template is either a literal string
|
||||||
|
// or a replacement field (as in a format_string).
|
||||||
|
// Fields cannot contain format specs and might
|
||||||
|
// have one of the following modifiers:
|
||||||
|
// - ?: optional segment
|
||||||
|
// - *: zero or more segments
|
||||||
|
// - +: one or more segments
|
||||||
|
struct segment_template_rule_t
|
||||||
|
{
|
||||||
|
using value_type = segment_template;
|
||||||
|
|
||||||
|
system::result<value_type>
|
||||||
|
parse(
|
||||||
|
char const*& it,
|
||||||
|
char const* end
|
||||||
|
) const noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr auto segment_template_rule = segment_template_rule_t{};
|
||||||
|
|
||||||
|
constexpr auto path_template_rule =
|
||||||
|
grammar::tuple_rule(
|
||||||
|
grammar::squelch(
|
||||||
|
grammar::optional_rule(
|
||||||
|
grammar::delim_rule('/'))),
|
||||||
|
grammar::range_rule(
|
||||||
|
segment_template_rule,
|
||||||
|
grammar::tuple_rule(
|
||||||
|
grammar::squelch(grammar::delim_rule('/')),
|
||||||
|
segment_template_rule)));
|
||||||
|
|
||||||
|
bool
|
||||||
|
segment_template::
|
||||||
|
match(pct_string_view seg) const
|
||||||
|
{
|
||||||
|
if (is_literal_)
|
||||||
|
return *seg == str_;
|
||||||
|
|
||||||
|
// other nodes match any string
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
core::string_view
|
||||||
|
segment_template::
|
||||||
|
id() const
|
||||||
|
{
|
||||||
|
// if (is_literal_) return {};
|
||||||
|
BOOST_ASSERT(!is_literal());
|
||||||
|
core::string_view r = {str_};
|
||||||
|
r.remove_prefix(1);
|
||||||
|
r.remove_suffix(1);
|
||||||
|
if (r.ends_with('?') ||
|
||||||
|
r.ends_with('+') ||
|
||||||
|
r.ends_with('*'))
|
||||||
|
r.remove_suffix(1);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto
|
||||||
|
segment_template_rule_t::
|
||||||
|
parse(
|
||||||
|
char const*& it,
|
||||||
|
char const* end) const noexcept
|
||||||
|
-> system::result<value_type>
|
||||||
|
{
|
||||||
|
segment_template t;
|
||||||
|
if (it != end &&
|
||||||
|
*it == '{')
|
||||||
|
{
|
||||||
|
// replacement field
|
||||||
|
auto it0 = it;
|
||||||
|
++it;
|
||||||
|
auto send =
|
||||||
|
grammar::find_if(
|
||||||
|
it, end, grammar::lut_chars('}'));
|
||||||
|
if (send != end)
|
||||||
|
{
|
||||||
|
core::string_view s(it, send);
|
||||||
|
static constexpr auto modifiers_cs =
|
||||||
|
grammar::lut_chars("?*+");
|
||||||
|
static constexpr auto id_rule =
|
||||||
|
grammar::tuple_rule(
|
||||||
|
grammar::optional_rule(
|
||||||
|
arg_id_rule),
|
||||||
|
grammar::optional_rule(
|
||||||
|
grammar::delim_rule(modifiers_cs)));
|
||||||
|
if (s.empty() ||
|
||||||
|
grammar::parse(s, id_rule))
|
||||||
|
{
|
||||||
|
it = send + 1;
|
||||||
|
|
||||||
|
t.str_ = core::string_view(it0, send + 1);
|
||||||
|
t.is_literal_ = false;
|
||||||
|
if (s.ends_with('?'))
|
||||||
|
t.modifier_ =
|
||||||
|
segment_template::modifier::optional;
|
||||||
|
else if (s.ends_with('*'))
|
||||||
|
t.modifier_ =
|
||||||
|
segment_template::modifier::star;
|
||||||
|
else if (s.ends_with('+'))
|
||||||
|
t.modifier_ =
|
||||||
|
segment_template::modifier::plus;
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it = it0;
|
||||||
|
}
|
||||||
|
// literal segment
|
||||||
|
auto rv = grammar::parse(
|
||||||
|
it, end, urls::detail::segment_rule);
|
||||||
|
BOOST_ASSERT(rv);
|
||||||
|
rv->decode({}, urls::string_token::assign_to(t.str_));
|
||||||
|
t.is_literal_ = true;
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a small vector for child nodes...
|
||||||
|
// we shouldn't expect many children per node, and
|
||||||
|
// we don't want to allocate for that. But we also
|
||||||
|
// cannot cap the max number of child nodes because
|
||||||
|
// especially the root nodes can potentially an
|
||||||
|
// exponentially higher number of child nodes.
|
||||||
|
class child_idx_vector
|
||||||
|
{
|
||||||
|
static constexpr std::size_t N = 5;
|
||||||
|
std::size_t static_child_idx_[N]{};
|
||||||
|
std::size_t* child_idx{nullptr};
|
||||||
|
std::size_t size_{0};
|
||||||
|
std::size_t cap_{0};
|
||||||
|
|
||||||
|
public:
|
||||||
|
~child_idx_vector()
|
||||||
|
{
|
||||||
|
delete[] child_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
child_idx_vector() = default;
|
||||||
|
|
||||||
|
child_idx_vector(child_idx_vector const& other)
|
||||||
|
: size_{other.size_}
|
||||||
|
, cap_{other.cap_}
|
||||||
|
{
|
||||||
|
if (other.child_idx)
|
||||||
|
{
|
||||||
|
child_idx = new std::size_t[cap_];
|
||||||
|
std::memcpy(child_idx, other.child_idx, size_ * sizeof(std::size_t));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::memcpy(static_child_idx_, other.static_child_idx_, size_ * sizeof(std::size_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
child_idx_vector(child_idx_vector&& other)
|
||||||
|
: child_idx{other.child_idx}
|
||||||
|
, size_{other.size_}
|
||||||
|
, cap_{other.cap_}
|
||||||
|
{
|
||||||
|
std::memcpy(static_child_idx_, other.static_child_idx_, N);
|
||||||
|
other.child_idx = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
empty() const
|
||||||
|
{
|
||||||
|
return size_ == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t
|
||||||
|
size() const
|
||||||
|
{
|
||||||
|
return size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t*
|
||||||
|
begin()
|
||||||
|
{
|
||||||
|
if (child_idx)
|
||||||
|
return child_idx;
|
||||||
|
return static_child_idx_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t*
|
||||||
|
end()
|
||||||
|
{
|
||||||
|
return begin() + size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t const*
|
||||||
|
begin() const
|
||||||
|
{
|
||||||
|
if (child_idx)
|
||||||
|
return child_idx;
|
||||||
|
return static_child_idx_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t const*
|
||||||
|
end() const
|
||||||
|
{
|
||||||
|
return begin() + size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
erase(std::size_t* it)
|
||||||
|
{
|
||||||
|
BOOST_ASSERT(it - begin() >= 0);
|
||||||
|
std::memmove(it - 1, it, end() - it);
|
||||||
|
--size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
push_back(std::size_t v)
|
||||||
|
{
|
||||||
|
if (size_ == N && !child_idx)
|
||||||
|
{
|
||||||
|
child_idx = new std::size_t[N*2];
|
||||||
|
cap_ = N*2;
|
||||||
|
std::memcpy(child_idx, static_child_idx_, N * sizeof(std::size_t));
|
||||||
|
}
|
||||||
|
else if (child_idx && size_ == cap_)
|
||||||
|
{
|
||||||
|
auto* tmp = new std::size_t[cap_*2];
|
||||||
|
std::memcpy(tmp, child_idx, cap_ * sizeof(std::size_t));
|
||||||
|
delete[] child_idx;
|
||||||
|
child_idx = tmp;
|
||||||
|
cap_ = cap_*2;
|
||||||
|
}
|
||||||
|
begin()[size_++] = v;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// A node in the resource tree
|
||||||
|
// Each segment in the resource tree might be
|
||||||
|
// associated with
|
||||||
|
struct node
|
||||||
|
{
|
||||||
|
static constexpr std::size_t npos{std::size_t(-1)};
|
||||||
|
|
||||||
|
// literal segment or replacement field
|
||||||
|
detail::segment_template seg{};
|
||||||
|
|
||||||
|
// A pointer to the resource
|
||||||
|
router_base::any_resource const* resource{nullptr};
|
||||||
|
|
||||||
|
// The complete match for the resource
|
||||||
|
std::string path_template;
|
||||||
|
|
||||||
|
// Index of the parent node in the
|
||||||
|
// implementation pool of nodes
|
||||||
|
std::size_t parent_idx{npos};
|
||||||
|
|
||||||
|
// Index of child nodes in the pool
|
||||||
|
detail::child_idx_vector child_idx;
|
||||||
|
};
|
||||||
|
|
||||||
|
class impl
|
||||||
|
{
|
||||||
|
// Pool of nodes in the resource tree
|
||||||
|
std::vector<node> nodes_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
impl()
|
||||||
|
{
|
||||||
|
// root node with no resource
|
||||||
|
nodes_.push_back(node{});
|
||||||
|
}
|
||||||
|
|
||||||
|
~impl()
|
||||||
|
{
|
||||||
|
for (auto &r: nodes_)
|
||||||
|
delete r.resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
// include a node for a resource
|
||||||
|
void
|
||||||
|
insert_impl(
|
||||||
|
core::string_view path,
|
||||||
|
router_base::any_resource const* v);
|
||||||
|
|
||||||
|
// match a node and return the element
|
||||||
|
router_base::any_resource const*
|
||||||
|
find_impl(
|
||||||
|
segments_encoded_view path,
|
||||||
|
core::string_view*& matches,
|
||||||
|
core::string_view*& ids) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// try to match from this root node
|
||||||
|
node const*
|
||||||
|
try_match(
|
||||||
|
segments_encoded_view::const_iterator it,
|
||||||
|
segments_encoded_view::const_iterator end,
|
||||||
|
node const* root,
|
||||||
|
int level,
|
||||||
|
core::string_view*& matches,
|
||||||
|
core::string_view*& ids) const;
|
||||||
|
|
||||||
|
// check if a node has a resource when we
|
||||||
|
// also consider optional paths through
|
||||||
|
// the child nodes.
|
||||||
|
static
|
||||||
|
node const*
|
||||||
|
find_optional_resource(
|
||||||
|
const node* root,
|
||||||
|
std::vector<node> const& ns,
|
||||||
|
core::string_view*& matches,
|
||||||
|
core::string_view*& ids);
|
||||||
|
};
|
||||||
|
|
||||||
|
node const*
|
||||||
|
impl::
|
||||||
|
find_optional_resource(
|
||||||
|
const node* root,
|
||||||
|
std::vector<node> const& ns,
|
||||||
|
core::string_view*& matches,
|
||||||
|
core::string_view*& ids)
|
||||||
|
{
|
||||||
|
BOOST_ASSERT(root);
|
||||||
|
if (root->resource)
|
||||||
|
return root;
|
||||||
|
BOOST_ASSERT(!root->child_idx.empty());
|
||||||
|
for (auto i: root->child_idx)
|
||||||
|
{
|
||||||
|
auto& c = ns[i];
|
||||||
|
if (!c.seg.is_optional() &&
|
||||||
|
!c.seg.is_star())
|
||||||
|
continue;
|
||||||
|
// Child nodes are also
|
||||||
|
// potentially optional.
|
||||||
|
auto matches0 = matches;
|
||||||
|
auto ids0 = ids;
|
||||||
|
*matches++ = {};
|
||||||
|
*ids++ = c.seg.id();
|
||||||
|
auto n = find_optional_resource(
|
||||||
|
&c, ns, matches, ids);
|
||||||
|
if (n)
|
||||||
|
return n;
|
||||||
|
matches = matches0;
|
||||||
|
ids = ids0;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
impl::
|
||||||
|
insert_impl(
|
||||||
|
core::string_view path,
|
||||||
|
router_base::any_resource const* v)
|
||||||
|
{
|
||||||
|
// Parse dynamic route segments
|
||||||
|
if (path.starts_with("/"))
|
||||||
|
path.remove_prefix(1);
|
||||||
|
auto segsr =
|
||||||
|
grammar::parse(path, detail::path_template_rule);
|
||||||
|
if (!segsr)
|
||||||
|
{
|
||||||
|
delete v;
|
||||||
|
segsr.value();
|
||||||
|
}
|
||||||
|
auto segs = *segsr;
|
||||||
|
auto it = segs.begin();
|
||||||
|
auto end = segs.end();
|
||||||
|
|
||||||
|
// Iterate existing nodes
|
||||||
|
node* cur = &nodes_.front();
|
||||||
|
int level = 0;
|
||||||
|
while (it != end)
|
||||||
|
{
|
||||||
|
core::string_view seg = (*it).string();
|
||||||
|
if (seg == ".")
|
||||||
|
{
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (seg == "..")
|
||||||
|
{
|
||||||
|
// discount unmatched leaf or
|
||||||
|
// keep track of levels behind root
|
||||||
|
if (cur == &nodes_.front())
|
||||||
|
{
|
||||||
|
--level;
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// move to parent deleting current
|
||||||
|
// if it carries no resource
|
||||||
|
std::size_t p_idx = cur->parent_idx;
|
||||||
|
if (cur == &nodes_.back() &&
|
||||||
|
!cur->resource &&
|
||||||
|
cur->child_idx.empty())
|
||||||
|
{
|
||||||
|
node* p = &nodes_[p_idx];
|
||||||
|
std::size_t cur_idx = cur - nodes_.data();
|
||||||
|
p->child_idx.erase(
|
||||||
|
std::remove(
|
||||||
|
p->child_idx.begin(),
|
||||||
|
p->child_idx.end(),
|
||||||
|
cur_idx));
|
||||||
|
nodes_.pop_back();
|
||||||
|
}
|
||||||
|
cur = &nodes_[p_idx];
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// discount unmatched root parent
|
||||||
|
if (level < 0)
|
||||||
|
{
|
||||||
|
++level;
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// look for child
|
||||||
|
auto cit = std::find_if(
|
||||||
|
cur->child_idx.begin(),
|
||||||
|
cur->child_idx.end(),
|
||||||
|
[this, &it](std::size_t ci) -> bool
|
||||||
|
{
|
||||||
|
return nodes_[ci].seg == *it;
|
||||||
|
});
|
||||||
|
if (cit != cur->child_idx.end())
|
||||||
|
{
|
||||||
|
// move to existing child
|
||||||
|
cur = &nodes_[*cit];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// create child if it doesn't exist
|
||||||
|
node child;
|
||||||
|
child.seg = *it;
|
||||||
|
std::size_t cur_id = cur - nodes_.data();
|
||||||
|
child.parent_idx = cur_id;
|
||||||
|
nodes_.push_back(std::move(child));
|
||||||
|
nodes_[cur_id].child_idx.push_back(nodes_.size() - 1);
|
||||||
|
if (nodes_[cur_id].child_idx.size() > 1)
|
||||||
|
{
|
||||||
|
// keep nodes sorted
|
||||||
|
auto& cs = nodes_[cur_id].child_idx;
|
||||||
|
std::size_t n = cs.size() - 1;
|
||||||
|
while (n)
|
||||||
|
{
|
||||||
|
if (nodes_[cs.begin()[n]].seg < nodes_[cs.begin()[n - 1]].seg)
|
||||||
|
std::swap(cs.begin()[n], cs.begin()[n - 1]);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
--n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cur = &nodes_.back();
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
if (level != 0)
|
||||||
|
{
|
||||||
|
delete v;
|
||||||
|
urls::detail::throw_invalid_argument();
|
||||||
|
}
|
||||||
|
cur->resource = v;
|
||||||
|
cur->path_template = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
node const*
|
||||||
|
impl::
|
||||||
|
try_match(
|
||||||
|
segments_encoded_view::const_iterator it,
|
||||||
|
segments_encoded_view::const_iterator end,
|
||||||
|
node const* cur,
|
||||||
|
int level,
|
||||||
|
core::string_view*& matches,
|
||||||
|
core::string_view*& ids) const
|
||||||
|
{
|
||||||
|
while (it != end)
|
||||||
|
{
|
||||||
|
pct_string_view s = *it;
|
||||||
|
if (*s == ".")
|
||||||
|
{
|
||||||
|
// ignore segment
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (*s == "..")
|
||||||
|
{
|
||||||
|
|
||||||
|
// move back to the parent node
|
||||||
|
++it;
|
||||||
|
if (level <= 0 &&
|
||||||
|
cur != &nodes_.front())
|
||||||
|
{
|
||||||
|
if (!cur->seg.is_literal())
|
||||||
|
{
|
||||||
|
--matches;
|
||||||
|
--ids;
|
||||||
|
}
|
||||||
|
cur = &nodes_[cur->parent_idx];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// there's no parent, so we
|
||||||
|
// discount that from the implicit
|
||||||
|
// tree beyond terminals
|
||||||
|
--level;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we are in the implicit tree above the
|
||||||
|
// root, so discount that as a level
|
||||||
|
if (level < 0)
|
||||||
|
{
|
||||||
|
++level;
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the lower bound on the
|
||||||
|
// possible number of branches to
|
||||||
|
// determine if we need to branch.
|
||||||
|
// We branch when we might have more than
|
||||||
|
// one child matching node at this level.
|
||||||
|
// If so, we need to potentially branch
|
||||||
|
// to find which path leads to a valid
|
||||||
|
// resource. Otherwise, we can just
|
||||||
|
// consume the node and input without
|
||||||
|
// any recursive function calls.
|
||||||
|
bool branch = false;
|
||||||
|
if (cur->child_idx.size() > 1)
|
||||||
|
{
|
||||||
|
int branches_lb = 0;
|
||||||
|
for (auto i: cur->child_idx)
|
||||||
|
{
|
||||||
|
auto& c = nodes_[i];
|
||||||
|
if (c.seg.is_literal() ||
|
||||||
|
!c.seg.has_modifier())
|
||||||
|
{
|
||||||
|
// a literal path counts only
|
||||||
|
// if it matches
|
||||||
|
branches_lb += c.seg.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt to match each child node
|
||||||
|
node const* r = nullptr;
|
||||||
|
bool match_any = false;
|
||||||
|
for (auto i: cur->child_idx)
|
||||||
|
{
|
||||||
|
auto& c = nodes_[i];
|
||||||
|
if (c.seg.match(s))
|
||||||
|
{
|
||||||
|
if (c.seg.is_literal())
|
||||||
|
{
|
||||||
|
// just continue from the
|
||||||
|
// next segment
|
||||||
|
if (branch)
|
||||||
|
{
|
||||||
|
r = try_match(
|
||||||
|
std::next(it), end,
|
||||||
|
&c, level,
|
||||||
|
matches, ids);
|
||||||
|
if (r)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cur = &c;
|
||||||
|
match_any = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!c.seg.has_modifier())
|
||||||
|
{
|
||||||
|
// just continue from the
|
||||||
|
// next segment
|
||||||
|
if (branch)
|
||||||
|
{
|
||||||
|
auto matches0 = matches;
|
||||||
|
auto ids0 = ids;
|
||||||
|
*matches++ = *it;
|
||||||
|
*ids++ = c.seg.id();
|
||||||
|
r = try_match(
|
||||||
|
std::next(it), end, &c,
|
||||||
|
level, matches, ids);
|
||||||
|
if (r)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// rewind
|
||||||
|
matches = matches0;
|
||||||
|
ids = ids0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// only path possible
|
||||||
|
*matches++ = *it;
|
||||||
|
*ids++ = c.seg.id();
|
||||||
|
cur = &c;
|
||||||
|
match_any = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c.seg.is_optional())
|
||||||
|
{
|
||||||
|
// attempt to match by ignoring
|
||||||
|
// and not ignoring the segment.
|
||||||
|
// we first try the complete
|
||||||
|
// continuation consuming the
|
||||||
|
// input, which is the
|
||||||
|
// longest and most likely
|
||||||
|
// match
|
||||||
|
auto matches0 = matches;
|
||||||
|
auto ids0 = ids;
|
||||||
|
*matches++ = *it;
|
||||||
|
*ids++ = c.seg.id();
|
||||||
|
r = try_match(
|
||||||
|
std::next(it), end,
|
||||||
|
&c, level, matches, ids);
|
||||||
|
if (r)
|
||||||
|
break;
|
||||||
|
// rewind
|
||||||
|
matches = matches0;
|
||||||
|
ids = ids0;
|
||||||
|
// try complete continuation
|
||||||
|
// consuming no segment
|
||||||
|
*matches++ = {};
|
||||||
|
*ids++ = c.seg.id();
|
||||||
|
r = try_match(
|
||||||
|
it, end, &c,
|
||||||
|
level, matches, ids);
|
||||||
|
if (r)
|
||||||
|
break;
|
||||||
|
// rewind
|
||||||
|
matches = matches0;
|
||||||
|
ids = ids0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// check if the next segments
|
||||||
|
// won't send us to a parent
|
||||||
|
// directory
|
||||||
|
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 + c.seg.is_star()))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (*it1 != ".")
|
||||||
|
{
|
||||||
|
++nnondot;
|
||||||
|
}
|
||||||
|
++it1;
|
||||||
|
}
|
||||||
|
if (it1 != end)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// attempt to match many
|
||||||
|
// segments
|
||||||
|
auto matches0 = matches;
|
||||||
|
auto ids0 = ids;
|
||||||
|
*matches++ = *it;
|
||||||
|
*ids++ = c.seg.id();
|
||||||
|
// if this is a plus seg, we
|
||||||
|
// already consumed the first
|
||||||
|
// segment
|
||||||
|
if (c.seg.is_plus())
|
||||||
|
{
|
||||||
|
++first;
|
||||||
|
}
|
||||||
|
// {*} is usually the last
|
||||||
|
// match in a path.
|
||||||
|
// try complete continuation
|
||||||
|
// match for every subrange
|
||||||
|
// from {last, last} to
|
||||||
|
// {first, last}.
|
||||||
|
// We also try {last, last}
|
||||||
|
// first because it is the
|
||||||
|
// longest match.
|
||||||
|
auto start = end;
|
||||||
|
while (start != first)
|
||||||
|
{
|
||||||
|
r = try_match(
|
||||||
|
start, end, &c,
|
||||||
|
level, matches, ids);
|
||||||
|
if (r)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
// start == first
|
||||||
|
matches = matches0 + 1;
|
||||||
|
ids = ids0 + 1;
|
||||||
|
r = try_match(
|
||||||
|
start, end, &c,
|
||||||
|
level, matches, ids);
|
||||||
|
if (r)
|
||||||
|
{
|
||||||
|
if (!c.seg.is_plus())
|
||||||
|
*matches0 = {};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// r represent we already found a terminal
|
||||||
|
// node which is a match
|
||||||
|
if (r)
|
||||||
|
return r;
|
||||||
|
// if we couldn't match anything, we go
|
||||||
|
// one level up in the implicit tree
|
||||||
|
// because the path might still have a
|
||||||
|
// "..".
|
||||||
|
if (!match_any)
|
||||||
|
++level;
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
if (level != 0)
|
||||||
|
{
|
||||||
|
// the path ended below or above an
|
||||||
|
// existing node
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (!cur->resource)
|
||||||
|
{
|
||||||
|
// we consumed all the input and reached
|
||||||
|
// a node with no resource, but it might
|
||||||
|
// still have child optional segments
|
||||||
|
// with resources we can reach without
|
||||||
|
// consuming any input
|
||||||
|
return find_optional_resource(
|
||||||
|
cur, nodes_, matches, ids);
|
||||||
|
}
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
router_base::any_resource const*
|
||||||
|
impl::
|
||||||
|
find_impl(
|
||||||
|
segments_encoded_view path,
|
||||||
|
core::string_view*& matches,
|
||||||
|
core::string_view*& ids) const
|
||||||
|
{
|
||||||
|
// parse_path is inconsistent for empty paths
|
||||||
|
if (path.empty())
|
||||||
|
path = segments_encoded_view("./");
|
||||||
|
|
||||||
|
// Iterate nodes from the root
|
||||||
|
node const*p = try_match(
|
||||||
|
path.begin(), path.end(),
|
||||||
|
&nodes_.front(), 0,
|
||||||
|
matches, ids);
|
||||||
|
if (p)
|
||||||
|
return p->resource;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
router_base::
|
||||||
|
router_base()
|
||||||
|
: impl_(new impl{}) {}
|
||||||
|
|
||||||
|
router_base::
|
||||||
|
~router_base()
|
||||||
|
{
|
||||||
|
delete reinterpret_cast<impl*>(impl_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
router_base::
|
||||||
|
insert_impl(
|
||||||
|
core::string_view s,
|
||||||
|
any_resource const* v)
|
||||||
|
{
|
||||||
|
reinterpret_cast<impl*>(impl_)
|
||||||
|
->insert_impl(s, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto
|
||||||
|
router_base::
|
||||||
|
find_impl(
|
||||||
|
segments_encoded_view path,
|
||||||
|
core::string_view*& matches,
|
||||||
|
core::string_view*& ids) const noexcept
|
||||||
|
-> any_resource const*
|
||||||
|
{
|
||||||
|
return reinterpret_cast<impl*>(impl_)
|
||||||
|
->find_impl(path, matches, ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // detail
|
||||||
|
} // urls
|
||||||
|
} // boost
|
||||||
|
|
||||||
|
#endif
|
58
HttpProxy/detail/router.hpp
Normal file
58
HttpProxy/detail/router.hpp
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||||
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||||
|
//
|
||||||
|
// Official repository: https://github.com/boostorg/url
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef BOOST_URL_DETAIL_ROUTER_HPP
|
||||||
|
#define BOOST_URL_DETAIL_ROUTER_HPP
|
||||||
|
|
||||||
|
#include <boost/url/pct_string_view.hpp>
|
||||||
|
#include <boost/url/segments_encoded_view.hpp>
|
||||||
|
#include <boost/url/grammar/delim_rule.hpp>
|
||||||
|
#include <boost/url/grammar/optional_rule.hpp>
|
||||||
|
#include <boost/url/grammar/range_rule.hpp>
|
||||||
|
#include <boost/url/grammar/tuple_rule.hpp>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace boost {
|
||||||
|
namespace urls {
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
class router_base
|
||||||
|
{
|
||||||
|
void* impl_{nullptr};
|
||||||
|
|
||||||
|
public:
|
||||||
|
// A type-erased router resource
|
||||||
|
struct any_resource
|
||||||
|
{
|
||||||
|
virtual ~any_resource() = default;
|
||||||
|
virtual void const* get() const noexcept = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
router_base();
|
||||||
|
|
||||||
|
virtual ~router_base();
|
||||||
|
|
||||||
|
void
|
||||||
|
insert_impl(
|
||||||
|
core::string_view s,
|
||||||
|
any_resource const* v);
|
||||||
|
|
||||||
|
any_resource const*
|
||||||
|
find_impl(
|
||||||
|
segments_encoded_view path,
|
||||||
|
core::string_view*& matches,
|
||||||
|
core::string_view*& names) const noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // detail
|
||||||
|
} // urls
|
||||||
|
} // boost
|
||||||
|
|
||||||
|
#endif
|
98
HttpProxy/impl/matches.cpp
Normal file
98
HttpProxy/impl/matches.cpp
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2023 Alan de Freitas (alandefreitas@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||||
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||||
|
//
|
||||||
|
// Official repository: https://github.com/boostorg/url
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "../matches.hpp"
|
||||||
|
|
||||||
|
namespace boost {
|
||||||
|
namespace urls {
|
||||||
|
|
||||||
|
auto
|
||||||
|
matches_base::
|
||||||
|
at( size_type pos ) const
|
||||||
|
-> const_reference
|
||||||
|
{
|
||||||
|
if (pos < size())
|
||||||
|
{
|
||||||
|
return matches()[pos];
|
||||||
|
}
|
||||||
|
boost::throw_exception(
|
||||||
|
std::out_of_range(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto
|
||||||
|
matches_base::
|
||||||
|
operator[]( size_type pos ) const
|
||||||
|
-> const_reference
|
||||||
|
{
|
||||||
|
BOOST_ASSERT(pos < size());
|
||||||
|
return matches()[pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto
|
||||||
|
matches_base::
|
||||||
|
at( core::string_view id ) const
|
||||||
|
-> const_reference
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < size(); ++i)
|
||||||
|
{
|
||||||
|
if (ids()[i] == id)
|
||||||
|
return matches()[i];
|
||||||
|
}
|
||||||
|
boost::throw_exception(
|
||||||
|
std::out_of_range(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto
|
||||||
|
matches_base::
|
||||||
|
operator[]( core::string_view id ) const
|
||||||
|
-> const_reference
|
||||||
|
{
|
||||||
|
return at(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto
|
||||||
|
matches_base::
|
||||||
|
find( core::string_view id ) const
|
||||||
|
-> const_iterator
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < size(); ++i)
|
||||||
|
{
|
||||||
|
if (ids()[i] == id)
|
||||||
|
return matches() + i;
|
||||||
|
}
|
||||||
|
return matches() + size();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto
|
||||||
|
matches_base::
|
||||||
|
begin() const
|
||||||
|
-> const_iterator
|
||||||
|
{
|
||||||
|
return &matches()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto
|
||||||
|
matches_base::
|
||||||
|
end() const
|
||||||
|
-> const_iterator
|
||||||
|
{
|
||||||
|
return &matches()[size()];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto
|
||||||
|
matches_base::
|
||||||
|
empty() const noexcept
|
||||||
|
-> bool
|
||||||
|
{
|
||||||
|
return size() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // urls
|
||||||
|
} // boost
|
||||||
|
|
75
HttpProxy/impl/router.hpp
Normal file
75
HttpProxy/impl/router.hpp
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||||
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||||
|
//
|
||||||
|
// Official repository: https://github.com/boostorg/url
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <boost/url/detail/except.hpp>
|
||||||
|
#include <boost/url/decode_view.hpp>
|
||||||
|
#include <boost/url/grammar/unsigned_rule.hpp>
|
||||||
|
#include <boost/mp11/algorithm.hpp>
|
||||||
|
|
||||||
|
namespace boost {
|
||||||
|
namespace urls {
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
template <class U>
|
||||||
|
void
|
||||||
|
router<T>::
|
||||||
|
insert(core::string_view pattern, U&& v)
|
||||||
|
{
|
||||||
|
BOOST_STATIC_ASSERT(
|
||||||
|
std::is_same<T, U>::value ||
|
||||||
|
std::is_convertible<U, T>::value ||
|
||||||
|
std::is_base_of<T, U>::value);
|
||||||
|
using U_ = typename std::decay<
|
||||||
|
typename std::conditional<
|
||||||
|
std::is_base_of<T, U>::value, U, T
|
||||||
|
>::type>::type;
|
||||||
|
struct impl : any_resource
|
||||||
|
{
|
||||||
|
U_ u;
|
||||||
|
|
||||||
|
explicit
|
||||||
|
impl(U&& u_)
|
||||||
|
: u(std::forward<U>(u_))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void const*
|
||||||
|
get() const noexcept override
|
||||||
|
{
|
||||||
|
return static_cast<T const*>(&u);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
any_resource const* p = new impl(
|
||||||
|
std::forward<U>(v));
|
||||||
|
insert_impl( pattern, p );
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T const*
|
||||||
|
router<T>::
|
||||||
|
find(segments_encoded_view path, matches_base& m) const noexcept
|
||||||
|
{
|
||||||
|
core::string_view* matches_it = m.matches();
|
||||||
|
core::string_view* ids_it = m.ids();
|
||||||
|
any_resource const* p = find_impl(
|
||||||
|
path, matches_it, ids_it );
|
||||||
|
if (p)
|
||||||
|
{
|
||||||
|
BOOST_ASSERT(matches_it >= m.matches());
|
||||||
|
m.resize(static_cast<std::size_t>(
|
||||||
|
matches_it - m.matches()));
|
||||||
|
return reinterpret_cast<
|
||||||
|
T const*>(p->get());
|
||||||
|
}
|
||||||
|
m.resize(0);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // urls
|
||||||
|
} // boost
|
159
HttpProxy/matches.hpp
Normal file
159
HttpProxy/matches.hpp
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||||
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||||
|
//
|
||||||
|
// Official repository: https://github.com/boostorg/url
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef BOOST_URL_MATCHES_HPP
|
||||||
|
#define BOOST_URL_MATCHES_HPP
|
||||||
|
|
||||||
|
#include <boost/url/detail/config.hpp>
|
||||||
|
#include <boost/url/string_view.hpp>
|
||||||
|
|
||||||
|
namespace boost {
|
||||||
|
namespace urls {
|
||||||
|
|
||||||
|
// Base route match results
|
||||||
|
class matches_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using iterator = core::string_view*;
|
||||||
|
using const_iterator = core::string_view const*;
|
||||||
|
using size_type = std::size_t;
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using reference = core::string_view&;
|
||||||
|
using const_reference = core::string_view const&;
|
||||||
|
using pointer = core::string_view*;
|
||||||
|
using const_pointer = core::string_view const*;
|
||||||
|
|
||||||
|
matches_base() = default;
|
||||||
|
|
||||||
|
virtual ~matches_base() = default;
|
||||||
|
|
||||||
|
virtual
|
||||||
|
core::string_view const*
|
||||||
|
matches() const = 0;
|
||||||
|
|
||||||
|
virtual
|
||||||
|
core::string_view const*
|
||||||
|
ids() const = 0;
|
||||||
|
|
||||||
|
virtual
|
||||||
|
core::string_view*
|
||||||
|
matches() = 0;
|
||||||
|
|
||||||
|
virtual
|
||||||
|
core::string_view*
|
||||||
|
ids() = 0;
|
||||||
|
|
||||||
|
virtual
|
||||||
|
std::size_t
|
||||||
|
size() const = 0;
|
||||||
|
|
||||||
|
virtual
|
||||||
|
void
|
||||||
|
resize(std::size_t) = 0;
|
||||||
|
|
||||||
|
const_reference
|
||||||
|
at( size_type pos ) const;
|
||||||
|
|
||||||
|
const_reference
|
||||||
|
at( core::string_view id ) const;
|
||||||
|
|
||||||
|
const_reference
|
||||||
|
operator[]( size_type pos ) const;
|
||||||
|
|
||||||
|
const_reference
|
||||||
|
operator[]( core::string_view id ) const;
|
||||||
|
|
||||||
|
const_iterator
|
||||||
|
find( core::string_view id ) const;
|
||||||
|
|
||||||
|
const_iterator
|
||||||
|
begin() const;
|
||||||
|
|
||||||
|
const_iterator
|
||||||
|
end() const;
|
||||||
|
|
||||||
|
bool
|
||||||
|
empty() const noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A range type with the match results
|
||||||
|
template <std::size_t N = 20>
|
||||||
|
class matches_storage
|
||||||
|
: public matches_base
|
||||||
|
{
|
||||||
|
core::string_view matches_storage_[N];
|
||||||
|
core::string_view ids_storage_[N];
|
||||||
|
std::size_t size_;
|
||||||
|
|
||||||
|
matches_storage(
|
||||||
|
core::string_view matches[N],
|
||||||
|
core::string_view ids[N],
|
||||||
|
std::size_t n)
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
matches_storage_[i] = matches[i];
|
||||||
|
ids_storage_[i] = ids[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual
|
||||||
|
core::string_view*
|
||||||
|
matches() override
|
||||||
|
{
|
||||||
|
return matches_storage_;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual
|
||||||
|
core::string_view*
|
||||||
|
ids() override
|
||||||
|
{
|
||||||
|
return ids_storage_;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
matches_storage() = default;
|
||||||
|
|
||||||
|
virtual
|
||||||
|
core::string_view const*
|
||||||
|
matches() const override
|
||||||
|
{
|
||||||
|
return matches_storage_;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual
|
||||||
|
core::string_view const*
|
||||||
|
ids() const override
|
||||||
|
{
|
||||||
|
return ids_storage_;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual
|
||||||
|
std::size_t
|
||||||
|
size() const override
|
||||||
|
{
|
||||||
|
return size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual
|
||||||
|
void
|
||||||
|
resize(std::size_t n) override
|
||||||
|
{
|
||||||
|
size_ = n;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Default type for storing route match results
|
||||||
|
using matches = matches_storage<20>;
|
||||||
|
|
||||||
|
} // urls
|
||||||
|
} // boost
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
90
HttpProxy/router.hpp
Normal file
90
HttpProxy/router.hpp
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||||
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||||
|
//
|
||||||
|
// Official repository: https://github.com/boostorg/url
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef BOOST_URL_ROUTER_HPP
|
||||||
|
#define BOOST_URL_ROUTER_HPP
|
||||||
|
|
||||||
|
#include <boost/url/detail/config.hpp>
|
||||||
|
#include <boost/url/parse_path.hpp>
|
||||||
|
#include "detail/router.hpp"
|
||||||
|
#include "matches.hpp"
|
||||||
|
|
||||||
|
namespace boost {
|
||||||
|
namespace urls {
|
||||||
|
|
||||||
|
/** A URL router.
|
||||||
|
|
||||||
|
This container matches static and dynamic
|
||||||
|
URL requests to an object which represents
|
||||||
|
how the it should be handled. These
|
||||||
|
values are usually callback functions.
|
||||||
|
|
||||||
|
@tparam T type of resource associated with
|
||||||
|
each path template
|
||||||
|
|
||||||
|
@tparam N maximum number of replacement fields
|
||||||
|
in a path template
|
||||||
|
|
||||||
|
@par Exception Safety
|
||||||
|
|
||||||
|
@li Functions marked `noexcept` provide the
|
||||||
|
no-throw guarantee, otherwise:
|
||||||
|
|
||||||
|
@li Functions which throw offer the strong
|
||||||
|
exception safety guarantee.
|
||||||
|
|
||||||
|
@see
|
||||||
|
@ref parse_absolute_uri,
|
||||||
|
@ref parse_relative_ref,
|
||||||
|
@ref parse_uri,
|
||||||
|
@ref parse_uri_reference,
|
||||||
|
@ref resolve.
|
||||||
|
*/
|
||||||
|
template <class T>
|
||||||
|
class router
|
||||||
|
: private detail::router_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Constructor
|
||||||
|
router() = default;
|
||||||
|
|
||||||
|
/** Route the specified URL path to a resource
|
||||||
|
|
||||||
|
@param path A url path with dynamic segments
|
||||||
|
@param resource A resource the path corresponds to
|
||||||
|
|
||||||
|
@see
|
||||||
|
https://fmt.dev/latest/syntax.html
|
||||||
|
*/
|
||||||
|
template <class U>
|
||||||
|
void
|
||||||
|
insert(core::string_view pattern, U&& v);
|
||||||
|
|
||||||
|
/** Match URL path to corresponding resource
|
||||||
|
|
||||||
|
@param request Request path
|
||||||
|
@return The match results
|
||||||
|
*/
|
||||||
|
T const*
|
||||||
|
find(segments_encoded_view path, matches_base& m) const noexcept;
|
||||||
|
|
||||||
|
#ifdef BOOST_URL_DOCS
|
||||||
|
/// @copydoc find
|
||||||
|
T const*
|
||||||
|
find(segments_encoded_view path, matches& m) const noexcept;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
} // urls
|
||||||
|
} // boost
|
||||||
|
|
||||||
|
#include "impl/router.hpp"
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue
Block a user