Clio  develop
The XRP Ledger API server.
Loading...
Searching...
No Matches
Server.hpp
1#pragma once
2
3#include "data/LedgerCacheInterface.hpp"
4#include "util/Taggable.hpp"
5#include "util/log/Logger.hpp"
6#include "web/AdminVerificationStrategy.hpp"
7#include "web/HttpSession.hpp"
8#include "web/ProxyIpResolver.hpp"
9#include "web/SslHttpSession.hpp"
10#include "web/dosguard/DOSGuardInterface.hpp"
11#include "web/interface/Concepts.hpp"
12#include "web/ng/impl/ServerSslContext.hpp"
13
14#include <boost/asio/io_context.hpp>
15#include <boost/asio/ip/address.hpp>
16#include <boost/asio/ip/tcp.hpp>
17#include <boost/asio/socket_base.hpp>
18#include <boost/asio/spawn.hpp>
19#include <boost/asio/ssl/context.hpp>
20#include <boost/asio/ssl/error.hpp>
21#include <boost/asio/strand.hpp>
22#include <boost/beast/core/error.hpp>
23#include <boost/beast/core/flat_buffer.hpp>
24#include <boost/beast/core/stream_traits.hpp>
25#include <boost/beast/core/tcp_stream.hpp>
26#include <fmt/format.h>
27
28#include <atomic>
29#include <chrono>
30#include <cstdint>
31#include <exception>
32#include <functional>
33#include <memory>
34#include <optional>
35#include <stdexcept>
36#include <string>
37#include <utility>
38
47namespace web {
48
59template <
60 template <typename> class PlainSessionType,
61 template <typename> class SslSessionType,
62 SomeServerHandler HandlerType>
64 : public std::enable_shared_from_this<Detector<PlainSessionType, SslSessionType, HandlerType>> {
65 using std::enable_shared_from_this<
67
68 util::Logger log_{"WebServer"};
69 boost::beast::tcp_stream stream_;
70 std::optional<std::reference_wrapper<boost::asio::ssl::context>> ctx_;
71 std::reference_wrapper<util::TagDecoratorFactory const> tagFactory_;
72 std::reference_wrapper<dosguard::DOSGuardInterface> const dosGuard_;
73 std::shared_ptr<HandlerType> const handler_;
74 std::reference_wrapper<data::LedgerCacheInterface const> cache_;
75 boost::beast::flat_buffer buffer_;
76 std::shared_ptr<AdminVerificationStrategy> const adminVerification_;
77 std::uint32_t maxWsSendingQueueSize_;
78 std::shared_ptr<ProxyIpResolver> proxyIpResolver_;
79
80public:
95 tcp::socket&& socket,
96 std::optional<std::reference_wrapper<boost::asio::ssl::context>> ctx,
97 std::reference_wrapper<util::TagDecoratorFactory const> tagFactory,
98 std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
99 std::shared_ptr<HandlerType> handler,
100 std::reference_wrapper<data::LedgerCacheInterface const> cache,
101 std::shared_ptr<AdminVerificationStrategy> adminVerification,
102 std::uint32_t maxWsSendingQueueSize,
103 std::shared_ptr<ProxyIpResolver> proxyIpResolver
104 )
105 : stream_(std::move(socket))
106 , ctx_(ctx)
107 , tagFactory_(std::cref(tagFactory))
108 , dosGuard_(dosGuard)
109 , handler_(std::move(handler))
110 , cache_(cache)
111 , adminVerification_(std::move(adminVerification))
112 , maxWsSendingQueueSize_(maxWsSendingQueueSize)
113 , proxyIpResolver_(std::move(proxyIpResolver))
114 {
115 }
116
123 void
124 fail(boost::system::error_code ec, char const* message)
125 {
126 if (ec == boost::asio::ssl::error::stream_truncated)
127 return;
128
129 LOG(log_.info()) << "Detector failed (" << message << "): " << ec.message();
130 }
131
135 void
137 {
138 boost::beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
139 async_detect_ssl(
140 stream_,
141 buffer_,
142 boost::beast::bind_front_handler(&Detector::onDetect, shared_from_this())
143 );
144 }
145
152 void
153 onDetect(boost::beast::error_code ec, bool result)
154 {
155 if (ec)
156 return fail(ec, "detect");
157
158 std::string ip;
159 try {
160 ip = stream_.socket().remote_endpoint().address().to_string();
161 } catch (std::exception const&) {
162 return fail(ec, "cannot get remote endpoint");
163 }
164
165 if (result) {
166 if (!ctx_)
167 return fail(ec, "SSL is not supported by this server");
168
169 std::make_shared<SslSessionType<HandlerType>>(
170 stream_.release_socket(),
171 ip,
172 adminVerification_,
173 proxyIpResolver_,
174 *ctx_,
175 tagFactory_,
176 dosGuard_,
177 handler_,
178 cache_,
179 std::move(buffer_),
180 maxWsSendingQueueSize_
181 )
182 ->run();
183 return;
184 }
185
186 std::make_shared<PlainSessionType<HandlerType>>(
187 stream_.release_socket(),
188 ip,
189 adminVerification_,
190 proxyIpResolver_,
191 tagFactory_,
192 dosGuard_,
193 handler_,
194 cache_,
195 std::move(buffer_),
196 maxWsSendingQueueSize_
197 )
198 ->run();
199 }
200};
201
212template <
213 template <typename> class PlainSessionType,
214 template <typename> class SslSessionType,
215 SomeServerHandler HandlerType>
217 : public ServerTag,
218 public std::enable_shared_from_this<Server<PlainSessionType, SslSessionType, HandlerType>> {
219 using std::enable_shared_from_this<
221
222 util::Logger log_{"WebServer"};
223 std::reference_wrapper<boost::asio::io_context> ioc_;
224 std::optional<boost::asio::ssl::context> ctx_;
225 util::TagDecoratorFactory tagFactory_;
226 std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard_;
227 std::shared_ptr<HandlerType> handler_;
228 std::reference_wrapper<data::LedgerCacheInterface const> cache_;
229 tcp::acceptor acceptor_;
230 std::shared_ptr<AdminVerificationStrategy> adminVerification_;
231 std::uint32_t maxWsSendingQueueSize_;
232 std::shared_ptr<ProxyIpResolver> proxyIpResolver_;
233 std::atomic_bool isStopped_{false};
234
235public:
251 boost::asio::io_context& ioc,
252 std::optional<boost::asio::ssl::context> ctx,
253 tcp::endpoint endpoint,
254 util::TagDecoratorFactory tagFactory,
256 std::shared_ptr<HandlerType> handler,
257 std::reference_wrapper<data::LedgerCacheInterface const> cache,
258 std::shared_ptr<AdminVerificationStrategy> adminVerification,
259 std::uint32_t maxWsSendingQueueSize,
260 ProxyIpResolver proxyIpResolver
261 )
262 : ioc_(std::ref(ioc))
263 , ctx_(std::move(ctx))
264 , tagFactory_(tagFactory)
265 , dosGuard_(std::ref(dosGuard))
266 , handler_(std::move(handler))
267 , cache_(cache)
268 , acceptor_(boost::asio::make_strand(ioc))
269 , adminVerification_(std::move(adminVerification))
270 , maxWsSendingQueueSize_(maxWsSendingQueueSize)
271 , proxyIpResolver_(std::make_shared<ProxyIpResolver>(std::move(proxyIpResolver)))
272 {
273 boost::beast::error_code ec;
274
275 acceptor_.open(endpoint.protocol(), ec);
276 if (ec)
277 return;
278
279 acceptor_.set_option(boost::asio::socket_base::reuse_address(true), ec);
280 if (ec)
281 return;
282
283 acceptor_.bind(endpoint, ec);
284 if (ec) {
285 LOG(log_.error()) << "Failed to bind to endpoint: " << endpoint
286 << ". message: " << ec.message();
287 throw std::runtime_error(
288 fmt::format(
289 "Failed to bind to endpoint: {}:{}",
290 endpoint.address().to_string(),
291 endpoint.port()
292 )
293 );
294 }
295
296 acceptor_.listen(boost::asio::socket_base::max_listen_connections, ec);
297 if (ec) {
298 LOG(log_.error()) << "Failed to listen at endpoint: " << endpoint
299 << ". message: " << ec.message();
300 throw std::runtime_error(
301 fmt::format(
302 "Failed to listen at endpoint: {}:{}",
303 endpoint.address().to_string(),
304 endpoint.port()
305 )
306 );
307 }
308 }
309
313 void
315 {
316 doAccept();
317 }
318
322 void
323 stop(boost::asio::yield_context)
324 {
325 isStopped_ = true;
326 }
327
328private:
329 void
330 doAccept()
331 {
332 acceptor_.async_accept(
333 boost::asio::make_strand(ioc_.get()),
334 boost::beast::bind_front_handler(&Server::onAccept, shared_from_this())
335 );
336 }
337
338 void
339 onAccept(boost::beast::error_code ec, tcp::socket socket)
340 {
341 if (isStopped_) {
342 return;
343 }
344
345 if (!ec) {
346 auto ctxRef = ctx_
347 ? std::optional<std::reference_wrapper<boost::asio::ssl::context>>{*ctx_}
348 : std::nullopt;
349
350 std::make_shared<Detector<PlainSessionType, SslSessionType, HandlerType>>(
351 std::move(socket),
352 ctxRef,
353 std::cref(tagFactory_),
354 dosGuard_,
355 handler_,
356 cache_,
357 adminVerification_,
358 maxWsSendingQueueSize_,
359 proxyIpResolver_
360 )
361 ->run();
362 }
363
364 doAccept();
365 }
366};
367
371template <typename HandlerType>
373
385template <typename HandlerType>
386static std::expected<std::shared_ptr<HttpServer<HandlerType>>, std::string>
389 boost::asio::io_context& ioc,
391 std::shared_ptr<HandlerType> const& handler,
392 std::reference_wrapper<data::LedgerCacheInterface const> cache
393)
394{
395 static util::Logger const log{"WebServer"}; // NOLINT(readability-identifier-naming)
396
397 auto expectedSslContext = ng::impl::makeServerSslContext(config);
398 if (not expectedSslContext) {
399 return std::unexpected(
400 fmt::format("Failed to create SSL context: {}", expectedSslContext.error())
401 );
402 }
403
404 auto const serverConfig = config.getObject("server");
405
406 auto const ipFromConfig = serverConfig.get<std::string>("ip");
407 boost::system::error_code ec;
408 auto const address = boost::asio::ip::make_address(ipFromConfig, ec);
409 if (ec.failed())
410 return std::unexpected(fmt::format("Invalid 'server.ip' config value: {}", ipFromConfig));
411
412 auto const port = serverConfig.get<unsigned short>("port");
413
414 auto expectedAdminVerification = makeAdminVerificationStrategy(config);
415 if (not expectedAdminVerification.has_value())
416 return std::unexpected(expectedAdminVerification.error());
417
418 // If the transactions number is 200 per ledger, A client which subscribes everything will send
419 // 400+ feeds for each ledger. we allow user delay 3 ledgers by default
420 auto const maxWsSendingQueueSize = serverConfig.get<uint32_t>("ws_max_sending_queue_size");
421
422 auto proxyIpResolver = ProxyIpResolver::fromConfig(config);
423
424 auto server = std::make_shared<HttpServer<HandlerType>>(
425 ioc,
426 std::move(expectedSslContext).value(),
427 boost::asio::ip::tcp::endpoint{address, port},
429 dosGuard,
430 handler,
431 cache,
432 std::move(expectedAdminVerification).value(),
433 maxWsSendingQueueSize,
434 std::move(proxyIpResolver)
435 );
436
437 server->run();
438 return server;
439}
440
441} // namespace web
A simple thread-safe logger for the channel specified in the constructor.
Definition Logger.hpp:78
A factory for TagDecorator instantiation.
Definition Taggable.hpp:165
All the config data will be stored and extracted from this class.
Definition ConfigDefinition.hpp:31
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:44
T get(std::string_view key) const
Returns the specified value of given string if value exists.
Definition ObjectView.hpp:71
void onDetect(boost::beast::error_code ec, bool result)
Handles detection result.
Definition Server.hpp:153
void run()
Initiate the detection.
Definition Server.hpp:136
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:94
void fail(boost::system::error_code ec, char const *message)
A helper function that is called when any error occurs.
Definition Server.hpp:124
Resolves the client's IP address, considering proxy servers.
Definition ProxyIpResolver.hpp:25
static ProxyIpResolver fromConfig(util::config::ClioConfigDefinition const &config)
Creates a ProxyIpResolver from a configuration.
Definition ProxyIpResolver.cpp:33
The WebServer class. It creates server socket and start listening on it.
Definition Server.hpp:218
void run()
Start accepting incoming connections.
Definition Server.hpp:314
void stop(boost::asio::yield_context)
Stop accepting new connections.
Definition Server.hpp:323
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:250
The interface of a denial of service guard.
Definition DOSGuardInterface.hpp:27
Specifies the requirements a Webserver handler must fulfill.
Definition Concepts.hpp:18
This namespace implements the web server and related components.
Definition Types.hpp:24
Server< HttpSession, SslHttpSession, HandlerType > HttpServer
The final type of the HttpServer used by Clio.
Definition Server.hpp:372
std::shared_ptr< AdminVerificationStrategy > makeAdminVerificationStrategy(std::optional< std::string > password)
Factory function for creating an admin verification strategy.
Definition AdminVerificationStrategy.cpp:47
static std::expected< std::shared_ptr< HttpServer< HandlerType > >, std::string > 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:387
A tag class for server to help identify Server in templated code.
Definition Concepts.hpp:31