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