10#include <boost/asio/any_io_executor.hpp>
11#include <boost/asio/ssl/context.hpp>
12#include <boost/beast/http/field.hpp>
13#include <boost/beast/http/write.hpp>
14#include <boost/beast/http/read.hpp>
15#include <boost/beast/ssl/ssl_stream.hpp>
16#include <boost/beast/core/tcp_stream.hpp>
17#include <boost/asio/ip/tcp.hpp>
18#include <promise-cpp/promise.hpp>
24#include <unordered_map>
33 class Client :
public std::enable_shared_from_this<Client>
44 boost::asio::ssl::verify_mode
sslVerifyMode = boost::asio::ssl::verify_peer;
102 template <
typename T,
typename... ConstructionArgs>
105 attachedState_[tag] = std::make_any<T>(std::forward<ConstructionArgs>(args)...);
115 template <
typename T>
137 template <
typename BodyT>
141 return promise::newPromise([&,
this](promise::Defer d)
mutable {
142 const auto host =
request.host();
147 [weak = weak_from_this(), timeout,
request = std::move(
request), d = std::move(d)](
148 boost::beast::error_code ec, boost::asio::ip::tcp::resolver::results_type results)
mutable {
149 auto self = weak.lock();
151 return d.reject(
Error{.error = ec, .additionalInfo =
"Client is no longer alive."});
154 return d.reject(
Error{.
error = ec, .additionalInfo =
"DNS resolve failed."});
156 self->withLowerLayerDo([&](
auto& socket) {
157 socket.expires_after(timeout);
158 socket.async_connect(
160 [weak = self->weak_from_this(),
164 boost::beast::error_code ec,
165 boost::asio::ip::tcp::resolver::results_type::endpoint_type endpoint)
mutable {
166 auto self = weak.lock();
169 Error{.error = ec, .additionalInfo =
"Client is no longer alive."});
172 return d.reject(
Error{.
error = ec, .additionalInfo =
"TCP connect failed."});
174 self->endpoint_ = endpoint;
176 self->onConnect(std::move(
request), std::move(d), timeout);
194 template <
typename ResponseBodyT>
196 boost::beast::http::response_parser<ResponseBodyT>& parser,
197 std::chrono::seconds timeout = defaultTimeout)
199 return promise::newPromise([&,
this](promise::Defer d)
mutable {
200 withLowerLayerDo([timeout](
auto& socket) {
201 socket.expires_after(timeout);
203 withStreamDo([
this, timeout, d = std::move(d), &parser](
auto& socket)
mutable {
204 boost::beast::http::async_read_header(
208 [weak = weak_from_this(), buffer = this->buffer_, d = std::move(d), timeout, &parser](
209 boost::beast::error_code ec, std::size_t)
mutable {
210 auto self = weak.lock();
212 return d.reject(
Error{.error = ec, .additionalInfo =
"Client is no longer alive."});
215 return d.reject(
Error{.
error = ec, .additionalInfo =
"HTTP read failed."});
232 template <
typename ResponseBodyT>
238 return promise::newPromise([
this, timeout](promise::Defer d)
mutable {
239 withLowerLayerDo([timeout](
auto& socket) {
240 socket.expires_after(timeout);
242 withStreamDo([
this, d = std::move(d)](
auto& socket)
mutable {
247 auto context = std::make_shared<Context>();
248 boost::beast::http::async_read(
251 context->response.response(),
252 [weak = weak_from_this(), buffer = this->buffer_, d = std::move(d), context](
253 boost::beast::error_code ec, std::size_t)
mutable {
254 auto self = weak.lock();
256 return d.reject(
Error{.error = ec, .additionalInfo =
"Client is no longer alive."});
259 return d.reject(
Error{.
error = ec, .additionalInfo =
"HTTP read failed."});
275 template <
typename ResponseBodyT>
277 boost::beast::http::response_parser<ResponseBodyT>& parser,
278 std::chrono::seconds timeout = defaultTimeout)
280 return promise::newPromise([&,
this](promise::Defer d)
mutable {
281 withLowerLayerDo([timeout](
auto& socket) {
282 socket.expires_after(timeout);
284 withStreamDo([
this, timeout, d = std::move(d), &parser](
auto& socket)
mutable {
285 boost::beast::http::async_read(
289 [weak = weak_from_this(), buffer = this->buffer_, d = std::move(d), timeout, &parser](
290 boost::beast::error_code ec, std::size_t)
mutable {
291 auto self = weak.lock();
293 return d.reject(
Error{.error = ec, .additionalInfo =
"Client is no longer alive."});
296 return d.reject(
Error{.
error = ec, .additionalInfo =
"HTTP read failed."});
304 template <
typename ResponseBodyT,
typename RequestBodyT>
310 return promise::newPromise([r = std::move(request), timeout,
this](promise::Defer d)
mutable {
311 this->request(std::move(r), timeout)
312 .then([weak = weak_from_this(), timeout, d]() {
313 auto client = weak.lock();
315 return d.reject(
Error{.additionalInfo =
"Client is no longer alive."});
317 client->readResponse<ResponseBodyT>(timeout)
318 .then([d](
auto& response) {
321 .fail([d](
auto error) {
322 d.reject(std::move(error));
325 .fail([d](
auto error) {
326 d.reject(std::move(error));
333 return promise::newPromise([&,
this](promise::Defer d)
mutable {
335 withLowerLayerDo([&](
auto& socket) {
336 if (socket.socket().is_open())
349 return promise::newPromise([&,
this](promise::Defer d)
mutable {
350 if (std::holds_alternative<boost::beast::tcp_stream>(socket_))
353 withLowerLayerDo([timeout](
auto& socket) {
354 socket.expires_after(timeout);
356 auto& socket = std::get<boost::beast::ssl_stream<boost::beast::tcp_stream>>(socket_);
357 socket.async_shutdown([d = std::move(d)](boost::beast::error_code ec)
mutable {
358 if (ec == boost::asio::error::eof)
362 return d.reject(
Error{.
error = ec, .additionalInfo =
"Stream shutdown failed."});
370 template <
typename FunctionT>
373 std::visit(std::forward<FunctionT>(func), socket_);
376 template <
typename FunctionT>
379 std::visit(std::forward<FunctionT>(func), socket_);
382 template <
typename FunctionT>
387 [&func](boost::beast::ssl_stream<boost::beast::tcp_stream>& stream) {
388 return func(stream.next_layer());
390 [&func](boost::beast::tcp_stream& stream) {
395 template <
typename FunctionT>
400 [&func](boost::beast::ssl_stream<boost::beast::tcp_stream>
const& stream) {
401 return func(stream.next_layer());
403 [&func](boost::beast::tcp_stream
const& stream) {
410 std::string
const& host,
411 std::string
const&
port,
412 std::function<
void(boost::beast::error_code ec, boost::asio::ip::tcp::resolver::results_type results)>
415 template <
typename BodyT>
418 if (std::holds_alternative<boost::beast::ssl_stream<boost::beast::tcp_stream>>(socket_))
420 auto maybeError = setupSsl(request.host());
422 return d.reject(*maybeError);
424 auto& sslSocket = std::get<boost::beast::ssl_stream<boost::beast::tcp_stream>>(socket_);
425 withLowerLayerDo([&](
auto& socket) {
426 socket.expires_after(timeout);
428 sslSocket.async_handshake(
429 boost::asio::ssl::stream_base::client,
430 [weak = weak_from_this(), d = std::move(d), request = std::move(request), timeout](
431 boost::beast::error_code ec)
mutable {
432 auto self = weak.lock();
434 return d.reject(
Error{.error = ec, .additionalInfo =
"Client is no longer alive."});
437 return d.reject(
Error{.
error = ec, .additionalInfo =
"SSL handshake failed."});
439 self->performRequest(std::move(request), std::move(d), timeout);
443 performRequest(std::move(request), std::move(d), timeout);
446 template <
typename BodyT>
449 withLowerLayerDo([timeout](
auto& socket) {
450 socket.expires_after(timeout);
452 withStreamDo([
this, request, &d](
auto& socket)
mutable {
453 std::shared_ptr<Request<BodyT>> requestPtr = std::make_shared<Request<BodyT>>(std::move(request));
454 boost::beast::http::async_write(
457 [weak = weak_from_this(), d = std::move(d), requestPtr](
458 boost::beast::error_code ec, std::size_t)
mutable {
459 auto self = weak.lock();
461 return d.reject(
Error{.error = ec, .additionalInfo =
"Client is no longer alive."});
464 return d.reject(
Error{.
error = ec, .additionalInfo =
"HTTP write failed."});
471 std::optional<Error> setupSsl(std::string
const& host);
476 std::shared_ptr<boost::beast::flat_buffer>
buffer_;
477 std::variant<boost::beast::ssl_stream<boost::beast::tcp_stream>, boost::beast::tcp_stream>
socket_;
478 boost::asio::ip::tcp::resolver::results_type::endpoint_type
endpoint_;
static constexpr int port
Definition main.cpp:16
void emplaceState(std::string const &tag, ConstructionArgs &&... args)
Create state in place.
Definition client.hpp:103
void withStreamDo(FunctionT &&func)
Definition client.hpp:371
void withLowerLayerDo(FunctionT &&func)
Definition client.hpp:383
void onConnect(Request< BodyT > &&request, promise::Defer &&d, std::chrono::seconds timeout)
Definition client.hpp:416
void doResolve(std::string const &host, std::string const &port, std::function< void(boost::beast::error_code ec, boost::asio::ip::tcp::resolver::results_type results)> onResolve)
Definition client.cpp:134
std::shared_ptr< boost::beast::flat_buffer > buffer_
Definition client.hpp:476
Detail::PromiseTypeBind< Detail::PromiseTypeBindThen<>, Detail::PromiseTypeBindFail< Error > > readHeader(boost::beast::http::response_parser< ResponseBodyT > &parser, std::chrono::seconds timeout=defaultTimeout)
Reads only the header, will need be followed up by a readResponse.
Definition client.hpp:195
Detail::PromiseTypeBind< Detail::PromiseTypeBindThen<>, Detail::PromiseTypeBindFail< Error > > readResponse(boost::beast::http::response_parser< ResponseBodyT > &parser, std::chrono::seconds timeout=defaultTimeout)
Read a response using a beast response parser. You are responsible for keeping the parser alive!
Definition client.hpp:276
std::optional< SslOptions > sslOptions_
Definition client.hpp:474
T & state(std::string const &tag)
Retrieve attached state by tag.
Definition client.hpp:116
void performRequest(Request< BodyT > &&request, promise::Defer &&d, std::chrono::seconds timeout)
Definition client.hpp:447
void withStreamDo(FunctionT &&func) const
Definition client.hpp:377
boost::asio::ip::tcp::resolver::results_type::endpoint_type endpoint_
Definition client.hpp:478
ROAR_PIMPL_SPECIAL_FUNCTIONS_NO_MOVE(Client)
Detail::PromiseTypeBind< Detail::PromiseTypeBindThen< Detail::PromiseReferenceWrap< Response< ResponseBodyT > > >, Detail::PromiseTypeBindFail< Error > > requestAndReadResponse(Request< RequestBodyT > &&request, std::chrono::seconds timeout=defaultTimeout)
Definition client.hpp:308
void withLowerLayerDo(FunctionT &&func) const
Definition client.hpp:396
Detail::PromiseTypeBind< Detail::PromiseTypeBindThen<>, Detail::PromiseTypeBindFail< Error > > shutdownSsl(std::chrono::seconds timeout=defaultTimeout)
Definition client.hpp:347
boost::asio::ip::tcp::resolver resolver_
Definition client.hpp:475
static constexpr std::chrono::seconds defaultTimeout
Definition client.hpp:36
Detail::PromiseTypeBind< Detail::PromiseTypeBindThen< std::string_view, std::size_t >, Detail::PromiseTypeBindFail< Error > > read(std::chrono::seconds timeout=defaultTimeout)
Reads something from the server.
Definition client.cpp:109
void attachState(std::string const &tag, T &&state)
Attach some state to the client lifetime.
Definition client.hpp:90
void removeState(std::string const &tag)
Remove attached state.
Definition client.hpp:126
Detail::PromiseTypeBind< Detail::PromiseTypeBindThen<>, Detail::PromiseTypeBindFail< Error > > request(Request< BodyT > &&request, std::chrono::seconds timeout=defaultTimeout)
Connects the client to a server and performs a request.
Definition client.hpp:139
std::unordered_map< std::string, std::any > attachedState_
Definition client.hpp:479
Detail::PromiseTypeBind< Detail::PromiseTypeBindThen< std::size_t >, Detail::PromiseTypeBindFail< Error > > send(std::string message, std::chrono::seconds timeout=defaultTimeout)
Sends a string to the server.
Definition client.cpp:83
std::variant< boost::beast::ssl_stream< boost::beast::tcp_stream >, boost::beast::tcp_stream > socket_
Definition client.hpp:477
Detail::PromiseTypeBind< Detail::PromiseTypeBindThen<>, Detail::PromiseTypeBindFail< Error > > close()
Definition client.hpp:331
Detail::PromiseTypeBind< Detail::PromiseTypeBindThen< Detail::PromiseReferenceWrap< Response< ResponseBodyT > > >, Detail::PromiseTypeBindFail< Error > > readResponse(std::chrono::seconds timeout=defaultTimeout)
Read a response from the server.
Definition client.hpp:236
This class extends the boost::beast::http::request<BodyT> with additional convenience.
Definition request.hpp:52
Definition response.hpp:29
auto ref(T &thing)
Definition promise_compat.hpp:20
Definition authorization.hpp:10
auto visitOverloaded(std::variant< VariantTypes... > const &variant, VisitFunctionTypes &&... visitFunctions)
Definition visit_overloaded.hpp:10
boost::asio::any_io_executor executor
Required io executor for boost::asio.
Definition client.hpp:55
std::optional< SslOptions > sslOptions
Definition client.hpp:56
std::function< bool(bool, boost::asio::ssl::verify_context &)> sslVerifyCallback
sslVerifyCallback, you can use boost::asio::ssl::rfc2818_verification(host) most of the time.
Definition client.hpp:49
boost::asio::ssl::verify_mode sslVerifyMode
SSL verify mode:
Definition client.hpp:44
boost::asio::ssl::context sslContext
Supply for SSL support.
Definition client.hpp:41
Definition promise_compat.hpp:67
Definition promise_compat.hpp:63
Definition promise_compat.hpp:70
Holds errors that are produced asynchronously anywhere.
Definition error.hpp:20
std::variant< boost::system::error_code, std::string > error
Definition error.hpp:21