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::optional<Error>
63 sendShared(std::shared_ptr<std::string> message, boost::asio::yield_context yield) = 0;
64};
65
66template <typename StreamType>
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:
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;
106 operator=(WsConnection&&) = delete;
107 WsConnection(WsConnection const&) = delete;
109 operator=(WsConnection const&) = delete;
110
111 std::optional<Error>
112 performHandshake(boost::asio::yield_context yield)
113 {
114 Error error;
115 stream_.async_accept(initialRequest_, yield[error]);
116 if (error)
117 return error;
118 return std::nullopt;
119 }
120
121 bool
122 wasUpgraded() const override
123 {
124 return true;
125 }
126
127 std::optional<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(boost::beast::role_type::server);
138 wsTimeout.idle_timeout = newTimeout;
139 wsTimeout.handshake_timeout = newTimeout;
140 stream_.set_option(wsTimeout);
141 }
142
143 std::optional<Error>
144 send(Response response, boost::asio::yield_context yield) override
145 {
146 return sendingQueue_.send(std::move(response), yield);
147 }
148
149 std::expected<Request, Error>
150 receive(boost::asio::yield_context yield) override
151 {
152 Error error;
153 stream_.async_read(buffer_, yield[error]);
154 if (error)
155 return std::unexpected{error};
156
157 auto request = boost::beast::buffers_to_string(buffer_.data());
158 buffer_.consume(buffer_.size());
159
160 return Request{std::move(request), initialRequest_};
161 }
162
163 void
164 close(boost::asio::yield_context yield) override
165 {
166 if (closed_)
167 return;
168
169 // This should be set before the async_close(). Otherwise there is a possibility to have multiple coroutines
170 // waiting on async_close(), but only one will be woken up after the actual close happened, others will hang.
171 closed_ = true;
172
173 boost::system::error_code error; // unused
174 stream_.async_close(boost::beast::websocket::close_code::normal, yield[error]);
175 }
176
177private:
178 void
179 setupWsStream()
180 {
181 // Disable the timeout. The websocket::stream uses its own timeout settings.
182 boost::beast::get_lowest_layer(stream_).expires_never();
184 stream_.set_option(
185 boost::beast::websocket::stream_base::decorator([](boost::beast::websocket::response_type& res) {
186 res.set(boost::beast::http::field::server, util::build::getClioFullVersionString());
187 })
188 );
189 }
190};
191
192using PlainWsConnection = WsConnection<boost::beast::tcp_stream>;
193using SslWsConnection = WsConnection<boost::asio::ssl::stream<boost::beast::tcp_stream>>;
194
195template <typename StreamType>
196std::expected<std::unique_ptr<WsConnection<StreamType>>, Error>
197makeWsConnection(
198 StreamType&& stream,
199 std::string ip,
200 boost::beast::flat_buffer buffer,
201 boost::beast::http::request<boost::beast::http::string_body> request,
202 util::TagDecoratorFactory const& tagDecoratorFactory,
203 boost::asio::yield_context yield
204)
205{
206 auto connection = std::make_unique<WsConnection<StreamType>>(
207 std::forward<StreamType>(stream), std::move(ip), std::move(buffer), std::move(request), tagDecoratorFactory
208 );
209 auto maybeError = connection->performHandshake(yield);
210 if (maybeError.has_value())
211 return std::unexpected{maybeError.value()};
212 return connection;
213}
214
215} // namespace web::ng::impl
A factory for TagDecorator instantiation.
Definition Taggable.hpp:182
std::string const & ip() const
Get the ip of the client.
Definition Connection.cpp:37
A class representing a connection to a client.
Definition Connection.hpp:100
Connection(std::string ip, boost::beast::flat_buffer buffer, util::TagDecoratorFactory const &tagDecoratorFactory)
Construct a new Connection object.
Definition Connection.cpp:48
static constexpr std::chrono::steady_clock::duration kDEFAULT_TIMEOUT
The default timeout for send, receive, and close operations.
Definition Connection.hpp:109
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
Definition WsConnection.hpp:67
std::optional< Error > send(Response response, boost::asio::yield_context yield) override
Send a response to the client.
Definition WsConnection.hpp:144
std::expected< Request, Error > receive(boost::asio::yield_context yield) override
Receive a request from the client.
Definition WsConnection.hpp:150
void close(boost::asio::yield_context yield) override
Gracefully close the connection.
Definition WsConnection.hpp:164
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