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.cppm322
1 files changed, 164 insertions, 158 deletions
diff --git a/server/src/http_server.cppm b/server/src/http_server.cppm
index 366037f..f5b4c3e 100644
--- a/server/src/http_server.cppm
+++ b/server/src/http_server.cppm
@@ -41,7 +41,7 @@ private:
41 virtual ~impl_base() = default; 41 virtual ~impl_base() = default;
42 virtual auto header() -> bhttp::response_header<bhttp::fields>& = 0; 42 virtual auto header() -> bhttp::response_header<bhttp::fields>& = 0;
43 virtual auto header() const 43 virtual auto header() const
44 -> bhttp::response_header<bhttp::fields> const& = 0; 44 -> bhttp::response_header<bhttp::fields> const& = 0;
45 virtual auto is_done() const -> bool = 0; 45 virtual auto is_done() const -> bool = 0;
46 virtual auto prepare(beast::error_code&) -> const_buffers_type = 0; 46 virtual auto prepare(beast::error_code&) -> const_buffers_type = 0;
47 virtual auto consume(std::size_t n) -> void = 0; 47 virtual auto consume(std::size_t n) -> void = 0;
@@ -121,12 +121,12 @@ private:
121 auto keep_alive() const noexcept -> bool override 121 auto keep_alive() const noexcept -> bool override
122 { 122 {
123 return state_.visit( 123 return state_.visit(
124 util::overloaded{ 124 util::overloaded{
125 [](bhttp::response<Body> const& rsp) -> bool 125 [](bhttp::response<Body> const& rsp) -> bool
126 { return rsp.keep_alive(); }, 126 { return rsp.keep_alive(); },
127 [](bhttp::message_generator const& gen) -> bool 127 [](bhttp::message_generator const& gen) -> bool
128 { return gen.keep_alive(); }, 128 { return gen.keep_alive(); },
129 }); 129 });
130 } 130 }
131 }; 131 };
132 132
@@ -164,15 +164,15 @@ using next_handler_t = std::function<auto(Ctx)->net::awaitable<presponse>>;
164 164
165template <class OuterCtx, class InnerCtx> 165template <class OuterCtx, class InnerCtx>
166using middleware_t = 166using middleware_t =
167 std::function<auto( 167 std::function<auto(
168 OuterCtx, bhttp::request_header<bhttp::fields>&, 168 OuterCtx, bhttp::request_header<bhttp::fields>&,
169 next_handler_t<InnerCtx>) 169 next_handler_t<InnerCtx>)
170 ->net::awaitable<presponse>>; 170 ->net::awaitable<presponse>>;
171 171
172template <class Ctx> 172template <class Ctx>
173auto lax_cors_middleware( 173auto lax_cors_middleware(
174 Ctx ctx, bhttp::request_header<bhttp::fields>& req_hdr, 174 Ctx ctx, bhttp::request_header<bhttp::fields>& req_hdr,
175 next_handler_t<Ctx> next) -> net::awaitable<presponse> 175 next_handler_t<Ctx> next) -> net::awaitable<presponse>
176{ 176{
177 std::ignore = req_hdr; 177 std::ignore = req_hdr;
178 auto prersp = co_await next(ctx); 178 auto prersp = co_await next(ctx);
@@ -188,16 +188,16 @@ struct trace_id_ctx : InnerCtx
188 188
189template <class OuterCtx> 189template <class OuterCtx>
190auto trace_id_middleware( 190auto trace_id_middleware(
191 OuterCtx ctx0, bhttp::request_header<bhttp::fields>& req_hdr, 191 OuterCtx ctx0, bhttp::request_header<bhttp::fields>& req_hdr,
192 next_handler_t<trace_id_ctx<OuterCtx>> next) -> net::awaitable<presponse> 192 next_handler_t<trace_id_ctx<OuterCtx>> next) -> net::awaitable<presponse>
193{ 193{
194 std::ignore = req_hdr; 194 std::ignore = req_hdr;
195 auto ctx = trace_id_ctx{std::move(ctx0)}; 195 auto ctx = trace_id_ctx{std::move(ctx0)};
196 auto prersp = co_await next(std::move(ctx)); 196 auto prersp = co_await next(std::move(ctx));
197 prersp.header().set( 197 prersp.header().set(
198 "X-Routemon-Trace-Id", std::string_view{ctx.trace_id.as_string()}); 198 "X-Routemon-Trace-Id", std::string_view{ctx.trace_id.as_string()});
199 prersp.header().insert( 199 prersp.header().insert(
200 bhttp::field::access_control_expose_headers, "X-Routemon-Trace-Id"); 200 bhttp::field::access_control_expose_headers, "X-Routemon-Trace-Id");
201 co_return std::move(prersp); 201 co_return std::move(prersp);
202} 202}
203 203
@@ -208,8 +208,8 @@ struct base_ctx
208 208
209template <class Ctx> 209template <class Ctx>
210using basic_route_handler_fn_t = std::function< 210using basic_route_handler_fn_t = std::function<
211 auto(Ctx, readable_request, std::vector<std::string> const& matches) 211 auto(Ctx, readable_request, std::vector<std::string> const& matches)
212 ->net::awaitable<presponse>>; 212 ->net::awaitable<presponse>>;
213 213
214struct keep_alive 214struct keep_alive
215{ 215{
@@ -228,8 +228,8 @@ auto make_rsp(bhttp::status status, keep_alive ka) -> bhttp::response<Body>
228} 228}
229 229
230auto problem_rsp( 230auto problem_rsp(
231 base_ctx const& ctx, problem::details const& problem, keep_alive ka) 231 base_ctx const& ctx, problem::details const& problem, keep_alive ka)
232 -> presponse 232 -> presponse
233{ 233{
234 auto rsp = make_rsp<bhttp::string_body>(problem.status, ka); 234 auto rsp = make_rsp<bhttp::string_body>(problem.status, ka);
235 rsp.set(bhttp::field::content_type, "application/problem+json"); 235 rsp.set(bhttp::field::content_type, "application/problem+json");
@@ -244,17 +244,18 @@ struct preflight_response
244 std::vector<bhttp::field> allow_headers; 244 std::vector<bhttp::field> allow_headers;
245}; 245};
246auto make_preflight_rsp(preflight_response res, keep_alive ka) 246auto make_preflight_rsp(preflight_response res, keep_alive ka)
247 -> bhttp::response<bhttp::empty_body> 247 -> bhttp::response<bhttp::empty_body>
248{ 248{
249 auto rsp = make_rsp<bhttp::empty_body>(bhttp::status::no_content, ka); 249 auto rsp = make_rsp<bhttp::empty_body>(bhttp::status::no_content, ka);
250 auto allow_headers_str = res.allow_headers 250 auto allow_headers_str = res.allow_headers
251 | std::views::transform( 251 | std::views::transform(
252 [](auto const& field) -> std::string_view 252 [](auto const& field) -> std::string_view
253 { return bhttp::to_string(field); }) 253 { return bhttp::to_string(field); })
254 | std::views::join_with(std::string_view{", "}) 254 | std::views::join_with(std::string_view{", "})
255 | std::ranges::to<std::string>(); 255 | std::ranges::to<std::string>();
256 rsp.set( 256 rsp.set(
257 bhttp::field::access_control_allow_methods, res.allow_methods.to_string()); 257 bhttp::field::access_control_allow_methods,
258 res.allow_methods.to_string());
258 rsp.set(bhttp::field::access_control_allow_headers, allow_headers_str); 259 rsp.set(bhttp::field::access_control_allow_headers, allow_headers_str);
259 rsp.prepare_payload(); 260 rsp.prepare_payload();
260 return rsp; 261 return rsp;
@@ -265,7 +266,7 @@ auto make_preflight_rsp(preflight_response res, keep_alive ka)
265// anyway, but all context types should derive from base_ctx). 266// anyway, but all context types should derive from base_ctx).
266template <bhttp::concepts::body_reader Body> 267template <bhttp::concepts::body_reader Body>
267auto read_request(base_ctx const& ctx, readable_request&& r) 268auto read_request(base_ctx const& ctx, readable_request&& r)
268 -> net::awaitable<std::expected<bhttp::request<Body>, presponse>> 269 -> net::awaitable<std::expected<bhttp::request<Body>, presponse>>
269{ 270{
270 std::ignore = ctx; 271 std::ignore = ctx;
271 auto p = bhttp::request_parser<Body>{std::move(*r.p)}; 272 auto p = bhttp::request_parser<Body>{std::move(*r.p)};
@@ -275,10 +276,11 @@ auto read_request(base_ctx const& ctx, readable_request&& r)
275 276
276template <> 277template <>
277auto read_request<bhttp::empty_body>(base_ctx const& ctx, readable_request&& r) 278auto read_request<bhttp::empty_body>(base_ctx const& ctx, readable_request&& r)
278 -> net::awaitable<std::expected<bhttp::request<bhttp::empty_body>, presponse>> 279 -> net::awaitable<
280 std::expected<bhttp::request<bhttp::empty_body>, presponse>>
279{ 281{
280 auto [ec, _] = 282 auto [ec, _] =
281 co_await bhttp::async_read(*r.strm, *r.buf, *r.p, net::as_tuple); 283 co_await bhttp::async_read(*r.strm, *r.buf, *r.p, net::as_tuple);
282 if (ec == bhttp::error::unexpected_body) 284 if (ec == bhttp::error::unexpected_body)
283 { 285 {
284 auto tpl = problem::tpl{ 286 auto tpl = problem::tpl{
@@ -287,7 +289,7 @@ auto read_request<bhttp::empty_body>(base_ctx const& ctx, readable_request&& r)
287 .type_uri = "https://routemon.fautchen.eu/problems/unexpected-body", 289 .type_uri = "https://routemon.fautchen.eu/problems/unexpected-body",
288 }; 290 };
289 co_return std::unexpected{problem_rsp( 291 co_return std::unexpected{problem_rsp(
290 ctx, tpl.instantiate(), keep_alive{false})}; 292 ctx, tpl.instantiate(), keep_alive{false})};
291 } 293 }
292 else if (ec) 294 else if (ec)
293 { 295 {
@@ -308,8 +310,8 @@ template <class Ctx>
308 []<class InnerCtx>(routed_ctx<InnerCtx> const&) {}(ctx); 310 []<class InnerCtx>(routed_ctx<InnerCtx> const&) {}(ctx);
309 } 311 }
310auto default_options_handler( 312auto default_options_handler(
311 Ctx const& ctx, readable_request r, std::vector<std::string> const&) 313 Ctx const& ctx, readable_request r, std::vector<std::string> const&)
312 -> net::awaitable<presponse> 314 -> net::awaitable<presponse>
313{ 315{
314 auto mreq = co_await read_request<bhttp::empty_body>(ctx, std::move(r)); 316 auto mreq = co_await read_request<bhttp::empty_body>(ctx, std::move(r));
315 if (!mreq) 317 if (!mreq)
@@ -319,18 +321,18 @@ auto default_options_handler(
319 { 321 {
320 // CORS preflight request 322 // CORS preflight request
321 co_return make_preflight_rsp( 323 co_return make_preflight_rsp(
322 preflight_response{ 324 preflight_response{
323 // TODO: should access-control-allow-methods contain OPTIONS? 325 // TODO: should access-control-allow-methods contain OPTIONS?
324 .allow_methods = ctx.route_methods, 326 .allow_methods = ctx.route_methods,
325 .allow_headers = {bhttp::field::content_type}, 327 .allow_headers = {bhttp::field::content_type},
326 }, 328 },
327 keep_alive{mreq->keep_alive()}); 329 keep_alive{mreq->keep_alive()});
328 } 330 }
329 else 331 else
330 { 332 {
331 // Normal OPTIONS request 333 // Normal OPTIONS request
332 auto rsp = make_rsp<bhttp::empty_body>( 334 auto rsp = make_rsp<bhttp::empty_body>(
333 bhttp::status::no_content, keep_alive{mreq->keep_alive()}); 335 bhttp::status::no_content, keep_alive{mreq->keep_alive()});
334 rsp.set(bhttp::field::allow, ctx.route_methods.to_string()); 336 rsp.set(bhttp::field::allow, ctx.route_methods.to_string());
335 rsp.prepare_payload(); 337 rsp.prepare_payload();
336 co_return std::move(rsp); 338 co_return std::move(rsp);
@@ -338,7 +340,7 @@ auto default_options_handler(
338} 340}
339 341
340auto global_options_handler(base_ctx const& ctx, readable_request r) 342auto global_options_handler(base_ctx const& ctx, readable_request r)
341 -> net::awaitable<presponse> 343 -> net::awaitable<presponse>
342{ 344{
343 // TODO: switch to "small (4KB) discarded" body type, similar to what Go 345 // TODO: switch to "small (4KB) discarded" body type, similar to what Go
344 // does? Same goes for default_options_handler? Not sure. 346 // does? Same goes for default_options_handler? Not sure.
@@ -347,45 +349,46 @@ auto global_options_handler(base_ctx const& ctx, readable_request r)
347 co_return std::move(res.error()); 349 co_return std::move(res.error());
348 auto req = r.p->release(); 350 auto req = r.p->release();
349 auto rsp = make_rsp<bhttp::empty_body>( 351 auto rsp = make_rsp<bhttp::empty_body>(
350 bhttp::status::no_content, keep_alive{req.keep_alive()}); 352 bhttp::status::no_content, keep_alive{req.keep_alive()});
351 rsp.prepare_payload(); 353 rsp.prepare_payload();
352 co_return std::move(rsp); 354 co_return std::move(rsp);
353} 355}
354 356
355template <class Ctx> 357template <class Ctx>
356auto id_middleware( 358auto id_middleware(
357 Ctx ctx, bhttp::request_header<bhttp::fields>&, next_handler_t<Ctx> next) 359 Ctx ctx, bhttp::request_header<bhttp::fields>&, next_handler_t<Ctx> next)
358 -> net::awaitable<presponse> 360 -> net::awaitable<presponse>
359{ 361{
360 co_return co_await next(std::move(ctx)); 362 co_return co_await next(std::move(ctx));
361} 363}
362 364
363template <class A, class B, class C> 365template <class A, class B, class C>
364auto middleware_compose(middleware_t<A, B> ab, middleware_t<B, C> bc) 366auto middleware_compose(middleware_t<A, B> ab, middleware_t<B, C> bc)
365 -> middleware_t<A, C> 367 -> middleware_t<A, C>
366{ 368{
367 return [ab = std::move(ab), bc = std::move(bc)]( 369 return [ab = std::move(ab), bc = std::move(bc)](
368 A a, bhttp::request_header<bhttp::fields>& header, 370 A a, bhttp::request_header<bhttp::fields>& header,
369 next_handler_t<C> next) -> net::awaitable<presponse> 371 next_handler_t<C> next) -> net::awaitable<presponse>
370 { 372 {
371 co_return co_await ab( 373 co_return co_await ab(
372 std::move(a), header, [&](B b) -> net::awaitable<presponse> 374 std::move(a), header, [&](B b) -> net::awaitable<presponse>
373 { co_return co_await bc(std::move(b), header, next); }); 375 { co_return co_await bc(std::move(b), header, next); });
374 }; 376 };
375} 377}
376 378
377template <class A, class B> 379template <class A, class B>
378auto middleware_wrap_fn(middleware_t<A, B> ab, basic_route_handler_fn_t<B> fn) 380auto middleware_wrap_fn(middleware_t<A, B> ab, basic_route_handler_fn_t<B> fn)
379 -> basic_route_handler_fn_t<A> 381 -> basic_route_handler_fn_t<A>
380{ 382{
381 return [ab = std::move(ab), fn = std::move(fn)]( 383 return
382 A a_ctx, readable_request r, 384 [ab = std::move(ab), fn = std::move(fn)](
383 std::vector<std::string> const& matches) -> net::awaitable<presponse> 385 A a_ctx, readable_request r,
386 std::vector<std::string> const& matches) -> net::awaitable<presponse>
384 { 387 {
385 co_return co_await ab( 388 co_return co_await ab(
386 std::move(a_ctx), r.p->get().base(), 389 std::move(a_ctx), r.p->get().base(),
387 [&](B b_ctx) -> net::awaitable<presponse> 390 [&](B b_ctx) -> net::awaitable<presponse>
388 { co_return co_await fn(std::move(b_ctx), r, matches); }); 391 { co_return co_await fn(std::move(b_ctx), r, matches); });
389 }; 392 };
390} 393}
391 394
@@ -445,7 +448,7 @@ struct handler_map
445 template <std::default_initializable U> 448 template <std::default_initializable U>
446 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>
447 requires std::assignable_from< 450 requires std::assignable_from<
448 U&, std::invoke_result_t<decltype(f), V const&>> 451 U&, std::invoke_result_t<decltype(f), V const&>>
449 { 452 {
450 return { 453 return {
451 .options = static_cast<bool>(options) ? f(options) : U{}, 454 .options = static_cast<bool>(options) ? f(options) : U{},
@@ -475,29 +478,29 @@ struct route_tree
475 478
476template <class OuterCtx, class InnerCtx> 479template <class OuterCtx, class InnerCtx>
477auto middleware_wrap_tree( 480auto middleware_wrap_tree(
478 middleware_t<OuterCtx, InnerCtx> mw, route_tree<InnerCtx> const& tree) 481 middleware_t<OuterCtx, InnerCtx> mw, route_tree<InnerCtx> const& tree)
479 -> route_tree<OuterCtx> 482 -> route_tree<OuterCtx>
480{ 483{
481 auto new_leaves = tree.here.template map<basic_route_handler_fn_t<OuterCtx>>( 484 auto new_leaves = tree.here.template map<basic_route_handler_fn_t<OuterCtx>>(
482 std::bind_front(middleware_wrap_fn<OuterCtx, InnerCtx>, mw)); 485 std::bind_front(middleware_wrap_fn<OuterCtx, InnerCtx>, mw));
483 auto new_sub = tree.sub.visit( 486 auto new_sub = tree.sub.visit(
484 util::overloaded{ 487 util::overloaded{
485 [&mw](route_tree<InnerCtx>::named_subtrees const& subtrees) 488 [&mw](route_tree<InnerCtx>::named_subtrees const& subtrees)
486 -> decltype(route_tree<OuterCtx>::sub) 489 -> decltype(route_tree<OuterCtx>::sub)
487 { 490 {
488 auto new_subtrees = typename route_tree<OuterCtx>::named_subtrees{}; 491 auto new_subtrees = typename route_tree<OuterCtx>::named_subtrees{};
489 for (auto [seg, subtree] : subtrees) 492 for (auto [seg, subtree] : subtrees)
490 new_subtrees[seg] = middleware_wrap_tree(mw, subtree); 493 new_subtrees[seg] = middleware_wrap_tree(mw, subtree);
491 return new_subtrees; 494 return new_subtrees;
492 }, 495 },
493 [&mw](route_tree<InnerCtx>::wildcard_subtree const& subtree) 496 [&mw](route_tree<InnerCtx>::wildcard_subtree const& subtree)
494 -> decltype(route_tree<OuterCtx>::sub) 497 -> decltype(route_tree<OuterCtx>::sub)
495 { 498 {
496 return typename route_tree<OuterCtx>::wildcard_subtree{ 499 return typename route_tree<OuterCtx>::wildcard_subtree{
497 middleware_wrap_tree(mw, *subtree) 500 middleware_wrap_tree(mw, *subtree)
498 }; 501 };
499 }, 502 },
500 }); 503 });
501 return {.here = new_leaves, .sub = new_sub}; 504 return {.here = new_leaves, .sub = new_sub};
502} 505}
503 506
@@ -506,21 +509,23 @@ concept match_arg = std::constructible_from<T, std::string const&>;
506 509
507template <class Ctx, match_arg... MatchArgs> 510template <class Ctx, match_arg... MatchArgs>
508using route_handler_fn_t = std::function< 511using route_handler_fn_t = std::function<
509 auto(Ctx, readable_request, MatchArgs...)->net::awaitable<presponse>>; 512 auto(Ctx, readable_request, MatchArgs...)->net::awaitable<presponse>>;
510 513
511template <class Ctx, match_arg... MatchArgs> 514template <class Ctx, match_arg... MatchArgs>
512auto degen_route_handler(route_handler_fn_t<Ctx, MatchArgs...> fn) 515auto degen_route_handler(route_handler_fn_t<Ctx, MatchArgs...> fn)
513 -> basic_route_handler_fn_t<Ctx> 516 -> basic_route_handler_fn_t<Ctx>
514{ 517{
515 return [fn = std::move(fn)]( 518 return
516 Ctx ctx, readable_request r, 519 [fn = std::move(fn)](
517 std::vector<std::string> const& matches) -> net::awaitable<presponse> 520 Ctx ctx, readable_request r,
521 std::vector<std::string> const& matches) -> net::awaitable<presponse>
518 { 522 {
519 if (sizeof...(MatchArgs) != matches.size()) 523 if (sizeof...(MatchArgs) != matches.size())
520 throw std::runtime_error{"got unexpected amount of matches"}; 524 throw std::runtime_error{"got unexpected amount of matches"};
521 auto it = matches.begin(); 525 auto it = matches.begin();
522 co_return co_await fn( 526 co_return co_await fn(
523 std::move(ctx), r, MatchArgs{static_cast<std::string const&>(*it++)}...); 527 std::move(ctx), r,
528 MatchArgs{static_cast<std::string const&>(*it++)}...);
524 }; 529 };
525} 530}
526 531
@@ -529,7 +534,7 @@ struct ctree : route_tree<Ctx>
529{ 534{
530 template <class OuterCtx> 535 template <class OuterCtx>
531 auto wrap(middleware_t<OuterCtx, Ctx> mw) const 536 auto wrap(middleware_t<OuterCtx, Ctx> mw) const
532 -> ctree<OuterCtx, MatchArgs...> 537 -> ctree<OuterCtx, MatchArgs...>
533 { 538 {
534 return {middleware_wrap_tree(std::move(mw), *this)}; 539 return {middleware_wrap_tree(std::move(mw), *this)};
535 } 540 }
@@ -541,24 +546,25 @@ struct dtree : handler_map<route_handler_fn_t<Ctx, MatchArgs...>>
541 [[nodiscard]] auto to_leaves() const -> typename route_tree<Ctx>::leaves 546 [[nodiscard]] auto to_leaves() const -> typename route_tree<Ctx>::leaves
542 { 547 {
543 auto here = this->template map<basic_route_handler_fn_t<Ctx>>( 548 auto here = this->template map<basic_route_handler_fn_t<Ctx>>(
544 degen_route_handler<Ctx, MatchArgs...>); 549 degen_route_handler<Ctx, MatchArgs...>);
545 if (!here.verbs().empty() && !static_cast<bool>(this->options)) 550 if (!here.verbs().empty() && !static_cast<bool>(this->options))
546 here.options = default_options_handler<Ctx>; 551 here.options = default_options_handler<Ctx>;
547 return here; 552 return here;
548 } 553 }
549 554
550 [[nodiscard]] auto named_subtrees( 555 [[nodiscard]] auto named_subtrees(
551 std::initializer_list<std::pair<std::string, ctree<Ctx, MatchArgs...>>> 556 std::initializer_list<std::pair<std::string, ctree<Ctx, MatchArgs...>>>
552 subtrees) const -> ctree<Ctx, MatchArgs...> 557 subtrees) const -> ctree<Ctx, MatchArgs...>
553 { 558 {
554 auto sub = typename route_tree<Ctx>::named_subtrees{ 559 auto sub = typename route_tree<Ctx>::named_subtrees{
555 std::from_range, subtrees 560 std::from_range, subtrees
556 | std::views::transform( 561 | std::views::transform(
557 [](auto const& p) 562 [](auto const& p)
558 { 563 {
559 return std::make_pair( 564 return std::make_pair(
560 p.first, static_cast<route_tree<Ctx>>(p.second)); 565 p.first,
561 }) 566 static_cast<route_tree<Ctx>>(p.second));
567 })
562 }; 568 };
563 return {route_tree<Ctx>{.here = to_leaves(), .sub = sub}}; 569 return {route_tree<Ctx>{.here = to_leaves(), .sub = sub}};
564 } 570 }
@@ -566,7 +572,7 @@ struct dtree : handler_map<route_handler_fn_t<Ctx, MatchArgs...>>
566 template <match_arg MatchArg> 572 template <match_arg MatchArg>
567 [[nodiscard]] auto 573 [[nodiscard]] auto
568 wildcard_subtree(ctree<Ctx, MatchArgs..., MatchArg> subtree) 574 wildcard_subtree(ctree<Ctx, MatchArgs..., MatchArg> subtree)
569 -> ctree<Ctx, MatchArgs...> 575 -> ctree<Ctx, MatchArgs...>
570 { 576 {
571 return {route_tree<Ctx>{ 577 return {route_tree<Ctx>{
572 .here = to_leaves(), 578 .here = to_leaves(),
@@ -592,9 +598,9 @@ class server
592 598
593public: 599public:
594 explicit server( 600 explicit server(
595 log::logger const& l, locale::selector&& lsel, 601 log::logger const& l, locale::selector&& lsel,
596 middleware_t<base_ctx, PreRouteCtx> global_middleware, 602 middleware_t<base_ctx, PreRouteCtx> global_middleware,
597 route_tree<routed_ctx<PreRouteCtx>> routes) 603 route_tree<routed_ctx<PreRouteCtx>> routes)
598 : l_{l.sub("http_server")}, lsel_{std::move(lsel)}, 604 : l_{l.sub("http_server")}, lsel_{std::move(lsel)},
599 global_middleware_{std::move(global_middleware)}, 605 global_middleware_{std::move(global_middleware)},
600 routes_{std::move(routes)} 606 routes_{std::move(routes)}
@@ -604,36 +610,35 @@ public:
604 struct match_result 610 struct match_result
605 { 611 {
606 util::not_null< 612 util::not_null<
607 handler_map<basic_route_handler_fn_t<routed_ctx<PreRouteCtx>>> const*> 613 handler_map<basic_route_handler_fn_t<routed_ctx<PreRouteCtx>>> const*>
608 route_handlers; 614 route_handlers;
609 std::vector<std::string> wildcard_matches; 615 std::vector<std::string> wildcard_matches;
610 616
611 auto allowed_methods() const -> verb_set { return route_handlers->verbs(); } 617 auto allowed_methods() const -> verb_set { return route_handlers->verbs(); }
612 }; 618 };
613 619
614 auto match(boost::urls::segments_view segments) const 620 auto match(boost::urls::segments_view segments) const
615 -> std::optional<match_result> 621 -> std::optional<match_result>
616 { 622 {
617 auto const* tree = &routes_; 623 auto const* tree = &routes_;
618 auto wildcard_matches = std::vector<std::string>{}; 624 auto wildcard_matches = std::vector<std::string>{};
619 for (auto const& seg : segments) 625 for (auto const& seg : segments)
620 { 626 {
621 tree->sub.visit( 627 tree->sub.visit(
622 util::overloaded{ 628 util::overloaded{
623 [&]( 629 [&](route_tree<routed_ctx<PreRouteCtx>>::named_subtrees const&
624 route_tree<routed_ctx<PreRouteCtx>>::named_subtrees const& subtrees) 630 subtrees)
625 { 631 {
626 auto it = subtrees.find(seg); 632 auto it = subtrees.find(seg);
627 tree = it == subtrees.end() ? nullptr : &it->second; 633 tree = it == subtrees.end() ? nullptr : &it->second;
628 }, 634 },
629 [&]( 635 [&](route_tree<routed_ctx<PreRouteCtx>>::wildcard_subtree const&
630 route_tree<routed_ctx<PreRouteCtx>>::wildcard_subtree const& 636 wildcard_subtree)
631 wildcard_subtree) 637 {
632 { 638 wildcard_matches.push_back(seg);
633 wildcard_matches.push_back(seg); 639 tree = &*wildcard_subtree;
634 tree = &*wildcard_subtree; 640 },
635 }, 641 });
636 });
637 if (!tree) 642 if (!tree)
638 return std::nullopt; 643 return std::nullopt;
639 } 644 }
@@ -646,7 +651,7 @@ public:
646 } 651 }
647 652
648 auto route_request(PreRouteCtx ctx, readable_request r) const 653 auto route_request(PreRouteCtx ctx, readable_request r) const
649 -> net::awaitable<presponse> 654 -> net::awaitable<presponse>
650 { 655 {
651 auto req_base = r.p->get().base(); 656 auto req_base = r.p->get().base();
652 657
@@ -685,10 +690,10 @@ public:
685 if (!req_url.is_path_absolute()) 690 if (!req_url.is_path_absolute())
686 { 691 {
687 auto problem = bad_request_tpl.instantiate().set_detail(translate( 692 auto problem = bad_request_tpl.instantiate().set_detail(translate(
688 "Path of normalized (RFC 3986, § 6) " 693 "Path of normalized (RFC 3986, § 6) "
689 "origin-form request-target (RFC " 694 "origin-form request-target (RFC "
690 "9112, § 3.2.1) should be " 695 "9112, § 3.2.1) should be "
691 "absolute")); 696 "absolute"));
692 co_return problem_rsp(ctx, problem, keep_alive{false}); 697 co_return problem_rsp(ctx, problem, keep_alive{false});
693 } 698 }
694 699
@@ -719,7 +724,7 @@ public:
719 if (auto mhdl = mres->route_handlers->lookup(*mverb)) 724 if (auto mhdl = mres->route_handlers->lookup(*mverb))
720 { 725 {
721 auto new_ctx = 726 auto new_ctx =
722 routed_ctx<PreRouteCtx>{std::move(ctx), mres->allowed_methods()}; 727 routed_ctx<PreRouteCtx>{std::move(ctx), mres->allowed_methods()};
723 co_return co_await mhdl(std::move(new_ctx), r, mres->wildcard_matches); 728 co_return co_await mhdl(std::move(new_ctx), r, mres->wildcard_matches);
724 } 729 }
725 else 730 else
@@ -733,7 +738,7 @@ public:
733 }; 738 };
734 auto rsp = problem_rsp(ctx, tpl.instantiate(), keep_alive{false}); 739 auto rsp = problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
735 rsp.header().set( 740 rsp.header().set(
736 bhttp::field::allow, mres->allowed_methods().to_string()); 741 bhttp::field::allow, mres->allowed_methods().to_string());
737 co_return std::move(rsp); 742 co_return std::move(rsp);
738 } 743 }
739 } 744 }
@@ -742,9 +747,9 @@ public:
742 // We do not accept any other request-target forms. 747 // We do not accept any other request-target forms.
743 748
744 auto problem = bad_request_tpl.instantiate().set_detail(translate( 749 auto problem = bad_request_tpl.instantiate().set_detail(translate(
745 "Invalid request-target, expected " 750 "Invalid request-target, expected "
746 "asterisk-form or origin-form " 751 "asterisk-form or origin-form "
747 "(see RFC 9112, § 3.2)")); 752 "(see RFC 9112, § 3.2)"));
748 co_return problem_rsp(ctx, problem, keep_alive{false}); 753 co_return problem_rsp(ctx, problem, keep_alive{false});
749 } 754 }
750 } 755 }
@@ -756,8 +761,9 @@ public:
756 auto ctx0 = base_ctx{.locale = locale}; 761 auto ctx0 = base_ctx{.locale = locale};
757 762
758 co_return co_await global_middleware_( 763 co_return co_await global_middleware_(
759 std::move(ctx0), header, [&](PreRouteCtx ctx) -> net::awaitable<presponse> 764 std::move(ctx0), header,
760 { co_return co_await route_request(std::move(ctx), std::move(r)); }); 765 [&](PreRouteCtx ctx) -> net::awaitable<presponse>
766 { co_return co_await route_request(std::move(ctx), std::move(r)); });
761 } 767 }
762 768
763 auto do_session(beast::tcp_stream strm) -> net::awaitable<void> 769 auto do_session(beast::tcp_stream strm) -> net::awaitable<void>
@@ -769,7 +775,7 @@ public:
769 auto p0 = bhttp::request_parser<bhttp::empty_body>{}; 775 auto p0 = bhttp::request_parser<bhttp::empty_body>{};
770 p0.body_limit(boost::none); 776 p0.body_limit(boost::none);
771 auto [ec, _] = 777 auto [ec, _] =
772 co_await bhttp::async_read_header(strm, buf, p0, net::as_tuple); 778 co_await bhttp::async_read_header(strm, buf, p0, net::as_tuple);
773 if (ec == bhttp::error::end_of_stream) 779 if (ec == bhttp::error::end_of_stream)
774 break; 780 break;
775 else if (ec) 781 else if (ec)
@@ -777,11 +783,11 @@ public:
777 783
778 auto http_version = p0.get().version(); 784 auto http_version = p0.get().version();
779 auto&& rsp = co_await handle_request( 785 auto&& rsp = co_await handle_request(
780 readable_request{ 786 readable_request{
781 .p = util::not_null{&p0}, 787 .p = util::not_null{&p0},
782 .strm = util::not_null{&strm}, 788 .strm = util::not_null{&strm},
783 .buf = util::not_null{&buf}, 789 .buf = util::not_null{&buf},
784 }); 790 });
785 rsp.header().version(http_version); 791 rsp.header().version(http_version);
786 bool keep_alive = rsp.keep_alive(); 792 bool keep_alive = rsp.keep_alive();
787 co_await beast::async_write(strm, std::move(rsp)); 793 co_await beast::async_write(strm, std::move(rsp));
@@ -800,27 +806,27 @@ public:
800 auto acceptor = tcp::acceptor{executor, endpoint}; 806 auto acceptor = tcp::acceptor{executor, endpoint};
801 807
802 l_.with("endpoint", endpoint.address().to_string()) 808 l_.with("endpoint", endpoint.address().to_string())
803 .with("port", std::to_string(endpoint.port())) 809 .with("port", std::to_string(endpoint.port()))
804 .info("Serving"); 810 .info("Serving");
805 while (true) 811 while (true)
806 { 812 {
807 net::co_spawn( 813 net::co_spawn(
808 executor, 814 executor,
809 do_session(beast::tcp_stream{co_await acceptor.async_accept()}), 815 do_session(beast::tcp_stream{co_await acceptor.async_accept()}),
810 [this](std::exception_ptr e) 816 [this](std::exception_ptr e)
811 {
812 if (e)
813 { 817 {
814 try 818 if (e)
815 {
816 std::rethrow_exception(e);
817 }
818 catch (std::exception const& e)
819 { 819 {
820 l_.error("Error in session: {}", e.what()); 820 try
821 {
822 std::rethrow_exception(e);
823 }
824 catch (std::exception const& e)
825 {
826 l_.error("Error in session: {}", e.what());
827 }
821 } 828 }
822 } 829 });
823 });
824 } 830 }
825 } 831 }
826 832
@@ -831,21 +837,21 @@ public:
831 837
832 // TODO: make exception handling as nice as in srv.cpp 838 // TODO: make exception handling as nice as in srv.cpp
833 net::co_spawn( 839 net::co_spawn(
834 ioc, do_listen(endpoint), 840 ioc, do_listen(endpoint),
835 [this](std::exception_ptr e) 841 [this](std::exception_ptr e)
836 {
837 if (e)
838 { 842 {
839 try 843 if (e)
840 {
841 std::rethrow_exception(e);
842 }
843 catch (std::exception const& e)
844 { 844 {
845 l_.error("Error: {}", e.what()); 845 try
846 {
847 std::rethrow_exception(e);
848 }
849 catch (std::exception const& e)
850 {
851 l_.error("Error: {}", e.what());
852 }
846 } 853 }
847 } 854 });
848 });
849 } 855 }
850}; 856};
851 857