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() {
110 return [
this]<
typename R>(
const auto& req, R&& resp,
const auto& conn) {
112 [&,
this]<
typename Re>(Re&& resp) {
113 detail::send_response(req, std::forward<Re>(resp), conn, m_server_str);
115 std::forward<R>(resp)
120 class abstract_req_validator
123 virtual ~abstract_req_validator() =
default;
127 process(
const boost::beast::http::request_header<>&,
const http::connection_t& conn) = 0;
130 template<concepts::request_val
idator V,
typename Writer>
131 class req_validator_impl :
132 public abstract_req_validator
137 req_validator_impl(V validator, Writer writer_) :
138 writer{std::move(writer_)},
139 m_validator{std::move(validator)}
144 process(
const boost::beast::http::request_header<>& h,
const http::connection_t& conn)
override
146 auto maybe_resp = std::invoke(m_validator, h);
150 writer(h, std::move(*maybe_resp), conn);
162 policy_store(std::string reg, std::unique_ptr<abstract_req_validator> validator) :
163 m_validator{std::move(validator)},
164 m_raw_reg{std::move(reg)}
170 process(
const boost::beast::http::request_header<>& h,
const http::connection_t& conn)
const
172 if (!matches(h.target()))
175 return m_validator->process(h, conn);
180 matches(std::string_view url)
const
183 compile_match_expr();
184 std::string surl{url.begin(), url.end()};
185 return std::regex_match(surl, *m_compiled_reg);
189 compile_match_expr()
const
191 m_compiled_reg = std::regex{m_raw_reg};
194 std::unique_ptr<abstract_req_validator> m_validator;
195 mutable std::optional<std::regex> m_compiled_reg;
196 std::string m_raw_reg;
200 template<
typename Derived>
201 using req_generator = std::shared_ptr<typename http::connection<Derived>::request_generator>;
203 using request_header = boost::beast::http::request_header<>;
231 router(std::shared_ptr<spdlog::logger> logger);
272 set_logger(std::shared_ptr<spdlog::logger> logger);
282 add_subrouter(std::string resource, std::unique_ptr<router> sub_router);
306 add(
const method_type method,
const std::string_view target, Func&& handler, ExtraInfo&& extra)
308 using func_t = std::decay_t<Func>;
310 constexpr bool uses_captures = std::invocable<func_t, const request_type&, const std::vector<std::string>&>;
312 if constexpr (uses_captures) {
313 return add_regex_endpoint<
315 std::invoke_result_t<func_t, const request_type&, const std::vector<std::string>&>
317 method, target, std::forward<Func>(handler), std::forward<ExtraInfo>(extra)
321 return add_regex_endpoint<
323 std::invoke_result_t<func_t, const request_type&>
325 method, target, std::forward<Func>(handler), std::forward<ExtraInfo>(extra)
330 template<concepts::route_handler<
typename detail::default_route_filter::request_type> Func>
332 add(
const method_type method,
const std::string_view target, Func&& handler)
338 add_preflight(std::string_view target, http::preflight_config cfg);
349 template<malloy::concepts::callable_
string CacheControl>
351 add_file_serving(std::string resource, std::filesystem::path storage_base_path,
const CacheControl& cc)
355 m_logger->trace(
"adding file serving location: {} -> {}", resource, storage_base_path.string());
358 auto ep = std::make_unique<endpoint_http_files>();
359 ep->resource_base = resource;
360 ep->base_path = std::move(storage_base_path);
361 ep->cache_control = cc();
362 ep->writer = make_endpt_writer_callback();
365 return add_http_endpoint(std::move(ep));
380 std::move(storage_base_path),
381 []() -> std::string {
return ""; }
404 add_websocket(std::string&& resource,
typename websocket::connection::handler_t&& handler);
414 template<concepts::request_val
idator Policy>
419 m_logger->trace(
"adding policy: {}", resource);
421 using policy_t = std::decay_t<Policy>;
422 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); };
424 m_policies.emplace_back(resource, std::make_unique<req_validator_impl<policy_t,
decltype(writer)>>(std::forward<Policy>(policy), std::move(writer)));
444 bool isWebsocket =
false,
448 const std::filesystem::path& doc_root,
449 const req_generator<Derived>& req,
450 Connection&& connection
454 if constexpr (!isWebsocket) {
455 if (is_handled_by_policies<Derived>(req, connection))
460 for (
const auto& [resource_base,
router] : m_sub_routers) {
462 const auto res_str = malloy::http::resource_string(req->header());
463 if (!res_str.starts_with(resource_base))
467 malloy::http::chop_resource(req->header(), resource_base);
470 router->template handle_request<isWebsocket, Derived>(doc_root, std::move(req), connection);
481 if constexpr (isWebsocket)
482 handle_ws_request<Derived>(std::move(req), connection);
484 handle_http_request<Derived>(doc_root, std::move(req), connection);
489 server_string()
const
495 std::shared_ptr<spdlog::logger> m_logger{
nullptr};
496 std::unordered_map<std::string, std::unique_ptr<router>> m_sub_routers;
497 std::vector<std::unique_ptr<endpoint_http>> m_endpoints_http;
498 std::vector<std::unique_ptr<endpoint_websocket>> m_endpoints_websocket;
499 std::vector<policy_store> m_policies;
500 std::string_view m_server_str;
502 friend class routing_context;
504 router(std::shared_ptr<spdlog::logger> logger, std::string_view m_server_str);
507 set_server_string(std::string_view str);
509 template<
typename Derived>
512 is_handled_by_policies(
const req_generator<Derived>& req,
const http::connection_t& connection)
514 return std::any_of(std::cbegin(m_policies), std::cend(m_policies), [&](
const policy_store& policy) {
515 return policy.process(req->header(), connection);
527 template<
typename Derived>
528 void handle_http_request(
529 const std::filesystem::path&,
530 const req_generator<Derived>& req,
531 const http::connection_t& connection
536 m_logger->trace(
"handling HTTP request: {} {}",
537 std::string_view{req->header().method_string()},
538 std::string_view{req->header().target()}
542 const auto& header = req->header();
545 for (
const auto& ep : m_endpoints_http) {
547 if (!ep->matches(header))
551 auto resp = ep->handle(req, connection);
554 detail::send_response(req->header(), std::move(*resp), connection, m_server_str);
572 template<
typename Derived>
573 void handle_ws_request(
574 const req_generator<Derived>& gen,
575 const std::shared_ptr<websocket::connection>& connection
578 const auto res_string = malloy::http::resource_string(gen->header());
579 m_logger->trace(
"handling WS request: {} {}",
580 std::string_view{gen->header().method_string()},
585 for (
const auto& ep : m_endpoints_websocket) {
587 if (ep->resource != res_string)
592 m_logger->warn(
"websocket route with resource path \"{}\" has no valid handler assigned.");
597 req.base() = gen->header();
598 ep->handler(std::move(req), connection);
608 concepts::request_filter ExtraInfo,
611 add_regex_endpoint(
method_type method, std::string_view target, Func&& handler, ExtraInfo&& extra)
615 m_logger->trace(
"adding route: {}", target);
620 regex = std::regex{target.cbegin(), target.cend()};
622 catch (
const std::regex_error& e) {
624 m_logger->error(
"invalid route target supplied \"{}\": {}", target, e.what());
629 using bodies_t = std::conditional_t<wrapped, Body, std::variant<Body>>;
632 auto ep = std::make_unique<endpoint_http_regex<bodies_t, std::decay_t<ExtraInfo>, UsesCaptures>>();
633 ep->resource_base = std::move(regex);
635 ep->filter = std::forward<ExtraInfo>(extra);
636 if constexpr (wrapped) {
637 ep->handler = std::move(handler);
641 [w = std::forward<Func>(handler)](
auto&&... args) {
642 return std::variant<Body>{w(std::forward<
decltype(args)>(args)...)};
649 m_logger->warn(
"route has invalid handler. ignoring.");
653 ep->writer = make_endpt_writer_callback();
656 return add_http_endpoint(std::move(ep));
668 add_http_endpoint(std::unique_ptr<endpoint_http>&& ep);
679 add_websocket_endpoint(std::unique_ptr<endpoint_websocket>&& ep);
692 template<
typename FormatString,
typename... Args>
695 const std::exception& exception,
696 const spdlog::level::level_enum level,
697 const FormatString& fmt,
703#
if MALLOY_DETAIL_HAS_FMT_8
709 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:351
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:306
malloy::http::method method_type
Definition: router.hpp:208
void add_policy(const std::string &resource, Policy &&policy)
Definition: router.hpp:416
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:376
void handle_request(const std::filesystem::path &doc_root, const req_generator< Derived > &req, Connection &&connection)
Definition: router.hpp:447
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