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