-
Notifications
You must be signed in to change notification settings - Fork 13
doc work #262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
doc work #262
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,10 +7,12 @@ | |
| // Official repository: https://github.com/cppalliance/http | ||
| // | ||
|
|
||
| #include "src/server/detail/any_router.hpp" | ||
| #include "src/server/detail/router_base.hpp" | ||
| #include <boost/http/server/detail/router_base.hpp> | ||
| #include <boost/http/detail/except.hpp> | ||
| #include <boost/http/error.hpp> | ||
| #include <boost/http/field.hpp> | ||
| #include <boost/http/status.hpp> | ||
| #include <boost/url/grammar/ci_string.hpp> | ||
| #include <boost/url/grammar/hexdig_chars.hpp> | ||
| #include "src/server/detail/pct_decode.hpp" | ||
|
|
@@ -206,8 +208,8 @@ dispatch_loop(route_params& p, bool is_options) const | |
| std::size_t last_matched = SIZE_MAX; | ||
| std::uint32_t current_depth = 0; | ||
|
|
||
| std::uint64_t options_methods = 0; | ||
| std::vector<std::string> options_custom_verbs; | ||
| std::uint64_t matched_methods = 0; | ||
| std::vector<std::string> matched_custom_verbs; | ||
|
|
||
| std::size_t path_stack[router_base::max_path_depth]; | ||
| path_stack[0] = 0; | ||
|
|
@@ -286,12 +288,12 @@ dispatch_loop(route_params& p, bool is_options) const | |
| if(!ancestors_ok) | ||
| continue; | ||
|
|
||
| // Collect methods from matching end-route matchers for OPTIONS | ||
| if(is_options && m.end_) | ||
| // Collect methods from matching end-route matchers | ||
| if(m.end_) | ||
| { | ||
| options_methods |= m.allowed_methods_; | ||
| matched_methods |= m.allowed_methods_; | ||
| for(auto const& v : m.custom_verbs_) | ||
| options_custom_verbs.push_back(v); | ||
| matched_custom_verbs.push_back(v); | ||
| } | ||
|
Comment on lines
+291
to
297
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Method collection runs unconditionally for every matching end-route matcher.
Proposed fix — deduplicate before building if(is_options && matched_methods != 0 && options_handler_)
{
+ std::sort(matched_custom_verbs.begin(), matched_custom_verbs.end());
+ matched_custom_verbs.erase(
+ std::unique(matched_custom_verbs.begin(), matched_custom_verbs.end()),
+ matched_custom_verbs.end());
std::string allow = build_allow_header(matched_methods, matched_custom_verbs);
co_return co_await options_handler_->invoke(p, allow);
}
// 405 fallback: path matched but method didn't
if(!is_options &&
(matched_methods != 0 || !matched_custom_verbs.empty()))
{
+ std::sort(matched_custom_verbs.begin(), matched_custom_verbs.end());
+ matched_custom_verbs.erase(
+ std::unique(matched_custom_verbs.begin(), matched_custom_verbs.end()),
+ matched_custom_verbs.end());
std::string allow = build_allow_header(matched_methods, matched_custom_verbs);🤖 Prompt for AI Agents |
||
|
|
||
| if(m.end_ && !e.match_method( | ||
|
|
@@ -364,12 +366,23 @@ dispatch_loop(route_params& p, bool is_options) const | |
| co_return route_error(pv.ec_); | ||
|
|
||
| // OPTIONS fallback | ||
| if(is_options && options_methods != 0 && options_handler_) | ||
| if(is_options && matched_methods != 0 && options_handler_) | ||
| { | ||
| std::string allow = build_allow_header(options_methods, options_custom_verbs); | ||
| std::string allow = build_allow_header(matched_methods, matched_custom_verbs); | ||
| co_return co_await options_handler_->invoke(p, allow); | ||
| } | ||
|
|
||
| // 405 fallback: path matched but method didn't | ||
| if(!is_options && | ||
| (matched_methods != 0 || !matched_custom_verbs.empty())) | ||
| { | ||
| std::string allow = build_allow_header(matched_methods, matched_custom_verbs); | ||
| p.res.set(field::allow, allow); | ||
| p.res.set_status(status::method_not_allowed); | ||
| (void)(co_await p.send()); | ||
| co_return route_done; | ||
| } | ||
|
Comment on lines
+375
to
+384
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n src/server/detail/router_base.cpp | sed -n '350,400p'Repository: cppalliance/http Length of output: 1718 🏁 Script executed: # Search for matched_methods initialization and usage
rg -n 'matched_methods' src/server/detail/router_base.cpp -B2 -A2Repository: cppalliance/http Length of output: 1049 🏁 Script executed: # Look for route_next and 405 related logic
rg -n 'route_next\|method_not_allowed' src/server/detail/router_base.cpp -B3 -A3Repository: cppalliance/http Length of output: 42 🏁 Script executed: # Search for any tests related to this scenario
find . -name "*test*.cpp" -o -name "*_test.cpp" | xargs grep -l "route_next\|method_not_allowed" 2>/dev/null | head -5Repository: cppalliance/http Length of output: 42 🏁 Script executed: cat -n src/server/detail/router_base.cpp | sed -n '200,310p'Repository: cppalliance/http Length of output: 4102 🏁 Script executed: # Search for where the current request method is checked against allowed methods
rg -n 'pv\.verb_|allowed_methods' src/server/detail/router_base.cpp -B2 -A2Repository: cppalliance/http Length of output: 1227 🏁 Script executed: # Look for the handler invocation and route_next handling
rg -n 'handler.*invoke|route_next' src/server/detail/router_base.cpp -B3 -A3Repository: cppalliance/http Length of output: 913 🏁 Script executed: cat -n src/server/detail/router_base.cpp | sed -n '300,375p'Repository: cppalliance/http Length of output: 2412 🏁 Script executed: # Look for where handlers are invoked in the loop
rg -n '\.invoke\(p' src/server/detail/router_base.cpp -B5 -A5Repository: cppalliance/http Length of output: 42 🏁 Script executed: find . -type f -name "*.cpp" | xargs grep -l "405\|method_not_allowed" 2>/dev/null | head -10Repository: cppalliance/http Length of output: 327 🏁 Script executed: # Search for test files related to router
find . -type f \( -name "*test*.cpp" -o -name "test_*.cpp" \) | head -10Repository: cppalliance/http Length of output: 88 🏁 Script executed: # Look for tests that cover route_next behavior
rg -l 'route_next' --type=cpp | grep -i test | head -5Repository: cppalliance/http Length of output: 227 🏁 Script executed: # Search for 405 tests in router test files
rg -n '405\|method_not_allowed' test/unit/server/router.cpp -B3 -A3Repository: cppalliance/http Length of output: 42 🏁 Script executed: # Look for tests with route_next and method handling
rg -n 'route_next' test/unit/server/router.cpp -B5 -A5 | head -50Repository: cppalliance/http Length of output: 1615 🏁 Script executed: # Search for test cases that test 405 or method_not_allowed behavior
rg -n 'TEST\|BOOST_AUTO_TEST\|method_not_allowed\|405' test/unit/server/router.cpp | head -30Repository: cppalliance/http Length of output: 42 🏁 Script executed: # Look for tests combining route_next with method-specific routes
rg -n 'on.*GET\|on.*POST.*h_next\|\.get\(' test/unit/server/router.cpp -B2 -A2 | head -40Repository: cppalliance/http Length of output: 42 🏁 Script executed: # Get an overview of router test structure
cat -n test/unit/server/router.cpp | head -150Repository: cppalliance/http Length of output: 5283 🏁 Script executed: # Search for any tests involving method selection and route_next
rg -n 'get\(|post\(|put\(' test/unit/server/router.cpp | head -20Repository: cppalliance/http Length of output: 42 🏁 Script executed: cat -n test/unit/server/router.cpp | sed -n '150,400p'Repository: cppalliance/http Length of output: 10382 🏁 Script executed: # Look for test methods that test method-based routing
rg -n 'BOOST_FIXTURE_TEST_CASE|void|auto.*test' test/unit/server/router.cpp | grep -A2 'test' | head -30Repository: cppalliance/http Length of output: 604 🏁 Script executed: cat -n test/unit/server/router.cpp | sed -n '394,470p'Repository: cppalliance/http Length of output: 2933 🏁 Script executed: # Look for any 405-related test cases
rg -n 'status.*method\|method.*status\|405\|method_not_allowed' test/unit/server/router.cppRepository: cppalliance/http Length of output: 42 🏁 Script executed: rg -n '405\|method_not_allowed' test/unit/server/detail/router_base.cppRepository: cppalliance/http Length of output: 42 🏁 Script executed: # Check if there are tests that specifically test the scenario described in the bug
# (handler matches both path and method, returns route_next, then falls through)
cat -n test/unit/server/detail/router_base.cpp | head -100Repository: cppalliance/http Length of output: 3321 🏁 Script executed: # Let me trace through the exact scenario: what is pv.verb_ and how is it set?
rg -n 'pv.verb_.*=' src/server/detail/router_base.cppRepository: cppalliance/http Length of output: 190 🏁 Script executed: # Examine the match_method function to understand how method matching works
rg -n 'match_method' src/server/detail/router_base.cpp -B5 -A10Repository: cppalliance/http Length of output: 509 🏁 Script executed: # Let me understand the complete picture of route entry and what e.match_method does
rg -n 'match_method' src/server/ -B2 -A5 --type=cpp | head -50Repository: cppalliance/http Length of output: 967 🏁 Script executed: # Find the router_entry structure to understand match_method
rg -n 'struct.*router_entry\|bool match_method' src/server/ --type=cpp -A5 | head -40Repository: cppalliance/http Length of output: 42 🏁 Script executed: cat -n src/server/detail/router_base.hpp | sed -n '68,100p'Repository: cppalliance/http Length of output: 1066 🏁 Script executed: # Get the complete router_entry structure
rg -n 'struct router_entry' src/server/detail/router_base.hpp -A20Repository: cppalliance/http Length of output: 42 🏁 Script executed: # Now let me verify the complete picture: after handler returns route_next, is there any way
# the current request's method could be saved or flagged?
rg -n 'route_what::next' src/server/detail/router_base.cpp -B10 -A10Repository: cppalliance/http Length of output: 807 🏁 Script executed: # Check if there's any tracking of whether current method was matched
rg -n 'current.*method\|method.*matched' src/server/detail/router_base.cpp -iRepository: cppalliance/http Length of output: 42 🏁 Script executed: # Now let me verify the logic of the proposed fix by understanding pv.verb_ and matched_methods structure
cat -n src/server/detail/router_base.cpp | sed -n '206,220p'Repository: cppalliance/http Length of output: 625 🏁 Script executed: # Understand the bit manipulation: how are methods stored in matched_methods?
rg -n '1ULL << static_cast' src/server/ --type=cpp -B2 -A2Repository: cppalliance/http Length of output: 1237 🏁 Script executed: # Verify the complete scenario by creating a simple trace
cat > /tmp/verify_405.txt << 'EOF'
Scenario: GET handler at "/" returns route_next
1. Request: GET /
2. Path matching succeeds
3. matched_methods gets 0 (initially)
4. Loop iteration 1: find handler for GET /
- Path matcher matches (end_ = true)
- Line 294: matched_methods |= m.allowed_methods_ (now has GET bit set)
- Line 299-304: e.match_method() returns true (GET == GET)
- Line 319: Handler invoked, returns route_next
- Line 330-333: Loop continues (++i)
5. No more handlers match
6. Loop exits
7. Line 376-384: 405 check fires
- matched_methods != 0 (true - has GET bit)
- is_options is false (true)
- Condition is true, sends 405 Method Not Allowed
- BUT: the request method WAS in matched_methods!
The bug: The 405 check never verifies that the CURRENT REQUEST'S METHOD
is absent from matched_methods. It only checks that SOME method was matched.
EOF
cat /tmp/verify_405.txtRepository: cppalliance/http Length of output: 910 🏁 Script executed: # Verify the proposed fix checks the correct logic
cat > /tmp/verify_fix.txt << 'EOF'
Proposed fix logic:
1. method_is_matched = check if pv.verb_ is in matched_methods
2. For custom verbs: check if pv.verb_str_ is in matched_custom_verbs
3. Handle "all methods" case: matched_methods == ~0ULL
4. Only fire 405 if:
- NOT is_options AND
- NOT method_is_matched AND <-- NEW: ensure current method isn't matched
- (matched_methods != 0 OR !matched_custom_verbs.empty())
This is correct!
EOF
cat /tmp/verify_fix.txtRepository: cppalliance/http Length of output: 468 Bug: 405 fires incorrectly even when the request method IS in the matched set. The 405 fallback at lines 376-384 checks only that The condition must additionally verify that the current request's method is not in the matched set before returning 405. Proposed fix // 405 fallback: path matched but method didn't
- if(!is_options &&
- (matched_methods != 0 || !matched_custom_verbs.empty()))
+ bool method_is_matched = false;
+ if(pv.verb_ != http::method::unknown)
+ method_is_matched = (matched_methods & (1ULL << static_cast<unsigned>(pv.verb_))) != 0;
+ else
+ {
+ for(auto const& v : matched_custom_verbs)
+ {
+ if(v == pv.verb_str_)
+ {
+ method_is_matched = true;
+ break;
+ }
+ }
+ }
+ // Also treat "all methods" as matching
+ if(matched_methods == ~0ULL)
+ method_is_matched = true;
+
+ if(!is_options && !method_is_matched &&
+ (matched_methods != 0 || !matched_custom_verbs.empty()))
{🤖 Prompt for AI Agents |
||
|
|
||
| co_return route_next; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -74,10 +74,20 @@ do_http_session() | |
|
|
||
| { | ||
| auto rv = co_await fr.dispatch(rp.req.method(), rp.url, rp); | ||
|
|
||
| if(rv.what() == route_what::close) | ||
| break; | ||
|
|
||
| if(rv.what() == route_what::next) | ||
| { | ||
| rp.status(http::status::not_found); | ||
| (void)(co_await rp.send()); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Discarded send errors may leave the connection in a bad state. Both Also applies to: 90-90 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| if(rv.failed()) | ||
| { | ||
| // VFALCO log rv.error() | ||
| break; | ||
| rp.status(http::status::internal_server_error); | ||
| (void)(co_await rp.send()); | ||
| } | ||
|
|
||
| if(! rp.res.keep_alive()) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Default OPTIONS handler discards send error.
Same pattern as in
http_worker.cpp— ifrp.send()fails, the error is silently discarded androute_doneis returned. This means the caller won't know the response wasn't actually sent. Low severity since OPTIONS responses are lightweight, but worth noting for consistency.🤖 Prompt for AI Agents