diff --git a/httplib.h b/httplib.h index dfa6c0e821..cbc137f686 100644 --- a/httplib.h +++ b/httplib.h @@ -620,6 +620,7 @@ using Ranges = std::vector; struct Request { std::string method; std::string path; + const char *matched_route = nullptr; Params params; Headers headers; std::string body; @@ -871,10 +872,16 @@ namespace detail { class MatcherBase { public: + MatcherBase(std::string pattern) : pattern_(pattern) {} virtual ~MatcherBase() = default; + const std::string &pattern() const { return pattern_; } + // Match request path and populate its matches and virtual bool match(Request &request) const = 0; + +private: + std::string pattern_; }; /** @@ -926,7 +933,8 @@ class PathParamsMatcher final : public MatcherBase { */ class RegexMatcher final : public MatcherBase { public: - RegexMatcher(const std::string &pattern) : regex_(pattern) {} + RegexMatcher(const std::string &pattern) + : MatcherBase(pattern), regex_(pattern) {} bool match(Request &request) const override; @@ -6084,7 +6092,8 @@ inline time_t BufferStream::duration() const { return 0; } inline const std::string &BufferStream::get_buffer() const { return buffer; } -inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) { +inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) + : MatcherBase(pattern) { static constexpr char marker[] = "/:"; // One past the last ending position of a path param substring @@ -6987,6 +6996,7 @@ inline bool Server::dispatch_request(Request &req, Response &res, const auto &handler = x.second; if (matcher->match(req)) { + req.matched_route = matcher->pattern().c_str(); handler(req, res); return true; } @@ -7107,6 +7117,7 @@ inline bool Server::dispatch_request_for_content_reader( const auto &handler = x.second; if (matcher->match(req)) { + req.matched_route = matcher->pattern().c_str(); handler(req, res, content_reader); return true; } diff --git a/test/test.cc b/test/test.cc index 81a5e33a6c..65c5421338 100644 --- a/test/test.cc +++ b/test/test.cc @@ -8468,3 +8468,54 @@ TEST(ClientInThreadTest, Issue2068) { t.join(); } } + +TEST(MatchedRouteTest, BasicAndFileRequests) { + std::vector paths{ + "/route1/1/2", + "/route1/1", + }; + std::vector routes{ + "/route1/:arg1/:arg2", + "/route1/:arg1", + }; + + Server svr; + svr.set_mount_point("/", "./www"); + svr.Get("/route1/:arg1", [&](const Request & /*req*/, Response & /*res*/) {}); + svr.Get("/route1/:arg1/:arg2", + [&](const Request & /*req*/, Response & /*res*/) {}); + + svr.set_post_routing_handler([&](const Request &req, Response & /*res*/) { + if (!routes.empty()) { + EXPECT_EQ(req.path, paths.back()); + ASSERT_EQ(req.matched_route, routes.back()); + paths.pop_back(); + routes.pop_back(); + } else { + EXPECT_EQ(req.path, "/file"); + EXPECT_EQ(req.matched_route, nullptr); + } + }); + + auto listen_thread = std::thread([&svr]() { svr.listen(HOST, PORT); }); + auto se = detail::scope_exit([&] { + svr.stop(); + + listen_thread.join(); + + ASSERT_FALSE(svr.is_running()); + }); + + svr.wait_until_ready(); + + Client cli(HOST, PORT); + + auto res = cli.Get("/route1/1"); + ASSERT_TRUE(res); + + res = cli.Get("/route1/1/2"); + ASSERT_TRUE(res); + + res = cli.Get("/file"); + ASSERT_TRUE(res); +}