diff options
| author | Rutger Broekhoff | 2026-09-10 23:10:57 +0200 |
|---|---|---|
| committer | Rutger Broekhoff | 2026-09-10 23:11:03 +0200 |
| commit | 48a99fd71b11321b85c385245b5f2d36fed22df1 (patch) | |
| tree | eef558a1f8b5d69f9a1431d03f3999be66757fdc /server | |
| parent | a4408b5ac4ec10421bf9a7d5bf277946ec9f786e (diff) | |
| download | routemon-48a99fd71b11321b85c385245b5f2d36fed22df1.tar.gz routemon-48a99fd71b11321b85c385245b5f2d36fed22df1.zip | |
Fun with (raw) deflate
Diffstat (limited to 'server')
| -rw-r--r-- | server/src/http_server.cpp | 3 | ||||
| -rw-r--r-- | server/src/http_server.cppm | 13 | ||||
| -rw-r--r-- | server/src/srv.cpp | 141 |
3 files changed, 138 insertions, 19 deletions
diff --git a/server/src/http_server.cpp b/server/src/http_server.cpp index c3a9ed7..ff96091 100644 --- a/server/src/http_server.cpp +++ b/server/src/http_server.cpp | |||
| @@ -79,8 +79,7 @@ auto global_options_handler(base_ctx const& ctx, readable_request r) | |||
| 79 | { | 79 | { |
| 80 | // TODO: switch to "small (4KB) discarded" body type, similar to what Go | 80 | // TODO: switch to "small (4KB) discarded" body type, similar to what Go |
| 81 | // does? Same goes for default_options_handler? Not sure. | 81 | // does? Same goes for default_options_handler? Not sure. |
| 82 | if (auto res = co_await read_request<bhttp::empty_body>(ctx, std::move(r)); | 82 | if (auto res = co_await read_empty_request(ctx, std::move(r)); !res) |
| 83 | !res) | ||
| 84 | co_return std::move(res.error()); | 83 | co_return std::move(res.error()); |
| 85 | auto req = r.p->release(); | 84 | auto req = r.p->release(); |
| 86 | auto rsp = make_rsp<bhttp::empty_body>( | 85 | auto rsp = make_rsp<bhttp::empty_body>( |
diff --git a/server/src/http_server.cppm b/server/src/http_server.cppm index 2bf3c63..e70110e 100644 --- a/server/src/http_server.cppm +++ b/server/src/http_server.cppm | |||
| @@ -266,7 +266,7 @@ auto make_preflight_rsp(preflight_response res, keep_alive ka) | |||
| 266 | // anyway, but all context types should derive from base_ctx). | 266 | // anyway, but all context types should derive from base_ctx). |
| 267 | template <bhttp::concepts::body_reader Body> | 267 | template <bhttp::concepts::body_reader Body> |
| 268 | auto read_request(base_ctx const& ctx, readable_request&& r) | 268 | auto read_request(base_ctx const& ctx, readable_request&& r) |
| 269 | -> net::awaitable<std::expected<bhttp::request<Body>, presponse>> | 269 | -> net::awaitable<bhttp::request<Body>> |
| 270 | { | 270 | { |
| 271 | std::ignore = ctx; | 271 | std::ignore = ctx; |
| 272 | auto p = bhttp::request_parser<Body>{std::move(*r.p)}; | 272 | auto p = bhttp::request_parser<Body>{std::move(*r.p)}; |
| @@ -274,10 +274,8 @@ auto read_request(base_ctx const& ctx, readable_request&& r) | |||
| 274 | co_return std::move(p.release()); | 274 | co_return std::move(p.release()); |
| 275 | } | 275 | } |
| 276 | 276 | ||
| 277 | template <> | 277 | auto read_empty_request(base_ctx const& ctx, readable_request&& r) -> net:: |
| 278 | auto read_request<bhttp::empty_body>(base_ctx const& ctx, readable_request&& r) | 278 | awaitable<std::expected<bhttp::request<bhttp::empty_body>, presponse>> |
| 279 | -> net:: | ||
| 280 | awaitable<std::expected<bhttp::request<bhttp::empty_body>, presponse>> | ||
| 281 | { | 279 | { |
| 282 | auto [ec, _] = | 280 | auto [ec, _] = |
| 283 | co_await bhttp::async_read(*r.strm, *r.buf, *r.p, net::as_tuple); | 281 | co_await bhttp::async_read(*r.strm, *r.buf, *r.p, net::as_tuple); |
| @@ -313,7 +311,7 @@ auto default_options_handler( | |||
| 313 | Ctx const& ctx, readable_request r, std::vector<std::string> const&) | 311 | Ctx const& ctx, readable_request r, std::vector<std::string> const&) |
| 314 | -> net::awaitable<presponse> | 312 | -> net::awaitable<presponse> |
| 315 | { | 313 | { |
| 316 | auto mreq = co_await read_request<bhttp::empty_body>(ctx, std::move(r)); | 314 | auto mreq = co_await read_empty_request(ctx, std::move(r)); |
| 317 | if (!mreq) | 315 | if (!mreq) |
| 318 | co_return std::move(mreq.error()); | 316 | co_return std::move(mreq.error()); |
| 319 | 317 | ||
| @@ -324,7 +322,8 @@ auto default_options_handler( | |||
| 324 | preflight_response{ | 322 | preflight_response{ |
| 325 | // TODO: should access-control-allow-methods contain OPTIONS? | 323 | // TODO: should access-control-allow-methods contain OPTIONS? |
| 326 | .allow_methods = ctx.route_methods, | 324 | .allow_methods = ctx.route_methods, |
| 327 | .allow_headers = {bhttp::field::content_type}, | 325 | .allow_headers = |
| 326 | {bhttp::field::content_type, bhttp::field::content_encoding}, | ||
| 328 | }, | 327 | }, |
| 329 | keep_alive{mreq->keep_alive()}); | 328 | keep_alive{mreq->keep_alive()}); |
| 330 | } | 329 | } |
diff --git a/server/src/srv.cpp b/server/src/srv.cpp index adfef18..a901f8e 100644 --- a/server/src/srv.cpp +++ b/server/src/srv.cpp | |||
| @@ -6,6 +6,7 @@ module; | |||
| 6 | #include <boost/asio/ip/tcp.hpp> | 6 | #include <boost/asio/ip/tcp.hpp> |
| 7 | #include <boost/beast/core.hpp> | 7 | #include <boost/beast/core.hpp> |
| 8 | #include <boost/beast/http.hpp> | 8 | #include <boost/beast/http.hpp> |
| 9 | #include <boost/beast/zlib.hpp> | ||
| 9 | #include <boost/config.hpp> | 10 | #include <boost/config.hpp> |
| 10 | #include <boost/json.hpp> | 11 | #include <boost/json.hpp> |
| 11 | #include <boost/locale/generator.hpp> | 12 | #include <boost/locale/generator.hpp> |
| @@ -156,10 +157,8 @@ struct readable_gpx_body | |||
| 156 | auto total = 0uz; | 157 | auto total = 0uz; |
| 157 | try | 158 | try |
| 158 | { | 159 | { |
| 159 | for (auto it = net::buffer_sequence_begin(b); | 160 | for (auto cur_in_buf : beast::buffers_range(b)) |
| 160 | it != net::buffer_sequence_end(b); it++) | ||
| 161 | { | 161 | { |
| 162 | auto cur_in_buf = net::const_buffer{*it}; | ||
| 163 | while (cur_in_buf.size() > 0) | 162 | while (cur_in_buf.size() > 0) |
| 164 | { | 163 | { |
| 165 | auto to_read = | 164 | auto to_read = |
| @@ -217,6 +216,120 @@ struct readable_gpx_body | |||
| 217 | static_assert(bhttp::concepts::body<readable_gpx_body>); | 216 | static_assert(bhttp::concepts::body<readable_gpx_body>); |
| 218 | static_assert(bhttp::concepts::body_reader<readable_gpx_body>); | 217 | static_assert(bhttp::concepts::body_reader<readable_gpx_body>); |
| 219 | 218 | ||
| 219 | template <bhttp::concepts::body_reader Inner> | ||
| 220 | struct raw_deflate_body | ||
| 221 | { | ||
| 222 | using value_type = Inner::value_type; | ||
| 223 | |||
| 224 | class reader | ||
| 225 | { | ||
| 226 | static constexpr auto const chunk_size = 16384; | ||
| 227 | |||
| 228 | Inner::reader r_; | ||
| 229 | bool stream_ended_ = false; | ||
| 230 | beast::zlib::z_params zs_ = {}; | ||
| 231 | beast::zlib::inflate_stream inflate_strm_ = {}; | ||
| 232 | std::unique_ptr<std::array<char, chunk_size>> in_buf_ = nullptr; | ||
| 233 | std::unique_ptr<std::array<char, chunk_size>> out_buf_ = nullptr; | ||
| 234 | |||
| 235 | auto inflate_chunk(beast::error_code& ec) -> void | ||
| 236 | { | ||
| 237 | assert(zs_.avail_in > 0); | ||
| 238 | zs_.next_in = in_buf_->data(); | ||
| 239 | |||
| 240 | do | ||
| 241 | { | ||
| 242 | zs_.avail_out = out_buf_->size(); | ||
| 243 | zs_.next_out = out_buf_->data(); | ||
| 244 | |||
| 245 | inflate_strm_.write(zs_, beast::zlib::Flush::none, ec); | ||
| 246 | assert(ec != beast::zlib::error::stream_error); | ||
| 247 | if (ec == beast::zlib::error::need_buffers) | ||
| 248 | ec = {}; | ||
| 249 | if (ec == beast::zlib::error::end_of_stream) | ||
| 250 | { | ||
| 251 | ec = {}; | ||
| 252 | stream_ended_ = true; | ||
| 253 | } | ||
| 254 | if (ec) | ||
| 255 | break; | ||
| 256 | |||
| 257 | auto have = chunk_size - zs_.avail_out; | ||
| 258 | auto cbuf = | ||
| 259 | net::buffer(static_cast<void const*>(out_buf_->data()), have); | ||
| 260 | |||
| 261 | auto inner_ec = beast::error_code{}; | ||
| 262 | do | ||
| 263 | cbuf += r_.put(cbuf, inner_ec); | ||
| 264 | while (cbuf.size() > 0 && !inner_ec); | ||
| 265 | if (inner_ec) | ||
| 266 | { | ||
| 267 | ec = inner_ec; | ||
| 268 | break; | ||
| 269 | } | ||
| 270 | } | ||
| 271 | while (zs_.avail_out == 0); | ||
| 272 | |||
| 273 | // In the last case (stream_ended_), the user has sent too much data | ||
| 274 | assert(zs_.avail_in == 0 || ec || stream_ended_); | ||
| 275 | } | ||
| 276 | |||
| 277 | public: | ||
| 278 | template <bool isRequest, bhttp::concepts::fields Fields> | ||
| 279 | explicit reader(bhttp::header<isRequest, Fields>& hdr, value_type& v) | ||
| 280 | : r_{hdr, v} | ||
| 281 | { | ||
| 282 | } | ||
| 283 | |||
| 284 | auto | ||
| 285 | init(boost::optional<std::uint64_t> /* n */, beast::error_code& ec) noexcept | ||
| 286 | -> void | ||
| 287 | { | ||
| 288 | in_buf_.reset(new std::array<char, chunk_size>{}); | ||
| 289 | out_buf_.reset(new std::array<char, chunk_size>{}); | ||
| 290 | ec = {}; | ||
| 291 | } | ||
| 292 | |||
| 293 | auto | ||
| 294 | put(beast::concepts::const_buffer_sequence auto b, | ||
| 295 | beast::error_code& ec) noexcept -> std::size_t | ||
| 296 | { | ||
| 297 | auto in_buf = net::buffer(in_buf_->data(), in_buf_->size()); | ||
| 298 | in_buf += zs_.avail_in; | ||
| 299 | |||
| 300 | auto consumed_from_b = net::buffer_copy(in_buf, b); | ||
| 301 | zs_.avail_in += consumed_from_b; | ||
| 302 | |||
| 303 | if (zs_.avail_in == in_buf_->size()) | ||
| 304 | inflate_chunk(ec); | ||
| 305 | else | ||
| 306 | ec = {}; | ||
| 307 | |||
| 308 | return consumed_from_b; | ||
| 309 | } | ||
| 310 | |||
| 311 | auto finish(beast::error_code& ec) noexcept | ||
| 312 | { | ||
| 313 | ec = {}; | ||
| 314 | if (zs_.avail_in > 0) | ||
| 315 | inflate_chunk(ec); | ||
| 316 | if (!ec && !stream_ended_) | ||
| 317 | ec = beast::zlib::error::stream_error; | ||
| 318 | if (stream_ended_ && zs_.avail_in > 0) | ||
| 319 | // client sent data past deflate stream end | ||
| 320 | ec = beast::zlib::error::stream_error; | ||
| 321 | |||
| 322 | in_buf_.reset(); | ||
| 323 | out_buf_.reset(); | ||
| 324 | inflate_strm_.reset(); | ||
| 325 | if (!ec) | ||
| 326 | { | ||
| 327 | r_.finish(ec); | ||
| 328 | } | ||
| 329 | } | ||
| 330 | }; | ||
| 331 | }; | ||
| 332 | |||
| 220 | auto handler::handle_process_gpx(l0_ctx ctx, http::readable_request r) | 333 | auto handler::handle_process_gpx(l0_ctx ctx, http::readable_request r) |
| 221 | -> net::awaitable<http::presponse> | 334 | -> net::awaitable<http::presponse> |
| 222 | { | 335 | { |
| @@ -228,23 +341,31 @@ auto handler::handle_process_gpx(l0_ctx ctx, http::readable_request r) | |||
| 228 | { | 341 | { |
| 229 | l.debug("Reading GPX request body"); | 342 | l.debug("Reading GPX request body"); |
| 230 | auto const before_read_gpx = chrono::steady_clock::now(); | 343 | auto const before_read_gpx = chrono::steady_clock::now(); |
| 231 | auto req = | 344 | auto parse_res = |
| 232 | co_await http::read_request<readable_gpx_body>(ctx, std::move(r)); | 345 | r.p->get()[bhttp::field::content_encoding] == "x-deflate-raw" |
| 346 | ? std::move( | ||
| 347 | (co_await http::read_request<raw_deflate_body<readable_gpx_body>>( | ||
| 348 | ctx, std::move(r))) | ||
| 349 | .body()) | ||
| 350 | : std::move((co_await http::read_request<readable_gpx_body>( | ||
| 351 | ctx, std::move(r))) | ||
| 352 | .body()); | ||
| 233 | l.debug( | 353 | l.debug( |
| 234 | "Read GPX request body in {}; init took {}, actual parsing {}, " | 354 | "Read GPX request body in {}; init took {}, actual parsing {}, " |
| 235 | "finishing {}", | 355 | "finishing {}", |
| 236 | chrono::duration<double, std::milli>{ | 356 | chrono::duration<double, std::milli>{ |
| 237 | chrono::steady_clock::now() - before_read_gpx | 357 | chrono::steady_clock::now() - before_read_gpx |
| 238 | }, | 358 | }, |
| 239 | chrono::duration<double, std::milli>{req->body().init_parse_duration()}, | 359 | chrono::duration<double, std::milli>{parse_res.init_parse_duration()}, |
| 240 | chrono::duration<double, std::milli>{req->body().put_parse_duration()}, | 360 | chrono::duration<double, std::milli>{parse_res.put_parse_duration()}, |
| 241 | chrono::duration<double, std::milli>{ | 361 | chrono::duration<double, std::milli>{ |
| 242 | req->body().finish_parse_duration() | 362 | parse_res.finish_parse_duration() |
| 243 | }); | 363 | }); |
| 244 | gpx_file = std::move(req->body().unwrap()); | 364 | gpx_file = std::move(parse_res.unwrap()); |
| 245 | } | 365 | } |
| 246 | catch (std::exception& ex) | 366 | catch (std::exception& ex) |
| 247 | { | 367 | { |
| 368 | l_.warn("Failed to parse GPX file: {}", ex.what()); | ||
| 248 | // TODO: more detailed problem reporting | 369 | // TODO: more detailed problem reporting |
| 249 | auto tpl = problem::tpl{ | 370 | auto tpl = problem::tpl{ |
| 250 | .status = bhttp::status::bad_request, | 371 | .status = bhttp::status::bad_request, |
| @@ -280,7 +401,7 @@ auto handler::handle_process_gpx(l0_ctx ctx, http::readable_request r) | |||
| 280 | auto handler::handle_sysinfo(l0_ctx ctx, http::readable_request r) | 401 | auto handler::handle_sysinfo(l0_ctx ctx, http::readable_request r) |
| 281 | -> net::awaitable<http::presponse> | 402 | -> net::awaitable<http::presponse> |
| 282 | { | 403 | { |
| 283 | auto req = co_await http::read_request<bhttp::empty_body>(ctx, std::move(r)); | 404 | auto req = co_await http::read_empty_request(ctx, std::move(r)); |
| 284 | auto info = inner_.sysinfo(); | 405 | auto info = inner_.sysinfo(); |
| 285 | 406 | ||
| 286 | auto rsp = http::make_rsp<bhttp::string_body>( | 407 | auto rsp = http::make_rsp<bhttp::string_body>( |