libs/http/src/server/router_types.cpp

51.1% Lines (23/45) 50.0% Functions (5/10) 45.5% Branches (10/22)
libs/http/src/server/router_types.cpp
Line Branch Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/http
8 //
9
10 #include <boost/http/server/router_types.hpp>
11 #include <boost/http/server/router.hpp>
12 #include <boost/http/server/etag.hpp>
13 #include <boost/http/server/fresh.hpp>
14 #include <boost/http/detail/except.hpp>
15 #include <boost/url/grammar/ci_string.hpp>
16 #include <boost/capy/buffers/make_buffer.hpp>
17 #include <boost/assert.hpp>
18 #include <cstring>
19
20 namespace boost {
21 namespace system {
22 template<>
23 struct is_error_code_enum<
24 ::boost::http::route_what>
25 {
26 static bool const value = true;
27 };
28 } // system
29 } // boost
30
31 namespace boost {
32 namespace http {
33
34 namespace {
35
36 struct route_what_cat_type
37 : system::error_category
38 {
39 constexpr route_what_cat_type()
40 : error_category(0x7a8b3c4d5e6f1029)
41 {
42 }
43
44 const char* name() const noexcept override
45 {
46 return "boost.http.route_what";
47 }
48
49 std::string message(int code) const override
50 {
51 return message(code, nullptr, 0);
52 }
53
54 char const* message(
55 int code,
56 char*,
57 std::size_t) const noexcept override
58 {
59 switch(static_cast<route_what>(code))
60 {
61 case route_what::done: return "done";
62 case route_what::error: return "error";
63 case route_what::next: return "next";
64 case route_what::next_route: return "next_route";
65 case route_what::close: return "close";
66 default:
67 return "?";
68 }
69 }
70 };
71
72 route_what_cat_type route_what_cat;
73
74 } // (anon)
75
76 system::error_code
77 150 make_error_code(
78 route_what w) noexcept
79 {
80 return system::error_code{
81 150 static_cast<int>(w), route_what_cat};
82 }
83
84 //----------------------------------------------------------
85
86 13 route_result::
87 route_result(
88 13 system::error_code ec)
89 13 : ec_(ec)
90 {
91
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
13 if(! ec.failed())
92 detail::throw_invalid_argument();
93 13 }
94
95 void
96 120 route_result::
97 set(route_what w)
98 {
99 120 ec_ = make_error_code(w);
100 120 }
101
102 auto
103 428 route_result::
104 what() const noexcept ->
105 route_what
106 {
107
2/2
✓ Branch 1 taken 362 times.
✓ Branch 2 taken 66 times.
428 if(! ec_.failed())
108 362 return route_what::done;
109
2/2
✓ Branch 1 taken 40 times.
✓ Branch 2 taken 26 times.
66 if(&ec_.category() != &route_what_cat)
110 40 return route_what::error;
111
2/2
✓ Branch 2 taken 22 times.
✓ Branch 3 taken 4 times.
26 if( ec_ == route_what::next)
112 22 return route_what::next;
113
2/2
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 time.
4 if( ec_ == route_what::next_route)
114 3 return route_what::next_route;
115 //if( ec_ == route_what::close)
116 1 return route_what::close;
117 }
118
119 auto
120 9 route_result::
121 error() const noexcept ->
122 system::error_code
123 {
124
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 if(&ec_.category() != &route_what_cat)
125 9 return ec_;
126 return {};
127 }
128
129 //------------------------------------------------
130
131 bool
132 route_params_base::
133 is_method(
134 core::string_view s) const noexcept
135 {
136 auto m = http::string_to_method(s);
137 if(m != http::method::unknown)
138 return verb_ == m;
139 return s == verb_str_;
140 }
141
142 //------------------------------------------------
143
144 capy::io_task<>
145 route_params::
146 send(std::string_view body)
147 {
148 auto const sc = res.status();
149
150 // 204 No Content / 304 Not Modified: strip headers, no body
151 if(sc == status::no_content ||
152 sc == status::not_modified)
153 {
154 res.erase(field::content_type);
155 res.erase(field::content_length);
156 res.erase(field::transfer_encoding);
157 co_return co_await res_body.write_eof();
158 }
159
160 // 205 Reset Content: Content-Length=0, no body
161 if(sc == status::reset_content)
162 {
163 res.erase(field::transfer_encoding);
164 res.set_payload_size(0);
165 co_return co_await res_body.write_eof();
166 }
167
168 // Set Content-Type if not already set
169 if(! res.exists(field::content_type))
170 {
171 if(! body.empty() && body[0] == '<')
172 res.set(field::content_type,
173 "text/html; charset=utf-8");
174 else
175 res.set(field::content_type,
176 "text/plain; charset=utf-8");
177 }
178
179 // Generate ETag if not already set
180 if(! res.exists(field::etag))
181 res.set(field::etag, etag(body));
182
183 // Set Content-Length if not already set
184 if(! res.exists(field::content_length))
185 res.set_payload_size(body.size());
186
187 // Freshness check: auto-304 for conditional GET
188 if(is_fresh(req, res))
189 {
190 res.set_status(status::not_modified);
191 res.erase(field::content_type);
192 res.erase(field::content_length);
193 res.erase(field::transfer_encoding);
194 co_return co_await res_body.write_eof();
195 }
196
197 // HEAD: send headers only, skip body
198 if(req.method() == method::head)
199 {
200 co_return co_await res_body.write_eof();
201 }
202
203 auto [ec, n] = co_await res_body.write(
204 capy::make_buffer(body), true);
205 co_return {ec};
206 }
207
208 } // http
209 } // boost
210