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 "data/LedgerCacheInterface.hpp"
23#include "util/Taggable.hpp"
24#include "util/log/Logger.hpp"
25#include "web/AdminVerificationStrategy.hpp"
26#include "web/HttpSession.hpp"
27#include "web/ProxyIpResolver.hpp"
28#include "web/SslHttpSession.hpp"
29#include "web/dosguard/DOSGuardInterface.hpp"
30#include "web/interface/Concepts.hpp"
31#include "web/ng/impl/ServerSslContext.hpp"
32
33#include <boost/asio/io_context.hpp>
34#include <boost/asio/ip/address.hpp>
35#include <boost/asio/ip/tcp.hpp>
36#include <boost/asio/socket_base.hpp>
37#include <boost/asio/ssl/context.hpp>
38#include <boost/asio/ssl/error.hpp>
39#include <boost/asio/strand.hpp>
40#include <boost/beast/core/error.hpp>
41#include <boost/beast/core/flat_buffer.hpp>
42#include <boost/beast/core/stream_traits.hpp>
43#include <boost/beast/core/tcp_stream.hpp>
44#include <fmt/format.h>
45
46#include <chrono>
47#include <cstdint>
48#include <exception>
49#include <functional>
50#include <memory>
51#include <optional>
52#include <stdexcept>
53#include <string>
54#include <utility>
55
64namespace web {
65
76template <
77 template <typename> class PlainSessionType,
78 template <typename> class SslSessionType,
79 SomeServerHandler HandlerType>
80class Detector : public std::enable_shared_from_this<Detector<PlainSessionType, SslSessionType, HandlerType>> {
81 using std::enable_shared_from_this<Detector<PlainSessionType, SslSessionType, HandlerType>>::shared_from_this;
82
83 util::Logger log_{"WebServer"};
84 boost::beast::tcp_stream stream_;
85 std::optional<std::reference_wrapper<boost::asio::ssl::context>> ctx_;
86 std::reference_wrapper<util::TagDecoratorFactory const> tagFactory_;
87 std::reference_wrapper<dosguard::DOSGuardInterface> const dosGuard_;
88 std::shared_ptr<HandlerType> const handler_;
89 std::reference_wrapper<data::LedgerCacheInterface const> cache_;
90 boost::beast::flat_buffer buffer_;
91 std::shared_ptr<AdminVerificationStrategy> const adminVerification_;
92 std::uint32_t maxWsSendingQueueSize_;
93 std::shared_ptr<ProxyIpResolver> proxyIpResolver_;
94
95public:
110 tcp::socket&& socket,
111 std::optional<std::reference_wrapper<boost::asio::ssl::context>> ctx,
112 std::reference_wrapper<util::TagDecoratorFactory const> tagFactory,
113 std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
114 std::shared_ptr<HandlerType> handler,
115 std::reference_wrapper<data::LedgerCacheInterface const> cache,
116 std::shared_ptr<AdminVerificationStrategy> adminVerification,
117 std::uint32_t maxWsSendingQueueSize,
118 std::shared_ptr<ProxyIpResolver> proxyIpResolver
119 )
120 : stream_(std::move(socket))
121 , ctx_(ctx)
122 , tagFactory_(std::cref(tagFactory))
123 , dosGuard_(dosGuard)
124 , handler_(std::move(handler))
125 , cache_(cache)
126 , adminVerification_(std::move(adminVerification))
127 , maxWsSendingQueueSize_(maxWsSendingQueueSize)
128 , proxyIpResolver_(std::move(proxyIpResolver))
129 {
130 }
131
138 void
139 fail(boost::system::error_code ec, char const* message)
140 {
141 if (ec == boost::asio::ssl::error::stream_truncated)
142 return;
143
144 LOG(log_.info()) << "Detector failed (" << message << "): " << ec.message();
145 }
146
148 void
150 {
151 boost::beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
152 async_detect_ssl(stream_, buffer_, boost::beast::bind_front_handler(&Detector::onDetect, shared_from_this()));
153 }
154
161 void
162 onDetect(boost::beast::error_code ec, bool result)
163 {
164 if (ec)
165 return fail(ec, "detect");
166
167 std::string ip;
168 try {
169 ip = stream_.socket().remote_endpoint().address().to_string();
170 } catch (std::exception const&) {
171 return fail(ec, "cannot get remote endpoint");
172 }
173
174 if (result) {
175 if (!ctx_)
176 return fail(ec, "SSL is not supported by this server");
177
178 std::make_shared<SslSessionType<HandlerType>>(
179 stream_.release_socket(),
180 ip,
181 adminVerification_,
182 proxyIpResolver_,
183 *ctx_,
184 tagFactory_,
185 dosGuard_,
186 handler_,
187 cache_,
188 std::move(buffer_),
189 maxWsSendingQueueSize_
190 )
191 ->run();
192 return;
193 }
194
195 std::make_shared<PlainSessionType<HandlerType>>(
196 stream_.release_socket(),
197 ip,
198 adminVerification_,
199 proxyIpResolver_,
200 tagFactory_,
201 dosGuard_,
202 handler_,
203 cache_,
204 std::move(buffer_),
205 maxWsSendingQueueSize_
206 )
207 ->run();
208 }
209};
210
220template <
221 template <typename> class PlainSessionType,
222 template <typename> class SslSessionType,
223 SomeServerHandler HandlerType>
224class Server : public std::enable_shared_from_this<Server<PlainSessionType, SslSessionType, HandlerType>> {
225 using std::enable_shared_from_this<Server<PlainSessionType, SslSessionType, HandlerType>>::shared_from_this;
226
227 util::Logger log_{"WebServer"};
228 std::reference_wrapper<boost::asio::io_context> ioc_;
229 std::optional<boost::asio::ssl::context> ctx_;
230 util::TagDecoratorFactory tagFactory_;
231 std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard_;
232 std::shared_ptr<HandlerType> handler_;
233 std::reference_wrapper<data::LedgerCacheInterface const> cache_;
234 tcp::acceptor acceptor_;
235 std::shared_ptr<AdminVerificationStrategy> adminVerification_;
236 std::uint32_t maxWsSendingQueueSize_;
237 std::shared_ptr<ProxyIpResolver> proxyIpResolver_;
238
239public:
255 boost::asio::io_context& ioc,
256 std::optional<boost::asio::ssl::context> ctx,
257 tcp::endpoint endpoint,
258 util::TagDecoratorFactory tagFactory,
260 std::shared_ptr<HandlerType> handler,
261 std::reference_wrapper<data::LedgerCacheInterface const> cache,
262 std::shared_ptr<AdminVerificationStrategy> adminVerification,
263 std::uint32_t maxWsSendingQueueSize,
264 ProxyIpResolver proxyIpResolver
265 )
266 : ioc_(std::ref(ioc))
267 , ctx_(std::move(ctx))
268 , tagFactory_(tagFactory)
269 , dosGuard_(std::ref(dosGuard))
270 , handler_(std::move(handler))
271 , cache_(cache)
272 , acceptor_(boost::asio::make_strand(ioc))
273 , adminVerification_(std::move(adminVerification))
274 , maxWsSendingQueueSize_(maxWsSendingQueueSize)
275 , proxyIpResolver_(std::make_shared<ProxyIpResolver>(std::move(proxyIpResolver)))
276 {
277 boost::beast::error_code ec;
278
279 acceptor_.open(endpoint.protocol(), ec);
280 if (ec)
281 return;
282
283 acceptor_.set_option(boost::asio::socket_base::reuse_address(true), ec);
284 if (ec)
285 return;
286
287 acceptor_.bind(endpoint, ec);
288 if (ec) {
289 LOG(log_.error()) << "Failed to bind to endpoint: " << endpoint << ". message: " << ec.message();
290 throw std::runtime_error(
291 fmt::format("Failed to bind to endpoint: {}:{}", endpoint.address().to_string(), endpoint.port())
292 );
293 }
294
295 acceptor_.listen(boost::asio::socket_base::max_listen_connections, ec);
296 if (ec) {
297 LOG(log_.error()) << "Failed to listen at endpoint: " << endpoint << ". message: " << ec.message();
298 throw std::runtime_error(
299 fmt::format("Failed to listen at endpoint: {}:{}", endpoint.address().to_string(), endpoint.port())
300 );
301 }
302 }
303
305 void
307 {
308 doAccept();
309 }
310
311private:
312 void
313 doAccept()
314 {
315 acceptor_.async_accept(
316 boost::asio::make_strand(ioc_.get()),
317 boost::beast::bind_front_handler(&Server::onAccept, shared_from_this())
318 );
319 }
320
321 void
322 onAccept(boost::beast::error_code ec, tcp::socket socket)
323 {
324 if (!ec) {
325 auto ctxRef =
326 ctx_ ? std::optional<std::reference_wrapper<boost::asio::ssl::context>>{ctx_.value()} : std::nullopt;
327
328 std::make_shared<Detector<PlainSessionType, SslSessionType, HandlerType>>(
329 std::move(socket),
330 ctxRef,
331 std::cref(tagFactory_),
332 dosGuard_,
333 handler_,
334 cache_,
335 adminVerification_,
336 maxWsSendingQueueSize_,
337 proxyIpResolver_
338 )
339 ->run();
340 }
341
342 doAccept();
343 }
344};
345
347template <typename HandlerType>
349
361template <typename HandlerType>
362static std::shared_ptr<HttpServer<HandlerType>>
365 boost::asio::io_context& ioc,
367 std::shared_ptr<HandlerType> const& handler,
368 std::reference_wrapper<data::LedgerCacheInterface const> cache
369)
370{
371 static util::Logger const log{"WebServer"}; // NOLINT(readability-identifier-naming)
372
373 auto expectedSslContext = ng::impl::makeServerSslContext(config);
374 if (not expectedSslContext) {
375 LOG(log.error()) << "Failed to create SSL context: " << expectedSslContext.error();
376 return nullptr;
377 }
378
379 auto const serverConfig = config.getObject("server");
380 auto const address = boost::asio::ip::make_address(serverConfig.get<std::string>("ip"));
381 auto const port = serverConfig.get<unsigned short>("port");
382
383 auto expectedAdminVerification = makeAdminVerificationStrategy(config);
384 if (not expectedAdminVerification.has_value()) {
385 LOG(log.error()) << expectedAdminVerification.error();
386 throw std::logic_error{expectedAdminVerification.error()};
387 }
388
389 // If the transactions number is 200 per ledger, A client which subscribes everything will send 400+ feeds for
390 // each ledger. we allow user delay 3 ledgers by default
391 auto const maxWsSendingQueueSize = serverConfig.get<uint32_t>("ws_max_sending_queue_size");
392
393 auto proxyIpResolver = ProxyIpResolver::fromConfig(config);
394
395 auto server = std::make_shared<HttpServer<HandlerType>>(
396 ioc,
397 std::move(expectedSslContext).value(),
398 boost::asio::ip::tcp::endpoint{address, port},
400 dosGuard,
401 handler,
402 cache,
403 std::move(expectedAdminVerification).value(),
404 maxWsSendingQueueSize,
405 std::move(proxyIpResolver)
406 );
407
408 server->run();
409 return server;
410}
411
412} // namespace web
A simple thread-safe logger for the channel specified in the constructor.
Definition Logger.hpp:94
Pump error(SourceLocationType const &loc=CURRENT_SRC_LOCATION) const
Interface for logging at Severity::ERR severity.
Definition Logger.cpp:473
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
void onDetect(boost::beast::error_code ec, bool result)
Handles detection result.
Definition Server.hpp:162
void run()
Initiate the detection.
Definition Server.hpp:149
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::reference_wrapper< data::LedgerCacheInterface const > cache, std::shared_ptr< AdminVerificationStrategy > adminVerification, std::uint32_t maxWsSendingQueueSize, std::shared_ptr< ProxyIpResolver > proxyIpResolver)
Create a new detector.
Definition Server.hpp:109
void fail(boost::system::error_code ec, char const *message)
A helper function that is called when any error occurs.
Definition Server.hpp:139
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:224
void run()
Start accepting incoming connections.
Definition Server.hpp:306
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::reference_wrapper< data::LedgerCacheInterface const > cache, std::shared_ptr< AdminVerificationStrategy > adminVerification, std::uint32_t maxWsSendingQueueSize, ProxyIpResolver proxyIpResolver)
Create a new instance of the web server.
Definition Server.hpp:254
The interface of a denial of service guard.
Definition DOSGuardInterface.hpp:46
Specifies the requirements a Webserver handler must fulfill.
Definition Concepts.hpp:36
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, std::reference_wrapper< data::LedgerCacheInterface const > cache)
A factory function that spawns a ready to use HTTP server.
Definition Server.hpp:363
Server< HttpSession, SslHttpSession, HandlerType > HttpServer
The final type of the HttpServer used by Clio.
Definition Server.hpp:348
std::shared_ptr< AdminVerificationStrategy > makeAdminVerificationStrategy(std::optional< std::string > password)
Factory function for creating an admin verification strategy.
Definition AdminVerificationStrategy.cpp:67