3#include "endpoint_http.hpp"
4#include "endpoint_http_regex.hpp"
5#include "endpoint_http_files.hpp"
6#include "endpoint_websocket.hpp"
7#include "type_traits.hpp"
8#include "../http/connection.hpp"
9#include "../http/connection_plain.hpp"
10#include "../http/connection_t.hpp"
11#include "../http/preflight_config.hpp"
12#include "../../core/type_traits.hpp"
13#include "../../core/detail/version_checks.hpp"
14#include "../../core/http/generator.hpp"
15#include "../../core/http/http.hpp"
16#include "../../core/http/request.hpp"
17#include "../../core/http/response.hpp"
18#include "../../core/http/utils.hpp"
20 #include "../http/connection_tls.hpp"
23#include <boost/beast/core.hpp>
24#include <boost/beast/http.hpp>
25#include <boost/beast/version.hpp>
26#include <spdlog/logger.h>
42namespace malloy::server
44 class routing_context;
49 template<
typename T,
typename... Args>
52 t.do_write(std::forward<Args>(args)...);
64 using header_type = boost::beast::http::request_header<>;
66 void setup_body(
const header_type&,
typename request_type::body_type::value_type&)
const {}
78 template<
typename Body>
80 send_response(
const boost::beast::http::request_header<>& req,
malloy::http::response<Body>&& resp, http::connection_t connection, std::string_view server_str)
84 resp.version(req.version());
85 if (!malloy::http::has_field(resp, malloy::http::field::server))
86 resp.set(malloy::http::field::server, server_str);
87 resp.prepare_payload();
90 [resp = std::move(resp)](
auto& c)
mutable {
91 c->do_write(std::move(resp));
109 make_endpt_writer_callback()
111 return [
this]<
typename R>(
const auto& req, R&& resp,
const auto& conn) {
113 [&,
this]<
typename Re>(Re&& resp) {
114 detail::send_response(req, std::forward<Re>(resp), conn, m_server_str);
116 std::forward<R>(resp)
121 class abstract_req_validator
125 ~abstract_req_validator() =
default;
129 process(
const boost::beast::http::request_header<>&,
const http::connection_t& conn) = 0;
132 template<concepts::request_val
idator V,
typename Writer>
133 class req_validator_impl :
134 public abstract_req_validator
139 req_validator_impl(V validator, Writer writer_) :
140 writer{std::move(writer_)},
141 m_validator{std::move(validator)}
146 process(
const boost::beast::http::request_header<>& h,
const http::connection_t& conn)
override
148 auto maybe_resp = std::invoke(m_validator, h);
152 writer(h, std::move(*maybe_resp), conn);
164 policy_store(std::string reg, std::unique_ptr<abstract_req_validator> validator) :
165 m_validator{std::move(validator)},
166 m_raw_reg{std::move(reg)}
172 process(
const boost::beast::http::request_header<>& h,
const http::connection_t& conn)
const
174 if (!matches(h.target()))
177 return m_validator->process(h, conn);
182 matches(std::string_view url)
const
185 compile_match_expr();
186 std::string surl{url.begin(), url.end()};
187 return std::regex_match(surl, *m_compiled_reg);
191 compile_match_expr()
const
193 m_compiled_reg = std::regex{m_raw_reg};
196 std::unique_ptr<abstract_req_validator> m_validator;
197 mutable std::optional<std::regex> m_compiled_reg;
198 std::string m_raw_reg;
202 template<
typename Derived>
203 using req_generator = std::shared_ptr<typename http::connection<Derived>::request_generator>;
205 using request_header = boost::beast::http::request_header<>;
233 router(std::shared_ptr<spdlog::logger> logger);
274 set_logger(std::shared_ptr<spdlog::logger> logger);
284 add_subrouter(std::string resource, std::unique_ptr<router> sub_router);
308 add(
const method_type method,
const std::string_view target, Func&& handler, ExtraInfo&& extra)
310 using func_t = std::decay_t<Func>;
312 constexpr bool uses_captures = std::invocable<func_t, const request_type&, const std::vector<std::string>&>;
314 if constexpr (uses_captures) {
315 return add_regex_endpoint<
317 std::invoke_result_t<func_t, const request_type&, const std::vector<std::string>&>
319 method, target, std::forward<Func>(handler), std::forward<ExtraInfo>(extra)
323 return add_regex_endpoint<
325 std::invoke_result_t<func_t, const request_type&>
327 method, target, std::forward<Func>(handler), std::forward<ExtraInfo>(extra)
332 template<concepts::route_handler<
typename detail::default_route_filter::request_type> Func>
334 add(
const method_type method,
const std::string_view target, Func&& handler)
340 add_preflight(std::string_view target, http::preflight_config cfg);
351 template<malloy::concepts::callable_
string CacheControl>
353 add_file_serving(std::string resource, std::filesystem::path storage_base_path,
const CacheControl& cc)
357 m_logger->trace(
"adding file serving location: {} -> {}", resource, storage_base_path.string());
360 auto ep = std::make_unique<endpoint_http_files>();
361 ep->resource_base = resource;
362 ep->base_path = std::move(storage_base_path);
363 ep->cache_control = cc();
364 ep->writer = make_endpt_writer_callback();
367 return add_http_endpoint(std::move(ep));
382 std::move(storage_base_path),
383 []() -> std::string {
return ""; }
406 add_websocket(std::string&& resource,
typename websocket::connection::handler_t&& handler);
416 template<concepts::request_val
idator Policy>
421 m_logger->trace(
"adding policy: {}", resource);
423 using policy_t = std::decay_t<Policy>;
424 auto writer = [
this](
const auto& header,
auto&& resp,
auto&& conn) { detail::send_response(header, std::forward<
decltype(resp)>(resp), std::forward<
decltype(conn)>(conn), m_server_str); };
426 m_policies.emplace_back(resource, std::make_unique<req_validator_impl<policy_t,
decltype(writer)>>(std::forward<Policy>(policy), std::move(writer)));
446 bool isWebsocket =
false,
451 const std::filesystem::path& doc_root,
452 const req_generator<Derived>& req,
453 Connection&& connection
457 if constexpr (!isWebsocket) {
458 if (is_handled_by_policies<Derived>(req, connection))
463 for (
const auto& [resource_base,
router] : m_sub_routers) {
465 const auto res_str = malloy::http::resource_string(req->header());
466 if (!res_str.starts_with(resource_base))
470 malloy::http::chop_resource(req->header(), resource_base);
473 router->template handle_request<isWebsocket, Derived>(doc_root, std::move(req), connection);
484 if constexpr (isWebsocket)
485 handle_ws_request<Derived>(std::move(req), connection);
487 handle_http_request<Derived>(doc_root, std::move(req), connection);
492 server_string()
const
498 std::shared_ptr<spdlog::logger> m_logger{
nullptr};
499 std::unordered_map<std::string, std::unique_ptr<router>> m_sub_routers;
500 std::vector<std::unique_ptr<endpoint_http>> m_endpoints_http;
501 std::vector<std::unique_ptr<endpoint_websocket>> m_endpoints_websocket;
502 std::vector<policy_store> m_policies;
503 std::string_view m_server_str;
505 friend class routing_context;
507 router(std::shared_ptr<spdlog::logger> logger, std::string_view m_server_str);
510 set_server_string(std::string_view str);
512 template<
typename Derived>
515 is_handled_by_policies(
const req_generator<Derived>& req,
const http::connection_t& connection)
518 std::cbegin(m_policies),
519 std::cend(m_policies),
520 [&](
const policy_store& policy) {
521 return policy.process(req->header(), connection);
533 template<
typename Derived>
536 const std::filesystem::path&,
537 const req_generator<Derived>& req,
538 const http::connection_t& connection
543 m_logger->trace(
"handling HTTP request: {} {}",
544 std::string_view{req->header().method_string()},
545 std::string_view{req->header().target()}
549 const auto& header = req->header();
552 for (
const auto& ep : m_endpoints_http) {
554 if (!ep->matches(header))
558 auto resp = ep->handle(req, connection);
561 detail::send_response(req->header(), std::move(*resp), connection, m_server_str);
579 template<
typename Derived>
582 const req_generator<Derived>& gen,
583 const std::shared_ptr<websocket::connection>& connection
586 const auto res_string = malloy::http::resource_string(gen->header());
587 m_logger->trace(
"handling WS request: {} {}",
588 std::string_view{gen->header().method_string()},
593 for (
const auto& ep : m_endpoints_websocket) {
595 if (ep->resource != res_string)
600 m_logger->warn(
"websocket route with resource path \"{}\" has no valid handler assigned.");
605 req.base() = gen->header();
606 ep->handler(std::move(req), connection);
616 concepts::request_filter ExtraInfo,
619 add_regex_endpoint(
method_type method, std::string_view target, Func&& handler, ExtraInfo&& extra)
623 m_logger->trace(
"adding route: {}", target);
628 regex = std::regex{target.cbegin(), target.cend()};
630 catch (
const std::regex_error& e) {
632 m_logger->error(
"invalid route target supplied \"{}\": {}", target, e.what());
637 using bodies_t = std::conditional_t<wrapped, Body, std::variant<Body>>;
640 auto ep = std::make_unique<endpoint_http_regex<bodies_t, std::decay_t<ExtraInfo>, UsesCaptures>>();
641 ep->resource_base = std::move(regex);
643 ep->filter = std::forward<ExtraInfo>(extra);
644 if constexpr (wrapped) {
645 ep->handler = std::move(handler);
649 [w = std::forward<Func>(handler)](
auto&&... args) {
650 return std::variant<Body>{w(std::forward<
decltype(args)>(args)...)};
657 m_logger->warn(
"route has invalid handler. ignoring.");
661 ep->writer = make_endpt_writer_callback();
664 return add_http_endpoint(std::move(ep));
676 add_http_endpoint(std::unique_ptr<endpoint_http>&& ep);
687 add_websocket_endpoint(std::unique_ptr<endpoint_websocket>&& ep);
700 template<
typename FormatString,
typename... Args>
703 const std::exception& exception,
704 const spdlog::level::level_enum level,
705 const FormatString& fmt,
711#
if MALLOY_DETAIL_HAS_FMT_8
717 std::forward<Args>(args)...
static response bad_request(std::string_view reason)
Definition: generator.cpp:27
Definition: request.hpp:19
Definition: response.hpp:22
Definition: router.hpp:104
bool add_websocket(std::string &&resource, typename websocket::connection::handler_t &&handler)
Definition: router.cpp:189
router & operator=(router &&rhs) noexcept=default
bool add_redirect(malloy::http::status status, std::string &&resource_old, std::string &&resource_new)
Definition: router.cpp:157
router & operator=(const router &rhs)=delete
bool add_subrouter(std::string resource, std::unique_ptr< router > sub_router)
Definition: router.cpp:59
bool add_file_serving(std::string resource, std::filesystem::path storage_base_path, const CacheControl &cc)
Definition: router.hpp:353
void set_logger(std::shared_ptr< spdlog::logger > logger)
Definition: router.cpp:21
bool add(const method_type method, const std::string_view target, Func &&handler, ExtraInfo &&extra)
Definition: router.hpp:308
malloy::http::method method_type
Definition: router.hpp:210
void add_policy(const std::string &resource, Policy &&policy)
Definition: router.hpp:418
router(const router &other)=delete
router(router &&other) noexcept=default
bool add_file_serving(std::string resource, std::filesystem::path storage_base_path)
Definition: router.hpp:378
void handle_request(const std::filesystem::path &doc_root, const req_generator< Derived > &req, Connection &&connection)
Definition: router.hpp:450
Definition: type_traits.hpp:104
Definition: type_traits.hpp:34
Definition: type_traits.hpp:26
Definition: router.hpp:50
boost::beast::http::verb method
Definition: types.hpp:18
boost::beast::http::status status
Definition: types.hpp:23
Definition: router.hpp:62