summaryrefslogtreecommitdiffstats
path: root/server/src/srv.cppm
diff options
context:
space:
mode:
authorRutger Broekhoff2026-08-28 21:12:55 +0200
committerRutger Broekhoff2026-08-28 21:12:55 +0200
commita5d95afb96eb9a3b65d82f5f84bf8e4a4fb4c8ac (patch)
treeb4e0ec57c3799e9f649c6bfe27cb6c3313b97364 /server/src/srv.cppm
parent973aec43ea54bbf95b64fbcb636403401d1ca60e (diff)
downloadroutemon-a5d95afb96eb9a3b65d82f5f84bf8e4a4fb4c8ac.tar.gz
routemon-a5d95afb96eb9a3b65d82f5f84bf8e4a4fb4c8ac.zip
clang-format C++ sources
Diffstat (limited to 'server/src/srv.cppm')
-rw-r--r--server/src/srv.cppm370
1 files changed, 214 insertions, 156 deletions
diff --git a/server/src/srv.cppm b/server/src/srv.cppm
index 2ca48c6..d9ff880 100644
--- a/server/src/srv.cppm
+++ b/server/src/srv.cppm
@@ -1,12 +1,12 @@
1module; 1module;
2 2
3#include <boost/config.hpp>
4#include <boost/asio/ip/tcp.hpp>
5#include <boost/asio/as_tuple.hpp> 3#include <boost/asio/as_tuple.hpp>
6#include <boost/asio/awaitable.hpp> 4#include <boost/asio/awaitable.hpp>
7#include <boost/asio/co_spawn.hpp> 5#include <boost/asio/co_spawn.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.hpp> 10#include <boost/json.hpp>
11#include <boost/locale/generator.hpp> 11#include <boost/locale/generator.hpp>
12 12
@@ -32,190 +32,248 @@ using tcp = boost::asio::ip::tcp;
32 32
33namespace routemon::srv { 33namespace routemon::srv {
34 34
35 class gpx_parse_error_category_impl : public std::error_category { 35class gpx_parse_error_category_impl : public std::error_category
36 public: 36{
37 char const* name() const noexcept override { return "gpx_parse"; } 37public:
38 auto message(int condition) const noexcept -> std::string override { 38 char const* name() const noexcept override { return "gpx_parse"; }
39 std::ignore = condition; 39 auto message(int condition) const noexcept -> std::string override
40 return "failed to parse GPX file"; 40 {
41 } 41 std::ignore = condition;
42 }; 42 return "failed to parse GPX file";
43 auto gpx_parse_error_category() noexcept -> gpx_parse_error_category_impl const& {
44 static auto const inst = gpx_parse_error_category_impl{};
45 return inst;
46 }
47 auto gpx_parse_error() noexcept -> std::error_code {
48 return std::error_code{1, gpx_parse_error_category()};
49 } 43 }
44};
45auto gpx_parse_error_category() noexcept -> gpx_parse_error_category_impl const&
46{
47 static auto const inst = gpx_parse_error_category_impl{};
48 return inst;
49}
50auto gpx_parse_error() noexcept -> std::error_code
51{
52 return std::error_code{1, gpx_parse_error_category()};
53}
50 54
51 class gpx_parse_result { 55class gpx_parse_result
52 std::variant<std::exception_ptr, gpx::file> res_; 56{
57 std::variant<std::exception_ptr, gpx::file> res_;
53 58
54 public: 59public:
55 auto set_exception(std::exception_ptr ex) noexcept { 60 auto set_exception(std::exception_ptr ex) noexcept { res_ = ex; }
56 res_ = ex; 61 auto set_gpx_file(gpx::file&& f) noexcept { res_ = std::move(f); }
57 }
58 auto set_gpx_file(gpx::file&& f) noexcept {
59 res_ = std::move(f);
60 }
61 62
62 auto unwrap() -> gpx::file&& { 63 auto unwrap() -> gpx::file&&
63 return std::visit(util::overloaded{ 64 {
64 [](std::exception_ptr ex) -> gpx::file&& { 65 return std::visit(
65 if (ex) std::rethrow_exception(ex); 66 util::overloaded{
66 else throw std::runtime_error{"no GPX file parse result available"}; 67 [](std::exception_ptr ex) -> gpx::file&&
67 }, 68 {
68 [](gpx::file&& f) -> gpx::file&& { return std::move(f); }, 69 if (ex)
69 }, std::move(res_)); 70 std::rethrow_exception(ex);
70 } 71 else
71 }; 72 throw std::runtime_error{"no GPX file parse result available"};
73 },
74 [](gpx::file&& f) -> gpx::file&& { return std::move(f); },
75 },
76 std::move(res_));
77 }
78};
72 79
73 struct readable_gpx_body { 80struct readable_gpx_body
74 using value_type = gpx_parse_result; 81{
82 using value_type = gpx_parse_result;
75 83
76 class reader { 84 class reader
77 gpx::reader r_; 85 {
78 util::not_null<value_type*> res_; 86 gpx::reader r_;
87 util::not_null<value_type*> res_;
79 88
80 public: 89 public:
81 template<bool isRequest, bhttp::concepts::fields Fields> 90 template <bool isRequest, bhttp::concepts::fields Fields>
82 explicit reader(bhttp::header<isRequest, Fields>&, value_type& v) 91 explicit reader(bhttp::header<isRequest, Fields>&, value_type& v) : res_{&v}
83 : res_{&v} 92 {
84 {} 93 }
85 94
86 // The following methods (which are called by Beast) are marked 95 // The following methods (which are called by Beast) are marked
87 // noexcept, since Beast does not ensure that exceptions thrown 96 // noexcept, since Beast does not ensure that exceptions thrown
88 // here are appropriately directed to the caller of 97 // here are appropriately directed to the caller of
89 // (async_)read(_some), so throwing here might cause the program 98 // (async_)read(_some), so throwing here might cause the program
90 // to crash. 99 // to crash.
91 100
92 auto init(boost::optional<std::uint64_t> /* n */, beast::error_code& ec) noexcept -> void { 101 auto
93 try { 102 init(boost::optional<std::uint64_t> /* n */, beast::error_code& ec) noexcept
94 r_.init(); 103 -> void
95 ec = {}; 104 {
96 } catch (std::exception& ex) { 105 try
97 res_->set_exception(std::current_exception()); 106 {
98 ec = gpx_parse_error(); 107 r_.init();
99 } 108 ec = {};
109 }
110 catch (std::exception& ex)
111 {
112 res_->set_exception(std::current_exception());
113 ec = gpx_parse_error();
100 } 114 }
115 }
101 116
102 auto put(beast::concepts::const_buffer_sequence auto b, beast::error_code& ec) noexcept -> std::size_t { 117 auto
103 auto total = 0uz; 118 put(beast::concepts::const_buffer_sequence auto b,
104 try { 119 beast::error_code& ec) noexcept -> std::size_t
105 for (auto it = net::buffer_sequence_begin(b); it != net::buffer_sequence_end(b); it++) { 120 {
106 r_.put(std::string_view{static_cast<char const*>(it->data()), it->size()}); 121 auto total = 0uz;
107 total += it->size(); 122 try
108 } 123 {
109 ec = {}; 124 for (auto it = net::buffer_sequence_begin(b);
110 } catch (std::exception& ex) { 125 it != net::buffer_sequence_end(b); it++)
111 res_->set_exception(std::current_exception()); 126 {
112 ec = gpx_parse_error(); 127 r_.put(
128 std::string_view{
129 static_cast<char const*>(it->data()), it->size()
130 });
131 total += it->size();
113 } 132 }
114 return total; 133 ec = {};
134 }
135 catch (std::exception& ex)
136 {
137 res_->set_exception(std::current_exception());
138 ec = gpx_parse_error();
115 } 139 }
140 return total;
141 }
116 142
117 auto finish(beast::error_code& ec) noexcept { 143 auto finish(beast::error_code& ec) noexcept
118 try { 144 {
119 res_->set_gpx_file(r_.finish()); 145 try
120 ec = {}; 146 {
121 } catch (std::exception& ex) { 147 res_->set_gpx_file(r_.finish());
122 res_->set_exception(std::current_exception()); 148 ec = {};
123 ec = gpx_parse_error(); 149 }
124 } 150 catch (std::exception& ex)
151 {
152 res_->set_exception(std::current_exception());
153 ec = gpx_parse_error();
125 } 154 }
126 }; 155 }
127 }; 156 };
128 static_assert(bhttp::concepts::body<readable_gpx_body>); 157};
129 static_assert(bhttp::concepts::body_reader<readable_gpx_body>); 158static_assert(bhttp::concepts::body<readable_gpx_body>);
159static_assert(bhttp::concepts::body_reader<readable_gpx_body>);
130 160
131 class handler { 161class handler
132 api::handler inner_; 162{
163 api::handler inner_;
133 164
134 public: 165public:
135 using outer_ctx = http::trace_id_ctx<http::base_ctx>; 166 using outer_ctx = http::trace_id_ctx<http::base_ctx>;
136 using l0_ctx = http::routed_ctx<outer_ctx>; 167 using l0_ctx = http::routed_ctx<outer_ctx>;
137 168
138 private: 169private:
139 auto handle_process_gpx(l0_ctx ctx, http::readable_request r) -> net::awaitable<http::presponse> { 170 auto handle_process_gpx(l0_ctx ctx, http::readable_request r)
140 auto gpx_file = gpx::file{}; 171 -> net::awaitable<http::presponse>
141 try { 172 {
142 auto req = co_await http::read_request<readable_gpx_body>(ctx, std::move(r)); 173 auto gpx_file = gpx::file{};
143 gpx_file = std::move(req->body().unwrap()); 174 try
144 } catch (std::exception& ex) { 175 {
145 // TODO: more detailed problem reporting 176 auto req =
146 auto tpl = problem::tpl{ 177 co_await http::read_request<readable_gpx_body>(ctx, std::move(r));
147 .status = bhttp::status::bad_request, 178 gpx_file = std::move(req->body().unwrap());
148 .title = translate("Failed to parse GPX file"), 179 }
180 catch (std::exception& ex)
181 {
182 // TODO: more detailed problem reporting
183 auto tpl = problem::tpl{
184 .status = bhttp::status::bad_request,
185 .title = translate("Failed to parse GPX file"),
149 .type_uri = "https://routemon.fautchen.eu/problems/gpx-parse-failed", 186 .type_uri = "https://routemon.fautchen.eu/problems/gpx-parse-failed",
150 }; 187 };
151 co_return http::problem_rsp(ctx, tpl.instantiate(), http::keep_alive{false}); 188 co_return http::problem_rsp(
152 } 189 ctx, tpl.instantiate(), http::keep_alive{false});
153 190 }
154 // TODO: catch handler exceptions and return 500 when raised?
155 // (keep-alive depends on whether whole request was read)
156 auto mres = inner_.process_gpx(std::move(gpx_file));
157 if (!mres) {
158 auto tpl = problem::tpl{
159 .status = bhttp::status::internal_server_error,
160 .title = translate("Internal server error"),
161 .type_uri = "https://routemon.fautchen.eu/problems/internal-server-error",
162 };
163 co_return http::problem_rsp(ctx, tpl.instantiate(), http::keep_alive{true});
164 }
165 191
166 auto rsp = http::make_rsp<bhttp::string_body>(bhttp::status::ok, http::keep_alive{true}); 192 // TODO: catch handler exceptions and return 500 when raised?
167 rsp.set(bhttp::field::content_type, "application/json"); 193 // (keep-alive depends on whether whole request was read)
168 rsp.body() = json::serialize(json::value_from(*mres)); 194 auto mres = inner_.process_gpx(std::move(gpx_file));
169 rsp.prepare_payload(); 195 if (!mres)
170 co_return rsp; 196 {
197 auto tpl = problem::tpl{
198 .status = bhttp::status::internal_server_error,
199 .title = translate("Internal server error"),
200 .type_uri = "https://routemon.fautchen.eu/problems/"
201 "internal-server-error",
202 };
203 co_return http::problem_rsp(
204 ctx, tpl.instantiate(), http::keep_alive{true});
171 } 205 }
172 206
173 auto handle_sysinfo(l0_ctx ctx, http::readable_request r) -> net::awaitable<http::presponse> { 207 auto rsp = http::make_rsp<bhttp::string_body>(
174 auto req = co_await http::read_request<bhttp::empty_body>(ctx, std::move(r)); 208 bhttp::status::ok, http::keep_alive{true});
175 auto info = inner_.sysinfo(); 209 rsp.set(bhttp::field::content_type, "application/json");
210 rsp.body() = json::serialize(json::value_from(*mres));
211 rsp.prepare_payload();
212 co_return rsp;
213 }
176 214
177 auto rsp = http::make_rsp<bhttp::string_body>(bhttp::status::ok, http::keep_alive{true}); 215 auto handle_sysinfo(l0_ctx ctx, http::readable_request r)
178 rsp.set(bhttp::field::content_type, "application/json"); 216 -> net::awaitable<http::presponse>
179 rsp.body() = json::serialize(json::value_from(info)); 217 {
180 rsp.prepare_payload(); 218 auto req =
181 co_return rsp; 219 co_await http::read_request<bhttp::empty_body>(ctx, std::move(r));
182 } 220 auto info = inner_.sysinfo();
183 221
184 public: 222 auto rsp = http::make_rsp<bhttp::string_body>(
185 handler(api::handler&& inner) : inner_{std::move(inner)} {} 223 bhttp::status::ok, http::keep_alive{true});
224 rsp.set(bhttp::field::content_type, "application/json");
225 rsp.body() = json::serialize(json::value_from(info));
226 rsp.prepare_payload();
227 co_return rsp;
228 }
186 229
187 auto make_routes() -> http::route_tree<http::routed_ctx<outer_ctx>> { 230public:
188 auto handler = [this]<class MemFn>(MemFn member) { 231 handler(api::handler&& inner) : inner_{std::move(inner)} {}
189 return std::bind_front(member, this);
190 };
191 232
192 return http::dtree<http::routed_ctx<outer_ctx>>{}.named_subtrees({ 233 auto make_routes() -> http::route_tree<http::routed_ctx<outer_ctx>>
193 {"gpx", http::dtree<l0_ctx>{{ 234 {
194 .post = handler(&handler::handle_process_gpx), 235 auto handler = [this]<class MemFn>(MemFn member)
195 }}.no_subtrees()}, 236 { return std::bind_front(member, this); };
196 {"sysinfo", http::dtree<l0_ctx>{{
197 .get = handler(&handler::handle_sysinfo),
198 }}.no_subtrees()},
199 });
200 }
201 };
202 237
203 export class server { 238 return http::dtree<http::routed_ctx<outer_ctx>>{}.named_subtrees({
204 handler handler_; 239 {"gpx",
205 http::server<handler::outer_ctx> srv_; 240 http::dtree<l0_ctx>{
241 {
242 .post = handler(&handler::handle_process_gpx),
243 }
244 }.no_subtrees()},
245 {"sysinfo", http::dtree<l0_ctx>{
246 {
247 .get = handler(&handler::handle_sysinfo),
248 }
249 }.no_subtrees()},
250 });
251 }
252};
206 253
207 static auto make_global_middleware() -> http::middleware_t<http::base_ctx, handler::outer_ctx> { 254export class server
208 return http::middleware_compose<http::base_ctx, http::trace_id_ctx<http::base_ctx>, http::trace_id_ctx<http::base_ctx>>(http::trace_id_middleware<http::base_ctx>, http::lax_cors_middleware<http::trace_id_ctx<http::base_ctx>>); 255{
209 } 256 handler handler_;
257 http::server<handler::outer_ctx> srv_;
210 258
211 public: 259 static auto make_global_middleware()
212 server(log::logger const& l, locale::selector&& lsel, api::handler&& inner) 260 -> http::middleware_t<http::base_ctx, handler::outer_ctx>
213 : handler_{std::move(inner)}, srv_{l, std::move(lsel), make_global_middleware(), handler_.make_routes()} 261 {
214 {} 262 return http::middleware_compose<
263 http::base_ctx, http::trace_id_ctx<http::base_ctx>,
264 http::trace_id_ctx<http::base_ctx>>(
265 http::trace_id_middleware<http::base_ctx>,
266 http::lax_cors_middleware<http::trace_id_ctx<http::base_ctx>>);
267 }
215 268
216 auto spawn(net::io_context& ioc) -> void { 269public:
217 srv_.spawn(ioc); 270 server(log::logger const& l, locale::selector&& lsel, api::handler&& inner)
218 } 271 : handler_{std::move(inner)},
219 }; 272 srv_{l, std::move(lsel), make_global_middleware(), handler_.make_routes()}
273 {
274 }
275
276 auto spawn(net::io_context& ioc) -> void { srv_.spawn(ioc); }
277};
220 278
221} // namespace routemon::srv 279} // namespace routemon::srv