Clio  develop
The XRP Ledger API server.
Loading...
Searching...
No Matches
WsConnection.hpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of clio: https://github.com/XRPLF/clio
4 Copyright (c) 2024, the clio developers.
5
6 Permission to use, copy, modify, and distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#pragma once
21
22#include "util/OverloadSet.hpp"
23#include "util/Taggable.hpp"
24#include "util/build/Build.hpp"
25#include "web/ng/Connection.hpp"
26#include "web/ng/Error.hpp"
27#include "web/ng/Request.hpp"
28#include "web/ng/Response.hpp"
29#include "web/ng/impl/Concepts.hpp"
30#include "web/ng/impl/SendingQueue.hpp"
31
32#include <boost/asio/buffer.hpp>
33#include <boost/asio/ip/tcp.hpp>
34#include <boost/asio/spawn.hpp>
35#include <boost/asio/ssl/context.hpp>
36#include <boost/asio/ssl/stream.hpp>
37#include <boost/beast/core/buffers_to_string.hpp>
38#include <boost/beast/core/flat_buffer.hpp>
39#include <boost/beast/core/role.hpp>
40#include <boost/beast/core/tcp_stream.hpp>
41#include <boost/beast/http/field.hpp>
42#include <boost/beast/http/message.hpp>
43#include <boost/beast/http/string_body.hpp>
44#include <boost/beast/ssl.hpp>
45#include <boost/beast/websocket/rfc6455.hpp>
46#include <boost/beast/websocket/stream.hpp>
47#include <boost/beast/websocket/stream_base.hpp>
48
49#include <chrono>
50#include <memory>
51#include <optional>
52#include <string>
53#include <utility>
54#include <variant>
55
56namespace web::ng::impl {
57
59public:
61
62 virtual std::expected<void, Error>
63 sendShared(std::shared_ptr<std::string> message, boost::asio::yield_context yield) = 0;
64};
65
66template <typename StreamType>
67class WsConnection : public WsConnectionBase {
68 boost::beast::websocket::stream<StreamType> stream_;
69 boost::beast::http::request<boost::beast::http::string_body> initialRequest_;
70
71 using MessageType = std::variant<Response, std::shared_ptr<std::string>>;
72 SendingQueue<MessageType> sendingQueue_;
73
74 bool closed_{false};
75
76public:
77 WsConnection(
78 StreamType&& stream,
79 std::string ip,
80 boost::beast::flat_buffer buffer,
81 boost::beast::http::request<boost::beast::http::string_body> initialRequest,
82 util::TagDecoratorFactory const& tagDecoratorFactory
83 )
84 : WsConnectionBase(std::move(ip), std::move(buffer), tagDecoratorFactory)
85 , stream_(std::move(stream))
86 , initialRequest_(std::move(initialRequest))
87 , sendingQueue_{[this](MessageType const& message, auto&& yield) {
88 boost::asio::const_buffer const buffer = std::visit(
90 [](Response const& r) -> boost::asio::const_buffer { return r.asWsResponse(); },
91 [](std::shared_ptr<std::string> const& m) -> boost::asio::const_buffer {
92 return boost::asio::buffer(*m);
93 }
94 },
95 message
96 );
97 stream_.async_write(buffer, yield);
98 }}
99 {
100 setupWsStream();
101 }
102
103 ~WsConnection() override = default;
104 WsConnection(WsConnection&&) = delete;
105 WsConnection&
106 operator=(WsConnection&&) = delete;
107 WsConnection(WsConnection const&) = delete;
108 WsConnection&
109 operator=(WsConnection const&) = delete;
110
111 std::expected<void, Error>
112 performHandshake(boost::asio::yield_context yield)
113 {
114 Error error;
115 stream_.async_accept(initialRequest_, yield[error]);
116 if (error)
117 return std::unexpected{error};
118 return {};
119 }
120
121 bool
122 wasUpgraded() const override
123 {
124 return true;
125 }
126
127 std::expected<void, Error>
128 sendShared(std::shared_ptr<std::string> message, boost::asio::yield_context yield) override
129 {
130 return sendingQueue_.send(std::move(message), yield);
131 }
132
133 void
134 setTimeout(std::chrono::steady_clock::duration newTimeout) override
135 {
136 boost::beast::websocket::stream_base::timeout wsTimeout =
137 boost::beast::websocket::stream_base::timeout::suggested(
138 boost::beast::role_type::server
139 );
140 wsTimeout.idle_timeout = newTimeout;
141 wsTimeout.handshake_timeout = newTimeout;
142 stream_.set_option(wsTimeout);
143 }
144
145 std::expected<void, Error>
146 send(Response response, boost::asio::yield_context yield) override
147 {
148 return sendingQueue_.send(std::move(response), yield);
149 }
150
151 std::expected<Request, Error>
152 receive(boost::asio::yield_context yield) override
153 {
154 Error error;
155 stream_.async_read(buffer_, yield[error]);
156 if (error)
157 return std::unexpected{error};
158
159 auto request = boost::beast::buffers_to_string(buffer_.data());
160 buffer_.consume(buffer_.size());
161
162 return Request{std::move(request), initialRequest_};
163 }
164
165 void
166 close(boost::asio::yield_context yield) override
167 {
168 if (closed_)
169 return;
170
171 // This should be set before the async_close(). Otherwise there is a possibility to have
172 // multiple coroutines waiting on async_close(), but only one will be woken up after the
173 // actual close happened, others will hang.
174 closed_ = true;
175
176 boost::system::error_code error; // unused
177 stream_.async_close(boost::beast::websocket::close_code::normal, yield[error]);
178 }
179
180private:
181 void
182 setupWsStream()
183 {
184 // Disable the timeout. The websocket::stream uses its own timeout settings.
185 boost::beast::get_lowest_layer(stream_).expires_never();
187 stream_.set_option(
188 boost::beast::websocket::stream_base::decorator(
189 [](boost::beast::websocket::response_type& res) {
190 res.set(
191 boost::beast::http::field::server, util::build::getClioFullVersionString()
192 );
193 }
194 )
195 );
196 }
197};
198
199using PlainWsConnection = WsConnection<boost::beast::tcp_stream>;
200using SslWsConnection = WsConnection<boost::asio::ssl::stream<boost::beast::tcp_stream>>;
201
202template <typename StreamType>
203std::expected<std::unique_ptr<WsConnection<StreamType>>, Error>
204makeWsConnection(
205 StreamType&& stream,
206 std::string ip,
207 boost::beast::flat_buffer buffer,
208 boost::beast::http::request<boost::beast::http::string_body> request,
209 util::TagDecoratorFactory const& tagDecoratorFactory,
210 boost::asio::yield_context yield
211)
212{
213 auto connection = std::make_unique<WsConnection<StreamType>>(
214 std::forward<StreamType>(stream),
215 std::move(ip),
216 std::move(buffer),
217 std::move(request),
218 tagDecoratorFactory
219 );
220 auto const expectedSuccess = connection->performHandshake(yield);
221 if (not expectedSuccess.has_value())
222 return std::unexpected{expectedSuccess.error()};
223 return connection;
224}
225
226} // namespace web::ng::impl
A factory for TagDecorator instantiation.
Definition Taggable.hpp:184
std::string const & ip() const
Get the ip of the client.
Definition Connection.cpp:40
Connection(std::string ip, boost::beast::flat_buffer buffer, util::TagDecoratorFactory const &tagDecoratorFactory)
Construct a new Connection object.
Definition Connection.cpp:51
static constexpr std::chrono::steady_clock::duration kDEFAULT_TIMEOUT
The default timeout for send, receive, and close operations.
Definition Connection.hpp:122
Represents an HTTP or WebSocket request.
Definition Request.hpp:37
Represents an HTTP or Websocket response.
Definition Response.hpp:40
Definition SendingQueue.hpp:35
Definition WsConnection.hpp:58
Connection(std::string ip, boost::beast::flat_buffer buffer, util::TagDecoratorFactory const &tagDecoratorFactory)
Construct a new Connection object.
Definition Connection.cpp:51
std::expected< Request, Error > receive(boost::asio::yield_context yield) override
Receive a request from the client.
Definition WsConnection.hpp:152
void close(boost::asio::yield_context yield) override
Gracefully close the connection.
Definition WsConnection.hpp:166
std::expected< void, Error > send(Response response, boost::asio::yield_context yield) override
Send a response to the client.
Definition WsConnection.hpp:146
void setTimeout(std::chrono::steady_clock::duration newTimeout) override
Get the timeout for send, receive, and close operations. For WebSocket connections,...
Definition WsConnection.hpp:134
bool wasUpgraded() const override
Whether the connection was upgraded. Upgraded connections are websocket connections.
Definition WsConnection.hpp:122
Overload set for lambdas.
Definition OverloadSet.hpp:30