diff --git a/HttpProxy/CMakeLists.txt b/HttpProxy/CMakeLists.txt index fa5e1f9..fc9cb8c 100644 --- a/HttpProxy/CMakeLists.txt +++ b/HttpProxy/CMakeLists.txt @@ -1,5 +1,12 @@ add_library(HttpProxy NetworkUtility.h NetworkUtility.cpp + ProxyHttpSession.h ProxyHttpSession.cpp + ProxyListener.h ProxyListener.cpp + ProxyTcpSession.h ProxyTcpSession.cpp + TemplateMatchs.h TemplateMatchs.cpp + TemplateSegmentRule.h TemplateSegmentRule.cpp + UrlRouter.h UrlRouter.cpp + UrlRouterPrivate.h UrlRouterPrivate.cpp ) target_include_directories(HttpProxy diff --git a/HttpProxy/ProxyHttpSession.cpp b/HttpProxy/ProxyHttpSession.cpp new file mode 100644 index 0000000..fdcb704 --- /dev/null +++ b/HttpProxy/ProxyHttpSession.cpp @@ -0,0 +1,94 @@ +#include "ProxyHttpSession.h" +#include "BoostLog.h" +#include "ProxyTcpSession.h" +#include +#include +#include + +ProxyHttpSession::ProxyHttpSession(boost::asio::io_context &ioContext, boost::asio::ip::tcp::socket &&socket) + : m_resolver(ioContext), m_clientStream(std::move(socket)), m_serverStream(boost::asio::make_strand(ioContext)) { +} + +void ProxyHttpSession::run() { + doRead(); +} + +void ProxyHttpSession::doRead() { + // Construct a new parser for each message + m_parser.emplace(); + + // Apply a reasonable limit to the allowed size + // of the body in bytes to prevent abuse. + m_parser->body_limit(std::numeric_limits::max()); + m_parser->header_limit(std::numeric_limits::max()); + m_buffer.clear(); + + // Set the timeout. + // m_stream.expires_after(std::chrono::seconds(30)); + m_clientStream.expires_never(); + boost::beast::http::async_read( + m_clientStream, m_buffer, *m_parser, + [self{shared_from_this()}](const boost::system::error_code &ec, std::size_t bytes_transferred) { + self->onRead(ec, bytes_transferred); + }); +} + +void ProxyHttpSession::onRead(boost::beast::error_code ec, size_t) { // This means they closed the connection + if (ec == boost::beast::http::error::end_of_stream) { + LOG(info) << ec << " : " << ec.message(); + m_clientStream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec); + return; + } + if (ec) { + if (ec == boost::asio::error::operation_aborted) return; + LOG(info) << ec << " : " << ec.message(); + return; + } + + auto request = m_parser->release(); + if (request.count("Host") <= 0) return; + auto host = request.at("Host"); + if (host.empty()) { + LOG(error) << "Host field not existed."; + return; + } + + auto colonPos = host.find_first_of(':'); + + std::string_view serverAddress; + std::string_view serverPort; + if (colonPos != std::string_view::npos) { + serverAddress = host.substr(0, colonPos); + serverPort = host.substr(colonPos + 1); + } else { + serverAddress = host; + serverPort = "80"; + } + + boost::system::error_code ee; + auto const results = m_resolver.resolve(serverAddress, serverPort, ee); + if (ee) { + LOG(error) << ee.message(); + return; + } + m_serverStream.connect(results, ee); + if (ee) { + LOG(error) << ee.message(); + return; + } + + if (request.method() == boost::beast::http::verb::connect) { + LOG(info) << "proxy host [" << serverAddress << "], port [" << serverPort << "]"; + boost::beast::http::response res{boost::beast::http::status::ok, + request.version()}; + res.reason("Connection Established"); + res.keep_alive(request.keep_alive()); + res.set("Proxy-agent", "amass"); + res.prepare_payload(); + boost::beast::http::write(m_clientStream, res); + } else { + boost::beast::http::write(m_serverStream, request); + } + auto tcp = std::make_shared(m_clientStream.release_socket(), m_serverStream.release_socket()); + tcp->run(); +} diff --git a/HttpProxy/ProxyHttpSession.h b/HttpProxy/ProxyHttpSession.h new file mode 100644 index 0000000..c30a859 --- /dev/null +++ b/HttpProxy/ProxyHttpSession.h @@ -0,0 +1,27 @@ +#ifndef PROXYHTTPSESSION_H +#define PROXYHTTPSESSION_H + +#include +#include +#include +#include +#include + +class ProxyHttpSession : public std::enable_shared_from_this { +public: + ProxyHttpSession(boost::asio::io_context &ioContext, boost::asio::ip::tcp::socket &&socket); + void run(); + +protected: + void doRead(); + void onRead(boost::beast::error_code ec, std::size_t); + +private: + boost::asio::ip::tcp::resolver m_resolver; + std::optional> m_parser; + boost::beast::flat_buffer m_buffer{std::numeric_limits::max()}; + boost::beast::tcp_stream m_clientStream; + boost::beast::tcp_stream m_serverStream; +}; + +#endif // PROXYHTTPSESSION_H diff --git a/HttpProxy/ProxyListener.cpp b/HttpProxy/ProxyListener.cpp new file mode 100644 index 0000000..077a498 --- /dev/null +++ b/HttpProxy/ProxyListener.cpp @@ -0,0 +1,51 @@ +#include "ProxyListener.h" +#include "BoostLog.h" +#include "ProxyHttpSession.h" +#include +#include + +ProxyListener::ProxyListener(boost::asio::io_context &ioContext, boost::asio::ip::tcp::endpoint endpoint) + : m_ioContext(ioContext), m_acceptor(m_ioContext), m_endpoint(std::move(endpoint)) { +} + +void ProxyListener::run(boost::system::error_code &error) { + m_acceptor.open(m_endpoint.protocol(), error); + if (error) { + LOG(error) << error.message(); + return; + } + m_acceptor.set_option(boost::asio::ip::tcp::socket::reuse_address(true), error); + if (error) { + LOG(error) << error.message(); + return; + } + + m_acceptor.bind(m_endpoint, error); + if (error) { + LOG(error) << error.message(); + return; + } + + m_acceptor.listen(boost::asio::ip::tcp::socket::max_connections, error); + if (error) { + LOG(error) << error.message(); + return; + } + doAccept(); +} + +void ProxyListener::doAccept() { + m_acceptor.async_accept( + boost::asio::make_strand(m_ioContext), + [ptr{weak_from_this()}](boost::beast::error_code error, boost::asio::ip::tcp::socket socket) { + if (ptr.expired()) return; + auto self = ptr.lock(); + if (error) { + LOG(error) << error.message(); + return; + } + auto session = std::make_shared(self->m_ioContext, std::move(socket)); + session->run(); + self->doAccept(); + }); +} diff --git a/HttpProxy/ProxyListener.h b/HttpProxy/ProxyListener.h new file mode 100644 index 0000000..62b21f4 --- /dev/null +++ b/HttpProxy/ProxyListener.h @@ -0,0 +1,21 @@ +#ifndef PROXYLISTENER_H +#define PROXYLISTENER_H + +#include +#include + +class ProxyListener : public std::enable_shared_from_this { +public: + ProxyListener(boost::asio::io_context &ioContext, boost::asio::ip::tcp::endpoint endpoint); + void run(boost::system::error_code &error); + +protected: + void doAccept(); + +private: + boost::asio::io_context &m_ioContext; + boost::asio::ip::tcp::acceptor m_acceptor; + boost::asio::ip::tcp::endpoint m_endpoint; +}; + +#endif // PROXYLISTENER_H diff --git a/HttpProxy/ProxyTcpSession.cpp b/HttpProxy/ProxyTcpSession.cpp new file mode 100644 index 0000000..cb5e466 --- /dev/null +++ b/HttpProxy/ProxyTcpSession.cpp @@ -0,0 +1,50 @@ +#include "ProxyTcpSession.h" +#include "BoostLog.h" +#include +#include + +ProxyTcpSession::ProxyTcpSession(boost::asio::ip::tcp::socket &&client, boost::asio::ip::tcp::socket &&server) + : m_client(std::move(client)), m_server(std::move(server)) { +} + +ProxyTcpSession::~ProxyTcpSession() { +} + +void ProxyTcpSession::run() { + clientRead(); + serverRead(); +} + +void ProxyTcpSession::clientRead() { + m_client.async_read_some( + boost::asio::buffer(m_clientBuffer), + [self = shared_from_this()](boost::system::error_code error, std::size_t bytes_transferred) { + if (error) { + LOG(error) << error.message(); + self->m_server.close(); + return; + } + boost::asio::write(self->m_server, boost::asio::buffer(self->m_clientBuffer, bytes_transferred), error); + if (error) { + LOG(info) << error.message(); + } + self->clientRead(); + }); +} + +void ProxyTcpSession::serverRead() { + m_server.async_read_some( + boost::asio::buffer(m_serverBuffer), + [self = shared_from_this()](boost::system::error_code error, std::size_t bytes_transferred) { + if (error) { + self->m_client.close(); + LOG(error) << error.message(); + return; + } + boost::asio::write(self->m_client, boost::asio::buffer(self->m_serverBuffer, bytes_transferred), error); + if (error) { + LOG(info) << error.message(); + } + self->serverRead(); + }); +} diff --git a/HttpProxy/ProxyTcpSession.h b/HttpProxy/ProxyTcpSession.h new file mode 100644 index 0000000..e9b9a5b --- /dev/null +++ b/HttpProxy/ProxyTcpSession.h @@ -0,0 +1,24 @@ +#ifndef PROXYTCPSESSION_H +#define PROXYTCPSESSION_H + +#include + +class ProxyTcpSession : public std::enable_shared_from_this { +public: + ProxyTcpSession(boost::asio::ip::tcp::socket &&client, boost::asio::ip::tcp::socket &&server); + ~ProxyTcpSession(); + void run(); + +protected: + void clientRead(); + void serverRead(); + +private: + boost::asio::ip::tcp::socket m_client; + boost::asio::ip::tcp::socket m_server; + + std::array m_clientBuffer; + std::array m_serverBuffer; +}; + +#endif // PROXYTCPSESSION_H diff --git a/HttpProxy/TemplateMatchs.cpp b/HttpProxy/TemplateMatchs.cpp new file mode 100644 index 0000000..1e98bfb --- /dev/null +++ b/HttpProxy/TemplateMatchs.cpp @@ -0,0 +1,12 @@ +#include "TemplateMatchs.h" + +TemplateMatchStorageBase::const_reference TemplateMatchStorageBase::at(boost::urls::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::urls::string_view id) const { + return at(id); +} diff --git a/HttpProxy/TemplateMatchs.h b/HttpProxy/TemplateMatchs.h new file mode 100644 index 0000000..b35ef05 --- /dev/null +++ b/HttpProxy/TemplateMatchs.h @@ -0,0 +1,57 @@ +#ifndef __TEMPLATEMATCHS_H__ +#define __TEMPLATEMATCHS_H__ + +#include + +class TemplateMatchStorageBase { +public: + using const_reference = boost::urls::string_view const &; + + virtual boost::urls::string_view *matches() = 0; + virtual const boost::urls::string_view *matches() const = 0; + + virtual boost::urls::string_view *ids() = 0; + virtual const boost::urls::string_view *ids() const = 0; + virtual std::size_t size() const = 0; + virtual void resize(std::size_t) = 0; + + const_reference at(boost::urls::string_view id) const; + + const_reference operator[](boost::urls::string_view id) const; +}; + +template +class TemplateMatchStorage : public TemplateMatchStorageBase { +public: + boost::urls::string_view *matches() final { + return m_matches; + } + + virtual const boost::urls::string_view *matches() const final { + return m_matches; + } + + boost::urls::string_view *ids() final { + return m_ids; + } + + const boost::urls::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::urls::string_view m_matches[N]; + boost::urls::string_view m_ids[N]; + std::size_t m_size; +}; + +using TemplateMatches = TemplateMatchStorage<20>; + +#endif // __TEMPLATEMATCHS_H__ diff --git a/HttpProxy/TemplateSegmentRule.cpp b/HttpProxy/TemplateSegmentRule.cpp new file mode 100644 index 0000000..d4291b1 --- /dev/null +++ b/HttpProxy/TemplateSegmentRule.cpp @@ -0,0 +1,96 @@ +#include "TemplateSegmentRule.h" +#include "BoostLog.h" +#include +#include +#include +#include +#include +#include + +boost::urls::result 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::urls::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::urls::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::urls::string_view TemplateSegment::id() const { + BOOST_ASSERT(!isLiteral()); + boost::urls::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::urls::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; +} diff --git a/HttpProxy/TemplateSegmentRule.h b/HttpProxy/TemplateSegmentRule.h new file mode 100644 index 0000000..98d78b3 --- /dev/null +++ b/HttpProxy/TemplateSegmentRule.h @@ -0,0 +1,62 @@ +#ifndef __TEMPLATESEGMENTRULE_H__ +#define __TEMPLATESEGMENTRULE_H__ + +#include +#include +#include +#include +#include +#include + +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::urls::string_view id() const; + boost::urls::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::urls::result 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__ diff --git a/HttpProxy/UrlRouter.cpp b/HttpProxy/UrlRouter.cpp new file mode 100644 index 0000000..50fd831 --- /dev/null +++ b/HttpProxy/UrlRouter.cpp @@ -0,0 +1 @@ +#include "UrlRouter.h" \ No newline at end of file diff --git a/HttpProxy/UrlRouter.h b/HttpProxy/UrlRouter.h new file mode 100644 index 0000000..46b67d5 --- /dev/null +++ b/HttpProxy/UrlRouter.h @@ -0,0 +1,47 @@ +#ifndef __URLROUTER_H__ +#define __URLROUTER_H__ + +#include "TemplateMatchs.h" +#include "UrlRouterPrivate.h" +#include + +template +class UrlRouter : public UrlRouterPrivate { +public: + template + void insert(std::string_view pattern, U &&v) { + BOOST_STATIC_ASSERT(std::is_same::value || std::is_convertible::value || + std::is_base_of::value); + using ResourceType = + typename std::decay_t, U, Resource>>; + + class Implementation : public AnyResource { + public: + explicit Implementation(U &&u_) : resource(std::forward(u_)) { + } + void const *get() const noexcept override { + return static_cast(&resource); + } + + private: + ResourceType resource; + }; + auto resource = std::make_shared(std::forward(v)); + insertImpl(pattern, std::dynamic_pointer_cast(resource)); + } + + const Resource *find(boost::urls::segments_encoded_view path, TemplateMatchStorageBase &matches) const noexcept { + boost::urls::string_view *matches_it = matches.matches(); + boost::urls::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(matches_it - matches.matches())); + return reinterpret_cast(p->get()); + } + matches.resize(0); + return nullptr; + }; +}; + +#endif // __URLROUTER_H__ diff --git a/HttpProxy/UrlRouterPrivate.cpp b/HttpProxy/UrlRouterPrivate.cpp new file mode 100644 index 0000000..1c963dd --- /dev/null +++ b/HttpProxy/UrlRouterPrivate.cpp @@ -0,0 +1,323 @@ +#include "UrlRouterPrivate.h" +#include "BoostLog.h" +#include "TemplateSegmentRule.h" +#include +#include + +UrlRouterPrivate::UrlRouterPrivate() { + m_nodes.push_back(SegementNode{}); +} + +void UrlRouterPrivate::insertImpl(boost::urls::string_view pattern, const std::shared_ptr &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::urls::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::urls::string_view *&matches, + boost::urls::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::urls::string_view *&matches, + boost::urls::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::urls::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 &ns, + boost::urls::string_view *&matches, + boost::urls::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(this)->begin(); +} + +size_t *ChildIndexVector::end() { + return begin() + m_size; +} + +const size_t *ChildIndexVector::end() const { + return const_cast(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; +} diff --git a/HttpProxy/UrlRouterPrivate.h b/HttpProxy/UrlRouterPrivate.h new file mode 100644 index 0000000..a7722b7 --- /dev/null +++ b/HttpProxy/UrlRouterPrivate.h @@ -0,0 +1,63 @@ +#ifndef __URLROUTERPRIVATE_H__ +#define __URLROUTERPRIVATE_H__ + +#include "TemplateSegmentRule.h" +#include +#include +#include + +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 resource; +}; + +class UrlRouterPrivate { +public: + UrlRouterPrivate(); + void insertImpl(boost::urls::string_view pattern, const std::shared_ptr &resource); + const AnyResource *findImpl(boost::urls::segments_encoded_view path, boost::urls::string_view *&matches, + boost::urls::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::urls::string_view *&matches, boost::urls::string_view *&ids) const; + static SegementNode const *findOptionalResource(const SegementNode *root, std::vector const &ns, + boost::urls::string_view *&matches, boost::urls::string_view *&ids); + +private: + std::vector m_nodes; +}; + +#endif // __URLROUTERPRIVATE_H__