RESTinio
Loading...
Searching...
No Matches
acceptor.hpp
Go to the documentation of this file.
1/*
2 restinio
3*/
4
5/*!
6 HTTP-Acceptor handler routine.
7*/
8
9#pragma once
10
11#include <memory>
12
13#include <restinio/connection_count_limiter.hpp>
14
15#include <restinio/impl/include_fmtlib.hpp>
16
17#include <restinio/impl/connection.hpp>
18
19#include <restinio/utils/suppress_exceptions.hpp>
20
21namespace restinio
22{
23
24namespace impl
25{
26
27//
28// socket_supplier_t
29//
30
31/*
32 A helper base class that hides a pool of socket instances.
33
34 It prepares a socket for new connections.
35 And as it is template class over a socket type
36 it givies an oportunity to customize details for
37 other types of sockets (like `asio::ssl::stream< asio::ip::tcp::socket >`)
38 that can be used.
39*/
40template < typename Socket >
42{
43 protected:
44 template < typename Settings >
46 //! Server settings.
47 Settings & settings,
48 //! A context the server runs on.
49 asio_ns::io_context & io_context )
50 : m_io_context{ io_context }
51 {
52 m_sockets.reserve( settings.concurrent_accepts_count() );
53
54 std::generate_n(
55 std::back_inserter( m_sockets ),
56 settings.concurrent_accepts_count(),
57 [this]{
58 return Socket{m_io_context};
59 } );
60
61 assert( m_sockets.size() == settings.concurrent_accepts_count() );
62 }
63
64 //! Get the reference to socket.
65 Socket &
67 //! Index of a socket in the pool.
68 std::size_t idx )
69 {
70 return m_sockets.at( idx );
71 }
72
73 //! Extract the socket via move.
74 Socket
76 //! Index of a socket in the pool.
77 std::size_t idx )
78 {
79 return std::move( socket(idx ) );
80 }
81
82 //! The number of sockets that can be used for
83 //! cuncurrent accept operations.
84 auto
86 {
87 return m_sockets.size();
88 }
89
90 private:
91 //! io_context for sockets to run on.
92 asio_ns::io_context & m_io_context;
93
94 //! A temporary socket for receiving new connections.
95 //! \note Must never be empty.
97};
98
100{
101
102/*!
103 * @brief A class for holding actual IP-blocker.
104 *
105 * This class holds shared pointer to actual IP-blocker object and
106 * provides actual inspect_incoming() implementation.
107 *
108 * @since v.0.5.1
109 */
110template< typename Ip_Blocker >
112{
113 std::shared_ptr< Ip_Blocker > m_ip_blocker;
114
115 template< typename Settings >
117 const Settings & settings )
118 : m_ip_blocker{ settings.ip_blocker() }
119 {}
120
121 template< typename Socket >
123 inspect_incoming( Socket & socket ) const noexcept
124 {
125 return m_ip_blocker->inspect(
126 restinio::ip_blocker::incoming_info_t{
127 socket.lowest_layer().remote_endpoint()
128 } );
129 }
130};
131
132/*!
133 * @brief A specialization of ip_blocker_holder for case of
134 * noop_ip_blocker.
135 *
136 * This class doesn't hold anything and doesn't do anything.
137 *
138 * @since v.0.5.1
139 */
140template<>
142{
143 template< typename Settings >
144 ip_blocker_holder_t( const Settings & ) { /* nothing to do */ }
145
146 template< typename Socket >
148 inspect_incoming( Socket & /*socket*/ ) const noexcept
149 {
151 }
152};
153
154} /* namespace acceptor_details */
155
156//
157// acceptor_t
158//
159
160//! Context for accepting http connections.
161template < typename Traits >
162class acceptor_t final
163 : public std::enable_shared_from_this< acceptor_t< Traits > >
164 , protected socket_supplier_t< typename Traits::stream_socket_t >
165 , protected acceptor_details::ip_blocker_holder_t< typename Traits::ip_blocker_t >
167{
169 typename Traits::ip_blocker_t >;
170
171 /// An alias for actual connection count limiter type.
173 typename connection_count_limit_types< Traits >::limiter_t;
174
175 /// An alias for actual connection lifetime monitor type.
178
179 public:
182 std::shared_ptr< connection_factory_t >;
183 using logger_t = typename Traits::logger_t;
184 using strand_t = typename Traits::strand_t;
185 using stream_socket_t = typename Traits::stream_socket_t;
187
188 template < typename Settings >
190 Settings & settings,
191 //! ASIO io_context to run on.
192 asio_ns::io_context & io_context,
193 //! Connection factory.
194 connection_factory_shared_ptr_t connection_factory,
195 //! Logger.
196 logger_t & logger )
197 : socket_holder_base_t{ settings, io_context }
198 , ip_blocker_base_t{ settings }
199 , m_port{ settings.port() }
200 , m_protocol{ settings.protocol() }
203 , m_acceptor{ io_context }
205 , m_executor{ io_context.get_executor() }
206 , m_open_close_operations_executor{ io_context.get_executor() }
207 , m_separate_accept_and_create_connect{ settings.separate_accept_and_create_connect() }
208 , m_connection_factory{ std::move( connection_factory ) }
209 , m_logger{ logger }
213 settings.max_parallel_connections()
214 },
216 settings.concurrent_accepts_count()
217 }
218 }
219 {}
220
221 //! Start listen on port specified in ctor.
222 void
224 {
225 if( m_acceptor.is_open() )
226 {
227 const auto ep = m_acceptor.local_endpoint();
228 m_logger.warn( [&]{
229 return fmt::format(
230 RESTINIO_FMT_FORMAT_STRING( "server already started on {}" ),
231 fmtlib_tools::streamed( ep ) );
232 } );
233 return;
234 }
235
236 asio_ns::ip::tcp::endpoint ep{ m_protocol, m_port };
237
238 const auto actual_address = try_extract_actual_address_from_variant(
239 m_address );
240 if( actual_address )
241 ep.address( *actual_address );
242
243 try
244 {
245 m_logger.trace( [&]{
246 return fmt::format(
247 RESTINIO_FMT_FORMAT_STRING( "starting server on {}" ),
248 fmtlib_tools::streamed( ep ) );
249 } );
250
251 m_acceptor.open( ep.protocol() );
252
253 {
254 // Set acceptor options.
255 acceptor_options_t options{ m_acceptor };
256
257 (*m_acceptor_options_setter)( options );
258 }
259
260 m_acceptor.bind( ep );
261 // Since v.0.6.11 the post-bind hook should be invoked.
262 m_acceptor_post_bind_hook( m_acceptor );
263 // server end-point can be replaced if port is allocated by
264 // the operating system (e.g. zero is specified as port number
265 // by a user).
266 ep = m_acceptor.local_endpoint();
267
268 // Now we can switch acceptor to listen state.
269 m_acceptor.listen( asio_ns::socket_base::max_listen_connections );
270
271 // Call accept connections routine.
272 for( std::size_t i = 0; i< this->concurrent_accept_sockets_count(); ++i )
273 {
274 m_logger.info( [&]{
275 return fmt::format(
276 RESTINIO_FMT_FORMAT_STRING( "init accept #{}" ), i );
277 } );
278
279 accept_next( i );
280 }
281
282 m_logger.info( [&]{
283 return fmt::format(
284 RESTINIO_FMT_FORMAT_STRING( "server started on {}" ),
285 fmtlib_tools::streamed( ep ) );
286 } );
287 }
288 catch( const std::exception & ex )
289 {
290 // Acceptor should be closes in the case of an error.
291 if( m_acceptor.is_open() )
292 m_acceptor.close();
293
294 m_logger.error( [&]() -> auto {
295 return fmt::format(
297 "failed to start server on {}: {}" ),
298 fmtlib_tools::streamed( ep ),
299 ex.what() );
300 } );
301
302 throw;
303 }
304 }
305
306 //! Close listener if any.
307 void
309 {
310 if( m_acceptor.is_open() )
311 {
313 }
314 else
315 {
316 // v.0.7.0: suppress exceptions from logging.
317 restinio::utils::log_trace_noexcept( m_logger,
318 [&]{
319 return fmt::format(
320 RESTINIO_FMT_FORMAT_STRING( "server already closed" ) );
321 } );
322 }
323 }
324
325 //! Get an executor for close operation.
326 auto &
331
332 private:
333 //! Get executor for acceptor.
334 auto & get_executor() noexcept { return m_executor; }
335
336 // Begin of implementation of acceptor_callback_iface_t.
337 /*!
338 * @since v.0.6.12
339 */
340 void
341 call_accept_now( std::size_t index ) noexcept override
342 {
343 m_acceptor.async_accept(
344 this->socket( index ).lowest_layer(),
345 asio_ns::bind_executor(
347 [index, ctx = this->shared_from_this()]
348 ( const auto & ec ) noexcept
349 {
350 if( !ec )
351 {
352 ctx->accept_current_connection( index, ec );
353 }
354 } ) );
355 }
356
357 /*!
358 * @since v.0.6.12
359 */
360 void
361 schedule_next_accept_attempt( std::size_t index ) noexcept override
362 {
363 asio_ns::post(
364 asio_ns::bind_executor(
366 [index, ctx = this->shared_from_this()]() noexcept
367 {
368 ctx->accept_next( index );
369 } ) );
370 }
371
372 /*!
373 * @brief Helper for suppressing warnings of using `this` in
374 * initilizer list.
375 *
376 * @since v.0.6.12
377 */
380 {
381 return this;
382 }
383 // End of implementation of acceptor_callback_iface_t.
384
385 //! Set a callback for a new connection.
386 /*!
387 * @note
388 * This method is marked as noexcept in v.0.6.0.
389 * It seems that nothing prevents exceptions from a call to
390 * async_accept. But we just don't know what to do in that case.
391 * So at the moment the call to `std::terminate` because an
392 * exception is raised inside `noexcept` method seems to be an
393 * appropriate solution.
394 */
395 void
396 accept_next( std::size_t i ) noexcept
397 {
398 m_connection_count_limiter.accept_next( i );
399 }
400
401 //! Accept current connection.
402 /*!
403 * @note
404 * This method is marked as noexcept in v.0.6.0.
405 */
406 void
408 //! socket index in the pool of sockets.
409 std::size_t i,
410 const std::error_code & ec ) noexcept
411 {
412 if( !ec )
413 {
414 restinio::utils::suppress_exceptions(
415 m_logger,
416 "accept_current_connection",
417 [this, i] {
419 } );
420 }
421 else
422 {
423 // Something goes wrong with connection.
424 restinio::utils::log_error_noexcept( m_logger,
425 [&]{
426 return fmt::format(
428 "failed to accept connection on socket #{}: {}" ),
429 i,
430 ec.message() );
431 } );
432 }
433
434 // Continue accepting.
435 accept_next( i );
436 }
437
438 /*!
439 * @brief Performs actual actions for accepting a new connection.
440 *
441 * @note
442 * This method can throw. An we expect that it can throw sometimes.
443 *
444 * @since v.0.6.0
445 */
446 void
448 //! socket index in the pool of sockets.
449 std::size_t i )
450 {
451 auto incoming_socket = this->move_socket( i );
452
453 auto remote_endpoint =
454 incoming_socket.lowest_layer().remote_endpoint();
455
456 m_logger.trace( [&]{
457 return fmt::format(
459 "accept connection from {} on socket #{}" ),
460 fmtlib_tools::streamed( remote_endpoint ),
461 i );
462 } );
463
464 // Since v.0.5.1 the incoming connection must be
465 // inspected by IP-blocker.
466 const auto inspection_result = this->inspect_incoming(
467 incoming_socket );
468
469 switch( inspection_result )
470 {
471 case restinio::ip_blocker::inspection_result_t::deny:
472 // New connection can be used. It is disabled by IP-blocker.
473 m_logger.warn( [&]{
474 return fmt::format(
476 "accepted connection from {} on socket #{} denied by"
477 " IP-blocker" ),
478 fmtlib_tools::streamed( remote_endpoint ),
479 i );
480 } );
481 // incoming_socket will be closed automatically.
482 break;
483
484 case restinio::ip_blocker::inspection_result_t::allow:
485 // Acception of the connection can be continued.
487 std::move(incoming_socket),
488 remote_endpoint );
489 break;
490 }
491 }
492
493 void
495 stream_socket_t incoming_socket,
496 endpoint_t remote_endpoint )
497 {
498 auto create_and_init_connection =
499 [sock = std::move(incoming_socket),
500 factory = m_connection_factory,
501 ep = std::move(remote_endpoint),
502 lifetime_monitor = connection_lifetime_monitor_t{
503 *this,
504 &m_connection_count_limiter
505 },
506 logger = &m_logger]
507 () mutable noexcept
508 {
509 // NOTE: this code block shouldn't throw!
510 restinio::utils::suppress_exceptions(
511 *logger,
512 "do_accept_current_connection.create_and_init_connection",
513 [&] {
514 // Create new connection handler.
515 // NOTE: since v.0.6.3 this method throws in
516 // the case of an error. Because of that there is
517 // no need to check the value returned.
518 auto conn = factory->create_new_connection(
519 std::move(sock),
520 std::move(ep),
521 std::move(lifetime_monitor) );
522
523 // Start waiting for request message.
524 conn->init();
525 } );
526 };
527
529 {
530 asio_ns::post(
532 std::move( create_and_init_connection ) );
533 }
534 else
535 {
536 create_and_init_connection();
537 }
538 }
539
540 //! Close opened acceptor.
541 void
543 {
544 const auto ep = m_acceptor.local_endpoint();
545
546 // An exception in logger should not prevent a call of close()
547 // for m_acceptor.
548 restinio::utils::log_trace_noexcept( m_logger,
549 [&]{
550 return fmt::format(
551 RESTINIO_FMT_FORMAT_STRING( "closing server on {}" ),
552 fmtlib_tools::streamed( ep ) );
553 } );
554
555 m_acceptor.close();
556
557 // v.0.7.0: Suppress exceptions from this logging too.
558 restinio::utils::log_info_noexcept( m_logger,
559 [&]{
560 return fmt::format(
561 RESTINIO_FMT_FORMAT_STRING( "server closed on {}" ),
562 fmtlib_tools::streamed( ep ) );
563 } );
564 }
565
566 //! Server endpoint.
567 //! \{
568 const std::uint16_t m_port;
569 const asio_ns::ip::tcp m_protocol;
571 //! \}
572
573 //! Server port listener and connection receiver routine.
574 //! \{
576 asio_ns::ip::tcp::acceptor m_acceptor;
577
578 //! A hook to be called just after a successful call to bind for acceptor.
579 /*!
580 * @since v.0.6.11
581 */
583 //! \}
584
585 //! Asio executor.
586 default_asio_executor m_executor;
588
589 //! Do separate an accept operation and connection instantiation.
591
592 //! Factory for creating connections.
594
596
597 /*!
598 * @brief Actual limiter of active parallel connections.
599 *
600 * @since v.0.6.12
601 */
603
604 /*!
605 * @brief Helper for extraction of an actual IP-address from an
606 * instance of address_variant.
607 *
608 * Returns an empty value if there is no address inside @a from.
609 *
610 * @since v.0.6.11
611 */
612 [[nodiscard]]
613 static std::optional< asio_ns::ip::address >
615 const restinio::details::address_variant_t & from )
616 {
617 std::optional< asio_ns::ip::address > result;
618
619 if( auto * str_v = std::get_if<std::string>( &from ) )
620 {
621 auto str_addr = *str_v;
622 if( str_addr == "localhost" )
623 str_addr = "127.0.0.1";
624 else if( str_addr == "ip6-localhost" )
625 str_addr = "::1";
626
627 result = asio_ns::ip::make_address( str_addr );
628 }
629 else if( auto * addr_v = std::get_if<asio_ns::ip::address>( &from ) )
630 {
631 result = *addr_v;
632 }
633
634 return result;
635 }
636};
637
638} /* namespace impl */
639
640} /* namespace restinio */
An interface of acceptor to be used by connection count limiters.
void open()
Start listen on port specified in ctor.
Definition acceptor.hpp:223
asio_ns::ip::tcp::acceptor m_acceptor
Definition acceptor.hpp:576
void close_impl()
Close opened acceptor.
Definition acceptor.hpp:542
void accept_connection_for_socket_with_index(std::size_t i)
Performs actual actions for accepting a new connection.
Definition acceptor.hpp:447
::restinio::connection_count_limits::impl::acceptor_callback_iface_t * self_as_acceptor_callback() noexcept
Helper for suppressing warnings of using this in initilizer list.
Definition acceptor.hpp:379
typename Traits::strand_t strand_t
Definition acceptor.hpp:184
static std::optional< asio_ns::ip::address > try_extract_actual_address_from_variant(const restinio::details::address_variant_t &from)
Helper for extraction of an actual IP-address from an instance of address_variant.
Definition acceptor.hpp:614
const asio_ns::ip::tcp m_protocol
Definition acceptor.hpp:569
connection_factory_shared_ptr_t m_connection_factory
Factory for creating connections.
Definition acceptor.hpp:593
connection_count_limiter_t m_connection_count_limiter
Actual limiter of active parallel connections.
Definition acceptor.hpp:602
impl::connection_factory_t< Traits > connection_factory_t
Definition acceptor.hpp:180
std::shared_ptr< connection_factory_t > connection_factory_shared_ptr_t
Definition acceptor.hpp:181
strand_t m_open_close_operations_executor
Definition acceptor.hpp:587
void do_accept_current_connection(stream_socket_t incoming_socket, endpoint_t remote_endpoint)
Definition acceptor.hpp:494
void accept_next(std::size_t i) noexcept
Set a callback for a new connection.
Definition acceptor.hpp:396
void schedule_next_accept_attempt(std::size_t index) noexcept override
Definition acceptor.hpp:361
typename connection_count_limit_types< Traits >::lifetime_monitor_t connection_lifetime_monitor_t
An alias for actual connection lifetime monitor type.
Definition acceptor.hpp:176
typename Traits::stream_socket_t stream_socket_t
Definition acceptor.hpp:185
void call_accept_now(std::size_t index) noexcept override
Definition acceptor.hpp:341
typename connection_count_limit_types< Traits >::limiter_t connection_count_limiter_t
An alias for actual connection count limiter type.
Definition acceptor.hpp:172
const std::uint16_t m_port
Server endpoint.
Definition acceptor.hpp:568
acceptor_details::ip_blocker_holder_t< typename Traits::ip_blocker_t > ip_blocker_base_t
Definition acceptor.hpp:168
void accept_current_connection(std::size_t i, const std::error_code &ec) noexcept
Accept current connection.
Definition acceptor.hpp:407
std::unique_ptr< acceptor_options_setter_t > m_acceptor_options_setter
Server port listener and connection receiver routine.
Definition acceptor.hpp:575
auto & get_open_close_operations_executor() noexcept
Get an executor for close operation.
Definition acceptor.hpp:327
void close()
Close listener if any.
Definition acceptor.hpp:308
acceptor_post_bind_hook_t m_acceptor_post_bind_hook
A hook to be called just after a successful call to bind for acceptor.
Definition acceptor.hpp:582
const restinio::details::address_variant_t m_address
Definition acceptor.hpp:570
typename Traits::logger_t logger_t
Definition acceptor.hpp:183
default_asio_executor m_executor
Asio executor.
Definition acceptor.hpp:586
const bool m_separate_accept_and_create_connect
Do separate an accept operation and connection instantiation.
Definition acceptor.hpp:590
auto & get_executor() noexcept
Get executor for acceptor.
Definition acceptor.hpp:334
socket_supplier_t< stream_socket_t > socket_holder_base_t
Definition acceptor.hpp:186
acceptor_t(Settings &settings, asio_ns::io_context &io_context, connection_factory_shared_ptr_t connection_factory, logger_t &logger)
Definition acceptor.hpp:189
auto concurrent_accept_sockets_count() const noexcept
The number of sockets that can be used for cuncurrent accept operations.
Definition acceptor.hpp:85
Socket & socket(std::size_t idx)
Get the reference to socket.
Definition acceptor.hpp:66
std::vector< Socket > m_sockets
A temporary socket for receiving new connections.
Definition acceptor.hpp:96
socket_supplier_t(Settings &settings, asio_ns::io_context &io_context)
Definition acceptor.hpp:45
Socket move_socket(std::size_t idx)
Extract the socket via move.
Definition acceptor.hpp:75
asio_ns::io_context & m_io_context
io_context for sockets to run on.
Definition acceptor.hpp:92
#define RESTINIO_FMT_FORMAT_STRING(s)
restinio::utils::tagged_scalar_t< std::size_t, max_active_accepts_tag > max_active_accepts_t
A kind of strict typedef for maximum count of active accepts.
restinio::utils::tagged_scalar_t< std::size_t, max_parallel_connections_tag > max_parallel_connections_t
A kind of strict typedef for maximum count of active connections.
asio_ns::ip::tcp::endpoint endpoint_t
An alias for endpoint type from Asio.
A kind of metafunction that deduces actual types related to connection count limiter in the dependecy...
A class for holding actual IP-blocker.
Definition acceptor.hpp:112
restinio::ip_blocker::inspection_result_t inspect_incoming(Socket &socket) const noexcept
Definition acceptor.hpp:123