8#define ATL_TRANSPORT_LAYER_PRESENT 1
10#include <boost/asio/ip/tcp.hpp>
11#include <boost/asio/post.hpp>
12#include <boost/asio/steady_timer.hpp>
13#include <boost/asio/strand.hpp>
14#include <boost/beast/core.hpp>
15#include <boost/beast/websocket.hpp>
30namespace beast = boost::beast;
31namespace http = beast::http;
32namespace websocket = beast::websocket;
33namespace net = boost::asio;
35using tcp = net::ip::tcp;
71template <
typename Derived,
typename Stream>
80 self->stop_requested_ =
false;
93 void send(std::string message) {
95 self->write_queue_.push_back(std::move(message));
96 if (self->connected_ && !self->closing_ && !self->writing_) {
108 self->stop_requested_ =
true;
109 self->reconnect_timer_.cancel();
110 if (self->closing_) {
113 self->closing_ =
true;
114 if (self->connecting_ && !self->connected_) {
115 self->resolver_.cancel();
117 beast::error_code ignored;
118 beast::get_lowest_layer(*self->ws_).socket().cancel(ignored);
122 if (self->connected_ && !self->writing_) {
124 }
else if (!self->connected_) {
125 self->closing_ =
false;
140 net::io_context& ioc,
167 virtual void on_error(std::string_view stage, beast::error_code error) {
168 boost::ignore_unused(stage, error);
175 net::strand<net::io_context::executor_type>
strand_;
181 std::unique_ptr<Stream>
ws_;
214 return std::static_pointer_cast<Derived>(this->shared_from_this());
230 beast::get_lowest_layer(*ws_).expires_never();
231 ws_->set_option(websocket::stream_base::timeout::suggested(beast::role_type::client));
232 ws_->set_option(websocket::stream_base::decorator([
this](websocket::request_type& request) {
233 request.set(http::field::user_agent,
"algotradelib");
235 request.set(header.first, header.second);
238 ws_->async_handshake(
241 beast::bind_front_handler(&WebSocketSessionBase::on_handshake,
shared_derived())
262 beast::error_code ignored;
263 beast::get_lowest_layer(*ws_).socket().close(ignored);
275 void fail(std::string_view stage, beast::error_code error) {
282 beast::error_code ignored;
283 beast::get_lowest_layer(*ws_).socket().close(ignored);
289 schedule_reconnect();
293 friend struct WebSocketSessionTestAccess;
295 void on_resolve(beast::error_code error, tcp::resolver::results_type endpoints) {
297 fail(
"resolve", error);
304 beast::get_lowest_layer(*ws_).async_connect(
305 endpoints, beast::bind_front_handler(&Derived::on_connect,
shared_derived())
309 void on_handshake(beast::error_code error) {
311 fail(
"handshake", error);
331 beast::bind_front_handler(&WebSocketSessionBase::on_write,
shared_derived())
335 void on_write(beast::error_code error, std::size_t bytes_transferred) {
336 boost::ignore_unused(bytes_transferred);
339 fail(
"write", error);
356 void on_read(beast::error_code error, std::size_t bytes_transferred) {
357 boost::ignore_unused(bytes_transferred);
358 if (error == websocket::error::closed) {
363 schedule_reconnect();
370 const auto message = beast::buffers_to_string(
buffer_.data());
378 websocket::close_code::normal,
379 beast::bind_front_handler(&WebSocketSessionBase::on_close,
shared_derived())
383 void on_close(beast::error_code error) {
392 schedule_reconnect();
396 void schedule_reconnect() {
401 const auto multiplier =
static_cast<std::int64_t
>(1ULL << shift);
404 const auto base_delay = std::chrono::duration_cast<std::chrono::milliseconds>(delay);
405 const auto jitter_window = base_delay / 5;
406 const auto jitter = jitter_window.count() <= 0 ?
407 std::chrono::milliseconds(0) :
408 std::chrono::milliseconds(
409 std::uniform_int_distribution<std::int64_t>(0, jitter_window.count())(
reconnect_rng_)
virtual ~WebSocketSessionBase()=default
Destroys the session.
std::string host_
The host.
unsigned int reconnect_attempt_
Reconnect attempts since the last successful connect.
bool abandon_if_closing()
Ends a connect that close() interrupted, if it did; called first by each connect stage that completed...
virtual void on_closed()
Called when the session has closed (by close() or by the server).
virtual void on_open()
Called when the session is connected (after each successful upgrade, reconnects included).
void start_ws_handshake()
Sends the upgrade request; called once connected (WsSession) or after the TLS handshake (WssSession).
WebSocketSessionOptions options_
The options.
void fail(std::string_view stage, beast::error_code error)
Tears the session down after a failure, calls on_error (and on_closed if closing),...
net::steady_timer reconnect_timer_
The reconnect timer.
bool stop_requested_
Whether close() was called (no reconnection).
std::string host_header_
The Host header value (host:port), set on connect.
bool writing_
Whether a write is in progress.
bool connected_
Whether the session is connected.
WebSocketSessionBase(net::io_context &ioc, std::string host, std::string port, WebSocketSessionOptions options)
Constructs the session; nothing connects until connect().
void start_resolve()
Starts the DNS lookup; called by the transport class's do_connect() once ws_ is ready.
tcp::resolver resolver_
The resolver.
bool connecting_
Whether a connect is in progress.
beast::flat_buffer buffer_
The read buffer.
void send(std::string message)
Queues a message; messages are sent in order once connected.
bool closing_
Whether a close is in progress.
void connect()
Connects asynchronously (does nothing if connected, connecting or closing), and re-enables reconnecti...
std::unique_ptr< Stream > ws_
The stream, created by the transport class on each connect.
std::shared_ptr< Derived > shared_derived()
Returns a shared pointer to this session as the transport type.
virtual void on_message(std::string_view message)=0
Called with each complete message received.
void close()
Closes the session asynchronously after any write in progress, and disables reconnection; on_closed i...
net::strand< net::io_context::executor_type > strand_
The strand every handler runs on.
virtual void on_error(std::string_view stage, beast::error_code error)
Called after a failed stage has torn the session down; it then reconnects if automatic_reconnect_ is ...
std::string port_
The port.
std::deque< std::string > write_queue_
Messages waiting to be sent; the front one may be being written.
std::minstd_rand reconnect_rng_
The random source for reconnect jitter.
HTTP and WebSocket transport, OAuth tokens, and request pacing.
The library: broker-neutral types (core), and the modules below.
net::ip::tcp tcp
Short for boost::asio::ip::tcp.
Options for a WebSocket session (WsSession, WssSession).
std::map< std::string, std::string > headers_
Extra headers sent with the upgrade request.
std::chrono::seconds operation_timeout_
The deadline for the TCP connect and (for WssSession) the TLS handshake. The DNS lookup has no deadli...
std::chrono::seconds reconnect_max_
The longest reconnect delay (before jitter).
bool automatic_reconnect_
Whether to reconnect after a failure or a close not requested with close().
std::string target_
The path of the upgrade request.
std::chrono::seconds reconnect_initial_
The first reconnect delay; it doubles on each attempt, plus up to 20% random jitter.