Switch from epee http client to boost::beast. All HTTP now non-blocking. (#150)

This commit is contained in:
Lee *!* Clagett
2024-12-04 17:25:07 -05:00
committed by Lee *!* Clagett
parent 66b7497a34
commit 29358f1323
22 changed files with 1095 additions and 257 deletions

View File

@@ -26,8 +26,10 @@
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
add_subdirectory(http)
set(monero-lws-net_sources zmq_async.cpp)
set(monero-lws-net_headers zmq_async.h)
add_library(monero-lws-net ${monero-lws-net_sources} ${monero-lws-net_headers})
target_link_libraries(monero-lws-net monero::libraries)
target_link_libraries(monero-lws-net monero-lws-net-http monero::libraries)

View File

@@ -0,0 +1,33 @@
# Copyright (c) 2024, The Monero Project
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set(monero-lws-net-http_sources client.cpp)
set(monero-lws-net-http_headers client.h slice_body.h)
add_library(monero-lws-net-http ${monero-lws-net-http_sources} ${monero-lws-net-http_headers})
target_link_libraries(monero-lws-net-http ${Boost_SYSTEM_LIBRARY} monero::libraries)

482
src/net/http/client.cpp Normal file
View File

@@ -0,0 +1,482 @@
// Copyright (c) 2024, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "client.h"
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/ip/address_v6.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/strand.hpp>
#include <boost/beast/core/flat_static_buffer.hpp>
#include <boost/beast/http/parser.hpp>
#include <boost/beast/http/read.hpp>
#include <boost/beast/http/string_body.hpp>
#include <boost/beast/http/write.hpp>
#include <boost/beast/version.hpp>
#include <boost/optional/optional.hpp>
#include <boost/thread/lock_types.hpp>
#include <cstdint>
#include <deque>
#include <limits>
#include <ostream>
#include "error.h"
#include "misc_log_ex.h" // monero/contrib/epee/include
#include "net/http_base.h" // monero/contrib/epee/include
#include "net/http/slice_body.h"
#include "net/net_parse_helpers.h" // monero/contrib/epee/include
namespace net { namespace http
{
namespace
{
constexpr const unsigned http_version = 11;
constexpr const std::size_t http_parser_buffer_size = 16 * 1024;
constexpr const std::size_t max_body_size = 4 * 1024;
//! Timeout for 1 entire HTTP request (connect, handshake, send, receive).
constexpr const std::chrono::seconds message_timeout{30};
struct message
{
message(epee::byte_slice json_body, std::string host, std::string target, std::uint16_t port, bool https, std::function<server_response_func>&& notifier)
: json_body(std::move(json_body)),
notifier(std::move(notifier)),
host(std::move(host)),
target(std::move(target)),
port(port),
https(https)
{}
message(message&&) = default;
message(const message& rhs)
: json_body(rhs.json_body.clone()),
notifier(rhs.notifier),
host(rhs.host),
target(rhs.target),
port(rhs.port),
https(rhs.https)
{}
epee::byte_slice json_body;
std::function<server_response_func> notifier;
std::string host;
std::string target;
std::uint16_t port;
bool https;
};
std::ostream& operator<<(std::ostream& out, const message& src)
{
out << (src.https ? "https://" : "http://") << src.host << ':' << src.port << src.target;
return out;
}
} // anonymous
struct client_state
{
std::shared_ptr<boost::asio::ssl::context> ssl;
boost::beast::flat_static_buffer<http_parser_buffer_size> buffer;
std::deque<message> outgoing;
std::string last_host;
boost::asio::ip::tcp::resolver resolver;
boost::asio::steady_timer timer;
boost::asio::io_context::strand strand;
boost::asio::ip::tcp::endpoint endpoint;
boost::beast::http::request<slice_body> request;
boost::optional<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> sock;
boost::optional<boost::beast::http::parser<false, boost::beast::http::string_body>> parser;
std::size_t iteration;
client_state(boost::asio::io_context& io, std::shared_ptr<boost::asio::ssl::context> in)
: ssl(std::move(in)),
buffer{},
outgoing(),
last_host(),
resolver(io),
timer(io),
strand(io),
endpoint(),
request{},
sock(),
parser(),
iteration(0)
{
assert(ssl);
sock.emplace(io, *ssl);
}
template<typename F>
void async_write(F&& callback)
{
assert(sock);
assert(!outgoing.empty());
const bool no_body = outgoing.front().json_body.empty();
request = {
outgoing.front().notifier ? boost::beast::http::verb::get : boost::beast::http::verb::post,
outgoing.front().target,
http_version,
std::move(outgoing.front().json_body)
};
request.set(boost::beast::http::field::user_agent, BOOST_BEAST_VERSION_STRING);
if (!no_body)
request.set(boost::beast::http::field::content_type, "application/json");
// Setting Host is tricky. Check for v6 and non-standard ports
boost::system::error_code error{};
boost::asio::ip::make_address_v6(outgoing.front().host, error);
if (!error)
request.set(boost::beast::http::field::host, "[" + outgoing.front().host + "]:" + std::to_string(outgoing.front().port));
else if ((outgoing.front().https && outgoing.front().port == 443) || (!outgoing.front().https && outgoing.front().port == 80))
request.set(boost::beast::http::field::host, outgoing.front().host);
else
request.set(boost::beast::http::field::host, outgoing.front().host + ":" + std::to_string(outgoing.front().port));
request.prepare_payload();
if (outgoing.front().https)
boost::beast::http::async_write(*sock, request, boost::asio::bind_executor(strand, std::forward<F>(callback)));
else
boost::beast::http::async_write(sock->next_layer(), request, boost::asio::bind_executor(strand, std::forward<F>(callback)));
}
template<typename F>
void async_read(F&& callback)
{
assert(sock);
assert(!outgoing.empty());
parser.emplace();
parser->body_limit(max_body_size);
if (outgoing.front().https)
boost::beast::http::async_read(*sock, buffer, *parser, boost::asio::bind_executor(strand, std::forward<F>(callback)));
else
boost::beast::http::async_read(sock->next_layer(), buffer, *parser, boost::asio::bind_executor(strand, std::forward<F>(callback)));
}
void notify_error(const boost::system::error_code& error) const
{
assert(!outgoing.empty());
if (outgoing.front().notifier)
outgoing.front().notifier(error, {});
}
};
namespace
{
class client_loop : public boost::asio::coroutine
{
std::shared_ptr<client_state> self_;
public:
explicit client_loop(std::shared_ptr<client_state> self) noexcept
: boost::asio::coroutine(), self_(std::move(self))
{}
bool set_timeout(const std::chrono::steady_clock::duration timeout)
{
if (!self_)
return false;
struct on_timeout
{
on_timeout() = delete;
std::shared_ptr<client_state> self_;
std::size_t iteration;
void operator()(boost::system::error_code error) const
{
if (!self_ || error == boost::asio::error::operation_aborted)
return;
if (iteration < self_->iteration)
return;
assert(self_->strand.running_in_this_thread());
assert(self_->sock);
if (!self_->outgoing.empty())
MWARNING("Timeout in HTTP attempt to " << self_->outgoing.front());
else
MERROR("Unexpected empty message stack");
self_->resolver.cancel();
self_->sock->next_layer().cancel(error);
self_->sock->next_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, error);
}
};
self_->timer.expires_after(timeout);
self_->timer.async_wait(boost::asio::bind_executor(self_->strand, on_timeout{self_, self_->iteration}));
return true;
}
void operator()(boost::system::error_code error = {}, std::size_t = 0)
{
if (!self_)
return;
bool reuse = false;
bool is_https = false;
std::uint16_t last_port = 0;
client_state& self = *self_;
assert(self.strand.running_in_this_thread());
assert(self.sock);
BOOST_ASIO_CORO_REENTER(*this)
{
while (!self.outgoing.empty())
{
struct resolve
{
client_loop continue_;
void operator()(const boost::system::error_code error, const boost::asio::ip::tcp::resolver::results_type& ips)
{
if (error)
std::move(continue_)(error, 0);
else if (ips.empty())
std::move(continue_)(boost::asio::error::host_not_found, 0);
else if (continue_.self_)
{
continue_.self_->endpoint = *ips.begin();
std::move(continue_)(error, 0);
}
}
};
set_timeout(message_timeout);
if (!reuse)
{
MDEBUG("Resolving " << self.outgoing.front().host << " for HTTP");
BOOST_ASIO_CORO_YIELD self.resolver.async_resolve(
self.outgoing.front().host,
std::to_string(self.outgoing.front().port),
boost::asio::bind_executor(self.strand, resolve{std::move(*this)})
);
}
if (!error)
{
if (!reuse)
{
MDEBUG("Connecting to " << self.endpoint << " / " << self.outgoing.front().host << " for HTTP");
BOOST_ASIO_CORO_YIELD self.sock->next_layer().async_connect(
self.endpoint, boost::asio::bind_executor(self.strand, std::move(*this))
);
self.buffer.clear(); // do not re-use http buffer
}
if (!error)
{
if (!reuse && self.outgoing.front().https)
{
{
SSL* const ssl_ctx = self.sock->native_handle();
if (ssl_ctx)
SSL_set_tlsext_host_name(ssl_ctx, self.outgoing.front().host.c_str());
}
MDEBUG("Starting SSL handshake to " << self.outgoing.front().host << " for HTTP");
BOOST_ASIO_CORO_YIELD self.sock->async_handshake(
boost::asio::ssl::stream<boost::asio::ip::tcp::socket>::client,
boost::asio::bind_executor(self.strand, std::move(*this))
);
}
if (!error)
{
reuse = false;
MDEBUG("Sending " << self.outgoing.front().json_body.size() << " bytes in HTTP " << (self.outgoing.front().notifier ? "GET" : "POST") << " to " << self.outgoing.front());
BOOST_ASIO_CORO_YIELD self.async_write(std::move(*this));
if (!error)
{
MDEBUG("Starting read from " << self.outgoing.front() << " to previous HTTP message");
BOOST_ASIO_CORO_YIELD self.async_read(std::move(*this));
if (error)
MERROR("Failed to parse HTTP response from " << self.outgoing.front() << ": " << error.message());
else if (self.parser->get().result_int() != 200 && self.parser->get().result_int() != 201)
{
MERROR(self.outgoing.front() << " returned " << self.parser->get().result_int() << " status code");
self.notify_error(boost::asio::error::operation_not_supported);
}
else
{
MDEBUG(self.outgoing.front() << " successful");
reuse = self.parser->get().keep_alive();
if (self.outgoing.front().notifier)
self.outgoing.front().notifier({}, std::move(self.parser->get()).body());
}
}
else
MERROR("Failed HTTP " << (self.outgoing.front().notifier ? "GET" : "POST") << " to " << self.outgoing.front() << ": " << error.message());
}
else
MERROR("SSL handshake to " << self.outgoing.front().host << " failed: " << error.message());
}
else
MERROR("Failed to connect to " << self.outgoing.front().host << ": " << error.message());
}
else
MERROR("Failed to resolve TCP/IP address for " << self.outgoing.front().host << ": " << error.message());
if (error)
self.notify_error(error);
is_https = self.outgoing.front().https;
self.last_host = std::move(self.outgoing.front().host);
last_port = self.outgoing.front().port;
self.outgoing.pop_front();
reuse = reuse &&
!self.outgoing.empty() &&
self.last_host == self.outgoing.front().host &&
last_port == self.outgoing.front().port;
if (!reuse)
{
if (is_https)
{
MDEBUG("Starting SSL shutdown on " << self.last_host);
BOOST_ASIO_CORO_YIELD self.sock->async_shutdown(
boost::asio::bind_executor(self.strand, std::move(*this))
);
is_https = true;
}
MDEBUG("Cleaning up connection to " << self.last_host);
self.sock->next_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, error);
self.sock->next_layer().close(error);
if (is_https) // must clear SSL state
self.sock.emplace(self.timer.get_executor(), *self.ssl);
}
++self.iteration;
}
}
}
};
boost::asio::ssl::context make_context(epee::net_utils::ssl_verification_t verify)
{
epee::net_utils::ssl_options_t ssl{
epee::net_utils::ssl_support_t::e_ssl_support_enabled
};
ssl.verification = verify;
return ssl.create_context();
}
} // anonymous
expect<void> client::queue_async(boost::asio::io_context& io, std::string url, epee::byte_slice json_body, std::function<server_response_func> notifier)
{
static constexpr const std::uint16_t max_port = std::numeric_limits<std::uint16_t>::max();
epee::net_utils::http::url_content parsed{};
if (!epee::net_utils::parse_url(std::move(url), parsed) || max_port < parsed.port)
return {lws::error::bad_url};
if (parsed.schema != "http" && parsed.schema != "https")
return {lws::error::bad_url};
if (!parsed.port)
parsed.port = (parsed.schema == "http") ? 80 : 443;
if (parsed.uri.empty())
parsed.uri = "/";
message msg{
std::move(json_body),
std::move(parsed.host),
std::move(parsed.uri),
std::uint16_t(parsed.port),
bool(parsed.schema == "https"),
std::move(notifier)
};
MDEBUG("Queueing HTTP " << (msg.notifier ? "GET" : "POST") << " to " << msg << " using " << this);
boost::unique_lock<boost::mutex> lock{sync_};
auto state = state_.lock();
if (!state)
{
// `make_shared` delays freeing of data section, use `make_unique`
MDEBUG("Creating new net::http::client_state for " << this);
state = {std::make_unique<client_state>(io, ssl_)};
state_ = state;
state->outgoing.push_back(std::move(msg));
lock.unlock();
boost::asio::post(state->strand, client_loop{state});
}
else
{
lock.unlock();
boost::asio::dispatch(
state->strand,
[state, msg = std::move(msg)] () mutable
{
const bool empty = state->outgoing.empty();
state->outgoing.push_back(std::move(msg));
if (empty)
boost::asio::post(state->strand, client_loop{state});
}
);
}
return success();
}
client::client(epee::net_utils::ssl_verification_t verify)
: state_(),
ssl_(std::make_shared<boost::asio::ssl::context>(make_context(verify))),
sync_()
{}
client::client(std::shared_ptr<boost::asio::ssl::context> ssl)
: state_(), ssl_(std::move(ssl)), sync_()
{
if (!ssl_)
throw std::logic_error{"boost::asio::ssl::context cannot be nullptr"};
}
client::~client()
{}
expect<void> client::post_async(boost::asio::io_context& io, std::string url, epee::byte_slice json_body)
{
return queue_async(io, std::move(url), std::move(json_body), {});
}
expect<void> client::get_async(boost::asio::io_context& io, std::string url, std::function<server_response_func> notifier)
{
if (!notifier)
throw std::logic_error{"net::http::client::get_async requires callback"};
return queue_async(io, std::move(url), epee::byte_slice{}, std::move(notifier));
}
}} // net // http

72
src/net/http/client.h Normal file
View File

@@ -0,0 +1,72 @@
// Copyright (c) 2024, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/system/error_code.hpp>
#include <boost/thread/mutex.hpp>
#include <functional>
#include <memory>
#include <string>
#include "byte_slice.h" // monero/contrib/epee/include
#include "common/expect.h" // monero/src
#include "net/net_ssl.h" // monero/contrib/epee/include
namespace net { namespace http
{
struct client_state;
using server_response_func = void(boost::system::error_code, std::string);
//! Primarily for webhooks, where the response is (basically) ignored.
class client
{
std::weak_ptr<client_state> state_;
std::shared_ptr<boost::asio::ssl::context> ssl_;
boost::mutex sync_;
expect<void> queue_async(boost::asio::io_context& io, std::string url, epee::byte_slice json_body, std::function<server_response_func> notifier);
public:
explicit client(epee::net_utils::ssl_verification_t verify);
explicit client(std::shared_ptr<boost::asio::ssl::context> ssl);
~client();
const std::shared_ptr<boost::asio::ssl::context>& ssl_context() const noexcept
{ return ssl_; }
//! Never blocks. Thread safe. \return `success()` if `url` is valid.
expect<void> post_async(boost::asio::io_context& io, std::string url, epee::byte_slice json_body);
/*! Never blocks. Thread safe. Calls `notifier` with server response iff
`success()` is returned.
\return `success()` if `url` is valid. */
expect<void> get_async(boost::asio::io_context& io, std::string url, std::function<server_response_func> notifier);
};
}} // net // http

77
src/net/http/slice_body.h Normal file
View File

@@ -0,0 +1,77 @@
// Copyright (c) 2024, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <boost/asio/buffer.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/beast/http/message.hpp>
#include <cstdint>
#include <limits>
#include "byte_slice.h" // monero/contrib/epee/include
namespace net { namespace http
{
//! Trait for `boost::beast` body type
struct slice_body
{
using value_type = epee::byte_slice;
static std::uint64_t size(const value_type& source) noexcept
{
static_assert(!std::numeric_limits<std::size_t>::is_signed, "expected unsigned");
static_assert(
std::numeric_limits<std::size_t>::max() <= std::numeric_limits<std::uint64_t>::max(),
"unexpected size_t max value"
);
return source.size();
}
struct writer
{
epee::byte_slice body_;
using const_buffers_type = boost::asio::const_buffer;
template<bool is_request, typename Fields>
explicit writer(boost::beast::http::header<is_request, Fields> const&, value_type const& body)
: body_(body.clone())
{}
void init(boost::beast::error_code& ec)
{
ec = {};
}
boost::optional<std::pair<const_buffers_type, bool>> get(boost::beast::error_code& ec)
{
ec = {};
return {{const_buffers_type{body_.data(), body_.size()}, false}};
}
};
};
}} // net // http