Line data Source code
1 : #include "route_handler.hpp"
2 : #include "../response.hpp"
3 : #include "../../../util/logger.hpp"
4 :
5 : namespace thinger::http {
6 :
7 11913 : route_handler::route_handler() = default;
8 :
9 34202 : route_builder route_handler::operator[](method http_method) {
10 34202 : return route_builder(http_method, routes_[http_method], trees_[http_method], &schema_components_);
11 : }
12 :
13 89548 : const route* route_handler::find_route(std::shared_ptr<request> req) {
14 89548 : auto http_request = req->get_http_request();
15 89548 : const auto& request_method = http_request->get_method();
16 89548 : std::string_view path = http_request->get_uri();
17 89548 : path = path.substr(0, path.find('?'));
18 :
19 89548 : LOG_DEBUG("Finding route for {} {}", get_method(request_method), path);
20 :
21 89548 : detail::route_captures captures;
22 89548 : const auto* found = match(request_method, path, captures);
23 : // HEAD is answered by the GET route of the path when no HEAD route matches it (the
24 : // response drops the body): only searched once HEAD missed, so GET pays nothing for it
25 89548 : if (!found && request_method == method::HEAD) {
26 294 : captures.clear();
27 294 : found = match(method::GET, path, captures);
28 : }
29 89548 : if (!found) {
30 60256 : LOG_DEBUG("No matching route found for {} {}", get_method(request_method), path);
31 60256 : return nullptr;
32 : }
33 29292 : LOG_DEBUG("Matched route: {}", found->target->get_pattern());
34 :
35 : // Path parameters, as captured (raw, not percent-decoded)
36 35282 : for (size_t i = 0; i < captures.size() && i < found->names.size(); ++i) {
37 5990 : if (!found->names[i].empty() && captures[i].data()) {
38 17970 : req->set_uri_parameter(found->names[i], std::string(captures[i]));
39 : }
40 : }
41 :
42 29292 : req->set_matched_route(found->target);
43 29292 : return found->target;
44 89548 : }
45 :
46 1219 : thinger::awaitable<void> route_handler::handle_unmatched(request& req, response& res) {
47 : if (fallback_handler_) {
48 : co_await fallback_handler_(req, res);
49 : co_return;
50 : }
51 :
52 : // A method with routes (HEAD has those of GET too) → 404
53 : auto http_request = req.get_http_request();
54 : const auto& request_method = http_request->get_method();
55 : if (routes_.contains(request_method) || (request_method == method::HEAD && routes_.contains(method::GET))) {
56 : res.error(http_response::status::not_found);
57 : co_return;
58 : }
59 :
60 : // A method without routes at all → 405, with the methods of the routes matching the path
61 : // (RFC 9110, 15.5.6), HEAD wherever GET: empty if none does
62 : std::string_view path = http_request->get_uri();
63 : path = path.substr(0, path.find('?'));
64 : std::string allowed;
65 : for (auto candidate : {method::GET, method::HEAD, method::POST, method::PUT, method::DELETE,
66 : method::TRACE, method::OPTIONS, method::CONNECT, method::PATCH}) {
67 : detail::route_captures captures;
68 : if (!match(candidate, path, captures) && !(candidate == method::HEAD && match(method::GET, path, captures))) {
69 : continue;
70 : }
71 : if (!allowed.empty()) allowed += ", ";
72 : allowed += get_method(candidate);
73 : }
74 : res.header("Allow", allowed);
75 : res.error(http_response::status::not_allowed);
76 2438 : }
77 :
78 : } // namespace thinger::http
|