libs/http/src/server/router_types.cpp

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