Clio  develop
The XRP Ledger API server.
Loading...
Searching...
No Matches
HttpConnection.hpp
1#pragma once
2
3#include "util/Assert.hpp"
4#include "util/Taggable.hpp"
5#include "web/ng/Connection.hpp"
6#include "web/ng/Error.hpp"
7#include "web/ng/Request.hpp"
8#include "web/ng/Response.hpp"
9#include "web/ng/impl/Concepts.hpp"
10#include "web/ng/impl/SendingQueue.hpp"
11#include "web/ng/impl/WsConnection.hpp"
12
13#include <boost/asio/buffer.hpp>
14#include <boost/asio/ip/tcp.hpp>
15#include <boost/asio/spawn.hpp>
16#include <boost/asio/ssl/context.hpp>
17#include <boost/asio/ssl/stream.hpp>
18#include <boost/asio/ssl/stream_base.hpp>
19#include <boost/beast/core/basic_stream.hpp>
20#include <boost/beast/core/error.hpp>
21#include <boost/beast/core/flat_buffer.hpp>
22#include <boost/beast/core/tcp_stream.hpp>
23#include <boost/beast/http.hpp>
24#include <boost/beast/http/message.hpp>
25#include <boost/beast/http/string_body.hpp>
26#include <boost/beast/websocket.hpp>
27
28#include <chrono>
29#include <cstddef>
30#include <memory>
31#include <optional>
32#include <string>
33#include <utility>
34
35namespace web::ng::impl {
36
38public:
40
41 virtual std::expected<bool, Error>
42 isUpgradeRequested(boost::asio::yield_context yield) = 0;
43
44 virtual std::expected<ConnectionPtr, Error>
45 upgrade(
46 util::TagDecoratorFactory const& tagDecoratorFactory,
47 size_t maxSendingQueueSize,
48 boost::asio::yield_context yield
49 ) = 0;
50
51 virtual std::expected<void, Error>
52 sendRaw(
53 boost::beast::http::response<boost::beast::http::string_body> response,
54 boost::asio::yield_context yield
55 ) = 0;
56};
57
58using UpgradableConnectionPtr = std::unique_ptr<UpgradableConnection>;
59
60template <typename StreamType>
61class HttpConnection : public UpgradableConnection {
62 StreamType stream_;
63 std::optional<boost::beast::http::request<boost::beast::http::string_body>> request_;
64 std::chrono::steady_clock::duration timeout_{kDefaultTimeout};
65
66 using MessageType = boost::beast::http::response<boost::beast::http::string_body>;
67 SendingQueue<MessageType> sendingQueue_;
68
69 bool closed_{false};
70
71public:
72 HttpConnection(
73 boost::asio::ip::tcp::socket socket,
74 std::string ip,
75 boost::beast::flat_buffer buffer,
76 util::TagDecoratorFactory const& tagDecoratorFactory,
77 size_t maxSendingQueueSize
78 )
80 : UpgradableConnection(std::move(ip), std::move(buffer), tagDecoratorFactory)
81 , stream_{std::move(socket)}
82 , sendingQueue_(
83 [this](MessageType const& message, auto&& yield) {
84 boost::beast::get_lowest_layer(stream_).expires_after(timeout_);
85 boost::beast::http::async_write(stream_, message, yield);
86 },
87 maxSendingQueueSize
88 )
89 {
90 }
91
92 HttpConnection(
93 boost::asio::ip::tcp::socket socket,
94 std::string ip,
95 boost::beast::flat_buffer buffer,
96 boost::asio::ssl::context& sslCtx,
97 util::TagDecoratorFactory const& tagDecoratorFactory,
98 size_t maxSendingQueueSize
99 )
101 : UpgradableConnection(std::move(ip), std::move(buffer), tagDecoratorFactory)
102 , stream_{std::move(socket), sslCtx}
103 , sendingQueue_(
104 [this](MessageType const& message, auto&& yield) {
105 boost::beast::get_lowest_layer(stream_).expires_after(timeout_);
106 boost::beast::http::async_write(stream_, message, yield);
107 },
108 maxSendingQueueSize
109 )
110 {
111 }
112
113 HttpConnection(HttpConnection&& other) = delete;
114 HttpConnection&
115 operator=(HttpConnection&& other) = delete;
116 HttpConnection(HttpConnection const& other) = delete;
117 HttpConnection&
118 operator=(HttpConnection const& other) = delete;
119
120 std::expected<void, Error>
121 sslHandshake(boost::asio::yield_context yield)
123 {
124 boost::system::error_code error;
125 boost::beast::get_lowest_layer(stream_).expires_after(timeout_);
126 auto const bytesUsed = stream_.async_handshake(
127 boost::asio::ssl::stream_base::server, buffer_.cdata(), yield[error]
128 );
129 if (error)
130 return std::unexpected{error};
131
132 buffer_.consume(bytesUsed);
133
134 return {};
135 }
136
137 [[nodiscard]] bool
138 wasUpgraded() const override
139 {
140 return false;
141 }
142
143 std::expected<void, Error>
144 sendRaw(
145 boost::beast::http::response<boost::beast::http::string_body> response,
146 boost::asio::yield_context yield
147 ) override
148 {
149 return sendingQueue_.send(std::move(response), yield);
150 }
151
152 void
153 setTimeout(std::chrono::steady_clock::duration newTimeout) override
154 {
155 timeout_ = newTimeout;
156 }
157
158 std::expected<void, Error>
159 send(Response response, boost::asio::yield_context yield) override
160 {
161 auto httpResponse = std::move(response).intoHttpResponse();
162 return sendRaw(std::move(httpResponse), yield);
163 }
164
165 std::expected<Request, Error>
166 receive(boost::asio::yield_context yield) override
167 {
168 if (request_.has_value()) {
169 Request result{*std::move(request_)};
170 request_.reset();
171 return result;
172 }
173 auto expectedRequest = fetch(yield);
174 if (expectedRequest.has_value())
175 return Request{std::move(expectedRequest).value()};
176
177 return std::unexpected{std::move(expectedRequest).error()};
178 }
179
180 void
181 close(boost::asio::yield_context yield) override
182 {
183 // This is needed because calling async_shutdown() multiple times may lead to hanging
184 // coroutines. See WsConnection for more details.
185 if (closed_)
186 return;
187
188 closed_ = true;
189
190 [[maybe_unused]] boost::system::error_code error;
191 if constexpr (IsSslTcpStream<StreamType>) {
192 boost::beast::get_lowest_layer(stream_).expires_after(timeout_);
193 stream_.async_shutdown(yield[error]); // Close the SSL connection gracefully
194 }
195 boost::beast::get_lowest_layer(stream_).socket().shutdown(
196 boost::asio::ip::tcp::socket::shutdown_type::shutdown_both, error
197 );
198 }
199
200 std::expected<bool, Error>
201 isUpgradeRequested(boost::asio::yield_context yield) override
202 {
203 auto expectedRequest = fetch(yield);
204 if (not expectedRequest.has_value())
205 return std::unexpected{std::move(expectedRequest).error()};
206
207 request_ = std::move(expectedRequest).value();
208
209 return boost::beast::websocket::is_upgrade(*request_);
210 }
211
212 std::expected<ConnectionPtr, Error>
213 upgrade(
214 util::TagDecoratorFactory const& tagDecoratorFactory,
215 size_t maxSendingQueueSize,
216 boost::asio::yield_context yield
217 ) override
218 {
219 ASSERT(request_.has_value(), "Request must be present to upgrade the connection");
220
221 return makeWsConnection(
222 std::move(stream_),
223 std::move(ip_),
224 std::move(buffer_),
225 std::move(*request_), // NOLINT(bugprone-unchecked-optional-access)
226 tagDecoratorFactory,
227 maxSendingQueueSize,
228 yield
229 );
230 }
231
232private:
233 std::expected<boost::beast::http::request<boost::beast::http::string_body>, Error>
234 fetch(boost::asio::yield_context yield)
235 {
236 boost::beast::http::request<boost::beast::http::string_body> request{};
237 boost::system::error_code error;
238 boost::beast::get_lowest_layer(stream_).expires_after(timeout_);
239 boost::beast::http::async_read(stream_, buffer_, request, yield[error]);
240 if (error)
241 return std::unexpected{error};
242 return request;
243 }
244};
245
246using PlainHttpConnection = HttpConnection<boost::beast::tcp_stream>;
247
249
250} // namespace web::ng::impl
A factory for TagDecorator instantiation.
Definition Taggable.hpp:165
std::string const & ip() const
Get the ip of the client.
Definition Connection.cpp:21
static constexpr std::chrono::steady_clock::duration kDefaultTimeout
The default timeout for send, receive, and close operations.
Definition Connection.hpp:124
Connection(std::string ip, boost::beast::flat_buffer buffer, util::TagDecoratorFactory const &tagDecoratorFactory)
Construct a new Connection object.
Definition Connection.cpp:32
Represents an HTTP or WebSocket request.
Definition Request.hpp:18
Represents an HTTP or Websocket response.
Definition Response.hpp:21
Definition HttpConnection.hpp:61
bool wasUpgraded() const override
Whether the connection was upgraded. Upgraded connections are websocket connections.
Definition HttpConnection.hpp:138
std::expected< void, Error > send(Response response, boost::asio::yield_context yield) override
Send a response to the client.
Definition HttpConnection.hpp:159
void setTimeout(std::chrono::steady_clock::duration newTimeout) override
Get the timeout for send, receive, and close operations. For WebSocket connections,...
Definition HttpConnection.hpp:153
void close(boost::asio::yield_context yield) override
Gracefully close the connection.
Definition HttpConnection.hpp:181
std::expected< Request, Error > receive(boost::asio::yield_context yield) override
Receive a request from the client.
Definition HttpConnection.hpp:166
Definition SendingQueue.hpp:19
Definition HttpConnection.hpp:37
Connection(std::string ip, boost::beast::flat_buffer buffer, util::TagDecoratorFactory const &tagDecoratorFactory)
Construct a new Connection object.
Definition Connection.cpp:32
Definition Concepts.hpp:14
Definition Concepts.hpp:11