RESTinio
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 
21 namespace restinio
22 {
23 
24 namespace 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 */
40 template < 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 )
51  {
53 
54  std::generate_n(
57  [this]{
58  return Socket{m_io_context};
59  } );
60 
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.
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  */
110 template< typename Ip_Blocker >
112 {
114 
115  template< typename Settings >
117  const Settings & settings )
119  {}
120 
121  template< typename Socket >
123  inspect_incoming( Socket & socket ) const noexcept
124  {
125  return m_ip_blocker->inspect(
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  */
140 template<>
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.
161 template < typename 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 {
168  using ip_blocker_base_t = acceptor_details::ip_blocker_holder_t<
169  typename Traits::ip_blocker_t >;
170 
175 
176  public:
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;
183  using socket_holder_base_t = socket_supplier_t< 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 )
196  , m_port{ settings.port() }
198  , m_address{ settings.address() }
206  , m_logger{ logger }
211  },
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 
234 
236  m_address );
237  if( 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.
253 
255  }
256 
257  m_acceptor.bind( ep );
258  // Since v.0.6.11 the post-bind hook should be invoked.
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).
264 
265  // Now we can switch acceptor to listen state.
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 {}: {}" ),
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  {
309  close_impl();
310  }
311  else
312  {
313  // v.0.7.0: suppress exceptions from logging.
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 &
325  {
327  }
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  {
341  this->socket( index ).lowest_layer(),
343  get_executor(),
344  [index, ctx = this->shared_from_this()]
345  ( const auto & ec ) noexcept
346  {
347  if( !ec )
348  {
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(
362  get_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  {
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  {
412  m_logger,
413  "accept_current_connection",
414  [this, i] {
416  } );
417  }
418  else
419  {
420  // Something goes wrong with connection.
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 =
452 
453  m_logger.trace( [&]{
454  return fmt::format(
456  "accept connection from {} on socket #{}" ),
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  {
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" ),
476  i );
477  } );
478  // incoming_socket will be closed automatically.
479  break;
480 
482  // Acception of the connection can be continued.
485  remote_endpoint );
486  break;
487  }
488  }
489 
490  void
492  stream_socket_t incoming_socket,
493  endpoint_t remote_endpoint )
494  {
501  },
502  logger = &m_logger]
503  () mutable noexcept
504  {
505  // NOTE: this code block shouldn't throw!
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.
515  std::move(sock),
516  std::move(ep),
518 
519  // Start waiting for request message.
520  conn->init();
521  } );
522  };
523 
525  {
526  asio_ns::post(
527  get_executor(),
529  }
530  else
531  {
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.
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.
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  //! \{
567  //! \}
568 
569  //! Server port listener and connection receiver routine.
570  //! \{
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.
584 
585  //! Do separate an accept operation and connection instantiation.
587 
588  //! Factory for creating connections.
590 
591  logger_t & m_logger;
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 >
612  {
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 
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 */
void accept_current_connection(std::size_t i, const std::error_code &ec) noexcept
Accept current connection.
Definition: acceptor.hpp:404
connection_factory_shared_ptr_t m_connection_factory
Factory for creating connections.
Definition: acceptor.hpp:589
void open()
Start listen on port specified in ctor.
Definition: acceptor.hpp:220
A class for holding actual IP-blocker.
Definition: acceptor.hpp:111
restinio::ip_blocker::inspection_result_t inspect_incoming(Socket &socket) const noexcept
Definition: acceptor.hpp:123
Socket move_socket(std::size_t idx)
Extract the socket via move.
Definition: acceptor.hpp:75
void close_impl()
Close opened acceptor.
Definition: acceptor.hpp:538
const asio_ns::ip::tcp m_protocol
Definition: acceptor.hpp:565
void call_accept_now(std::size_t index) noexcept override
Definition: acceptor.hpp:338
std::unique_ptr< acceptor_options_setter_t > m_acceptor_options_setter
Server port listener and connection receiver routine.
Definition: acceptor.hpp:571
restinio::ip_blocker::inspection_result_t inspect_incoming(Socket &) const noexcept
Definition: acceptor.hpp:148
std::enable_if< std::is_same< Parameter_Container, query_string_params_t >::value||std::is_same< Parameter_Container, router::route_params_t >::value, std::optional< Value_Type > >::type opt_value(const Parameter_Container &params, string_view_t key)
Gets the value of a parameter specified by key wrapped in std::optional<Value_Type> if parameter exis...
Definition: value_or.hpp:64
const bool m_separate_accept_and_create_connect
Do separate an accept operation and connection instantiation.
Definition: acceptor.hpp:586
Socket & socket(std::size_t idx)
Get the reference to socket.
Definition: acceptor.hpp:66
socket_supplier_t(Settings &settings, asio_ns::io_context &io_context)
Definition: acceptor.hpp:45
std::vector< Socket > m_sockets
A temporary socket for receiving new connections.
Definition: acceptor.hpp:96
void accept_connection_for_socket_with_index(std::size_t i)
Performs actual actions for accepting a new connection.
Definition: acceptor.hpp:444
const std::uint16_t m_port
Server endpoint.
Definition: acceptor.hpp:564
void schedule_next_accept_attempt(std::size_t index) noexcept override
Definition: acceptor.hpp:358
acceptor_t(Settings &settings, asio_ns::io_context &io_context, connection_factory_shared_ptr_t connection_factory, logger_t &logger)
Definition: acceptor.hpp:186
::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
void do_accept_current_connection(stream_socket_t incoming_socket, endpoint_t remote_endpoint)
Definition: acceptor.hpp:491
asio_ns::io_context & m_io_context
io_context for sockets to run on.
Definition: acceptor.hpp:92
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
void close()
Close listener if any.
Definition: acceptor.hpp:305
auto & get_open_close_operations_executor() noexcept
Get an executor for close operation.
Definition: acceptor.hpp:324
void accept_next(std::size_t i) noexcept
Set a callback for a new connection.
Definition: acceptor.hpp:393
strand_t m_open_close_operations_executor
Definition: acceptor.hpp:583
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
auto & get_executor() noexcept
Get executor for acceptor.
Definition: acceptor.hpp:331
auto concurrent_accept_sockets_count() const noexcept
The number of sockets that can be used for cuncurrent accept operations.
Definition: acceptor.hpp:85
const restinio::details::address_variant_t m_address
Definition: acceptor.hpp:566
asio_ns::ip::tcp::acceptor m_acceptor
Definition: acceptor.hpp:572
default_asio_executor m_executor
Asio executor.
Definition: acceptor.hpp:582
Context for accepting http connections.
Definition: acceptor.hpp:162
connection_count_limiter_t m_connection_count_limiter
Actual limiter of active parallel connections.
Definition: acceptor.hpp:598