summaryrefslogtreecommitdiffstats
path: root/server/src/http_server.cppm
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/http_server.cppm')
-rw-r--r--server/src/http_server.cppm1249
1 files changed, 723 insertions, 526 deletions
diff --git a/server/src/http_server.cppm b/server/src/http_server.cppm
index 0770d97..0f7fda7 100644
--- a/server/src/http_server.cppm
+++ b/server/src/http_server.cppm
@@ -1,12 +1,12 @@
1module; 1module;
2 2
3#include <boost/config.hpp>
4#include <boost/asio/as_tuple.hpp> 3#include <boost/asio/as_tuple.hpp>
5#include <boost/asio/awaitable.hpp> 4#include <boost/asio/awaitable.hpp>
6#include <boost/asio/co_spawn.hpp> 5#include <boost/asio/co_spawn.hpp>
7#include <boost/asio/ip/tcp.hpp> 6#include <boost/asio/ip/tcp.hpp>
8#include <boost/beast/core.hpp> 7#include <boost/beast/core.hpp>
9#include <boost/beast/http.hpp> 8#include <boost/beast/http.hpp>
9#include <boost/config.hpp>
10#include <boost/json/serialize.hpp> 10#include <boost/json/serialize.hpp>
11#include <boost/url.hpp> 11#include <boost/url.hpp>
12 12
@@ -23,639 +23,836 @@ using tcp = net::ip::tcp;
23 23
24namespace routemon::http { 24namespace routemon::http {
25 25
26 struct readable_request { 26struct readable_request
27 util::not_null<bhttp::request_parser<bhttp::empty_body>*> p; 27{
28 util::not_null<beast::tcp_stream*> strm; 28 util::not_null<bhttp::request_parser<bhttp::empty_body>*> p;
29 util::not_null<beast::flat_buffer*> buf; 29 util::not_null<beast::tcp_stream*> strm;
30 }; 30 util::not_null<beast::flat_buffer*> buf;
31};
31 32
32 class presponse { 33class presponse
33 public: 34{
34 using const_buffers_type = beast::span<net::const_buffer>; 35public:
36 using const_buffers_type = beast::span<net::const_buffer>;
35 37
36 private: 38private:
37 struct impl_base { 39 struct impl_base
38 virtual ~impl_base() = default; 40 {
39 virtual auto header() -> bhttp::response_header<bhttp::fields>& = 0; 41 virtual ~impl_base() = default;
40 virtual auto header() const -> bhttp::response_header<bhttp::fields> const& = 0; 42 virtual auto header() -> bhttp::response_header<bhttp::fields>& = 0;
41 virtual auto is_done() const -> bool = 0; 43 virtual auto header() const
42 virtual auto prepare(beast::error_code&) -> const_buffers_type = 0; 44 -> bhttp::response_header<bhttp::fields> const& = 0;
43 virtual auto consume(std::size_t n) -> void = 0; 45 virtual auto is_done() const -> bool = 0;
44 virtual auto keep_alive() const -> bool = 0; 46 virtual auto prepare(beast::error_code&) -> const_buffers_type = 0;
45 }; 47 virtual auto consume(std::size_t n) -> void = 0;
46 std::unique_ptr<impl_base> impl_; 48 virtual auto keep_alive() const -> bool = 0;
49 };
50 std::unique_ptr<impl_base> impl_;
47 51
48 template<bhttp::concepts::body Body> 52 template <bhttp::concepts::body Body>
49 class impl : public impl_base { 53 class impl : public impl_base
50 // Initializes in the response state. 54 {
51 // At the first call to prepare, we switch to the message generator state. 55 // Initializes in the response state.
52 // After that point, header may not be called anymore (it will throw). 56 // At the first call to prepare, we switch to the message generator
53 std::variant<bhttp::response<Body>, bhttp::message_generator> state_; 57 // state. After that point, header may not be called anymore (it will
58 // throw).
59 std::variant<bhttp::response<Body>, bhttp::message_generator> state_;
54 60
55 auto ensure_message_generator() -> bhttp::message_generator& { 61 auto ensure_message_generator() -> bhttp::message_generator&
56 if (auto prsp = std::get_if<bhttp::response<Body>>(&state_)) { 62 {
57 auto rsp = bhttp::response<Body>{std::move(*prsp)}; 63 if (auto prsp = std::get_if<bhttp::response<Body>>(&state_))
58 state_.template emplace<bhttp::message_generator>(std::move(rsp)); 64 {
59 } 65 auto rsp = bhttp::response<Body>{std::move(*prsp)};
60 return std::get<bhttp::message_generator>(state_); 66 state_.template emplace<bhttp::message_generator>(std::move(rsp));
61 } 67 }
68 return std::get<bhttp::message_generator>(state_);
69 }
62 70
63 public: 71 public:
64 explicit impl(bhttp::response<Body>&& rsp) : state_{std::move(rsp)} {} 72 explicit impl(bhttp::response<Body>&& rsp) : state_{std::move(rsp)} {}
65 73
66 auto header() -> bhttp::response_header<bhttp::fields>& override { 74 auto header() -> bhttp::response_header<bhttp::fields>& override
67 if (auto prsp = std::get_if<bhttp::response<Body>>(&state_)) { 75 {
68 return prsp->base(); 76 if (auto prsp = std::get_if<bhttp::response<Body>>(&state_))
69 } else { 77 {
70 // TODO: define custom exception type presponse::bad_header_access 78 return prsp->base();
71 throw std::logic_error{"header() may not be called after prepare()"};
72 }
73 } 79 }
74 auto header() const -> bhttp::response_header<bhttp::fields> const& override { 80 else
75 if (auto prsp = std::get_if<bhttp::response<Body>>(&state_)) { 81 {
76 return prsp->base(); 82 // TODO: define custom exception type
77 } else { 83 // presponse::bad_header_access
78 throw std::logic_error{"header() may not be called after prepare()"}; 84 throw std::logic_error{"header() may not be called after prepare()"};
79 }
80 } 85 }
81 86 }
82 auto is_done() const -> bool override { 87 auto header() const -> bhttp::response_header<bhttp::fields> const& override
83 if (auto pgen = std::get_if<bhttp::message_generator>(&state_)) { 88 {
84 return pgen->is_done(); 89 if (auto prsp = std::get_if<bhttp::response<Body>>(&state_))
85 } else /* still in the response state */ { 90 {
86 return false; 91 return prsp->base();
87 }
88 } 92 }
89 93 else
90 auto prepare(beast::error_code& ec) -> const_buffers_type override { 94 {
91 return ensure_message_generator().prepare(ec); 95 throw std::logic_error{"header() may not be called after prepare()"};
92 } 96 }
97 }
93 98
94 auto consume(std::size_t n) -> void override { 99 auto is_done() const -> bool override
95 ensure_message_generator().consume(n); 100 {
101 if (auto pgen = std::get_if<bhttp::message_generator>(&state_))
102 {
103 return pgen->is_done();
96 } 104 }
97 105 else /* still in the response state */
98 auto keep_alive() const noexcept -> bool override { 106 {
99 return state_.visit(util::overloaded{ 107 return false;
100 [](bhttp::response<Body> const& rsp) -> bool {
101 return rsp.keep_alive();
102 },
103 [](bhttp::message_generator const& gen) -> bool {
104 return gen.keep_alive();
105 },
106 });
107 } 108 }
108 };
109
110 public:
111 template<bhttp::concepts::body Body>
112 explicit presponse(bhttp::response<Body>&& rsp)
113 : impl_{new impl{std::move(rsp)}}
114 {}
115
116 auto header() -> bhttp::response_header<bhttp::fields>& {
117 return impl_->header();
118 }
119 auto header() const -> bhttp::response_header<bhttp::fields> const& {
120 return impl_->header();
121 }
122
123 auto is_done() const -> bool {
124 return impl_->is_done();
125 } 109 }
126 110
127 auto prepare(beast::error_code& ec) -> const_buffers_type { 111 auto prepare(beast::error_code& ec) -> const_buffers_type override
128 return impl_->prepare(ec); 112 {
113 return ensure_message_generator().prepare(ec);
129 } 114 }
130 115
131 auto consume(std::size_t n) -> void { 116 auto consume(std::size_t n) -> void override
132 return impl_->consume(n); 117 {
118 ensure_message_generator().consume(n);
133 } 119 }
134 120
135 auto keep_alive() const noexcept -> bool { 121 auto keep_alive() const noexcept -> bool override
136 return impl_->keep_alive(); 122 {
123 return state_.visit(
124 util::overloaded{
125 [](bhttp::response<Body> const& rsp) -> bool
126 { return rsp.keep_alive(); },
127 [](bhttp::message_generator const& gen) -> bool
128 { return gen.keep_alive(); },
129 });
137 } 130 }
138 }; 131 };
139 static_assert(beast::concepts::buffers_generator<presponse>);
140
141 template<class Ctx>
142 using next_handler_t = std::function<auto(Ctx) -> net::awaitable<presponse>>;
143 132
144 template<class OuterCtx, class InnerCtx> 133public:
145 using middleware_t = std::function<auto(OuterCtx, bhttp::request_header<bhttp::fields>&, next_handler_t<InnerCtx>) -> net::awaitable<presponse>>; 134 template <bhttp::concepts::body Body>
135 explicit presponse(bhttp::response<Body>&& rsp)
136 : impl_{new impl{std::move(rsp)}}
137 {
138 }
146 139
147 template<class Ctx> 140 auto header() -> bhttp::response_header<bhttp::fields>&
148 auto lax_cors_middleware(Ctx ctx, bhttp::request_header<bhttp::fields>& req_hdr, next_handler_t<Ctx> next) -> net::awaitable<presponse> { 141 {
149 std::ignore = req_hdr; 142 return impl_->header();
150 auto prersp = co_await next(ctx); 143 }
151 prersp.header().set(bhttp::field::access_control_allow_origin, "*"); 144 auto header() const -> bhttp::response_header<bhttp::fields> const&
152 co_return std::move(prersp); 145 {
146 return impl_->header();
153 } 147 }
154 148
155 template<class InnerCtx> 149 auto is_done() const -> bool { return impl_->is_done(); }
156 struct trace_id_ctx : InnerCtx {
157 trace::id trace_id = {};
158 };
159 150
160 template<class OuterCtx> 151 auto prepare(beast::error_code& ec) -> const_buffers_type
161 auto trace_id_middleware(OuterCtx ctx0, bhttp::request_header<bhttp::fields>& req_hdr, next_handler_t<trace_id_ctx<OuterCtx>> next) -> net::awaitable<presponse> { 152 {
162 std::ignore = req_hdr; 153 return impl_->prepare(ec);
163 auto ctx = trace_id_ctx{std::move(ctx0)};
164 auto prersp = co_await next(std::move(ctx));
165 prersp.header().set("X-Routemon-Trace-Id", std::string_view{ctx.trace_id.as_string()});
166 prersp.header().insert(bhttp::field::access_control_expose_headers, "X-Routemon-Trace-Id");
167 co_return std::move(prersp);
168 } 154 }
169 155
170 struct base_ctx { 156 auto consume(std::size_t n) -> void { return impl_->consume(n); }
171 std::locale locale;
172 };
173 157
174 template<class Ctx> 158 auto keep_alive() const noexcept -> bool { return impl_->keep_alive(); }
175 using basic_route_handler_fn_t = std::function<auto(Ctx, readable_request, std::vector<std::string> const& matches) -> net::awaitable<presponse>>; 159};
160static_assert(beast::concepts::buffers_generator<presponse>);
176 161
177 struct keep_alive { 162template <class Ctx>
178 bool value; 163using next_handler_t = std::function<auto(Ctx)->net::awaitable<presponse>>;
179 164
180 explicit keep_alive(bool value) : value{value} {} 165template <class OuterCtx, class InnerCtx>
181 }; 166using middleware_t =
167 std::function<auto(
168 OuterCtx, bhttp::request_header<bhttp::fields>&,
169 next_handler_t<InnerCtx>)
170 ->net::awaitable<presponse>>;
182 171
183 template<bhttp::concepts::body Body> 172template <class Ctx>
184 auto make_rsp(bhttp::status status, keep_alive ka) -> bhttp::response<Body> { 173auto lax_cors_middleware(
185 auto rsp = bhttp::response<Body>{}; // HTTP version gets set later 174 Ctx ctx, bhttp::request_header<bhttp::fields>& req_hdr,
186 rsp.result(status); 175 next_handler_t<Ctx> next) -> net::awaitable<presponse>
187 rsp.keep_alive(ka.value); 176{
188 return rsp; 177 std::ignore = req_hdr;
189 } 178 auto prersp = co_await next(ctx);
179 prersp.header().set(bhttp::field::access_control_allow_origin, "*");
180 co_return std::move(prersp);
181}
190 182
191 auto problem_rsp(base_ctx const& ctx, problem::details const& problem, keep_alive ka) -> presponse { 183template <class InnerCtx>
192 auto rsp = make_rsp<bhttp::string_body>(problem.status, ka); 184struct trace_id_ctx : InnerCtx
193 rsp.set(bhttp::field::content_type, "application/problem+json"); 185{
194 rsp.body() = json::serialize(json::value_from(problem, ctx.locale)); 186 trace::id trace_id = {};
195 rsp.prepare_payload(); 187};
196 return presponse{std::move(rsp)};
197 }
198 188
199 struct preflight_response { 189template <class OuterCtx>
200 verb_set allow_methods; 190auto trace_id_middleware(
201 std::vector<bhttp::field> allow_headers; 191 OuterCtx ctx0, bhttp::request_header<bhttp::fields>& req_hdr,
202 }; 192 next_handler_t<trace_id_ctx<OuterCtx>> next) -> net::awaitable<presponse>
203 auto make_preflight_rsp(preflight_response res, keep_alive ka) -> bhttp::response<bhttp::empty_body> { 193{
204 auto rsp = make_rsp<bhttp::empty_body>(bhttp::status::no_content, ka); 194 std::ignore = req_hdr;
205 auto allow_headers_str = res.allow_headers 195 auto ctx = trace_id_ctx{std::move(ctx0)};
206 | std::views::transform([](auto const& field) -> std::string_view { return bhttp::to_string(field); }) 196 auto prersp = co_await next(std::move(ctx));
207 | std::views::join_with(std::string_view{", "}) 197 prersp.header().set(
208 | std::ranges::to<std::string>(); 198 "X-Routemon-Trace-Id", std::string_view{ctx.trace_id.as_string()});
209 rsp.set(bhttp::field::access_control_allow_methods, res.allow_methods.to_string()); 199 prersp.header().insert(
210 rsp.set(bhttp::field::access_control_allow_headers, allow_headers_str); 200 bhttp::field::access_control_expose_headers, "X-Routemon-Trace-Id");
211 rsp.prepare_payload(); 201 co_return std::move(prersp);
212 return rsp; 202}
213 }
214 203
215 // Using base_ctx instead of a template here since that saves you 204struct base_ctx
216 // typing on invocation (and we do not care about the context type 205{
217 // anyway, but all context types should derive from base_ctx). 206 std::locale locale;
218 template<bhttp::concepts::body_reader Body> 207};
219 auto read_request(base_ctx const& ctx, readable_request&& r) -> net::awaitable<std::expected<bhttp::request<Body>, presponse>> { 208
220 std::ignore = ctx; 209template <class Ctx>
221 auto p = bhttp::request_parser<Body>{std::move(*r.p)}; 210using basic_route_handler_fn_t = std::function<
222 co_await bhttp::async_read(*r.strm, *r.buf, p); 211 auto(Ctx, readable_request, std::vector<std::string> const& matches)
223 co_return std::move(p.release()); 212 ->net::awaitable<presponse>>;
224 } 213
214struct keep_alive
215{
216 bool value;
225 217
226 template<> 218 explicit keep_alive(bool value) : value{value} {}
227 auto read_request<bhttp::empty_body>(base_ctx const& ctx, readable_request&& r) -> net::awaitable<std::expected<bhttp::request<bhttp::empty_body>, presponse>> { 219};
228 auto [ec, _] = co_await bhttp::async_read(*r.strm, *r.buf, *r.p, net::as_tuple); 220
229 if (ec == bhttp::error::unexpected_body) { 221template <bhttp::concepts::body Body>
230 auto tpl = problem::tpl{ 222auto make_rsp(bhttp::status status, keep_alive ka) -> bhttp::response<Body>
231 .status = bhttp::status::bad_request, 223{
232 .title = translate("No body expected for this request"), 224 auto rsp = bhttp::response<Body>{}; // HTTP version gets set later
225 rsp.result(status);
226 rsp.keep_alive(ka.value);
227 return rsp;
228}
229
230auto problem_rsp(
231 base_ctx const& ctx, problem::details const& problem, keep_alive ka)
232 -> presponse
233{
234 auto rsp = make_rsp<bhttp::string_body>(problem.status, ka);
235 rsp.set(bhttp::field::content_type, "application/problem+json");
236 rsp.body() = json::serialize(json::value_from(problem, ctx.locale));
237 rsp.prepare_payload();
238 return presponse{std::move(rsp)};
239}
240
241struct preflight_response
242{
243 verb_set allow_methods;
244 std::vector<bhttp::field> allow_headers;
245};
246auto make_preflight_rsp(preflight_response res, keep_alive ka)
247 -> bhttp::response<bhttp::empty_body>
248{
249 auto rsp = make_rsp<bhttp::empty_body>(bhttp::status::no_content, ka);
250 auto allow_headers_str = res.allow_headers
251 | std::views::transform(
252 [](auto const& field) -> std::string_view
253 { return bhttp::to_string(field); })
254 | std::views::join_with(std::string_view{", "})
255 | std::ranges::to<std::string>();
256 rsp.set(
257 bhttp::field::access_control_allow_methods,
258 res.allow_methods.to_string());
259 rsp.set(bhttp::field::access_control_allow_headers, allow_headers_str);
260 rsp.prepare_payload();
261 return rsp;
262}
263
264// Using base_ctx instead of a template here since that saves you
265// typing on invocation (and we do not care about the context type
266// anyway, but all context types should derive from base_ctx).
267template <bhttp::concepts::body_reader Body>
268auto read_request(base_ctx const& ctx, readable_request&& r)
269 -> net::awaitable<std::expected<bhttp::request<Body>, presponse>>
270{
271 std::ignore = ctx;
272 auto p = bhttp::request_parser<Body>{std::move(*r.p)};
273 co_await bhttp::async_read(*r.strm, *r.buf, p);
274 co_return std::move(p.release());
275}
276
277template <>
278auto read_request<bhttp::empty_body>(base_ctx const& ctx, readable_request&& r)
279 -> net::awaitable<
280 std::expected<bhttp::request<bhttp::empty_body>, presponse>>
281{
282 auto [ec, _] =
283 co_await bhttp::async_read(*r.strm, *r.buf, *r.p, net::as_tuple);
284 if (ec == bhttp::error::unexpected_body)
285 {
286 auto tpl = problem::tpl{
287 .status = bhttp::status::bad_request,
288 .title = translate("No body expected for this request"),
233 .type_uri = "https://routemon.fautchen.eu/problems/unexpected-body", 289 .type_uri = "https://routemon.fautchen.eu/problems/unexpected-body",
234 }; 290 };
235 co_return std::unexpected{problem_rsp(ctx, tpl.instantiate(), keep_alive{false})}; 291 co_return std::unexpected{problem_rsp(
236 } else if (ec) { 292 ctx, tpl.instantiate(), keep_alive{false})};
237 throw boost::system::system_error{ec};
238 }
239 co_return r.p->release();
240 } 293 }
294 else if (ec)
295 {
296 throw boost::system::system_error{ec};
297 }
298 co_return r.p->release();
299}
241 300
242 template<class InnerCtx> 301template <class InnerCtx>
243 struct routed_ctx : InnerCtx { 302struct routed_ctx : InnerCtx
244 verb_set route_methods; 303{
245 }; 304 verb_set route_methods;
305};
246 306
247 template<class Ctx> 307template <class Ctx>
248 requires requires(Ctx ctx) { 308 requires requires(Ctx ctx) {
249 // Ctx must be derived from an instantiation of routed_ctx 309 // Ctx must be derived from an instantiation of routed_ctx
250 []<class InnerCtx>(routed_ctx<InnerCtx> const&) {}(ctx); 310 []<class InnerCtx>(routed_ctx<InnerCtx> const&) {}(ctx);
251 } 311 }
252 auto default_options_handler(Ctx const& ctx, readable_request r, std::vector<std::string> const&) -> net::awaitable<presponse> { 312auto default_options_handler(
253 auto mreq = co_await read_request<bhttp::empty_body>(ctx, std::move(r)); 313 Ctx const& ctx, readable_request r, std::vector<std::string> const&)
254 if (!mreq) 314 -> net::awaitable<presponse>
255 co_return std::move(mreq.error()); 315{
316 auto mreq = co_await read_request<bhttp::empty_body>(ctx, std::move(r));
317 if (!mreq)
318 co_return std::move(mreq.error());
256 319
257 if (mreq->find(bhttp::field::access_control_request_method) != mreq->end()) { 320 if (mreq->find(bhttp::field::access_control_request_method) != mreq->end())
258 // CORS preflight request 321 {
259 co_return make_preflight_rsp(preflight_response{ 322 // CORS preflight request
260 // TODO: should access-control-allow-methods contain OPTIONS? 323 co_return make_preflight_rsp(
261 .allow_methods = ctx.route_methods, 324 preflight_response{
262 .allow_headers = {bhttp::field::content_type}, 325 // TODO: should access-control-allow-methods contain OPTIONS?
263 }, keep_alive{mreq->keep_alive()}); 326 .allow_methods = ctx.route_methods,
264 } else { 327 .allow_headers = {bhttp::field::content_type},
265 // Normal OPTIONS request 328 },
266 auto rsp = make_rsp<bhttp::empty_body>(bhttp::status::no_content, keep_alive{mreq->keep_alive()}); 329 keep_alive{mreq->keep_alive()});
267 rsp.set(bhttp::field::allow, ctx.route_methods.to_string());
268 rsp.prepare_payload();
269 co_return std::move(rsp);
270 }
271 } 330 }
272 331 else
273 auto global_options_handler(base_ctx const& ctx, readable_request r) -> net::awaitable<presponse> { 332 {
274 // TODO: switch to "small (4KB) discarded" body type, similar to what Go does? 333 // Normal OPTIONS request
275 // Same goes for default_options_handler? Not sure. 334 auto rsp = make_rsp<bhttp::empty_body>(
276 if (auto res = co_await read_request<bhttp::empty_body>(ctx, std::move(r)); !res) 335 bhttp::status::no_content, keep_alive{mreq->keep_alive()});
277 co_return std::move(res.error()); 336 rsp.set(bhttp::field::allow, ctx.route_methods.to_string());
278 auto req = r.p->release();
279 auto rsp = make_rsp<bhttp::empty_body>(bhttp::status::no_content, keep_alive{req.keep_alive()});
280 rsp.prepare_payload(); 337 rsp.prepare_payload();
281 co_return std::move(rsp); 338 co_return std::move(rsp);
282 } 339 }
340}
283 341
284 template<class Ctx> 342auto global_options_handler(base_ctx const& ctx, readable_request r)
285 auto id_middleware(Ctx ctx, bhttp::request_header<bhttp::fields>&, next_handler_t<Ctx> next) -> net::awaitable<presponse> { 343 -> net::awaitable<presponse>
286 co_return co_await next(std::move(ctx)); 344{
287 } 345 // TODO: switch to "small (4KB) discarded" body type, similar to what Go
346 // does? Same goes for default_options_handler? Not sure.
347 if (auto res = co_await read_request<bhttp::empty_body>(ctx, std::move(r));
348 !res)
349 co_return std::move(res.error());
350 auto req = r.p->release();
351 auto rsp = make_rsp<bhttp::empty_body>(
352 bhttp::status::no_content, keep_alive{req.keep_alive()});
353 rsp.prepare_payload();
354 co_return std::move(rsp);
355}
288 356
289 template<class A, class B, class C> 357template <class Ctx>
290 auto middleware_compose(middleware_t<A, B> ab, middleware_t<B, C> bc) -> middleware_t<A, C> { 358auto id_middleware(
291 return [ab = std::move(ab), bc = std::move(bc)](A a, bhttp::request_header<bhttp::fields>& header, next_handler_t<C> next) -> net::awaitable<presponse> { 359 Ctx ctx, bhttp::request_header<bhttp::fields>&, next_handler_t<Ctx> next)
292 co_return co_await ab(std::move(a), header, [&](B b) -> net::awaitable<presponse> { 360 -> net::awaitable<presponse>
293 co_return co_await bc(std::move(b), header, next); 361{
294 }); 362 co_return co_await next(std::move(ctx));
295 }; 363}
296 }
297 364
298 template<class A, class B> 365template <class A, class B, class C>
299 auto middleware_wrap_fn(middleware_t<A, B> ab, basic_route_handler_fn_t<B> fn) -> basic_route_handler_fn_t<A> { 366auto middleware_compose(middleware_t<A, B> ab, middleware_t<B, C> bc)
300 return [ab = std::move(ab), fn = std::move(fn)](A a_ctx, readable_request r, std::vector<std::string> const& matches) -> net::awaitable<presponse> { 367 -> middleware_t<A, C>
301 co_return co_await ab(std::move(a_ctx), r.p->get().base(), [&](B b_ctx) -> net::awaitable<presponse> { 368{
302 co_return co_await fn(std::move(b_ctx), r, matches); 369 return [ab = std::move(ab), bc = std::move(bc)](
303 }); 370 A a, bhttp::request_header<bhttp::fields>& header,
304 }; 371 next_handler_t<C> next) -> net::awaitable<presponse>
305 } 372 {
373 co_return co_await ab(
374 std::move(a), header, [&](B b) -> net::awaitable<presponse>
375 { co_return co_await bc(std::move(b), header, next); });
376 };
377}
378
379template <class A, class B>
380auto middleware_wrap_fn(middleware_t<A, B> ab, basic_route_handler_fn_t<B> fn)
381 -> basic_route_handler_fn_t<A>
382{
383 return
384 [ab = std::move(ab), fn = std::move(fn)](
385 A a_ctx, readable_request r,
386 std::vector<std::string> const& matches) -> net::awaitable<presponse>
387 {
388 co_return co_await ab(
389 std::move(a_ctx), r.p->get().base(),
390 [&](B b_ctx) -> net::awaitable<presponse>
391 { co_return co_await fn(std::move(b_ctx), r, matches); });
392 };
393}
306 394
307 template<std::default_initializable V> 395template <std::default_initializable V>
308 requires requires(V v) { 396 requires requires(V v) {
309 { static_cast<bool>(v) }; 397 { static_cast<bool>(v) };
310 } 398 }
311 struct handler_map { 399struct handler_map
312 V options = {}; 400{
313 V delete_ = {}; 401 V options = {};
314 V get = {}; 402 V delete_ = {};
315 V head = {}; 403 V get = {};
316 V post = {}; 404 V head = {};
317 V put = {}; 405 V post = {};
406 V put = {};
318 407
319 template<class Self> 408 template <class Self>
320 auto lookup(this Self&& self, supported_verb v) -> auto&& { 409 auto lookup(this Self&& self, supported_verb v) -> auto&&
321 switch (v.value) { 410 {
322 case supported_verb::options: return std::forward<Self>(self).options; 411 switch (v.value)
323 case supported_verb::delete_: return std::forward<Self>(self).delete_; 412 {
324 case supported_verb::get: return std::forward<Self>(self).get; 413 case supported_verb::options:
325 case supported_verb::head: return std::forward<Self>(self).head; 414 return std::forward<Self>(self).options;
326 case supported_verb::post: return std::forward<Self>(self).post; 415 case supported_verb::delete_:
327 case supported_verb::put: return std::forward<Self>(self).put; 416 return std::forward<Self>(self).delete_;
328 } 417 case supported_verb::get:
418 return std::forward<Self>(self).get;
419 case supported_verb::head:
420 return std::forward<Self>(self).head;
421 case supported_verb::post:
422 return std::forward<Self>(self).post;
423 case supported_verb::put:
424 return std::forward<Self>(self).put;
329 } 425 }
426 }
330 427
331 auto verbs() const -> verb_set { 428 auto verbs() const -> verb_set
332 auto set = verb_set{}; 429 {
333 if (static_cast<bool>(options)) 430 auto set = verb_set{};
334 set.enable(supported_verb::options); 431 if (static_cast<bool>(options))
335 if (static_cast<bool>(delete_)) 432 set.enable(supported_verb::options);
336 set.enable(supported_verb::delete_); 433 if (static_cast<bool>(delete_))
337 if (static_cast<bool>(get)) 434 set.enable(supported_verb::delete_);
338 set.enable(supported_verb::get); 435 if (static_cast<bool>(get))
339 if (static_cast<bool>(head)) 436 set.enable(supported_verb::get);
340 set.enable(supported_verb::head); 437 if (static_cast<bool>(head))
341 if (static_cast<bool>(post)) 438 set.enable(supported_verb::head);
342 set.enable(supported_verb::post); 439 if (static_cast<bool>(post))
343 if (static_cast<bool>(put)) 440 set.enable(supported_verb::post);
344 set.enable(supported_verb::put); 441 if (static_cast<bool>(put))
345 return set; 442 set.enable(supported_verb::put);
346 } 443 return set;
444 }
347 445
348 auto empty() const -> bool { 446 auto empty() const -> bool { return verbs().empty(); }
349 return verbs().empty();
350 }
351 447
352 template<std::default_initializable U> 448 template <std::default_initializable U>
353 auto map(std::invocable<V const&> auto f) const -> handler_map<U> 449 auto map(std::invocable<V const&> auto f) const -> handler_map<U>
354 requires std::assignable_from<U&, std::invoke_result_t<decltype(f), V const&>> 450 requires std::assignable_from<
355 { 451 U&, std::invoke_result_t<decltype(f), V const&>>
356 return { 452 {
453 return {
357 .options = static_cast<bool>(options) ? f(options) : U{}, 454 .options = static_cast<bool>(options) ? f(options) : U{},
358 .delete_ = static_cast<bool>(delete_) ? f(delete_) : U{}, 455 .delete_ = static_cast<bool>(delete_) ? f(delete_) : U{},
359 .get = static_cast<bool>(get) ? f(get) : U{}, 456 .get = static_cast<bool>(get) ? f(get) : U{},
360 .head = static_cast<bool>(head) ? f(head) : U{}, 457 .head = static_cast<bool>(head) ? f(head) : U{},
361 .post = static_cast<bool>(post) ? f(post) : U{}, 458 .post = static_cast<bool>(post) ? f(post) : U{},
362 .put = static_cast<bool>(put) ? f(put) : U{}, 459 .put = static_cast<bool>(put) ? f(put) : U{},
363 }; 460 };
364 } 461 }
365 }; 462};
366 463
367 template<class Ctx> 464template <class Ctx>
368 struct route_tree { 465struct route_tree
369 using leaves = handler_map<basic_route_handler_fn_t<Ctx>>; 466{
370 using named_subtrees = std::unordered_map<std::string, route_tree>; 467 using leaves = handler_map<basic_route_handler_fn_t<Ctx>>;
371 using wildcard_subtree = std::indirect<route_tree>; 468 using named_subtrees = std::unordered_map<std::string, route_tree>;
469 using wildcard_subtree = std::indirect<route_tree>;
372 470
373 leaves here; 471 leaves here;
374 // TODO: consider making the first alternative a radix tree 472 // TODO: consider making the first alternative a radix tree
375 // Note: the map is the first variant here; the variant will be 473 // Note: the map is the first variant here; the variant will be
376 // default-constructed with the default-constructed first 474 // default-constructed with the default-constructed first
377 // alternative. The empty map denotes a lack of subtrees. 475 // alternative. The empty map denotes a lack of subtrees.
378 std::variant<named_subtrees, wildcard_subtree> sub; 476 std::variant<named_subtrees, wildcard_subtree> sub;
379 }; 477};
380 478
381 template<class OuterCtx, class InnerCtx> 479template <class OuterCtx, class InnerCtx>
382 auto middleware_wrap_tree(middleware_t<OuterCtx, InnerCtx> mw, route_tree<InnerCtx> const& tree) -> route_tree<OuterCtx> { 480auto middleware_wrap_tree(
383 auto new_leaves = tree.here.template map<basic_route_handler_fn_t<OuterCtx>>(std::bind_front(middleware_wrap_fn<OuterCtx, InnerCtx>, mw)); 481 middleware_t<OuterCtx, InnerCtx> mw, route_tree<InnerCtx> const& tree)
384 auto new_sub = tree.sub.visit(util::overloaded{ 482 -> route_tree<OuterCtx>
385 [&mw](route_tree<InnerCtx>::named_subtrees const& subtrees) -> decltype(route_tree<OuterCtx>::sub) { 483{
386 auto new_subtrees = typename route_tree<OuterCtx>::named_subtrees{}; 484 auto new_leaves = tree.here.template map<basic_route_handler_fn_t<OuterCtx>>(
387 for (auto [seg, subtree] : subtrees) 485 std::bind_front(middleware_wrap_fn<OuterCtx, InnerCtx>, mw));
388 new_subtrees[seg] = middleware_wrap_tree(mw, subtree); 486 auto new_sub = tree.sub.visit(
389 return new_subtrees; 487 util::overloaded{
390 }, 488 [&mw](route_tree<InnerCtx>::named_subtrees const& subtrees)
391 [&mw](route_tree<InnerCtx>::wildcard_subtree const& subtree) -> decltype(route_tree<OuterCtx>::sub) { 489 -> decltype(route_tree<OuterCtx>::sub)
392 return typename route_tree<OuterCtx>::wildcard_subtree{middleware_wrap_tree(mw, *subtree)}; 490 {
393 }, 491 auto new_subtrees = typename route_tree<OuterCtx>::named_subtrees{};
492 for (auto [seg, subtree] : subtrees)
493 new_subtrees[seg] = middleware_wrap_tree(mw, subtree);
494 return new_subtrees;
495 },
496 [&mw](route_tree<InnerCtx>::wildcard_subtree const& subtree)
497 -> decltype(route_tree<OuterCtx>::sub)
498 {
499 return typename route_tree<OuterCtx>::wildcard_subtree{
500 middleware_wrap_tree(mw, *subtree)
501 };
502 },
394 }); 503 });
395 return {.here = new_leaves, .sub = new_sub}; 504 return {.here = new_leaves, .sub = new_sub};
396 } 505}
397 506
398 template<class T> 507template <class T>
399 concept match_arg = std::constructible_from<T, std::string const&>; 508concept match_arg = std::constructible_from<T, std::string const&>;
400 509
401 template<class Ctx, match_arg... MatchArgs> 510template <class Ctx, match_arg... MatchArgs>
402 using route_handler_fn_t = std::function<auto(Ctx, readable_request, MatchArgs...) -> net::awaitable<presponse>>; 511using route_handler_fn_t = std::function<
512 auto(Ctx, readable_request, MatchArgs...)->net::awaitable<presponse>>;
403 513
404 template<class Ctx, match_arg... MatchArgs> 514template <class Ctx, match_arg... MatchArgs>
405 auto degen_route_handler(route_handler_fn_t<Ctx, MatchArgs...> fn) -> basic_route_handler_fn_t<Ctx> { 515auto degen_route_handler(route_handler_fn_t<Ctx, MatchArgs...> fn)
406 return [fn = std::move(fn)](Ctx ctx, readable_request r, std::vector<std::string> const& matches) -> net::awaitable<presponse> { 516 -> basic_route_handler_fn_t<Ctx>
407 if (sizeof...(MatchArgs) != matches.size()) 517{
408 throw std::runtime_error{"got unexpected amount of matches"}; 518 return
409 auto it = matches.begin(); 519 [fn = std::move(fn)](
410 co_return co_await fn(std::move(ctx), r, MatchArgs{static_cast<std::string const&>(*it++)}...); 520 Ctx ctx, readable_request r,
411 }; 521 std::vector<std::string> const& matches) -> net::awaitable<presponse>
412 } 522 {
413 523 if (sizeof...(MatchArgs) != matches.size())
414 template<class Ctx, match_arg... MatchArgs> 524 throw std::runtime_error{"got unexpected amount of matches"};
415 struct ctree : route_tree<Ctx> { 525 auto it = matches.begin();
416 template<class OuterCtx> 526 co_return co_await fn(
417 auto wrap(middleware_t<OuterCtx, Ctx> mw) const -> ctree<OuterCtx, MatchArgs...> { 527 std::move(ctx), r,
418 return {middleware_wrap_tree(std::move(mw), *this)}; 528 MatchArgs{static_cast<std::string const&>(*it++)}...);
419 }
420 }; 529 };
530}
421 531
422 template<class Ctx, match_arg... MatchArgs> 532template <class Ctx, match_arg... MatchArgs>
423 struct dtree : handler_map<route_handler_fn_t<Ctx, MatchArgs...>> { 533struct ctree : route_tree<Ctx>
424 [[nodiscard]] auto to_leaves() const -> typename route_tree<Ctx>::leaves { 534{
425 auto here = this->template map<basic_route_handler_fn_t<Ctx>>(degen_route_handler<Ctx, MatchArgs...>); 535 template <class OuterCtx>
426 if (!here.verbs().empty() && !static_cast<bool>(this->options)) 536 auto wrap(middleware_t<OuterCtx, Ctx> mw) const
427 here.options = default_options_handler<Ctx>; 537 -> ctree<OuterCtx, MatchArgs...>
428 return here; 538 {
429 } 539 return {middleware_wrap_tree(std::move(mw), *this)};
540 }
541};
430 542
431 [[nodiscard]] auto named_subtrees(std::initializer_list<std::pair<std::string, ctree<Ctx, MatchArgs...>>> subtrees) const -> ctree<Ctx, MatchArgs...> { 543template <class Ctx, match_arg... MatchArgs>
432 auto sub = typename route_tree<Ctx>::named_subtrees{ 544struct dtree : handler_map<route_handler_fn_t<Ctx, MatchArgs...>>
433 std::from_range, 545{
434 subtrees | std::views::transform([](auto const& p) { 546 [[nodiscard]] auto to_leaves() const -> typename route_tree<Ctx>::leaves
435 return std::make_pair(p.first, static_cast<route_tree<Ctx>>(p.second)); 547 {
436 }) 548 auto here = this->template map<basic_route_handler_fn_t<Ctx>>(
437 }; 549 degen_route_handler<Ctx, MatchArgs...>);
438 return {route_tree<Ctx>{.here = to_leaves(), .sub = sub}}; 550 if (!here.verbs().empty() && !static_cast<bool>(this->options))
439 } 551 here.options = default_options_handler<Ctx>;
552 return here;
553 }
440 554
441 template<match_arg MatchArg> 555 [[nodiscard]] auto named_subtrees(
442 [[nodiscard]] auto wildcard_subtree(ctree<Ctx, MatchArgs..., MatchArg> subtree) -> ctree<Ctx, MatchArgs...> { 556 std::initializer_list<std::pair<std::string, ctree<Ctx, MatchArgs...>>>
443 return {route_tree<Ctx>{.here = to_leaves(), .sub = typename route_tree<Ctx>::wildcard_subtree{static_cast<route_tree<Ctx>>(subtree)}}}; 557 subtrees) const -> ctree<Ctx, MatchArgs...>
444 } 558 {
559 auto sub = typename route_tree<Ctx>::named_subtrees{
560 std::from_range, subtrees
561 | std::views::transform(
562 [](auto const& p)
563 {
564 return std::make_pair(
565 p.first,
566 static_cast<route_tree<Ctx>>(p.second));
567 })
568 };
569 return {route_tree<Ctx>{.here = to_leaves(), .sub = sub}};
570 }
445 571
446 [[nodiscard]] auto no_subtrees() const -> ctree<Ctx, MatchArgs...> { 572 template <match_arg MatchArg>
447 return {route_tree<Ctx>{.here = to_leaves(), .sub = {}}}; 573 [[nodiscard]] auto
448 } 574 wildcard_subtree(ctree<Ctx, MatchArgs..., MatchArg> subtree)
449 }; 575 -> ctree<Ctx, MatchArgs...>
576 {
577 return {route_tree<Ctx>{
578 .here = to_leaves(),
579 .sub = typename route_tree<Ctx>::wildcard_subtree{
580 static_cast<route_tree<Ctx>>(subtree)
581 }
582 }};
583 }
450 584
451 template<std::derived_from<base_ctx> PreRouteCtx> 585 [[nodiscard]] auto no_subtrees() const -> ctree<Ctx, MatchArgs...>
452 class server { 586 {
453 log::logger l_; 587 return {route_tree<Ctx>{.here = to_leaves(), .sub = {}}};
454 locale::selector lsel_; 588 }
455 middleware_t<base_ctx, PreRouteCtx> global_middleware_; 589};
456 route_tree<routed_ctx<PreRouteCtx>> routes_;
457 590
458 public: 591template <std::derived_from<base_ctx> PreRouteCtx>
459 explicit server(log::logger const& l, locale::selector&& lsel, middleware_t<base_ctx, PreRouteCtx> global_middleware, route_tree<routed_ctx<PreRouteCtx>> routes) 592class server
460 : l_{l.sub("http_server")}, lsel_{std::move(lsel)}, global_middleware_{std::move(global_middleware)}, routes_{std::move(routes)} 593{
461 {} 594 log::logger l_;
595 locale::selector lsel_;
596 middleware_t<base_ctx, PreRouteCtx> global_middleware_;
597 route_tree<routed_ctx<PreRouteCtx>> routes_;
462 598
463 struct match_result { 599public:
464 util::not_null<handler_map<basic_route_handler_fn_t<routed_ctx<PreRouteCtx>>> const*> route_handlers; 600 explicit server(
465 std::vector<std::string> wildcard_matches; 601 log::logger const& l, locale::selector&& lsel,
602 middleware_t<base_ctx, PreRouteCtx> global_middleware,
603 route_tree<routed_ctx<PreRouteCtx>> routes)
604 : l_{l.sub("http_server")}, lsel_{std::move(lsel)},
605 global_middleware_{std::move(global_middleware)},
606 routes_{std::move(routes)}
607 {
608 }
466 609
467 auto allowed_methods() const -> verb_set { 610 struct match_result
468 return route_handlers->verbs(); 611 {
469 } 612 util::not_null<
470 }; 613 handler_map<basic_route_handler_fn_t<routed_ctx<PreRouteCtx>>> const*>
614 route_handlers;
615 std::vector<std::string> wildcard_matches;
471 616
472 auto match(boost::urls::segments_view segments) const -> std::optional<match_result> { 617 auto allowed_methods() const -> verb_set { return route_handlers->verbs(); }
473 auto const* tree = &routes_; 618 };
474 auto wildcard_matches = std::vector<std::string>{}; 619
475 for (auto const& seg : segments) { 620 auto match(boost::urls::segments_view segments) const
476 tree->sub.visit(util::overloaded{ 621 -> std::optional<match_result>
477 [&](route_tree<routed_ctx<PreRouteCtx>>::named_subtrees const& subtrees) { 622 {
478 auto it = subtrees.find(seg); 623 auto const* tree = &routes_;
479 tree = it == subtrees.end() ? nullptr : &it->second; 624 auto wildcard_matches = std::vector<std::string>{};
480 }, 625 for (auto const& seg : segments)
481 [&](route_tree<routed_ctx<PreRouteCtx>>::wildcard_subtree const& wildcard_subtree) { 626 {
482 wildcard_matches.push_back(seg); 627 tree->sub.visit(
483 tree = &*wildcard_subtree; 628 util::overloaded{
484 }, 629 [&](route_tree<routed_ctx<PreRouteCtx>>::named_subtrees const&
630 subtrees)
631 {
632 auto it = subtrees.find(seg);
633 tree = it == subtrees.end() ? nullptr : &it->second;
634 },
635 [&](route_tree<routed_ctx<PreRouteCtx>>::wildcard_subtree const&
636 wildcard_subtree)
637 {
638 wildcard_matches.push_back(seg);
639 tree = &*wildcard_subtree;
640 },
485 }); 641 });
486 if (!tree) return std::nullopt; 642 if (!tree)
487 } 643 return std::nullopt;
488 if (tree->here.empty()) return std::nullopt;
489 return match_result{
490 .route_handlers = util::not_null{&tree->here},
491 .wildcard_matches = wildcard_matches,
492 };
493 } 644 }
645 if (tree->here.empty())
646 return std::nullopt;
647 return match_result{
648 .route_handlers = util::not_null{&tree->here},
649 .wildcard_matches = wildcard_matches,
650 };
651 }
494 652
495 auto route_request(PreRouteCtx ctx, readable_request r) const -> net::awaitable<presponse> { 653 auto route_request(PreRouteCtx ctx, readable_request r) const
496 auto req_base = r.p->get().base(); 654 -> net::awaitable<presponse>
655 {
656 auto req_base = r.p->get().base();
497 657
498 auto const bad_request_tpl = problem::tpl{ 658 auto const bad_request_tpl = problem::tpl{
499 .status = bhttp::status::bad_request, 659 .status = bhttp::status::bad_request,
500 .title = translate("Bad request"), 660 .title = translate("Bad request"),
501 .type_uri = "https://routemon.fautchen.eu/problems/bad-request", 661 .type_uri = "https://routemon.fautchen.eu/problems/bad-request",
502 }; 662 };
503 663
504 if (req_base.target() == "*") { 664 if (req_base.target() == "*")
505 // request-target is in asterisk-form (RFC 9112, § 3.2.4), 665 {
506 // so the request must be a server-wide OPTIONS request. 666 // request-target is in asterisk-form (RFC 9112, § 3.2.4),
667 // so the request must be a server-wide OPTIONS request.
507 668
508 if (req_base.method() != bhttp::verb::options) { 669 if (req_base.method() != bhttp::verb::options)
509 auto tpl = problem::tpl{ 670 {
510 .status = bhttp::status::method_not_allowed, 671 auto tpl = problem::tpl{
511 .title = translate("Method not allowed"), 672 .status = bhttp::status::method_not_allowed,
512 .type_uri = "https://routemon.fautchen.eu/problems/method-not-allowed", 673 .title = translate("Method not allowed"),
513 }; 674 .type_uri = "https://routemon.fautchen.eu/problems/"
514 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false}); 675 "method-not-allowed",
515 } 676 };
677 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
678 }
516 679
517 co_return co_await global_options_handler(ctx, r); 680 co_return co_await global_options_handler(ctx, r);
518 } else if (auto mreq_url0 = boost::urls::parse_origin_form(req_base.target())) { 681 }
519 // request-target is in origin-form (RFC 9112, § 3.2.1), 682 else if (auto mreq_url0 = boost::urls::parse_origin_form(req_base.target()))
520 // so it must be a normal request (not a CONNECT or 683 {
521 // server-wide OPTIONS request). 684 // request-target is in origin-form (RFC 9112, § 3.2.1),
685 // so it must be a normal request (not a CONNECT or
686 // server-wide OPTIONS request).
522 687
523 auto req_url = boost::urls::url{*mreq_url0}; 688 auto req_url = boost::urls::url{*mreq_url0};
524 req_url.normalize(); 689 req_url.normalize();
525 if (!req_url.is_path_absolute()) { 690 if (!req_url.is_path_absolute())
526 auto problem = bad_request_tpl.instantiate(). 691 {
527 set_detail(translate("Path of normalized (RFC 3986, § 6) " 692 auto problem = bad_request_tpl.instantiate().set_detail(translate(
528 "origin-form request-target (RFC " 693 "Path of normalized (RFC 3986, § 6) "
529 "9112, § 3.2.1) should be " 694 "origin-form request-target (RFC "
530 "absolute")); 695 "9112, § 3.2.1) should be "
531 co_return problem_rsp(ctx, problem, keep_alive{false}); 696 "absolute"));
532 } 697 co_return problem_rsp(ctx, problem, keep_alive{false});
698 }
533 699
534 auto mres = match(req_url.segments()); 700 auto mres = match(req_url.segments());
535 if (!mres) { 701 if (!mres)
536 auto tpl = problem::tpl{ 702 {
537 .status = bhttp::status::not_found, 703 auto tpl = problem::tpl{
538 .title = translate("Not found"), 704 .status = bhttp::status::not_found,
705 .title = translate("Not found"),
539 .type_uri = "https://routemon.fautchen.eu/problems/not-found", 706 .type_uri = "https://routemon.fautchen.eu/problems/not-found",
540 }; 707 };
541 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false}); 708 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
542 } 709 }
543
544 auto mverb = supported_verb::from(req_base.method());
545 if (!mverb) {
546 // Method not implemented.
547 auto tpl = problem::tpl{
548 .status = bhttp::status::not_implemented,
549 .title = translate("Method not implemented"),
550 .type_uri = "https://routemon.fautchen.eu/problems/method-not-implemented",
551 };
552 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
553 }
554 710
555 if (auto mhdl = mres->route_handlers->lookup(*mverb)) { 711 auto mverb = supported_verb::from(req_base.method());
556 auto new_ctx = routed_ctx<PreRouteCtx>{std::move(ctx), mres->allowed_methods()}; 712 if (!mverb)
557 co_return co_await mhdl(std::move(new_ctx), r, mres->wildcard_matches); 713 {
558 } else { 714 // Method not implemented.
559 // Path recognized, but method not allowed. 715 auto tpl = problem::tpl{
560 auto tpl = problem::tpl{ 716 .status = bhttp::status::not_implemented,
561 .status = bhttp::status::method_not_allowed, 717 .title = translate("Method not implemented"),
562 .title = translate("Method not allowed"), 718 .type_uri = "https://routemon.fautchen.eu/problems/"
563 .type_uri = "https://routemon.fautchen.eu/problems/method-not-allowed", 719 "method-not-implemented",
564 }; 720 };
565 auto rsp = problem_rsp(ctx, tpl.instantiate(), keep_alive{false}); 721 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
566 rsp.header().set(bhttp::field::allow, mres->allowed_methods().to_string()); 722 }
567 co_return std::move(rsp);
568 }
569 } else {
570 // We do not accept any other request-target forms.
571 723
572 auto problem = bad_request_tpl.instantiate(). 724 if (auto mhdl = mres->route_handlers->lookup(*mverb))
573 set_detail(translate("Invalid request-target, expected " 725 {
574 "asterisk-form or origin-form " 726 auto new_ctx =
575 "(see RFC 9112, § 3.2)")); 727 routed_ctx<PreRouteCtx>{std::move(ctx), mres->allowed_methods()};
576 co_return problem_rsp(ctx, problem, keep_alive{false}); 728 co_return co_await mhdl(std::move(new_ctx), r, mres->wildcard_matches);
729 }
730 else
731 {
732 // Path recognized, but method not allowed.
733 auto tpl = problem::tpl{
734 .status = bhttp::status::method_not_allowed,
735 .title = translate("Method not allowed"),
736 .type_uri = "https://routemon.fautchen.eu/problems/"
737 "method-not-allowed",
738 };
739 auto rsp = problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
740 rsp.header().set(
741 bhttp::field::allow, mres->allowed_methods().to_string());
742 co_return std::move(rsp);
577 } 743 }
578 } 744 }
745 else
746 {
747 // We do not accept any other request-target forms.
579 748
580 auto handle_request(readable_request r) const -> net::awaitable<presponse> { 749 auto problem = bad_request_tpl.instantiate().set_detail(translate(
581 auto header = r.p->get().base(); 750 "Invalid request-target, expected "
582 auto locale = lsel_.select(header[bhttp::field::accept_language]); 751 "asterisk-form or origin-form "
583 auto ctx0 = base_ctx{.locale = locale}; 752 "(see RFC 9112, § 3.2)"));
584 753 co_return problem_rsp(ctx, problem, keep_alive{false});
585 co_return co_await global_middleware_(std::move(ctx0), header, [&](PreRouteCtx ctx) -> net::awaitable<presponse> {
586 co_return co_await route_request(std::move(ctx), std::move(r));
587 });
588 } 754 }
755 }
589 756
590 auto do_session(beast::tcp_stream strm) -> net::awaitable<void> { 757 auto handle_request(readable_request r) const -> net::awaitable<presponse>
591 auto buf = beast::flat_buffer{}; 758 {
759 auto header = r.p->get().base();
760 auto locale = lsel_.select(header[bhttp::field::accept_language]);
761 auto ctx0 = base_ctx{.locale = locale};
592 762
593 while (true) { 763 co_return co_await global_middleware_(
594 auto p0 = bhttp::request_parser<bhttp::empty_body>{}; 764 std::move(ctx0), header,
595 p0.body_limit(boost::none); 765 [&](PreRouteCtx ctx) -> net::awaitable<presponse>
596 auto [ec, _] = co_await bhttp::async_read_header(strm, buf, p0, net::as_tuple); 766 { co_return co_await route_request(std::move(ctx), std::move(r)); });
597 if (ec == bhttp::error::end_of_stream) { 767 }
598 break;
599 } else if (ec) {
600 throw boost::system::system_error{ec};
601 }
602 768
603 auto http_version = p0.get().version(); 769 auto do_session(beast::tcp_stream strm) -> net::awaitable<void>
604 auto&& rsp = co_await handle_request(readable_request{ 770 {
605 .p = util::not_null{&p0}, 771 auto buf = beast::flat_buffer{};
606 .strm = util::not_null{&strm}, 772
607 .buf = util::not_null{&buf}, 773 while (true)
774 {
775 auto p0 = bhttp::request_parser<bhttp::empty_body>{};
776 p0.body_limit(boost::none);
777 auto [ec, _] =
778 co_await bhttp::async_read_header(strm, buf, p0, net::as_tuple);
779 if (ec == bhttp::error::end_of_stream)
780 break;
781 else if (ec)
782 throw boost::system::system_error{ec};
783
784 auto http_version = p0.get().version();
785 auto&& rsp = co_await handle_request(
786 readable_request{
787 .p = util::not_null{&p0},
788 .strm = util::not_null{&strm},
789 .buf = util::not_null{&buf},
608 }); 790 });
609 rsp.header().version(http_version); 791 rsp.header().version(http_version);
610 bool keep_alive = rsp.keep_alive(); 792 bool keep_alive = rsp.keep_alive();
611 co_await beast::async_write(strm, std::move(rsp)); 793 co_await beast::async_write(strm, std::move(rsp));
612 if (!keep_alive) { 794 if (!keep_alive)
613 break; 795 {
614 } 796 break;
615 } 797 }
616
617 strm.socket().shutdown(tcp::socket::shutdown_send);
618 } 798 }
619 799
620 auto do_listen(tcp::endpoint endpoint) -> net::awaitable<void> { 800 strm.socket().shutdown(tcp::socket::shutdown_send);
621 auto executor = co_await net::this_coro::executor; 801 }
622 auto acceptor = tcp::acceptor{executor, endpoint}; 802
803 auto do_listen(tcp::endpoint endpoint) -> net::awaitable<void>
804 {
805 auto executor = co_await net::this_coro::executor;
806 auto acceptor = tcp::acceptor{executor, endpoint};
623 807
624 l_.with("endpoint", endpoint.address().to_string()). 808 l_.with("endpoint", endpoint.address().to_string())
625 with("port", std::to_string(endpoint.port())). 809 .with("port", std::to_string(endpoint.port()))
626 info("Serving"); 810 .info("Serving");
627 while (true) { 811 while (true)
628 net::co_spawn(executor, 812 {
629 do_session(beast::tcp_stream{co_await acceptor.async_accept()}), 813 net::co_spawn(
630 [this](std::exception_ptr e) { 814 executor,
631 if (e) { 815 do_session(beast::tcp_stream{co_await acceptor.async_accept()}),
632 try { 816 [this](std::exception_ptr e)
633 std::rethrow_exception(e); 817 {
634 } catch (std::exception const& e) { 818 if (e)
635 l_.error("Error in session: {}", e.what()); 819 {
636 } 820 try
637 } 821 {
638 }); 822 std::rethrow_exception(e);
639 } 823 }
824 catch (std::exception const& e)
825 {
826 l_.error("Error in session: {}", e.what());
827 }
828 }
829 });
640 } 830 }
831 }
641 832
642 auto spawn(net::io_context& ioc) -> void { 833 auto spawn(net::io_context& ioc) -> void
643 auto const addr = net::ip::make_address("0.0.0.0"); 834 {
644 auto const endpoint = tcp::endpoint{addr, 8284}; 835 auto const addr = net::ip::make_address("0.0.0.0");
836 auto const endpoint = tcp::endpoint{addr, 8284};
645 837
646 // TODO: make exception handling as nice as in srv.cpp 838 // TODO: make exception handling as nice as in srv.cpp
647 net::co_spawn(ioc, 839 net::co_spawn(
648 do_listen(endpoint), 840 ioc, do_listen(endpoint),
649 [this](std::exception_ptr e) { 841 [this](std::exception_ptr e)
650 if (e) { 842 {
651 try { 843 if (e)
652 std::rethrow_exception(e); 844 {
653 } catch (std::exception const& e) { 845 try
654 l_.error("Error: {}", e.what()); 846 {
655 } 847 std::rethrow_exception(e);
656 } 848 }
657 }); 849 catch (std::exception const& e)
658 } 850 {
659 }; 851 l_.error("Error: {}", e.what());
852 }
853 }
854 });
855 }
856};
660 857
661} // namespace routemon::http 858} // namespace routemon::http