mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-19 11:19:36 +00:00
Match literal route patterns without building a std::regex (#2538)
Server::make_matcher() built a std::regex for every pattern that did not contain "/:", even though most route patterns are plain literals with no regular expression syntax in them. Matching those went through std::regex_match on every request, for every registered route the dispatcher scanned before reaching the one that matches. PathParamsMatcher already performs an exact literal comparison when it captures no parameter, so no new matcher class is needed: a pattern with no regex metacharacter can simply use it. Add an early return for the zero parameter case in PathParamsMatcher::match(), and select the matcher by also looking for the 14 ECMAScript metacharacters instead of only for "/:". Path params keep taking precedence, so a pattern that mixes both, such as "/users/:id/(.*)", is unaffected. Measured with clang -O2 on macOS, scanning routes that all miss until the last one: at 100 routes a scan drops from 10.3us to 0.46us, and end to end throughput rises by about 24%. At 1000 routes throughput is roughly 3 times higher. Registering 5000 routes drops from about 3.0ms to about 0.9ms, since no std::regex is built for literal patterns. This also keeps CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH confined to the routes it is meant for. That limit rejects overlong paths before calling std::regex_match, but until now every literal route was a RegexMatcher too, so a literal route longer than the limit stopped matching even though no regular expression was involved. Literal routes no longer go through RegexMatcher, so only real regex routes are capped. Patterns containing a metacharacter keep their current behavior, so "/index.html" still matches "/indexXhtml" the way it always has. One visible change: a literal route no longer populates Request::matches, which is now a default constructed std::smatch. Path parameter routes have always behaved that way, and Request::matches only carries useful information for regex routes.
This commit is contained in:
1 parent
88240172ea
commit
1e9d6f0b0b
2 files changed
+89
-2
No files matched your search
@@ -11603,6 +11603,10 @@ inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern)
|
||||
inline bool PathParamsMatcher::match(Request &request) const {
|
||||
request.matches = std::smatch();
|
||||
request.path_params.clear();
|
||||
|
||||
// A pattern without parameters is just a literal path to compare against
|
||||
if (param_names_.empty()) { return request.path == pattern(); }
|
||||
|
||||
request.path_params.reserve(param_names_.size());
|
||||
|
||||
// One past the position at which the path matched the pattern last time
|
||||
@@ -12075,11 +12079,21 @@ inline Server::~Server() = default;
|
||||
|
||||
inline std::unique_ptr<detail::MatcherBase>
|
||||
Server::make_matcher(const std::string &pattern) {
|
||||
// Path params take precedence, so "/users/:id/(.*)" keeps being matched as
|
||||
// a path params pattern
|
||||
if (pattern.find("/:") != std::string::npos) {
|
||||
return detail::make_unique<detail::PathParamsMatcher>(pattern);
|
||||
} else {
|
||||
return detail::make_unique<detail::RegexMatcher>(pattern);
|
||||
}
|
||||
|
||||
// A pattern with no regex metacharacter only has to be compared literally,
|
||||
// which is what PathParamsMatcher already does when it captures no
|
||||
// parameter, so std::regex is only worth building for the patterns that
|
||||
// actually need it
|
||||
if (pattern.find_first_of(".^$|()[]{}*+?\\") == std::string::npos) {
|
||||
return detail::make_unique<detail::PathParamsMatcher>(pattern);
|
||||
}
|
||||
|
||||
return detail::make_unique<detail::RegexMatcher>(pattern);
|
||||
}
|
||||
|
||||
inline Server &Server::Get(const std::string &pattern, Handler handler) {
|
||||
|
||||
Reference in new issue
Block a user