Clio develop
The XRP Ledger API server.
Loading...
Searching...
No Matches
Server.hpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of clio: https://github.com/XRPLF/clio
4 Copyright (c) 2023, 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/log/Logger.hpp"
24#include "web/AdminVerificationStrategy.hpp"
25#include "web/HttpSession.hpp"
26#include "web/ProxyIpResolver.hpp"
27#include "web/SslHttpSession.hpp"
28#include "web/dosguard/DOSGuardInterface.hpp"
29#include "web/interface/Concepts.hpp"
30#include "web/ng/impl/ServerSslContext.hpp"
31
32#include <boost/asio/io_context.hpp>
33#include <boost/asio/ip/address.hpp>
34#include <boost/asio/ip/tcp.hpp>
35#include <boost/asio/socket_base.hpp>
36#include <boost/asio/ssl/context.hpp>
37#include <boost/asio/ssl/error.hpp>
38#include <boost/asio/strand.hpp>
39#include <boost/beast/core/error.hpp>
40#include <boost/beast/core/flat_buffer.hpp>
41#include <boost/beast/core/stream_traits.hpp>
42#include <boost/beast/core/tcp_stream.hpp>
43#include <fmt/format.h>
44
45#include <chrono>
46#include <cstdint>
47#include <exception>
48#include <functional>
49#include <memory>
50#include <optional>
51#include <stdexcept>
52#include <string>
53#include <utility>
54
63namespace web {
64
75template <
76 template <typename> class PlainSessionType,
77 template <typename> class SslSessionType,
78 SomeServerHandler HandlerType>
79class Detector : public std::enable_shared_from_this<Detector<PlainSessionType, SslSessionType, HandlerType>> {
80 using std::enable_shared_from_this<Detector<PlainSessionType, SslSessionType, HandlerType>>::shared_from_this;
81
82 util::Logger log_{"WebServer"};
83 boost::beast::tcp_stream stream_;
84 std::optional<std::reference_wrapper<boost::asio::ssl::context>> ctx_;
85 std::reference_wrapper<util::TagDecoratorFactory const> tagFactory_;
86 std::reference_wrapper<dosguard::DOSGuardInterface> const dosGuard_;
87 std::shared_ptr<HandlerType> const handler_;
88 boost::beast::flat_buffer buffer_;
89 std::shared_ptr<AdminVerificationStrategy> const adminVerification_;
90 std::uint32_t maxWsSendingQueueSize_;
91 std::shared_ptr<ProxyIpResolver> proxyIpResolver_;
92
93public:
107 tcp::socket&& socket,
108 std::optional<std::reference_wrapper<boost::asio::ssl::context>> ctx,
109 std::reference_wrapper<util::TagDecoratorFactory const> tagFactory,
110 std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
111 std::shared_ptr<HandlerType> handler,
112 std::shared_ptr<AdminVerificationStrategy> adminVerification,
113 std::uint32_t maxWsSendingQueueSize,
114 std::shared_ptr<ProxyIpResolver> proxyIpResolver
115 )
116 : stream_(std::move(socket))
117 , ctx_(ctx)
118 , tagFactory_(std::cref(tagFactory))
119 , dosGuard_(dosGuard)
120 , handler_(std::move(handler))
121 , adminVerification_(std::move(adminVerification))
122 , maxWsSendingQueueSize_(maxWsSendingQueueSize)
123 , proxyIpResolver_(std::move(proxyIpResolver))
124 {
125 }
126
133 void
134 fail(boost::system::error_code ec, char const* message)
135 {
136 if (ec == boost::asio::ssl::error::stream_truncated)
137 return;
138
139 LOG(log_.info()) << "Detector failed (" << message << "): " << ec.message();
140 }
141
143 void
145 {
146 boost::beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
147 async_detect_ssl(stream_, buffer_, boost::beast::bind_front_handler(&Detector::onDetect, shared_from_this()));
148 }
149
156 void
157 onDetect(boost::beast::error_code ec, bool result)
158 {
159 if (ec)
160 return fail(ec, "detect");
161
162 std::string ip;
163 try {
164 ip = stream_.socket().remote_endpoint().address().to_string();
165 } catch (std::exception const&) {
166 return fail(ec, "cannot get remote endpoint");
167 }
168
169 if (result) {
170 if (!ctx_)
171 return fail(ec, "SSL is not supported by this server");
172
173 std::make_shared<SslSessionType<HandlerType>>(
174 stream_.release_socket(),
175 ip,
176 adminVerification_,
177 proxyIpResolver_,
178 *ctx_,
179 tagFactory_,
180 dosGuard_,
181 handler_,
182 std::move(buffer_),
183 maxWsSendingQueueSize_
184 )
185 ->run();
186 return;
187 }
188
189 std::make_shared<PlainSessionType<HandlerType>>(
190 stream_.release_socket(),
191 ip,
192 adminVerification_,
193 proxyIpResolver_,
194 tagFactory_,
195 dosGuard_,
196 handler_,
197 std::move(buffer_),
198 maxWsSendingQueueSize_
199 )
200 ->run();
201 }
202};
203
213template <
214 template <typename> class PlainSessionType,
215 template <typename> class SslSessionType,
216 SomeServerHandler HandlerType>
217class Server : public std::enable_shared_from_this<Server<PlainSessionType, SslSessionType, HandlerType>> {
218 using std::enable_shared_from_this<Server<PlainSessionType, SslSessionType, HandlerType>>::shared_from_this;
219
220 util::Logger log_{"WebServer"};
221 std::reference_wrapper<boost::asio::io_context> ioc_;
222 std::optional<boost::asio::ssl::context> ctx_;
223 util::TagDecoratorFactory tagFactory_;
224 std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard_;
225 std::shared_ptr<HandlerType> handler_;
226 tcp::acceptor acceptor_;
227 std::shared_ptr<AdminVerificationStrategy> adminVerification_;
228 std::uint32_t maxWsSendingQueueSize_;
229 std::shared_ptr<ProxyIpResolver> proxyIpResolver_;
230
231public:
246 boost::asio::io_context& ioc,
247 std::optional<boost::asio::ssl::context> ctx,
248 tcp::endpoint endpoint,
249 util::TagDecoratorFactory tagFactory,
251 std::shared_ptr<HandlerType> handler,
252 std::shared_ptr<AdminVerificationStrategy> adminVerification,
253 std::uint32_t maxWsSendingQueueSize,
254 ProxyIpResolver proxyIpResolver
255 )
256 : ioc_(std::ref(ioc))
257 , ctx_(std::move(ctx))
258 , tagFactory_(tagFactory)
259 , dosGuard_(std::ref(dosGuard))
260 , handler_(std::move(handler))
261 , acceptor_(boost::asio::make_strand(ioc))
262 , adminVerification_(std::move(adminVerification))
263 , maxWsSendingQueueSize_(maxWsSendingQueueSize)
264 , proxyIpResolver_(std::make_shared<ProxyIpResolver>(std::move(proxyIpResolver)))
265 {
266 boost::beast::error_code ec;
267
268 acceptor_.open(endpoint.protocol(), ec);
269 if (ec)
270 return;
271
272 acceptor_.set_option(boost::asio::socket_base::reuse_address(true), ec);
273 if (ec)
274 return;
275
276 acceptor_.bind(endpoint, ec);
277 if (ec) {
278 LOG(log_.error()) << "Failed to bind to endpoint: " << endpoint << ". message: " << ec.message();
279 throw std::runtime_error(
280 fmt::format("Failed to bind to endpoint: {}:{}", endpoint.address().to_string(), endpoint.port())
281 );
282 }
283
284 acceptor_.listen(boost::asio::socket_base::max_listen_connections, ec);
285 if (ec) {
286 LOG(log_.error()) << "Failed to listen at endpoint: " << endpoint << ". message: " << ec.message();
287 throw std::runtime_error(
288 fmt::format("Failed to listen at endpoint: {}:{}", endpoint.address().to_string(), endpoint.port())
289 );
290 }
291 }
292
294 void
296 {
297 doAccept();
298 }
299
300private:
301 void
302 doAccept()
303 {
304 acceptor_.async_accept(
305 boost::asio::make_strand(ioc_.get()),
306 boost::beast::bind_front_handler(&Server::onAccept, shared_from_this())
307 );
308 }
309
310 void
311 onAccept(boost::beast::error_code ec, tcp::socket socket)
312 {
313 if (!ec) {
314 auto ctxRef =
315 ctx_ ? std::optional<std::reference_wrapper<boost::asio::ssl::context>>{ctx_.value()} : std::nullopt;
316
317 std::make_shared<Detector<PlainSessionType, SslSessionType, HandlerType>>(
318 std::move(socket),
319 ctxRef,
320 std::cref(tagFactory_),
321 dosGuard_,
322 handler_,
323 adminVerification_,
324 maxWsSendingQueueSize_,
325 proxyIpResolver_
326 )
327 ->run();
328 }
329
330 doAccept();
331 }
332};
333
335template <typename HandlerType>
337
348template <typename HandlerType>
349static std::shared_ptr<HttpServer<HandlerType>>
352 boost::asio::io_context& ioc,
354 std::shared_ptr<HandlerType> const& handler
355)
356{
357 static util::Logger const log{"WebServer"}; // NOLINT(readability-identifier-naming)
358
359 auto expectedSslContext = ng::impl::makeServerSslContext(config);
360 if (not expectedSslContext) {
361 LOG(log.error()) << "Failed to create SSL context: " << expectedSslContext.error();
362 return nullptr;
363 }
364
365 auto const serverConfig = config.getObject("server");
366 auto const address = boost::asio::ip::make_address(serverConfig.get<std::string>("ip"));
367 auto const port = serverConfig.get<unsigned short>("port");
368
369 auto expectedAdminVerification = makeAdminVerificationStrategy(config);
370 if (not expectedAdminVerification.has_value()) {
371 LOG(log.error()) << expectedAdminVerification.error();
372 throw std::logic_error{expectedAdminVerification.error()};
373 }
374
375 // If the transactions number is 200 per ledger, A client which subscribes everything will send 400+ feeds for
376 // each ledger. we allow user delay 3 ledgers by default
377 auto const maxWsSendingQueueSize = serverConfig.get<uint32_t>("ws_max_sending_queue_size");
378
379 auto proxyIpResolver = ProxyIpResolver::fromConfig(config);
380
381 auto server = std::make_shared<HttpServer<HandlerType>>(
382 ioc,
383 std::move(expectedSslContext).value(),
384 boost::asio::ip::tcp::endpoint{address, port},
386 dosGuard,
387 handler,
388 std::move(expectedAdminVerification).value(),
389 maxWsSendingQueueSize,
390 std::move(proxyIpResolver)
391 );
392
393 server->run();
394 return server;
395}
396
397} // namespace web
A simple thread-safe logger for the channel specified in the constructor.
Definition Logger.hpp:87
Pump error(SourceLocationType const &loc=CURRENT_SRC_LOCATION) const
Interface for logging at Severity::ERR severity.
Definition Logger.cpp:414
Pump info(SourceLocationType const &loc=CURRENT_SRC_LOCATION) const
Interface for logging at Severity::NFO severity.
Definition Logger.cpp:404
A factory for TagDecorator instantiation.
Definition Taggable.hpp:182
All the config data will be stored and extracted from this class.
Definition ConfigDefinition.hpp:50
ObjectView getObject(std::string_view prefix, std::optional< std::size_t > idx=std::nullopt) const
Returns the ObjectView specified with the prefix.
Definition ConfigDefinition.cpp:63
The Detector class to detect if the connection is a ssl or not.
Definition Server.hpp:79
void onDetect(boost::beast::error_code ec, bool result)
Handles detection result.
Definition Server.hpp:157
void run()
Initiate the detection.
Definition Server.hpp:144
Detector(tcp::socket &&socket, std::optional< std::reference_wrapper< boost::asio::ssl::context > > ctx, std::reference_wrapper< util::TagDecoratorFactory const > tagFactory, std::reference_wrapper< dosguard::DOSGuardInterface > dosGuard, std::shared_ptr< HandlerType > handler, std::shared_ptr< AdminVerificationStrategy > adminVerification, std::uint32_t maxWsSendingQueueSize, std::shared_ptr< ProxyIpResolver > proxyIpResolver)
Create a new detector.
Definition Server.hpp:106
void fail(boost::system::error_code ec, char const *message)
A helper function that is called when any error occurs.
Definition Server.hpp:134
Resolves the client's IP address, considering proxy servers.
Definition ProxyIpResolver.hpp:44
static ProxyIpResolver fromConfig(util::config::ClioConfigDefinition const &config)
Creates a ProxyIpResolver from a configuration.
Definition ProxyIpResolver.cpp:47
The WebServer class. It creates server socket and start listening on it.
Definition Server.hpp:217
void run()
Start accepting incoming connections.
Definition Server.hpp:295
Server(boost::asio::io_context &ioc, std::optional< boost::asio::ssl::context > ctx, tcp::endpoint endpoint, util::TagDecoratorFactory tagFactory, dosguard::DOSGuardInterface &dosGuard, std::shared_ptr< HandlerType > handler, std::shared_ptr< AdminVerificationStrategy > adminVerification, std::uint32_t maxWsSendingQueueSize, ProxyIpResolver proxyIpResolver)
Create a new instance of the web server.
Definition Server.hpp:245
The interface of a denial of service guard.
Definition DOSGuardInterface.hpp:46
This namespace implements the web server and related components.
Definition Types.hpp:43
static std::shared_ptr< HttpServer< HandlerType > > makeHttpServer(util::config::ClioConfigDefinition const &config, boost::asio::io_context &ioc, dosguard::DOSGuardInterface &dosGuard, std::shared_ptr< HandlerType > const &handler)
A factory function that spawns a ready to use HTTP server.
Definition Server.hpp:350
std::shared_ptr< AdminVerificationStrategy > makeAdminVerificationStrategy(std::optional< std::string > password)
Factory function for creating an admin verification strategy.
Definition AdminVerificationStrategy.cpp:67