summaryrefslogtreecommitdiffstats
path: root/server/src/http_server.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/http_server.cpp')
-rw-r--r--server/src/http_server.cpp188
1 files changed, 188 insertions, 0 deletions
diff --git a/server/src/http_server.cpp b/server/src/http_server.cpp
new file mode 100644
index 0000000..da8e6f0
--- /dev/null
+++ b/server/src/http_server.cpp
@@ -0,0 +1,188 @@
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/serialize.hpp>
11#include <boost/url.hpp>
12
13module routemon:http.server$impl;
14
15import :http.server;
16
17namespace routemon::http {
18
19auto presponse::header() -> bhttp::response_header<bhttp::fields>&
20{
21 return impl_->header();
22}
23
24auto presponse::header() const -> bhttp::response_header<bhttp::fields> const&
25{
26 return impl_->header();
27}
28
29auto presponse::is_done() const -> bool { return impl_->is_done(); }
30
31auto presponse::prepare(beast::error_code& ec) -> const_buffers_type
32{
33 return impl_->prepare(ec);
34}
35
36auto presponse::consume(std::size_t n) -> void { return impl_->consume(n); }
37
38auto presponse::keep_alive() const noexcept -> bool
39{
40 return impl_->keep_alive();
41}
42
43keep_alive::keep_alive(bool value) : value{value} {}
44
45auto problem_rsp(
46 base_ctx const& ctx, problem::details const& problem, keep_alive ka)
47 -> presponse
48{
49 auto rsp = make_rsp<bhttp::string_body>(problem.status, ka);
50 rsp.set(bhttp::field::content_type, "application/problem+json");
51 rsp.body() = json::serialize(json::value_from(problem, ctx.locale));
52 rsp.prepare_payload();
53 return presponse{std::move(rsp)};
54}
55
56auto make_preflight_rsp(preflight_response res, keep_alive ka)
57 -> bhttp::response<bhttp::empty_body>
58{
59 auto rsp = make_rsp<bhttp::empty_body>(bhttp::status::no_content, ka);
60 auto allow_headers_str = res.allow_headers
61 | std::views::transform(
62 [](auto const& field) -> std::string_view
63 { return bhttp::to_string(field); })
64 | std::views::join_with(std::string_view{", "})
65 | std::ranges::to<std::string>();
66 rsp.set(
67 bhttp::field::access_control_allow_methods,
68 res.allow_methods.to_string());
69 rsp.set(bhttp::field::access_control_allow_headers, allow_headers_str);
70 rsp.prepare_payload();
71 return rsp;
72}
73
74auto global_options_handler(base_ctx const& ctx, readable_request r)
75 -> net::awaitable<presponse>
76{
77 // TODO: switch to "small (4KB) discarded" body type, similar to what Go
78 // does? Same goes for default_options_handler? Not sure.
79 if (auto res = co_await read_request<bhttp::empty_body>(ctx, std::move(r));
80 !res)
81 co_return std::move(res.error());
82 auto req = r.p->release();
83 auto rsp = make_rsp<bhttp::empty_body>(
84 bhttp::status::no_content, keep_alive{req.keep_alive()});
85 rsp.prepare_payload();
86 co_return std::move(rsp);
87}
88
89auto router::handle_request(readable_request r) const
90 -> net::awaitable<presponse>
91{
92 return impl_->handle_request(r);
93}
94
95server::server(log::logger const& l, router&& r)
96 : l_{l.sub("http_server")}, r_{std::move(r)}
97{
98}
99
100auto server::do_session(beast::tcp_stream strm) -> net::awaitable<void>
101{
102 auto buf = beast::flat_buffer{};
103
104 while (true)
105 {
106 auto p0 = bhttp::request_parser<bhttp::empty_body>{};
107 p0.body_limit(boost::none);
108 auto [ec, _] =
109 co_await bhttp::async_read_header(strm, buf, p0, net::as_tuple);
110 if (ec == bhttp::error::end_of_stream)
111 break;
112 else if (ec)
113 throw boost::system::system_error{ec};
114
115 auto http_version = p0.get().version();
116 auto&& rsp = co_await r_.handle_request(
117 readable_request{
118 .p = util::not_null{&p0},
119 .strm = util::not_null{&strm},
120 .buf = util::not_null{&buf},
121 });
122 rsp.header().version(http_version);
123 bool keep_alive = rsp.keep_alive();
124 co_await beast::async_write(strm, std::move(rsp));
125 if (!keep_alive)
126 {
127 break;
128 }
129 }
130
131 strm.socket().shutdown(tcp::socket::shutdown_send);
132}
133
134auto server::do_listen(tcp::endpoint endpoint) -> net::awaitable<void>
135{
136 auto executor = co_await net::this_coro::executor;
137 auto acceptor = tcp::acceptor{executor, endpoint};
138
139 l_.with("endpoint", endpoint.address().to_string())
140 .with("port", std::to_string(endpoint.port()))
141 .info("Serving");
142 while (true)
143 {
144 net::co_spawn(
145 executor,
146 do_session(beast::tcp_stream{co_await acceptor.async_accept()}),
147 [this](std::exception_ptr e)
148 {
149 if (e)
150 {
151 try
152 {
153 std::rethrow_exception(e);
154 }
155 catch (std::exception const& e)
156 {
157 l_.error("Error in session: {}", e.what());
158 }
159 }
160 });
161 }
162}
163
164auto server::spawn(net::io_context& ioc) -> void
165{
166 auto const addr = net::ip::make_address("0.0.0.0");
167 auto const endpoint = tcp::endpoint{addr, 8284};
168
169 // TODO: make exception handling as nice as in srv.cpp
170 net::co_spawn(
171 ioc, do_listen(endpoint),
172 [this](std::exception_ptr e)
173 {
174 if (e)
175 {
176 try
177 {
178 std::rethrow_exception(e);
179 }
180 catch (std::exception const& e)
181 {
182 l_.error("Error: {}", e.what());
183 }
184 }
185 });
186}
187
188} // namespace routemon::http