Skip to content

Fix HttpService route precedence so Any("*") no longer shadows specific handlers#832

Open
Copilot wants to merge 3 commits into
masterfrom
copilot/fix-httpservice-getroute-issue
Open

Fix HttpService route precedence so Any("*") no longer shadows specific handlers#832
Copilot wants to merge 3 commits into
masterfrom
copilot/fix-httpservice-getroute-issue

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 18, 2026

HttpService::GetRoute(HttpRequest*, ...) iterated pathHandlers via std::unordered_map, so match order was non-deterministic. In practice, a catch-all Any("*") could win before concrete routes (e.g. /ping), breaking expected router behavior.

  • Routing precedence in HttpService::GetRoute(HttpRequest*, ...)

    • Added explicit exact-path lookup first (pathHandlers.find(path)), with normal method resolution.
    • Refactored pattern matching into deterministic phases:
      • RESTful patterns (/:id, /{id}) first
      • wildcard patterns (*) after
    • Added stable ordering among pattern candidates (longer path first, then lexical tie-break) to avoid unordered-map iteration effects.
  • Routing matcher extraction

    • Extracted route pattern matching into a helper (match_route_path) to keep wildcard + RESTful matching behavior consistent while making precedence explicit.
  • Focused regression test

    • Added unittest/http_service_route_test.cpp and wired it into unittest/CMakeLists.txt.
    • Test asserts:
      • specific route /ping is selected over Any("*")
      • unknown path still falls through to catch-all
HttpService service;
service.Any("*", fallback_handler);
service.GET("/ping", specific_handler);

// /ping resolves to specific handler, not catch-all

Copilot AI and others added 2 commits May 18, 2026 07:12
Copilot AI changed the title [WIP] Fix HttpService route matching for specific handlers Fix HttpService route precedence so Any("*") no longer shadows specific handlers May 18, 2026
Copilot AI requested a review from ithewei May 18, 2026 07:16
}
return lhs->first < rhs->first;
};
std::sort(restful_iters.begin(), restful_iters.end(), route_compare);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetRoute的调用频率很高,每次都筛选出restful和wildcard再排序,影响性能,能在AddRoute时就自动分类好,来提高GetRoute效率吗

@ithewei ithewei marked this pull request as ready for review May 20, 2026 11:25
Copilot AI review requested due to automatic review settings May 20, 2026 11:25
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR makes HttpService::GetRoute deterministic and precedence-aware so catch-all routes like Any("*") no longer unpredictably shadow more specific handlers, and adds a regression test to lock the behavior.

Changes:

  • Added exact-path lookup before pattern matching, with deterministic RESTful-then-wildcard matching order.
  • Introduced stable ordering of pattern candidates (longer-first, then lexical) to avoid unordered_map iteration effects.
  • Added a focused regression test executable and integrated it into the unittest CMake targets.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
http/server/HttpService.cpp Deterministic route selection with explicit precedence and extracted path-matching helper.
unittest/http_service_route_test.cpp Regression test ensuring /ping beats Any("*") and unknown paths still hit the catch-all.
unittest/CMakeLists.txt Wires the new route precedence regression test into the unittest build.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +124 to +140
std::vector<http_path_handlers::const_iterator> restful_iters;
std::vector<http_path_handlers::const_iterator> wildcard_iters;
for (auto it = pathHandlers.begin(); it != pathHandlers.end(); ++it) {
if (it->first.find('*') != std::string::npos) {
wildcard_iters.push_back(it);
} else if (is_restful_route(it->first)) {
restful_iters.push_back(it);
}
}
auto route_compare = [](const http_path_handlers::const_iterator& lhs, const http_path_handlers::const_iterator& rhs) {
if (lhs->first.size() != rhs->first.size()) {
return lhs->first.size() > rhs->first.size();
}
return lhs->first < rhs->first;
};
std::sort(restful_iters.begin(), restful_iters.end(), route_compare);
std::sort(wildcard_iters.begin(), wildcard_iters.end(), route_compare);
Comment on lines +111 to +122
auto iter = pathHandlers.find(path);
if (iter != pathHandlers.end()) {
auto method_handlers = iter->second;
for (auto mh = method_handlers->begin(); mh != method_handlers->end(); ++mh) {
if (mh->method == req->method) {
if (handler) *handler = &mh->handler;
return 0;
}
}
if (handler) *handler = NULL;
return HTTP_STATUS_METHOD_NOT_ALLOWED;
}
Comment on lines +28 to +40
req.path = "/ping";
req.method = HTTP_GET;
if (service.GetRoute(&req, &handler) != 0 || handler == NULL || handler->sync_handler(&req, &resp) != 200) {
fprintf(stderr, "specific route should win over '*' catch-all\n");
return -1;
}

req.path = "/not-found";
req.method = HTTP_GET;
if (service.GetRoute(&req, &handler) != 0 || handler == NULL || handler->sync_handler(&req, &resp) != 418) {
fprintf(stderr, "catch-all should match unknown path\n");
return -2;
}
@@ -0,0 +1,43 @@
#include <stdio.h>
service.Any("*", fallback_handler);
service.GET("/ping", specific_handler);

http_handler* handler = NULL;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HttpService::GetRoute first-match-wins over std::unordered_map breaks ANY("*") + specific routes

3 participants