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
172 typename connection_count_limit_types< Traits >::limiter_t;
175
176 public:
179 std::shared_ptr< connection_factory_t >;
180 using logger_t = typename Traits::logger_t;
181 using strand_t = typename Traits::strand_t;
182 using stream_socket_t = typename Traits::stream_socket_t;
184
185 template < typename Settings >
187 Settings & settings,
188 //! ASIO io_context to run on.
189 asio_ns::io_context & io_context,
190 //! Connection factory.
191 connection_factory_shared_ptr_t connection_factory,
192 //! Logger.
193 logger_t & logger )
194 : socket_holder_base_t{ settings, io_context }
195 , ip_blocker_base_t{ settings }
196 , m_port{ settings.port() }
197 , m_protocol{ settings.protocol() }
200 , m_acceptor{ io_context }
202 , m_executor{ io_context.get_executor() }
203 , m_open_close_operations_executor{ io_context.get_executor() }
204 , m_separate_accept_and_create_connect{ settings.separate_accept_and_create_connect() }
205 , m_connection_factory{ std::move( connection_factory ) }
206 , m_logger{ logger }
210 settings.max_parallel_connections()
211 },
213 settings.concurrent_accepts_count()
214 }
215 }
216 {}
217
218 //! Start listen on port specified in ctor.
219 void
221 {
222 if( m_acceptor.is_open() )
223 {
224 const auto ep = m_acceptor.local_endpoint();
225 m_logger.warn( [&]{
226 return fmt::format(
227 RESTINIO_FMT_FORMAT_STRING( "server already started on {}" ),
228 fmtlib_tools::streamed( ep ) );
229 } );
230 return;
231 }
232
233 asio_ns::ip::tcp::endpoint ep{ m_protocol, m_port };
234
235 const auto actual_address = try_extract_actual_address_from_variant(
236 m_address );
237 if( actual_address )
238 ep.address( *actual_address );
239
240 try
241 {
242 m_logger.trace( [&]{
243 return fmt::format(
244 RESTINIO_FMT_FORMAT_STRING( "starting server on {}" ),
245 fmtlib_tools::streamed( ep ) );
246 } );
247
248 m_acceptor.open( ep.protocol() );
249
250 {
251 // Set acceptor options.
252 acceptor_options_t options{ m_acceptor };
253
254 (*m_acceptor_options_setter)( options );
255 }
256
257 m_acceptor.bind( ep );
258 // Since v.0.6.11 the post-bind hook should be invoked.
259 m_acceptor_post_bind_hook( m_acceptor );
260 // server end-point can be replaced if port is allocated by
261 // the operating system (e.g. zero is specified as port number
262 // by a user).
263 ep = m_acceptor.local_endpoint();
264
265 // Now we can switch acceptor to listen state.
266 m_acceptor.listen( asio_ns::socket_base::max_connections );
267
268 // Call accept connections routine.
269 for( std::size_t i = 0; i< this->concurrent_accept_sockets_count(); ++i )
270 {
271 m_logger.info( [&]{
272 return fmt::format(
273 RESTINIO_FMT_FORMAT_STRING( "init accept #{}" ), i );
274 } );
275
276 accept_next( i );
277 }
278
279 m_logger.info( [&]{
280 return fmt::format(
281 RESTINIO_FMT_FORMAT_STRING( "server started on {}" ),
282 fmtlib_tools::streamed( ep ) );
283 } );
284 }
285 catch( const std::exception & ex )
286 {
287 // Acceptor should be closes in the case of an error.
288 if( m_acceptor.is_open() )
289 m_acceptor.close();
290
291 m_logger.error( [&]() -> auto {
292 return fmt::format(
294 "failed to start server on {}: {}" ),
295 fmtlib_tools::streamed( ep ),
296 ex.what() );
297 } );
298
299 throw;
300 }
301 }
302
303 //! Close listener if any.
304 void
306 {
307 if( m_acceptor.is_open() )
308 {
310 }
311 else
312 {
313 // v.0.7.0: suppress exceptions from logging.
314 restinio::utils::log_trace_noexcept( m_logger,
315 [&]{
316 return fmt::format(
317 RESTINIO_FMT_FORMAT_STRING( "server already closed" ) );
318 } );
319 }
320 }
321
322 //! Get an executor for close operation.
323 auto &
328
329 private:
330 //! Get executor for acceptor.
331 auto & get_executor() noexcept { return m_executor; }
332
333 // Begin of implementation of acceptor_callback_iface_t.
334 /*!
335 * @since v.0.6.12
336 */
337 void
338 call_accept_now( std::size_t index ) noexcept override
339 {
340 m_acceptor.async_accept(
341 this->socket( index ).lowest_layer(),
342 asio_ns::bind_executor(
344 [index, ctx = this->shared_from_this()]
345 ( const auto & ec ) noexcept
346 {
347 if( !ec )
348 {
349 ctx->accept_current_connection( index, ec );
350 }
351 } ) );
352 }
353
354 /*!
355 * @since v.0.6.12
356 */
357 void
358 schedule_next_accept_attempt( std::size_t index ) noexcept override
359 {
360 asio_ns::post(
361 asio_ns::bind_executor(
363 [index, ctx = this->shared_from_this()]() noexcept
364 {
365 ctx->accept_next( index );
366 } ) );
367 }
368
369 /*!
370 * @brief Helper for suppressing warnings of using `this` in
371 * initilizer list.
372 *
373 * @since v.0.6.12
374 */
377 {
378 return this;
379 }
380 // End of implementation of acceptor_callback_iface_t.
381
382 //! Set a callback for a new connection.
383 /*!
384 * @note
385 * This method is marked as noexcept in v.0.6.0.
386 * It seems that nothing prevents exceptions from a call to
387 * async_accept. But we just don't know what to do in that case.
388 * So at the moment the call to `std::terminate` because an
389 * exception is raised inside `noexcept` method seems to be an
390 * appropriate solution.
391 */
392 void
393 accept_next( std::size_t i ) noexcept
394 {
395 m_connection_count_limiter.accept_next( i );
396 }
397
398 //! Accept current connection.
399 /*!
400 * @note
401 * This method is marked as noexcept in v.0.6.0.
402 */
403 void
405 //! socket index in the pool of sockets.
406 std::size_t i,
407 const std::error_code & ec ) noexcept
408 {
409 if( !ec )
410 {
411 restinio::utils::suppress_exceptions(
412 m_logger,
413 "accept_current_connection",
414 [this, i] {
416 } );
417 }
418 else
419 {
420 // Something goes wrong with connection.
421 restinio::utils::log_error_noexcept( m_logger,
422 [&]{
423 return fmt::format(
425 "failed to accept connection on socket #{}: {}" ),
426 i,
427 ec.message() );
428 } );
429 }
430
431 // Continue accepting.
432 accept_next( i );
433 }
434
435 /*!
436 * @brief Performs actual actions for accepting a new connection.
437 *
438 * @note
439 * This method can throw. An we expect that it can throw sometimes.
440 *
441 * @since v.0.6.0
442 */
443 void
445 //! socket index in the pool of sockets.
446 std::size_t i )
447 {
448 auto incoming_socket = this->move_socket( i );
449
450 auto remote_endpoint =
451 incoming_socket.lowest_layer().remote_endpoint();
452
453 m_logger.trace( [&]{
454 return fmt::format(
456 "accept connection from {} on socket #{}" ),
457 fmtlib_tools::streamed( remote_endpoint ),
458 i );
459 } );
460
461 // Since v.0.5.1 the incoming connection must be
462 // inspected by IP-blocker.
463 const auto inspection_result = this->inspect_incoming(
464 incoming_socket );
465
466 switch( inspection_result )
467 {
468 case restinio::ip_blocker::inspection_result_t::deny:
469 // New connection can be used. It is disabled by IP-blocker.
470 m_logger.warn( [&]{
471 return fmt::format(
473 "accepted connection from {} on socket #{} denied by"
474 " IP-blocker" ),
475 fmtlib_tools::streamed( remote_endpoint ),
476 i );
477 } );
478 // incoming_socket will be closed automatically.
479 break;
480
481 case restinio::ip_blocker::inspection_result_t::allow:
482 // Acception of the connection can be continued.
484 std::move(incoming_socket),
485 remote_endpoint );
486 break;
487 }
488 }
489
490 void
492 stream_socket_t incoming_socket,
493 endpoint_t remote_endpoint )
494 {
495 auto create_and_init_connection =
496 [sock = std::move(incoming_socket),
497 factory = m_connection_factory,
498 ep = std::move(remote_endpoint),
499 lifetime_monitor = connection_lifetime_monitor_t{
500 &m_connection_count_limiter
501 },
502 logger = &m_logger]
503 () mutable noexcept
504 {
505 // NOTE: this code block shouldn't throw!
506 restinio::utils::suppress_exceptions(
507 *logger,
508 "do_accept_current_connection.create_and_init_connection",
509 [&] {
510 // Create new connection handler.
511 // NOTE: since v.0.6.3 this method throws in
512 // the case of an error. Because of that there is
513 // no need to check the value returned.
514 auto conn = factory->create_new_connection(
515 std::move(sock),
516 std::move(ep),
517 std::move(lifetime_monitor) );
518
519 // Start waiting for request message.
520 conn->init();
521 } );
522 };
523
525 {
526 asio_ns::post(
528 std::move( create_and_init_connection ) );
529 }
530 else
531 {
532 create_and_init_connection();
533 }
534 }
535
536 //! Close opened acceptor.
537 void
539 {
540 const auto ep = m_acceptor.local_endpoint();
541
542 // An exception in logger should not prevent a call of close()
543 // for m_acceptor.
544 restinio::utils::log_trace_noexcept( m_logger,
545 [&]{
546 return fmt::format(
547 RESTINIO_FMT_FORMAT_STRING( "closing server on {}" ),
548 fmtlib_tools::streamed( ep ) );
549 } );
550
551 m_acceptor.close();
552
553 // v.0.7.0: Suppress exceptions from this logging too.
554 restinio::utils::log_info_noexcept( m_logger,
555 [&]{
556 return fmt::format(
557 RESTINIO_FMT_FORMAT_STRING( "server closed on {}" ),
558 fmtlib_tools::streamed( ep ) );
559 } );
560 }
561
562 //! Server endpoint.
563 //! \{
564 const std::uint16_t m_port;
565 const asio_ns::ip::tcp m_protocol;
567 //! \}
568
569 //! Server port listener and connection receiver routine.
570 //! \{
572 asio_ns::ip::tcp::acceptor m_acceptor;
573
574 //! A hook to be called just after a successful call to bind for acceptor.
575 /*!
576 * @since v.0.6.11
577 */
579 //! \}
580
581 //! Asio executor.
582 default_asio_executor m_executor;
584
585 //! Do separate an accept operation and connection instantiation.
587
588 //! Factory for creating connections.
590
592
593 /*!
594 * @brief Actual limiter of active parallel connections.
595 *
596 * @since v.0.6.12
597 */
599
600 /*!
601 * @brief Helper for extraction of an actual IP-address from an
602 * instance of address_variant.
603 *
604 * Returns an empty value if there is no address inside @a from.
605 *
606 * @since v.0.6.11
607 */
608 [[nodiscard]]
609 static std::optional< asio_ns::ip::address >
611 const restinio::details::address_variant_t & from )
612 {
613 std::optional< asio_ns::ip::address > result;
614
615 if( auto * str_v = std::get_if<std::string>( &from ) )
616 {
617 auto str_addr = *str_v;
618 if( str_addr == "localhost" )
619 str_addr = "127.0.0.1";
620 else if( str_addr == "ip6-localhost" )
621 str_addr = "::1";
622
623 result = asio_ns::ip::address::from_string( str_addr );
624 }
625 else if( auto * addr_v = std::get_if<asio_ns::ip::address>( &from ) )
626 {
627 result = *addr_v;
628 }
629
630 return result;
631 }
632};
633
634} /* namespace impl */
635
636} /* 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:220
asio_ns::ip::tcp::acceptor m_acceptor
Definition acceptor.hpp:572
void close_impl()
Close opened acceptor.
Definition acceptor.hpp:538
void accept_connection_for_socket_with_index(std::size_t i)
Performs actual actions for accepting a new connection.
Definition acceptor.hpp:444
::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:376
typename Traits::strand_t strand_t
Definition acceptor.hpp:181
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:610
const asio_ns::ip::tcp m_protocol
Definition acceptor.hpp:565
connection_factory_shared_ptr_t m_connection_factory
Factory for creating connections.
Definition acceptor.hpp:589
connection_count_limiter_t m_connection_count_limiter
Actual limiter of active parallel connections.
Definition acceptor.hpp:598
impl::connection_factory_t< Traits > connection_factory_t
Definition acceptor.hpp:177
strand_t m_open_close_operations_executor
Definition acceptor.hpp:583
void do_accept_current_connection(stream_socket_t incoming_socket, endpoint_t remote_endpoint)
Definition acceptor.hpp:491
void accept_next(std::size_t i) noexcept
Set a callback for a new connection.
Definition acceptor.hpp:393
void schedule_next_accept_attempt(std::size_t index) noexcept override
Definition acceptor.hpp:358
typename Traits::stream_socket_t stream_socket_t
Definition acceptor.hpp:182
void call_accept_now(std::size_t index) noexcept override
Definition acceptor.hpp:338
typename connection_count_limit_types< Traits >::limiter_t connection_count_limiter_t
Definition acceptor.hpp:171
const std::uint16_t m_port
Server endpoint.
Definition acceptor.hpp:564
std::shared_ptr< connection_factory_t > connection_factory_shared_ptr_t
Definition acceptor.hpp:178
void accept_current_connection(std::size_t i, const std::error_code &ec) noexcept
Accept current connection.
Definition acceptor.hpp:404
std::unique_ptr< acceptor_options_setter_t > m_acceptor_options_setter
Server port listener and connection receiver routine.
Definition acceptor.hpp:571
auto & get_open_close_operations_executor() noexcept
Get an executor for close operation.
Definition acceptor.hpp:324
void close()
Close listener if any.
Definition acceptor.hpp:305
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:578
const restinio::details::address_variant_t m_address
Definition acceptor.hpp:566
acceptor_details::ip_blocker_holder_t< typename Traits::ip_blocker_t > ip_blocker_base_t
Definition acceptor.hpp:168
typename Traits::logger_t logger_t
Definition acceptor.hpp:180
typename connection_count_limit_types< Traits >::lifetime_monitor_t connection_lifetime_monitor_t
Definition acceptor.hpp:173
default_asio_executor m_executor
Asio executor.
Definition acceptor.hpp:582
const bool m_separate_accept_and_create_connect
Do separate an accept operation and connection instantiation.
Definition acceptor.hpp:586
auto & get_executor() noexcept
Get executor for acceptor.
Definition acceptor.hpp:331
socket_supplier_t< stream_socket_t > socket_holder_base_t
Definition acceptor.hpp:183
acceptor_t(Settings &settings, asio_ns::io_context &io_context, connection_factory_shared_ptr_t connection_factory, logger_t &logger)
Definition acceptor.hpp:186
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_parallel_connections_tag > max_parallel_connections_t
A kind of strict typedef for maximum count of active connections.
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.
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