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/Taggable.hpp"
23#include "util/build/Build.hpp"
24#include "web/ng/Connection.hpp"
25#include "web/ng/Error.hpp"
26#include "web/ng/Request.hpp"
27#include "web/ng/Response.hpp"
28#include "web/ng/impl/Concepts.hpp"
29
30#include <boost/asio/buffer.hpp>
31#include <boost/asio/ip/tcp.hpp>
32#include <boost/asio/spawn.hpp>
33#include <boost/asio/ssl/context.hpp>
34#include <boost/asio/ssl/stream.hpp>
35#include <boost/beast/core/buffers_to_string.hpp>
36#include <boost/beast/core/flat_buffer.hpp>
37#include <boost/beast/core/role.hpp>
38#include <boost/beast/core/tcp_stream.hpp>
39#include <boost/beast/http/field.hpp>
40#include <boost/beast/http/message.hpp>
41#include <boost/beast/http/string_body.hpp>
42#include <boost/beast/ssl.hpp>
43#include <boost/beast/websocket/rfc6455.hpp>
44#include <boost/beast/websocket/stream.hpp>
45#include <boost/beast/websocket/stream_base.hpp>
46
47#include <chrono>
48#include <memory>
49#include <optional>
50#include <string>
51#include <utility>
52
53namespace web::ng::impl {
54
56public:
58
59 virtual std::optional<Error>
60 sendBuffer(boost::asio::const_buffer buffer, boost::asio::yield_context yield) = 0;
61};
62
63template <typename StreamType>
65 boost::beast::websocket::stream<StreamType> stream_;
66 boost::beast::http::request<boost::beast::http::string_body> initialRequest_;
67 bool closed_{false};
68
69public:
71 StreamType&& stream,
72 std::string ip,
73 boost::beast::flat_buffer buffer,
74 boost::beast::http::request<boost::beast::http::string_body> initialRequest,
75 util::TagDecoratorFactory const& tagDecoratorFactory
76 )
77 : WsConnectionBase(std::move(ip), std::move(buffer), tagDecoratorFactory)
78 , stream_(std::move(stream))
79 , initialRequest_(std::move(initialRequest))
80 {
81 setupWsStream();
82 }
83
84 std::optional<Error>
85 performHandshake(boost::asio::yield_context yield)
86 {
87 Error error;
88 stream_.async_accept(initialRequest_, yield[error]);
89 if (error)
90 return error;
91 return std::nullopt;
92 }
93
94 bool
95 wasUpgraded() const override
96 {
97 return true;
98 }
99
100 std::optional<Error>
101 sendBuffer(boost::asio::const_buffer buffer, boost::asio::yield_context yield) override
102 {
103 boost::beast::websocket::stream_base::timeout timeoutOption{};
104 stream_.get_option(timeoutOption);
105
106 boost::system::error_code error;
107 stream_.async_write(buffer, yield[error]);
108 if (error)
109 return error;
110 return std::nullopt;
111 }
112
113 void
114 setTimeout(std::chrono::steady_clock::duration newTimeout) override
115 {
116 boost::beast::websocket::stream_base::timeout wsTimeout =
117 boost::beast::websocket::stream_base::timeout::suggested(boost::beast::role_type::server);
118 wsTimeout.idle_timeout = newTimeout;
119 wsTimeout.handshake_timeout = newTimeout;
120 stream_.set_option(wsTimeout);
121 }
122
123 std::optional<Error>
124 send(Response response, boost::asio::yield_context yield) override
125 {
126 return sendBuffer(response.asWsResponse(), yield);
127 }
128
129 std::expected<Request, Error>
130 receive(boost::asio::yield_context yield) override
131 {
132 Error error;
133 stream_.async_read(buffer_, yield[error]);
134 if (error)
135 return std::unexpected{error};
136
137 auto request = boost::beast::buffers_to_string(buffer_.data());
138 buffer_.consume(buffer_.size());
139
140 return Request{std::move(request), initialRequest_};
141 }
142
143 void
144 close(boost::asio::yield_context yield) override
145 {
146 if (closed_)
147 return;
148
149 // This should be set before the async_close(). Otherwise there is a possibility to have multiple coroutines
150 // waiting on async_close(), but only one will be woken up after the actual close happened, others will hang.
151 closed_ = true;
152
153 boost::system::error_code error; // unused
154 stream_.async_close(boost::beast::websocket::close_code::normal, yield[error]);
155 }
156
157private:
158 void
159 setupWsStream()
160 {
161 // Disable the timeout. The websocket::stream uses its own timeout settings.
162 boost::beast::get_lowest_layer(stream_).expires_never();
164 stream_.set_option(
165 boost::beast::websocket::stream_base::decorator([](boost::beast::websocket::response_type& res) {
166 res.set(boost::beast::http::field::server, util::build::getClioFullVersionString());
167 })
168 );
169 }
170};
171
172using PlainWsConnection = WsConnection<boost::beast::tcp_stream>;
173using SslWsConnection = WsConnection<boost::asio::ssl::stream<boost::beast::tcp_stream>>;
174
175template <typename StreamType>
176std::expected<std::unique_ptr<WsConnection<StreamType>>, Error>
177makeWsConnection(
178 StreamType&& stream,
179 std::string ip,
180 boost::beast::flat_buffer buffer,
181 boost::beast::http::request<boost::beast::http::string_body> request,
182 util::TagDecoratorFactory const& tagDecoratorFactory,
183 boost::asio::yield_context yield
184)
185{
186 auto connection = std::make_unique<WsConnection<StreamType>>(
187 std::forward<StreamType>(stream), std::move(ip), std::move(buffer), std::move(request), tagDecoratorFactory
188 );
189 auto maybeError = connection->performHandshake(yield);
190 if (maybeError.has_value())
191 return std::unexpected{maybeError.value()};
192 return connection;
193}
194
195} // namespace web::ng::impl
A factory for TagDecorator instantiation.
Definition Taggable.hpp:169
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
boost::asio::const_buffer asWsResponse() const &
Get the message of the response as a const buffer.
Definition Response.cpp:188
Definition WsConnection.hpp:55
Definition WsConnection.hpp:64
std::optional< Error > send(Response response, boost::asio::yield_context yield) override
Send a response to the client.
Definition WsConnection.hpp:124
std::expected< Request, Error > receive(boost::asio::yield_context yield) override
Receive a request from the client.
Definition WsConnection.hpp:130
void close(boost::asio::yield_context yield) override
Gracefully close the connection.
Definition WsConnection.hpp:144
void setTimeout(std::chrono::steady_clock::duration newTimeout) override
Get the timeout for send, receive, and close operations. For WebSocket connections,...
Definition WsConnection.hpp:114
bool wasUpgraded() const override
Whether the connection was upgraded. Upgraded connections are websocket connections.
Definition WsConnection.hpp:95