aboutsummaryrefslogtreecommitdiff
path: root/src/zenhttp/include
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2025-11-24 10:32:52 +0100
committerGitHub Enterprise <[email protected]>2025-11-24 10:32:52 +0100
commitd6a2b1c247778e1bf2a847adba230ad20f44d21d (patch)
treea6eb8c33798456d9d68a6918a693bdc54158f562 /src/zenhttp/include
parentchangelog spelling (diff)
downloadzen-d6a2b1c247778e1bf2a847adba230ad20f44d21d.tar.xz
zen-d6a2b1c247778e1bf2a847adba230ad20f44d21d.zip
Add regex-free route matching support (#662)
This change adds support for non-regex matching of routes. Instead of using regex patterns you can associate matcher functions with pattern names and string literal components are identified and matched directly. Also implemented tests for `HttpRequestRouter` class.
Diffstat (limited to 'src/zenhttp/include')
-rw-r--r--src/zenhttp/include/zenhttp/httpserver.h114
1 files changed, 81 insertions, 33 deletions
diff --git a/src/zenhttp/include/zenhttp/httpserver.h b/src/zenhttp/include/zenhttp/httpserver.h
index 03e547bf3..f95ec51d2 100644
--- a/src/zenhttp/include/zenhttp/httpserver.h
+++ b/src/zenhttp/include/zenhttp/httpserver.h
@@ -45,15 +45,13 @@ public:
std::string_view GetValue(std::string_view ParamName) const
{
- for (const auto& Kv : KvPairs)
+ for (const auto& [Key, Value] : KvPairs)
{
- const std::string_view& Key = Kv.first;
-
if (Key.size() == ParamName.size())
{
if (0 == StrCaseCompare(Key.data(), ParamName.data(), Key.size()))
{
- return Kv.second;
+ return Value;
}
}
}
@@ -213,43 +211,53 @@ Ref<HttpServer> CreateHttpServer(const HttpServerConfig& Config);
class HttpRouterRequest
{
public:
- HttpRouterRequest(HttpServerRequest& Request) : m_HttpRequest(Request) {}
-
+ /** Get captured segment from matched URL
+ *
+ * @param Index Index of captured segment to retrieve. Note that due to
+ * backwards compatibility with regex-based routes, this index is 1-based
+ * and index=0 is the full matched URL
+ * @return Returns string view of captured segment
+ */
std::string_view GetCapture(uint32_t Index) const;
inline HttpServerRequest& ServerRequest() { return m_HttpRequest; }
private:
+ HttpRouterRequest(HttpServerRequest& Request) : m_HttpRequest(Request) {}
+ ~HttpRouterRequest() = default;
+
+ HttpRouterRequest(const HttpRouterRequest&) = delete;
+ HttpRouterRequest& operator=(const HttpRouterRequest&) = delete;
+
using MatchResults_t = std::match_results<std::string_view::const_iterator>;
- HttpServerRequest& m_HttpRequest;
- MatchResults_t m_Match;
+ HttpServerRequest& m_HttpRequest;
+ MatchResults_t m_Match;
+ std::vector<std::string_view> m_CapturedSegments; // for matcher-based routes
friend class HttpRequestRouter;
};
-inline std::string_view
-HttpRouterRequest::GetCapture(uint32_t Index) const
-{
- ZEN_ASSERT(Index < m_Match.size());
-
- const auto& Match = m_Match[Index];
-
- return std::string_view(&*Match.first, Match.second - Match.first);
-}
-
/** HTTP request router helper
*
* This helper class allows a service implementer to register one or more
- * endpoints using pattern matching (currently using regex matching)
+ * endpoints using pattern matching. We currently support a legacy regex-based
+ * matching system, but also a new matcher-function based system which is more
+ * efficient and should be used whenever possible.
*
* This is intended to be initialized once only, there is no thread
* safety so you can absolutely not add or remove endpoints once the handler
- * goes live
+ * goes live.
*/
class HttpRequestRouter
{
public:
+ HttpRequestRouter() = default;
+ ~HttpRequestRouter() = default;
+
+ HttpRequestRouter(const HttpRequestRouter&) = delete;
+ HttpRequestRouter& operator=(const HttpRequestRouter&) = delete;
+
typedef std::function<void(HttpRouterRequest&)> HandlerFunc_t;
/**
@@ -260,15 +268,21 @@ public:
void AddPattern(const char* Id, const char* Regex);
/**
- * @brief Register a an endpoint handler for the given route
- * @param Regex Regular expression used to match the handler to a request. This may
- * contain pattern aliases registered via AddPattern
+ * @brief Add matcher function which can be referenced by name, used for URL components
+ * @param Id String used to identify matchers in endpoint specifications
+ * @param Matcher Function which will be called to match the component
+ */
+ void AddMatcher(const char* Id, std::function<bool(std::string_view)>&& Matcher);
+
+ /**
+ * @brief Register an endpoint handler for the given route
+ * @param Pattern Pattern used to match the handler to a request. This should
+ * only contain literal URI segments and pattern aliases registered
+ via AddPattern() or AddMatcher()
* @param HandlerFunc Handler function to call for any matching request
* @param SupportedVerbs Supported HTTP verbs for this handler
*/
- void RegisterRoute(const char* Regex, HandlerFunc_t&& HandlerFunc, HttpVerb SupportedVerbs);
-
- void ProcessRegexSubstitutions(const char* Regex, StringBuilderBase& ExpandedRegex);
+ void RegisterRoute(const char* Pattern, HandlerFunc_t&& HandlerFunc, HttpVerb SupportedVerbs);
/**
* @brief HTTP request handling function - this should be called to route the
@@ -279,9 +293,11 @@ public:
bool HandleRequest(zen::HttpServerRequest& Request);
private:
- struct HandlerEntry
+ bool ProcessRegexSubstitutions(const char* Regex, StringBuilderBase& ExpandedRegex);
+
+ struct RegexEndpoint
{
- HandlerEntry(const char* Regex, HttpVerb SupportedVerbs, HandlerFunc_t&& Handler, const char* Pattern)
+ RegexEndpoint(const char* Regex, HttpVerb SupportedVerbs, HandlerFunc_t&& Handler, const char* Pattern)
: RegEx(Regex, std::regex::icase | std::regex::ECMAScript)
, Verbs(SupportedVerbs)
, Handler(std::move(Handler))
@@ -289,7 +305,7 @@ private:
{
}
- ~HandlerEntry() = default;
+ ~RegexEndpoint() = default;
std::regex RegEx;
HttpVerb Verbs;
@@ -297,12 +313,44 @@ private:
const char* Pattern;
private:
- HandlerEntry& operator=(const HandlerEntry&) = delete;
- HandlerEntry(const HandlerEntry&) = delete;
+ RegexEndpoint& operator=(const RegexEndpoint&) = delete;
+ RegexEndpoint(const RegexEndpoint&) = delete;
};
- std::list<HandlerEntry> m_Handlers;
+ std::list<RegexEndpoint> m_RegexHandlers;
std::unordered_map<std::string, std::string> m_PatternMap;
+
+ // New-style matcher endpoints. Should be preferred over regex endpoints where possible
+ // as it is considerably more efficient
+
+ struct MatcherEndpoint
+ {
+ MatcherEndpoint(std::vector<int>&& ComponentIndices, HttpVerb SupportedVerbs, HandlerFunc_t&& Handler, const char* Pattern)
+ : ComponentIndices(std::move(ComponentIndices))
+ , Verbs(SupportedVerbs)
+ , Handler(std::move(Handler))
+ , Pattern(Pattern)
+ {
+ }
+
+ ~MatcherEndpoint() = default;
+
+ // Negative indexes are literals, non-negative are matcher function indexes
+ std::vector<int> ComponentIndices;
+ HttpVerb Verbs;
+ HandlerFunc_t Handler;
+ const char* Pattern;
+
+ private:
+ MatcherEndpoint& operator=(const MatcherEndpoint&) = delete;
+ MatcherEndpoint(const MatcherEndpoint&) = delete;
+ };
+
+ std::unordered_map<std::string, int> m_MatcherNameMap;
+ std::vector<std::function<bool(std::string_view)>> m_MatcherFunctions;
+ std::vector<std::string> m_Literals;
+ std::list<MatcherEndpoint> m_MatcherEndpoints;
+ bool m_IsFinalized = false;
};
/** HTTP RPC request helper
@@ -310,7 +358,7 @@ private:
class RpcResult
{
- RpcResult(CbObject Result) : m_Result(std::move(Result)) {}
+ explicit RpcResult(CbObject Result) : m_Result(std::move(Result)) {}
private:
CbObject m_Result;