diff options
| author | Stefan Boberg <[email protected]> | 2025-11-24 10:32:52 +0100 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2025-11-24 10:32:52 +0100 |
| commit | d6a2b1c247778e1bf2a847adba230ad20f44d21d (patch) | |
| tree | a6eb8c33798456d9d68a6918a693bdc54158f562 /src/zenhttp/include | |
| parent | changelog spelling (diff) | |
| download | zen-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.h | 114 |
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; |