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