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>
83 : public std::enable_shared_from_this<Detector<PlainSessionType, SslSessionType, HandlerType>> {
84 using std::enable_shared_from_this<
86
87 util::Logger log_{"WebServer"};
88 boost::beast::tcp_stream stream_;
89 std::optional<std::reference_wrapper<boost::asio::ssl::context>> ctx_;
90 std::reference_wrapper<util::TagDecoratorFactory const> tagFactory_;
91 std::reference_wrapper<dosguard::DOSGuardInterface> const dosGuard_;
92 std::shared_ptr<HandlerType> const handler_;
93 std::reference_wrapper<data::LedgerCacheInterface const> cache_;
94 boost::beast::flat_buffer buffer_;
95 std::shared_ptr<AdminVerificationStrategy> const adminVerification_;
96 std::uint32_t maxWsSendingQueueSize_;
97 std::shared_ptr<ProxyIpResolver> proxyIpResolver_;
98
99public:
114 tcp::socket&& socket,
115 std::optional<std::reference_wrapper<boost::asio::ssl::context>> ctx,
116 std::reference_wrapper<util::TagDecoratorFactory const> tagFactory,
117 std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
118 std::shared_ptr<HandlerType> handler,
119 std::reference_wrapper<data::LedgerCacheInterface const> cache,
120 std::shared_ptr<AdminVerificationStrategy> adminVerification,
121 std::uint32_t maxWsSendingQueueSize,
122 std::shared_ptr<ProxyIpResolver> proxyIpResolver
123 )
124 : stream_(std::move(socket))
125 , ctx_(ctx)
126 , tagFactory_(std::cref(tagFactory))
127 , dosGuard_(dosGuard)
128 , handler_(std::move(handler))
129 , cache_(cache)
130 , adminVerification_(std::move(adminVerification))
131 , maxWsSendingQueueSize_(maxWsSendingQueueSize)
132 , proxyIpResolver_(std::move(proxyIpResolver))
133 {
134 }
135
142 void
143 fail(boost::system::error_code ec, char const* message)
144 {
145 if (ec == boost::asio::ssl::error::stream_truncated)
146 return;
147
148 LOG(log_.info()) << "Detector failed (" << message << "): " << ec.message();
149 }
150
152 void
154 {
155 boost::beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
156 async_detect_ssl(
157 stream_,
158 buffer_,
159 boost::beast::bind_front_handler(&Detector::onDetect, shared_from_this())
160 );
161 }
162
169 void
170 onDetect(boost::beast::error_code ec, bool result)
171 {
172 if (ec)
173 return fail(ec, "detect");
174
175 std::string ip;
176 try {
177 ip = stream_.socket().remote_endpoint().address().to_string();
178 } catch (std::exception const&) {
179 return fail(ec, "cannot get remote endpoint");
180 }
181
182 if (result) {
183 if (!ctx_)
184 return fail(ec, "SSL is not supported by this server");
185
186 std::make_shared<SslSessionType<HandlerType>>(
187 stream_.release_socket(),
188 ip,
189 adminVerification_,
190 proxyIpResolver_,
191 *ctx_,
192 tagFactory_,
193 dosGuard_,
194 handler_,
195 cache_,
196 std::move(buffer_),
197 maxWsSendingQueueSize_
198 )
199 ->run();
200 return;
201 }
202
203 std::make_shared<PlainSessionType<HandlerType>>(
204 stream_.release_socket(),
205 ip,
206 adminVerification_,
207 proxyIpResolver_,
208 tagFactory_,
209 dosGuard_,
210 handler_,
211 cache_,
212 std::move(buffer_),
213 maxWsSendingQueueSize_
214 )
215 ->run();
216 }
217};
218
229template <
230 template <typename> class PlainSessionType,
231 template <typename> class SslSessionType,
232 SomeServerHandler HandlerType>
234 : public ServerTag,
235 public std::enable_shared_from_this<Server<PlainSessionType, SslSessionType, HandlerType>> {
236 using std::enable_shared_from_this<
238
239 util::Logger log_{"WebServer"};
240 std::reference_wrapper<boost::asio::io_context> ioc_;
241 std::optional<boost::asio::ssl::context> ctx_;
242 util::TagDecoratorFactory tagFactory_;
243 std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard_;
244 std::shared_ptr<HandlerType> handler_;
245 std::reference_wrapper<data::LedgerCacheInterface const> cache_;
246 tcp::acceptor acceptor_;
247 std::shared_ptr<AdminVerificationStrategy> adminVerification_;
248 std::uint32_t maxWsSendingQueueSize_;
249 std::shared_ptr<ProxyIpResolver> proxyIpResolver_;
250 std::atomic_bool isStopped_{false};
251
252public:
268 boost::asio::io_context& ioc,
269 std::optional<boost::asio::ssl::context> ctx,
270 tcp::endpoint endpoint,
271 util::TagDecoratorFactory tagFactory,
273 std::shared_ptr<HandlerType> handler,
274 std::reference_wrapper<data::LedgerCacheInterface const> cache,
275 std::shared_ptr<AdminVerificationStrategy> adminVerification,
276 std::uint32_t maxWsSendingQueueSize,
277 ProxyIpResolver proxyIpResolver
278 )
279 : ioc_(std::ref(ioc))
280 , ctx_(std::move(ctx))
281 , tagFactory_(tagFactory)
282 , dosGuard_(std::ref(dosGuard))
283 , handler_(std::move(handler))
284 , cache_(cache)
285 , acceptor_(boost::asio::make_strand(ioc))
286 , adminVerification_(std::move(adminVerification))
287 , maxWsSendingQueueSize_(maxWsSendingQueueSize)
288 , proxyIpResolver_(std::make_shared<ProxyIpResolver>(std::move(proxyIpResolver)))
289 {
290 boost::beast::error_code ec;
291
292 acceptor_.open(endpoint.protocol(), ec);
293 if (ec)
294 return;
295
296 acceptor_.set_option(boost::asio::socket_base::reuse_address(true), ec);
297 if (ec)
298 return;
299
300 acceptor_.bind(endpoint, ec);
301 if (ec) {
302 LOG(log_.error()) << "Failed to bind to endpoint: " << endpoint
303 << ". message: " << ec.message();
304 throw std::runtime_error(
305 fmt::format(
306 "Failed to bind to endpoint: {}:{}",
307 endpoint.address().to_string(),
308 endpoint.port()
309 )
310 );
311 }
312
313 acceptor_.listen(boost::asio::socket_base::max_listen_connections, ec);
314 if (ec) {
315 LOG(log_.error()) << "Failed to listen at endpoint: " << endpoint
316 << ". message: " << ec.message();
317 throw std::runtime_error(
318 fmt::format(
319 "Failed to listen at endpoint: {}:{}",
320 endpoint.address().to_string(),
321 endpoint.port()
322 )
323 );
324 }
325 }
326
328 void
330 {
331 doAccept();
332 }
333
335 void
336 stop(boost::asio::yield_context)
337 {
338 isStopped_ = true;
339 }
340
341private:
342 void
343 doAccept()
344 {
345 acceptor_.async_accept(
346 boost::asio::make_strand(ioc_.get()),
347 boost::beast::bind_front_handler(&Server::onAccept, shared_from_this())
348 );
349 }
350
351 void
352 onAccept(boost::beast::error_code ec, tcp::socket socket)
353 {
354 if (isStopped_) {
355 return;
356 }
357
358 if (!ec) {
359 auto ctxRef = ctx_
360 ? std::optional<std::reference_wrapper<boost::asio::ssl::context>>{ctx_.value()}
361 : std::nullopt;
362
363 std::make_shared<Detector<PlainSessionType, SslSessionType, HandlerType>>(
364 std::move(socket),
365 ctxRef,
366 std::cref(tagFactory_),
367 dosGuard_,
368 handler_,
369 cache_,
370 adminVerification_,
371 maxWsSendingQueueSize_,
372 proxyIpResolver_
373 )
374 ->run();
375 }
376
377 doAccept();
378 }
379};
380
382template <typename HandlerType>
384
396template <typename HandlerType>
397static std::shared_ptr<HttpServer<HandlerType>>
400 boost::asio::io_context& ioc,
402 std::shared_ptr<HandlerType> const& handler,
403 std::reference_wrapper<data::LedgerCacheInterface const> cache
404)
405{
406 static util::Logger const log{"WebServer"}; // NOLINT(readability-identifier-naming)
407
408 auto expectedSslContext = ng::impl::makeServerSslContext(config);
409 if (not expectedSslContext) {
410 LOG(log.error()) << "Failed to create SSL context: " << expectedSslContext.error();
411 return nullptr;
412 }
413
414 auto const serverConfig = config.getObject("server");
415 auto const address = boost::asio::ip::make_address(serverConfig.get<std::string>("ip"));
416 auto const port = serverConfig.get<unsigned short>("port");
417
418 auto expectedAdminVerification = makeAdminVerificationStrategy(config);
419 if (not expectedAdminVerification.has_value()) {
420 LOG(log.error()) << expectedAdminVerification.error();
421 throw std::logic_error{expectedAdminVerification.error()};
422 }
423
424 // If the transactions number is 200 per ledger, A client which subscribes everything will send
425 // 400+ feeds for each ledger. we allow user delay 3 ledgers by default
426 auto const maxWsSendingQueueSize = serverConfig.get<uint32_t>("ws_max_sending_queue_size");
427
428 auto proxyIpResolver = ProxyIpResolver::fromConfig(config);
429
430 auto server = std::make_shared<HttpServer<HandlerType>>(
431 ioc,
432 std::move(expectedSslContext).value(),
433 boost::asio::ip::tcp::endpoint{address, port},
435 dosGuard,
436 handler,
437 cache,
438 std::move(expectedAdminVerification).value(),
439 maxWsSendingQueueSize,
440 std::move(proxyIpResolver)
441 );
442
443 server->run();
444 return server;
445}
446
447} // namespace web
A simple thread-safe logger for the channel specified in the constructor.
Definition Logger.hpp:96
Pump error(SourceLocationType const &loc=CURRENT_SRC_LOCATION) const
Interface for logging at Severity::ERR severity.
Definition Logger.cpp:517
A factory for TagDecorator instantiation.
Definition Taggable.hpp:184
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:170
void run()
Initiate the detection.
Definition Server.hpp:153
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:113
void fail(boost::system::error_code ec, char const *message)
A helper function that is called when any error occurs.
Definition Server.hpp:143
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:50
The WebServer class. It creates server socket and start listening on it.
Definition Server.hpp:235
void run()
Start accepting incoming connections.
Definition Server.hpp:329
void stop(boost::asio::yield_context)
Stop accepting new connections.
Definition Server.hpp:336
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:267
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:398
Server< HttpSession, SslHttpSession, HandlerType > HttpServer
The final type of the HttpServer used by Clio.
Definition Server.hpp:383
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:50