WebSocket++  0.8.0-dev
C++ websocket client/server library
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Pages
endpoint.hpp
1 /*
2  * Copyright (c) 2015, Peter Thorson. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  * * Redistributions of source code must retain the above copyright
7  * notice, this list of conditions and the following disclaimer.
8  * * Redistributions in binary form must reproduce the above copyright
9  * notice, this list of conditions and the following disclaimer in the
10  * documentation and/or other materials provided with the distribution.
11  * * Neither the name of the WebSocket++ Project nor the
12  * names of its contributors may be used to endorse or promote products
13  * derived from this software without specific prior written permission.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
19  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  *
26  */
27 
28 #ifndef WEBSOCKETPP_TRANSPORT_ASIO_HPP
29 #define WEBSOCKETPP_TRANSPORT_ASIO_HPP
30 
31 #include <websocketpp/transport/base/endpoint.hpp>
32 #include <websocketpp/transport/asio/connection.hpp>
33 #include <websocketpp/transport/asio/security/none.hpp>
34 
35 #include <websocketpp/uri.hpp>
36 #include <websocketpp/logger/levels.hpp>
37 
38 #include <websocketpp/common/functional.hpp>
39 
40 #include <sstream>
41 #include <string>
42 
43 namespace websocketpp {
44 namespace transport {
45 namespace asio {
46 
47 /// Asio based endpoint transport component
48 /**
49  * transport::asio::endpoint implements an endpoint transport component using
50  * Asio.
51  */
52 template <typename config>
53 class endpoint : public config::socket_type {
54 public:
55  /// Type of this endpoint transport component
56  typedef endpoint<config> type;
57 
58  /// Type of the concurrency policy
59  typedef typename config::concurrency_type concurrency_type;
60  /// Type of the socket policy
61  typedef typename config::socket_type socket_type;
62  /// Type of the error logging policy
63  typedef typename config::elog_type elog_type;
64  /// Type of the access logging policy
65  typedef typename config::alog_type alog_type;
66 
67  /// Type of the socket connection component
69  /// Type of a shared pointer to the socket connection component
70  typedef typename socket_con_type::ptr socket_con_ptr;
71 
72  /// Type of the connection transport component associated with this
73  /// endpoint transport component
74  typedef asio::connection<config> transport_con_type;
75  /// Type of a shared pointer to the connection transport component
76  /// associated with this endpoint transport component
77  typedef typename transport_con_type::ptr transport_con_ptr;
78 
79  /// Type of a pointer to the ASIO io_service being used
81  /// Type of a shared pointer to the acceptor being used
82  typedef lib::shared_ptr<lib::asio::ip::tcp::acceptor> acceptor_ptr;
83  /// Type of a shared pointer to the resolver being used
84  typedef lib::shared_ptr<lib::asio::ip::tcp::resolver> resolver_ptr;
85  /// Type of timer handle
86  typedef lib::shared_ptr<lib::asio::steady_timer> timer_ptr;
87  /// Type of a shared pointer to an io_service work object
88  typedef lib::shared_ptr<lib::asio::io_service::work> work_ptr;
89 
90  // generate and manage our own io_service
91  explicit endpoint()
92  : m_io_service(NULL)
93  , m_external_io_service(false)
94  , m_listen_backlog(0)
95  , m_reuse_addr(false)
96  , m_state(UNINITIALIZED)
97  {
98  //std::cout << "transport::asio::endpoint constructor" << std::endl;
99  }
100 
101  ~endpoint() {
102  // clean up our io_service if we were initialized with an internal one.
103 
104  // Explicitly destroy local objects
105  m_acceptor.reset();
106  m_resolver.reset();
107  m_work.reset();
108  if (m_state != UNINITIALIZED && !m_external_io_service) {
109  delete m_io_service;
110  }
111  }
112 
113  /// transport::asio objects are moveable but not copyable or assignable.
114  /// The following code sets this situation up based on whether or not we
115  /// have C++11 support or not
116 #ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
117  endpoint(const endpoint & src) = delete;
118  endpoint& operator= (const endpoint & rhs) = delete;
119 #else
120 private:
121  endpoint(const endpoint & src);
122  endpoint & operator= (const endpoint & rhs);
123 public:
124 #endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
125 
126 #ifdef _WEBSOCKETPP_MOVE_SEMANTICS_
127  endpoint (endpoint && src)
128  : config::socket_type(std::move(src))
129  , m_tcp_pre_init_handler(src.m_tcp_pre_init_handler)
130  , m_tcp_post_init_handler(src.m_tcp_post_init_handler)
131  , m_io_service(src.m_io_service)
132  , m_external_io_service(src.m_external_io_service)
133  , m_acceptor(src.m_acceptor)
134  , m_listen_backlog(lib::asio::socket_base::max_connections)
135  , m_reuse_addr(src.m_reuse_addr)
136  , m_elog(src.m_elog)
137  , m_alog(src.m_alog)
138  , m_state(src.m_state)
139  {
140  src.m_io_service = NULL;
141  src.m_external_io_service = false;
142  src.m_acceptor = NULL;
143  src.m_state = UNINITIALIZED;
144  }
145 
146  /*endpoint & operator= (const endpoint && rhs) {
147  if (this != &rhs) {
148  m_io_service = rhs.m_io_service;
149  m_external_io_service = rhs.m_external_io_service;
150  m_acceptor = rhs.m_acceptor;
151  m_listen_backlog = rhs.m_listen_backlog;
152  m_reuse_addr = rhs.m_reuse_addr;
153  m_state = rhs.m_state;
154 
155  rhs.m_io_service = NULL;
156  rhs.m_external_io_service = false;
157  rhs.m_acceptor = NULL;
158  rhs.m_listen_backlog = lib::asio::socket_base::max_connections;
159  rhs.m_state = UNINITIALIZED;
160 
161  // TODO: this needs to be updated
162  }
163  return *this;
164  }*/
165 #endif // _WEBSOCKETPP_MOVE_SEMANTICS_
166 
167  /// Return whether or not the endpoint produces secure connections.
168  bool is_secure() const {
169  return socket_type::is_secure();
170  }
171 
172  /// initialize asio transport with external io_service (exception free)
173  /**
174  * Initialize the ASIO transport policy for this endpoint using the provided
175  * io_service object. asio_init must be called exactly once on any endpoint
176  * that uses transport::asio before it can be used.
177  *
178  * @param ptr A pointer to the io_service to use for asio events
179  * @param ec Set to indicate what error occurred, if any.
180  */
181  void init_asio(io_service_ptr ptr, lib::error_code & ec) {
182  if (m_state != UNINITIALIZED) {
183  m_elog->write(log::elevel::library,
184  "asio::init_asio called from the wrong state");
185  using websocketpp::error::make_error_code;
186  ec = make_error_code(websocketpp::error::invalid_state);
187  return;
188  }
189 
190  m_alog->write(log::alevel::devel,"asio::init_asio");
191 
192  m_io_service = ptr;
193  m_external_io_service = true;
194  m_acceptor = lib::make_shared<lib::asio::ip::tcp::acceptor>(
195  lib::ref(*m_io_service));
196 
197  m_state = READY;
198  ec = lib::error_code();
199  }
200 
201  /// initialize asio transport with external io_service
202  /**
203  * Initialize the ASIO transport policy for this endpoint using the provided
204  * io_service object. asio_init must be called exactly once on any endpoint
205  * that uses transport::asio before it can be used.
206  *
207  * @param ptr A pointer to the io_service to use for asio events
208  */
210  lib::error_code ec;
211  init_asio(ptr,ec);
212  if (ec) { throw exception(ec); }
213  }
214 
215  /// Initialize asio transport with internal io_service (exception free)
216  /**
217  * This method of initialization will allocate and use an internally managed
218  * io_service.
219  *
220  * @see init_asio(io_service_ptr ptr)
221  *
222  * @param ec Set to indicate what error occurred, if any.
223  */
224  void init_asio(lib::error_code & ec) {
225  // Use a smart pointer until the call is successful and ownership has
226  // successfully been taken. Use unique_ptr when available.
227  // TODO: remove the use of auto_ptr when C++98/03 support is no longer
228  // necessary.
229 #ifdef _WEBSOCKETPP_CPP11_MEMORY_
230  lib::unique_ptr<lib::asio::io_service> service(new lib::asio::io_service());
231 #else
232  lib::auto_ptr<lib::asio::io_service> service(new lib::asio::io_service());
233 #endif
234  init_asio(service.get(), ec);
235  if( !ec ) service.release(); // Call was successful, transfer ownership
236  m_external_io_service = false;
237  }
238 
239  /// Initialize asio transport with internal io_service
240  /**
241  * This method of initialization will allocate and use an internally managed
242  * io_service.
243  *
244  * @see init_asio(io_service_ptr ptr)
245  */
246  void init_asio() {
247  // Use a smart pointer until the call is successful and ownership has
248  // successfully been taken. Use unique_ptr when available.
249  // TODO: remove the use of auto_ptr when C++98/03 support is no longer
250  // necessary.
251 #ifdef _WEBSOCKETPP_CPP11_MEMORY_
252  lib::unique_ptr<lib::asio::io_service> service(new lib::asio::io_service());
253 #else
254  lib::auto_ptr<lib::asio::io_service> service(new lib::asio::io_service());
255 #endif
256  init_asio( service.get() );
257  // If control got this far without an exception, then ownership has successfully been taken
258  service.release();
259  m_external_io_service = false;
260  }
261 
262  /// Sets the tcp pre init handler
263  /**
264  * The tcp pre init handler is called after the raw tcp connection has been
265  * established but before any additional wrappers (proxy connects, TLS
266  * handshakes, etc) have been performed.
267  *
268  * @since 0.3.0
269  *
270  * @param h The handler to call on tcp pre init.
271  */
272  void set_tcp_pre_init_handler(tcp_init_handler h) {
273  m_tcp_pre_init_handler = h;
274  }
275 
276  /// Sets the tcp pre init handler (deprecated)
277  /**
278  * The tcp pre init handler is called after the raw tcp connection has been
279  * established but before any additional wrappers (proxy connects, TLS
280  * handshakes, etc) have been performed.
281  *
282  * @deprecated Use set_tcp_pre_init_handler instead
283  *
284  * @param h The handler to call on tcp pre init.
285  */
286  void set_tcp_init_handler(tcp_init_handler h) {
287  set_tcp_pre_init_handler(h);
288  }
289 
290  /// Sets the tcp post init handler
291  /**
292  * The tcp post init handler is called after the tcp connection has been
293  * established and all additional wrappers (proxy connects, TLS handshakes,
294  * etc have been performed. This is fired before any bytes are read or any
295  * WebSocket specific handshake logic has been performed.
296  *
297  * @since 0.3.0
298  *
299  * @param h The handler to call on tcp post init.
300  */
301  void set_tcp_post_init_handler(tcp_init_handler h) {
302  m_tcp_post_init_handler = h;
303  }
304 
305  /// Sets the maximum length of the queue of pending connections.
306  /**
307  * Sets the maximum length of the queue of pending connections. Increasing
308  * this will allow WebSocket++ to queue additional incoming connections.
309  * Setting it higher may prevent failed connections at high connection rates
310  * but may cause additional latency.
311  *
312  * For this value to take effect you may need to adjust operating system
313  * settings.
314  *
315  * New values affect future calls to listen only.
316  *
317  * A value of zero will use the operating system default. This is the
318  * default value.
319  *
320  * @since 0.3.0
321  *
322  * @param backlog The maximum length of the queue of pending connections
323  */
324  void set_listen_backlog(int backlog) {
325  m_listen_backlog = backlog;
326  }
327 
328  /// Sets whether to use the SO_REUSEADDR flag when opening listening sockets
329  /**
330  * Specifies whether or not to use the SO_REUSEADDR TCP socket option. What
331  * this flag does depends on your operating system.
332  *
333  * Please consult operating system documentation for more details. There
334  * may be security consequences to enabling this option.
335  *
336  * New values affect future calls to listen only so set this value prior to
337  * calling listen.
338  *
339  * The default is false.
340  *
341  * @since 0.3.0
342  *
343  * @param value Whether or not to use the SO_REUSEADDR option
344  */
345  void set_reuse_addr(bool value) {
346  m_reuse_addr = value;
347  }
348 
349  /// Retrieve a reference to the endpoint's io_service
350  /**
351  * The io_service may be an internal or external one. This may be used to
352  * call methods of the io_service that are not explicitly wrapped by the
353  * endpoint.
354  *
355  * This method is only valid after the endpoint has been initialized with
356  * `init_asio`. No error will be returned if it isn't.
357  *
358  * @return A reference to the endpoint's io_service
359  */
361  return *m_io_service;
362  }
363 
364  /// Get local TCP endpoint
365  /**
366  * Extracts the local endpoint from the acceptor. This represents the
367  * address that WebSocket++ is listening on.
368  *
369  * Sets a bad_descriptor error if the acceptor is not currently listening
370  * or otherwise unavailable.
371  *
372  * @since 0.7.0
373  *
374  * @param ec Set to indicate what error occurred, if any.
375  * @return The local endpoint
376  */
378  if (m_acceptor) {
379  return m_acceptor->local_endpoint(ec);
380  } else {
381  ec = lib::asio::error::make_error_code(lib::asio::error::bad_descriptor);
382  return lib::asio::ip::tcp::endpoint();
383  }
384  }
385 
386  /// Set up endpoint for listening manually (exception free)
387  /**
388  * Bind the internal acceptor using the specified settings. The endpoint
389  * must have been initialized by calling init_asio before listening.
390  *
391  * @param ep An endpoint to read settings from
392  * @param ec Set to indicate what error occurred, if any.
393  */
394  void listen(lib::asio::ip::tcp::endpoint const & ep, lib::error_code & ec)
395  {
396  if (m_state != READY) {
397  m_elog->write(log::elevel::library,
398  "asio::listen called from the wrong state");
399  using websocketpp::error::make_error_code;
400  ec = make_error_code(websocketpp::error::invalid_state);
401  return;
402  }
403 
404  m_alog->write(log::alevel::devel,"asio::listen");
405 
406  lib::asio::error_code bec;
407 
408  m_acceptor->open(ep.protocol(),bec);
409  if (!bec) {
410  m_acceptor->set_option(lib::asio::socket_base::reuse_address(m_reuse_addr),bec);
411  }
412  if (!bec) {
413  m_acceptor->bind(ep,bec);
414  }
415  if (!bec) {
416  m_acceptor->listen(m_listen_backlog,bec);
417  }
418  if (bec) {
419  if (m_acceptor->is_open()) {
420  m_acceptor->close();
421  }
422  log_err(log::elevel::info,"asio listen",bec);
423  ec = make_error_code(error::pass_through);
424  } else {
425  m_state = LISTENING;
426  ec = lib::error_code();
427  }
428  }
429 
430  /// Set up endpoint for listening manually
431  /**
432  * Bind the internal acceptor using the settings specified by the endpoint e
433  *
434  * @param ep An endpoint to read settings from
435  */
436  void listen(lib::asio::ip::tcp::endpoint const & ep) {
437  lib::error_code ec;
438  listen(ep,ec);
439  if (ec) { throw exception(ec); }
440  }
441 
442  /// Set up endpoint for listening with protocol and port (exception free)
443  /**
444  * Bind the internal acceptor using the given internet protocol and port.
445  * The endpoint must have been initialized by calling init_asio before
446  * listening.
447  *
448  * Common options include:
449  * - IPv6 with mapped IPv4 for dual stack hosts lib::asio::ip::tcp::v6()
450  * - IPv4 only: lib::asio::ip::tcp::v4()
451  *
452  * @param internet_protocol The internet protocol to use.
453  * @param port The port to listen on.
454  * @param ec Set to indicate what error occurred, if any.
455  */
456  template <typename InternetProtocol>
457  void listen(InternetProtocol const & internet_protocol, uint16_t port,
458  lib::error_code & ec)
459  {
460  lib::asio::ip::tcp::endpoint ep(internet_protocol, port);
461  listen(ep,ec);
462  }
463 
464  /// Set up endpoint for listening with protocol and port
465  /**
466  * Bind the internal acceptor using the given internet protocol and port.
467  * The endpoint must have been initialized by calling init_asio before
468  * listening.
469  *
470  * Common options include:
471  * - IPv6 with mapped IPv4 for dual stack hosts lib::asio::ip::tcp::v6()
472  * - IPv4 only: lib::asio::ip::tcp::v4()
473  *
474  * @param internet_protocol The internet protocol to use.
475  * @param port The port to listen on.
476  */
477  template <typename InternetProtocol>
478  void listen(InternetProtocol const & internet_protocol, uint16_t port)
479  {
480  lib::asio::ip::tcp::endpoint ep(internet_protocol, port);
481  listen(ep);
482  }
483 
484  /// Set up endpoint for listening on a port (exception free)
485  /**
486  * Bind the internal acceptor using the given port. The IPv6 protocol with
487  * mapped IPv4 for dual stack hosts will be used. If you need IPv4 only use
488  * the overload that allows specifying the protocol explicitly.
489  *
490  * The endpoint must have been initialized by calling init_asio before
491  * listening.
492  *
493  * @param port The port to listen on.
494  * @param ec Set to indicate what error occurred, if any.
495  */
496  void listen(uint16_t port, lib::error_code & ec) {
497  listen(lib::asio::ip::tcp::v6(), port, ec);
498  }
499 
500  /// Set up endpoint for listening on a port
501  /**
502  * Bind the internal acceptor using the given port. The IPv6 protocol with
503  * mapped IPv4 for dual stack hosts will be used. If you need IPv4 only use
504  * the overload that allows specifying the protocol explicitly.
505  *
506  * The endpoint must have been initialized by calling init_asio before
507  * listening.
508  *
509  * @param port The port to listen on.
510  * @param ec Set to indicate what error occurred, if any.
511  */
512  void listen(uint16_t port) {
513  listen(lib::asio::ip::tcp::v6(), port);
514  }
515 
516  /// Set up endpoint for listening on a host and service (exception free)
517  /**
518  * Bind the internal acceptor using the given host and service. More details
519  * about what host and service can be are available in the Asio
520  * documentation for ip::basic_resolver_query::basic_resolver_query's
521  * constructors.
522  *
523  * The endpoint must have been initialized by calling init_asio before
524  * listening.
525  *
526  * @param host A string identifying a location. May be a descriptive name or
527  * a numeric address string.
528  * @param service A string identifying the requested service. This may be a
529  * descriptive name or a numeric string corresponding to a port number.
530  * @param ec Set to indicate what error occurred, if any.
531  */
532  void listen(std::string const & host, std::string const & service,
533  lib::error_code & ec)
534  {
535  using lib::asio::ip::tcp;
536  tcp::resolver r(*m_io_service);
537  tcp::resolver::query query(host, service);
538  tcp::resolver::iterator endpoint_iterator = r.resolve(query);
539  tcp::resolver::iterator end;
540  if (endpoint_iterator == end) {
541  m_elog->write(log::elevel::library,
542  "asio::listen could not resolve the supplied host or service");
543  ec = make_error_code(error::invalid_host_service);
544  return;
545  }
546  listen(*endpoint_iterator,ec);
547  }
548 
549  /// Set up endpoint for listening on a host and service
550  /**
551  * Bind the internal acceptor using the given host and service. More details
552  * about what host and service can be are available in the Asio
553  * documentation for ip::basic_resolver_query::basic_resolver_query's
554  * constructors.
555  *
556  * The endpoint must have been initialized by calling init_asio before
557  * listening.
558  *
559  * @param host A string identifying a location. May be a descriptive name or
560  * a numeric address string.
561  * @param service A string identifying the requested service. This may be a
562  * descriptive name or a numeric string corresponding to a port number.
563  * @param ec Set to indicate what error occurred, if any.
564  */
565  void listen(std::string const & host, std::string const & service)
566  {
567  lib::error_code ec;
568  listen(host,service,ec);
569  if (ec) { throw exception(ec); }
570  }
571 
572  /// Stop listening (exception free)
573  /**
574  * Stop listening and accepting new connections. This will not end any
575  * existing connections.
576  *
577  * @since 0.3.0-alpha4
578  * @param ec A status code indicating an error, if any.
579  */
580  void stop_listening(lib::error_code & ec) {
581  if (m_state != LISTENING) {
582  m_elog->write(log::elevel::library,
583  "asio::listen called from the wrong state");
584  using websocketpp::error::make_error_code;
585  ec = make_error_code(websocketpp::error::invalid_state);
586  return;
587  }
588 
589  m_acceptor->close();
590  m_state = READY;
591  ec = lib::error_code();
592  }
593 
594  /// Stop listening
595  /**
596  * Stop listening and accepting new connections. This will not end any
597  * existing connections.
598  *
599  * @since 0.3.0-alpha4
600  */
601  void stop_listening() {
602  lib::error_code ec;
603  stop_listening(ec);
604  if (ec) { throw exception(ec); }
605  }
606 
607  /// Check if the endpoint is listening
608  /**
609  * @return Whether or not the endpoint is listening.
610  */
611  bool is_listening() const {
612  return (m_state == LISTENING);
613  }
614 
615  /// wraps the run method of the internal io_service object
616  std::size_t run() {
617  return m_io_service->run();
618  }
619 
620  /// wraps the run_one method of the internal io_service object
621  /**
622  * @since 0.3.0-alpha4
623  */
624  std::size_t run_one() {
625  return m_io_service->run_one();
626  }
627 
628  /// wraps the stop method of the internal io_service object
629  void stop() {
630  m_io_service->stop();
631  }
632 
633  /// wraps the poll method of the internal io_service object
634  std::size_t poll() {
635  return m_io_service->poll();
636  }
637 
638  /// wraps the poll_one method of the internal io_service object
639  std::size_t poll_one() {
640  return m_io_service->poll_one();
641  }
642 
643  /// wraps the reset method of the internal io_service object
644  void reset() {
645  m_io_service->reset();
646  }
647 
648  /// wraps the stopped method of the internal io_service object
649  bool stopped() const {
650  return m_io_service->stopped();
651  }
652 
653  /// Marks the endpoint as perpetual, stopping it from exiting when empty
654  /**
655  * Marks the endpoint as perpetual. Perpetual endpoints will not
656  * automatically exit when they run out of connections to process. To stop
657  * a perpetual endpoint call `end_perpetual`.
658  *
659  * An endpoint may be marked perpetual at any time by any thread. It must be
660  * called either before the endpoint has run out of work or before it was
661  * started
662  *
663  * @since 0.3.0
664  */
666  m_work = lib::make_shared<lib::asio::io_service::work>(
667  lib::ref(*m_io_service)
668  );
669  }
670 
671  /// Clears the endpoint's perpetual flag, allowing it to exit when empty
672  /**
673  * Clears the endpoint's perpetual flag. This will cause the endpoint's run
674  * method to exit normally when it runs out of connections. If there are
675  * currently active connections it will not end until they are complete.
676  *
677  * @since 0.3.0
678  */
679  void stop_perpetual() {
680  m_work.reset();
681  }
682 
683  /// Call back a function after a period of time.
684  /**
685  * Sets a timer that calls back a function after the specified period of
686  * milliseconds. Returns a handle that can be used to cancel the timer.
687  * A cancelled timer will return the error code error::operation_aborted
688  * A timer that expired will return no error.
689  *
690  * @param duration Length of time to wait in milliseconds
691  * @param callback The function to call back when the timer has expired
692  * @return A handle that can be used to cancel the timer if it is no longer
693  * needed.
694  */
696  timer_ptr new_timer = lib::make_shared<lib::asio::steady_timer>(
697  *m_io_service,
698  lib::asio::milliseconds(duration)
699  );
700 
701  new_timer->async_wait(
702  lib::bind(
703  &type::handle_timer,
704  this,
705  new_timer,
706  callback,
707  lib::placeholders::_1
708  )
709  );
710 
711  return new_timer;
712  }
713 
714  /// Timer handler
715  /**
716  * The timer pointer is included to ensure the timer isn't destroyed until
717  * after it has expired.
718  *
719  * @param t Pointer to the timer in question
720  * @param callback The function to call back
721  * @param ec A status code indicating an error, if any.
722  */
723  void handle_timer(timer_ptr, timer_handler callback,
724  lib::asio::error_code const & ec)
725  {
726  if (ec) {
727  if (ec == lib::asio::error::operation_aborted) {
728  callback(make_error_code(transport::error::operation_aborted));
729  } else {
730  m_elog->write(log::elevel::info,
731  "asio handle_timer error: "+ec.message());
732  log_err(log::elevel::info,"asio handle_timer",ec);
733  callback(make_error_code(error::pass_through));
734  }
735  } else {
736  callback(lib::error_code());
737  }
738  }
739 
740  /// Accept the next connection attempt and assign it to con (exception free)
741  /**
742  * @param tcon The connection to accept into.
743  * @param callback The function to call when the operation is complete.
744  * @param ec A status code indicating an error, if any.
745  */
746  void async_accept(transport_con_ptr tcon, accept_handler callback,
747  lib::error_code & ec)
748  {
749  if (m_state != LISTENING || !m_acceptor) {
750  using websocketpp::error::make_error_code;
751  ec = make_error_code(websocketpp::error::async_accept_not_listening);
752  return;
753  }
754 
755  m_alog->write(log::alevel::devel, "asio::async_accept");
756 
757  if (config::enable_multithreading) {
758  m_acceptor->async_accept(
759  tcon->get_raw_socket(),
760  tcon->get_strand()->wrap(lib::bind(
761  &type::handle_accept,
762  this,
763  callback,
764  lib::placeholders::_1
765  ))
766  );
767  } else {
768  m_acceptor->async_accept(
769  tcon->get_raw_socket(),
770  lib::bind(
771  &type::handle_accept,
772  this,
773  callback,
774  lib::placeholders::_1
775  )
776  );
777  }
778  }
779 
780  /// Accept the next connection attempt and assign it to con.
781  /**
782  * @param tcon The connection to accept into.
783  * @param callback The function to call when the operation is complete.
784  */
785  void async_accept(transport_con_ptr tcon, accept_handler callback) {
786  lib::error_code ec;
787  async_accept(tcon,callback,ec);
788  if (ec) { throw exception(ec); }
789  }
790 protected:
791  /// Initialize logging
792  /**
793  * The loggers are located in the main endpoint class. As such, the
794  * transport doesn't have direct access to them. This method is called
795  * by the endpoint constructor to allow shared logging from the transport
796  * component. These are raw pointers to member variables of the endpoint.
797  * In particular, they cannot be used in the transport constructor as they
798  * haven't been constructed yet, and cannot be used in the transport
799  * destructor as they will have been destroyed by then.
800  */
801  void init_logging(const lib::shared_ptr<alog_type>& a, const lib::shared_ptr<elog_type>& e) {
802  m_alog = a;
803  m_elog = e;
804  }
805 
806  void handle_accept(accept_handler callback, lib::asio::error_code const &
807  asio_ec)
808  {
809  lib::error_code ret_ec;
810 
811  m_alog->write(log::alevel::devel, "asio::handle_accept");
812 
813  if (asio_ec) {
814  if (asio_ec == lib::asio::errc::operation_canceled) {
815  ret_ec = make_error_code(websocketpp::error::operation_canceled);
816  } else {
817  log_err(log::elevel::info,"asio handle_accept",asio_ec);
818  ret_ec = make_error_code(error::pass_through);
819  }
820  }
821 
822  callback(ret_ec);
823  }
824 
825  /// Initiate a new connection
826  // TODO: there have to be some more failure conditions here
827  void async_connect(transport_con_ptr tcon, uri_ptr u, connect_handler cb) {
828  using namespace lib::asio::ip;
829 
830  // Create a resolver
831  if (!m_resolver) {
832  m_resolver = lib::make_shared<lib::asio::ip::tcp::resolver>(
833  lib::ref(*m_io_service));
834  }
835 
836  tcon->set_uri(u);
837 
838  std::string proxy = tcon->get_proxy();
839  std::string host;
840  std::string port;
841 
842  if (proxy.empty()) {
843  host = u->get_host();
844  port = u->get_port_str();
845  } else {
846  lib::error_code ec;
847 
848  uri_ptr pu = lib::make_shared<uri>(proxy);
849 
850  if (!pu->get_valid()) {
851  cb(make_error_code(error::proxy_invalid));
852  return;
853  }
854 
855  ec = tcon->proxy_init(u->get_authority());
856  if (ec) {
857  cb(ec);
858  return;
859  }
860 
861  host = pu->get_host();
862  port = pu->get_port_str();
863  }
864 
865  tcp::resolver::query query(host,port);
866 
867  if (m_alog->static_test(log::alevel::devel)) {
868  m_alog->write(log::alevel::devel,
869  "starting async DNS resolve for "+host+":"+port);
870  }
871 
872  timer_ptr dns_timer;
873 
874  dns_timer = tcon->set_timer(
875  config::timeout_dns_resolve,
876  lib::bind(
877  &type::handle_resolve_timeout,
878  this,
879  dns_timer,
880  cb,
881  lib::placeholders::_1
882  )
883  );
884 
885  if (config::enable_multithreading) {
886  m_resolver->async_resolve(
887  query,
888  tcon->get_strand()->wrap(lib::bind(
889  &type::handle_resolve,
890  this,
891  tcon,
892  dns_timer,
893  cb,
894  lib::placeholders::_1,
895  lib::placeholders::_2
896  ))
897  );
898  } else {
899  m_resolver->async_resolve(
900  query,
901  lib::bind(
902  &type::handle_resolve,
903  this,
904  tcon,
905  dns_timer,
906  cb,
907  lib::placeholders::_1,
908  lib::placeholders::_2
909  )
910  );
911  }
912  }
913 
914  /// DNS resolution timeout handler
915  /**
916  * The timer pointer is included to ensure the timer isn't destroyed until
917  * after it has expired.
918  *
919  * @param dns_timer Pointer to the timer in question
920  * @param callback The function to call back
921  * @param ec A status code indicating an error, if any.
922  */
923  void handle_resolve_timeout(timer_ptr, connect_handler callback,
924  lib::error_code const & ec)
925  {
926  lib::error_code ret_ec;
927 
928  if (ec) {
929  if (ec == transport::error::operation_aborted) {
930  m_alog->write(log::alevel::devel,
931  "asio handle_resolve_timeout timer cancelled");
932  return;
933  }
934 
935  log_err(log::elevel::devel,"asio handle_resolve_timeout",ec);
936  ret_ec = ec;
937  } else {
938  ret_ec = make_error_code(transport::error::timeout);
939  }
940 
941  m_alog->write(log::alevel::devel,"DNS resolution timed out");
942  m_resolver->cancel();
943  callback(ret_ec);
944  }
945 
946  void handle_resolve(transport_con_ptr tcon, timer_ptr dns_timer,
947  connect_handler callback, lib::asio::error_code const & ec,
948  lib::asio::ip::tcp::resolver::iterator iterator)
949  {
950  if (ec == lib::asio::error::operation_aborted ||
951  lib::asio::is_neg(dns_timer->expires_from_now()))
952  {
953  m_alog->write(log::alevel::devel,"async_resolve cancelled");
954  return;
955  }
956 
957  dns_timer->cancel();
958 
959  if (ec) {
960  log_err(log::elevel::info,"asio async_resolve",ec);
961  callback(make_error_code(error::pass_through));
962  return;
963  }
964 
965  if (m_alog->static_test(log::alevel::devel)) {
966  std::stringstream s;
967  s << "Async DNS resolve successful. Results: ";
968 
969  lib::asio::ip::tcp::resolver::iterator it, end;
970  for (it = iterator; it != end; ++it) {
971  s << (*it).endpoint() << " ";
972  }
973 
974  m_alog->write(log::alevel::devel,s.str());
975  }
976 
977  m_alog->write(log::alevel::devel,"Starting async connect");
978 
979  timer_ptr con_timer;
980 
981  con_timer = tcon->set_timer(
982  config::timeout_connect,
983  lib::bind(
984  &type::handle_connect_timeout,
985  this,
986  tcon,
987  con_timer,
988  callback,
989  lib::placeholders::_1
990  )
991  );
992 
993  if (config::enable_multithreading) {
994  lib::asio::async_connect(
995  tcon->get_raw_socket(),
996  iterator,
997  tcon->get_strand()->wrap(lib::bind(
998  &type::handle_connect,
999  this,
1000  tcon,
1001  con_timer,
1002  callback,
1003  lib::placeholders::_1
1004  ))
1005  );
1006  } else {
1007  lib::asio::async_connect(
1008  tcon->get_raw_socket(),
1009  iterator,
1010  lib::bind(
1011  &type::handle_connect,
1012  this,
1013  tcon,
1014  con_timer,
1015  callback,
1016  lib::placeholders::_1
1017  )
1018  );
1019  }
1020  }
1021 
1022  /// Asio connect timeout handler
1023  /**
1024  * The timer pointer is included to ensure the timer isn't destroyed until
1025  * after it has expired.
1026  *
1027  * @param tcon Pointer to the transport connection that is being connected
1028  * @param con_timer Pointer to the timer in question
1029  * @param callback The function to call back
1030  * @param ec A status code indicating an error, if any.
1031  */
1032  void handle_connect_timeout(transport_con_ptr tcon, timer_ptr,
1033  connect_handler callback, lib::error_code const & ec)
1034  {
1035  lib::error_code ret_ec;
1036 
1037  if (ec) {
1038  if (ec == transport::error::operation_aborted) {
1039  m_alog->write(log::alevel::devel,
1040  "asio handle_connect_timeout timer cancelled");
1041  return;
1042  }
1043 
1044  log_err(log::elevel::devel,"asio handle_connect_timeout",ec);
1045  ret_ec = ec;
1046  } else {
1047  ret_ec = make_error_code(transport::error::timeout);
1048  }
1049 
1050  m_alog->write(log::alevel::devel,"TCP connect timed out");
1051  tcon->cancel_socket_checked();
1052  callback(ret_ec);
1053  }
1054 
1055  void handle_connect(transport_con_ptr tcon, timer_ptr con_timer,
1056  connect_handler callback, lib::asio::error_code const & ec)
1057  {
1058  if (ec == lib::asio::error::operation_aborted ||
1059  lib::asio::is_neg(con_timer->expires_from_now()))
1060  {
1061  m_alog->write(log::alevel::devel,"async_connect cancelled");
1062  return;
1063  }
1064 
1065  con_timer->cancel();
1066 
1067  if (ec) {
1068  log_err(log::elevel::info,"asio async_connect",ec);
1069  callback(make_error_code(error::pass_through));
1070  return;
1071  }
1072 
1073  if (m_alog->static_test(log::alevel::devel)) {
1074  m_alog->write(log::alevel::devel,
1075  "Async connect to "+tcon->get_remote_endpoint()+" successful.");
1076  }
1077 
1078  callback(lib::error_code());
1079  }
1080 
1081  /// Initialize a connection
1082  /**
1083  * init is called by an endpoint once for each newly created connection.
1084  * It's purpose is to give the transport policy the chance to perform any
1085  * transport specific initialization that couldn't be done via the default
1086  * constructor.
1087  *
1088  * @param tcon A pointer to the transport portion of the connection.
1089  *
1090  * @return A status code indicating the success or failure of the operation
1091  */
1093  m_alog->write(log::alevel::devel, "transport::asio::init");
1094 
1095  // Initialize the connection socket component
1096  socket_type::init(lib::static_pointer_cast<socket_con_type,
1097  transport_con_type>(tcon));
1098 
1099  lib::error_code ec;
1100 
1101  ec = tcon->init_asio(m_io_service);
1102  if (ec) {return ec;}
1103 
1104  tcon->set_tcp_pre_init_handler(m_tcp_pre_init_handler);
1105  tcon->set_tcp_post_init_handler(m_tcp_post_init_handler);
1106 
1107  return lib::error_code();
1108  }
1109 private:
1110  /// Convenience method for logging the code and message for an error_code
1111  template <typename error_type>
1112  void log_err(log::level l, char const * msg, error_type const & ec) {
1113  std::stringstream s;
1114  s << msg << " error: " << ec << " (" << ec.message() << ")";
1115  m_elog->write(l,s.str());
1116  }
1117 
1118  enum state {
1119  UNINITIALIZED = 0,
1120  READY = 1,
1121  LISTENING = 2
1122  };
1123 
1124  // Handlers
1125  tcp_init_handler m_tcp_pre_init_handler;
1126  tcp_init_handler m_tcp_post_init_handler;
1127 
1128  // Network Resources
1129  io_service_ptr m_io_service;
1130  bool m_external_io_service;
1131  acceptor_ptr m_acceptor;
1132  resolver_ptr m_resolver;
1133  work_ptr m_work;
1134 
1135  // Network constants
1136  int m_listen_backlog;
1137  bool m_reuse_addr;
1138 
1139  lib::shared_ptr<elog_type> m_elog;
1140  lib::shared_ptr<alog_type> m_alog;
1141 
1142  // Transport state
1143  state m_state;
1144 };
1145 
1146 } // namespace asio
1147 } // namespace transport
1148 } // namespace websocketpp
1149 
1150 #endif // WEBSOCKETPP_TRANSPORT_ASIO_HPP
void stop_listening(lib::error_code &ec)
Stop listening (exception free)
Definition: endpoint.hpp:580
void async_connect(transport_con_ptr tcon, uri_ptr u, connect_handler cb)
Initiate a new connection.
Definition: endpoint.hpp:827
void handle_connect_timeout(transport_con_ptr tcon, timer_ptr, connect_handler callback, lib::error_code const &ec)
Asio connect timeout handler.
Definition: endpoint.hpp:1032
void init_logging(const lib::shared_ptr< alog_type > &a, const lib::shared_ptr< elog_type > &e)
Initialize logging.
Definition: endpoint.hpp:801
config::alog_type alog_type
Type of the access logging policy.
Definition: endpoint.hpp:65
void listen(InternetProtocol const &internet_protocol, uint16_t port)
Set up endpoint for listening with protocol and port.
Definition: endpoint.hpp:478
void set_listen_backlog(int backlog)
Sets the maximum length of the queue of pending connections.
Definition: endpoint.hpp:324
void listen(uint16_t port)
Set up endpoint for listening on a port.
Definition: endpoint.hpp:512
void stop_listening()
Stop listening.
Definition: endpoint.hpp:601
void init_asio(lib::error_code &ec)
Initialize asio transport with internal io_service (exception free)
Definition: endpoint.hpp:224
void set_tcp_init_handler(tcp_init_handler h)
Sets the tcp pre init handler (deprecated)
Definition: endpoint.hpp:286
void start_perpetual()
Marks the endpoint as perpetual, stopping it from exiting when empty.
Definition: endpoint.hpp:665
bool is_secure() const
Return whether or not the endpoint produces secure connections.
Definition: endpoint.hpp:168
void init_asio()
Initialize asio transport with internal io_service.
Definition: endpoint.hpp:246
void listen(std::string const &host, std::string const &service, lib::error_code &ec)
Set up endpoint for listening on a host and service (exception free)
Definition: endpoint.hpp:532
void init_asio(io_service_ptr ptr, lib::error_code &ec)
initialize asio transport with external io_service (exception free)
Definition: endpoint.hpp:181
void set_reuse_addr(bool value)
Sets whether to use the SO_REUSEADDR flag when opening listening sockets.
Definition: endpoint.hpp:345
std::size_t run_one()
wraps the run_one method of the internal io_service object
Definition: endpoint.hpp:624
void listen(uint16_t port, lib::error_code &ec)
Set up endpoint for listening on a port (exception free)
Definition: endpoint.hpp:496
bool is_listening() const
Check if the endpoint is listening.
Definition: endpoint.hpp:611
void handle_timer(timer_ptr, timer_handler callback, lib::asio::error_code const &ec)
Timer handler.
Definition: endpoint.hpp:723
config::elog_type elog_type
Type of the error logging policy.
Definition: endpoint.hpp:63
void listen(lib::asio::ip::tcp::endpoint const &ep)
Set up endpoint for listening manually.
Definition: endpoint.hpp:436
lib::asio::io_service & get_io_service()
Retrieve a reference to the endpoint's io_service.
Definition: endpoint.hpp:360
lib::error_code init(transport_con_ptr tcon)
Initialize a connection.
Definition: endpoint.hpp:1092
void reset()
wraps the reset method of the internal io_service object
Definition: endpoint.hpp:644
socket_con_type::ptr socket_con_ptr
Type of a shared pointer to the socket connection component.
Definition: endpoint.hpp:70
void listen(InternetProtocol const &internet_protocol, uint16_t port, lib::error_code &ec)
Set up endpoint for listening with protocol and port (exception free)
Definition: endpoint.hpp:457
void stop_perpetual()
Clears the endpoint's perpetual flag, allowing it to exit when empty.
Definition: endpoint.hpp:679
std::size_t poll()
wraps the poll method of the internal io_service object
Definition: endpoint.hpp:634
void set_tcp_post_init_handler(tcp_init_handler h)
Sets the tcp post init handler.
Definition: endpoint.hpp:301
void stop()
wraps the stop method of the internal io_service object
Definition: endpoint.hpp:629
void handle_accept(connection_ptr con, lib::error_code const &ec)
Handler callback for start_accept.
timer_ptr set_timer(long duration, timer_handler callback)
Call back a function after a period of time.
Definition: endpoint.hpp:695
std::size_t run()
wraps the run method of the internal io_service object
Definition: endpoint.hpp:616
void async_accept(transport_con_ptr tcon, accept_handler callback, lib::error_code &ec)
Accept the next connection attempt and assign it to con (exception free)
Definition: endpoint.hpp:746
void listen(std::string const &host, std::string const &service)
Set up endpoint for listening on a host and service.
Definition: endpoint.hpp:565
void set_tcp_pre_init_handler(tcp_init_handler h)
Sets the tcp pre init handler.
Definition: endpoint.hpp:272
config::socket_type socket_type
Type of the socket policy.
Definition: endpoint.hpp:61
bool stopped() const
wraps the stopped method of the internal io_service object
Definition: endpoint.hpp:649
config::concurrency_type concurrency_type
Type of the concurrency policy.
Definition: endpoint.hpp:59
lib::asio::ip::tcp::endpoint get_local_endpoint(lib::asio::error_code &ec)
Get local TCP endpoint.
Definition: endpoint.hpp:377
void async_accept(transport_con_ptr tcon, accept_handler callback)
Accept the next connection attempt and assign it to con.
Definition: endpoint.hpp:785
lib::asio::io_service * io_service_ptr
Type of a pointer to the ASIO io_service being used.
Definition: endpoint.hpp:80
void init_asio(io_service_ptr ptr)
initialize asio transport with external io_service
Definition: endpoint.hpp:209
std::size_t poll_one()
wraps the poll_one method of the internal io_service object
Definition: endpoint.hpp:639
void handle_resolve_timeout(timer_ptr, connect_handler callback, lib::error_code const &ec)
DNS resolution timeout handler.
Definition: endpoint.hpp:923
endpoint< config > type
Type of this endpoint transport component.
Definition: endpoint.hpp:56
socket_type::socket_con_type socket_con_type
Type of the socket connection component.
Definition: endpoint.hpp:68
lib::function< void(lib::error_code const &)> connect_handler
The type and signature of the callback passed to the connect method.
Definition: endpoint.hpp:72