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