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