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