WebSocket++  0.7.0
C++ websocket client/server library
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
68  typedef typename socket_type::socket_con_type socket_con_type;
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
80  typedef lib::asio::io_service * io_service_ptr;
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  */
209  void init_asio(io_service_ptr ptr) {
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. Please consult operating
332  * system documentation for more details.
333  *
334  * New values affect future calls to listen only.
335  *
336  * The default is false.
337  *
338  * @since 0.3.0
339  *
340  * @param value Whether or not to use the SO_REUSEADDR option
341  */
342  void set_reuse_addr(bool value) {
343  m_reuse_addr = value;
344  }
345 
346  /// Retrieve a reference to the endpoint's io_service
347  /**
348  * The io_service may be an internal or external one. This may be used to
349  * call methods of the io_service that are not explicitly wrapped by the
350  * endpoint.
351  *
352  * This method is only valid after the endpoint has been initialized with
353  * `init_asio`. No error will be returned if it isn't.
354  *
355  * @return A reference to the endpoint's io_service
356  */
357  lib::asio::io_service & get_io_service() {
358  return *m_io_service;
359  }
360 
361  /// Get local TCP endpoint
362  /**
363  * Extracts the local endpoint from the acceptor. This represents the
364  * address that WebSocket++ is listening on.
365  *
366  * Sets a bad_descriptor error if the acceptor is not currently listening
367  * or otherwise unavailable.
368  *
369  * @since 0.7.0
370  *
371  * @param ec Set to indicate what error occurred, if any.
372  * @return The local endpoint
373  */
374  lib::asio::ip::tcp::endpoint get_local_endpoint(lib::asio::error_code & ec) {
375  if (m_acceptor) {
376  return m_acceptor->local_endpoint(ec);
377  } else {
378  ec = lib::asio::error::make_error_code(lib::asio::error::bad_descriptor);
379  return lib::asio::ip::tcp::endpoint();
380  }
381  }
382 
383  /// Set up endpoint for listening manually (exception free)
384  /**
385  * Bind the internal acceptor using the specified settings. The endpoint
386  * must have been initialized by calling init_asio before listening.
387  *
388  * @param ep An endpoint to read settings from
389  * @param ec Set to indicate what error occurred, if any.
390  */
391  void listen(lib::asio::ip::tcp::endpoint const & ep, lib::error_code & ec)
392  {
393  if (m_state != READY) {
394  m_elog->write(log::elevel::library,
395  "asio::listen called from the wrong state");
396  using websocketpp::error::make_error_code;
397  ec = make_error_code(websocketpp::error::invalid_state);
398  return;
399  }
400 
401  m_alog->write(log::alevel::devel,"asio::listen");
402 
403  lib::asio::error_code bec;
404 
405  m_acceptor->open(ep.protocol(),bec);
406  if (!bec) {
407  m_acceptor->set_option(lib::asio::socket_base::reuse_address(m_reuse_addr),bec);
408  }
409  if (!bec) {
410  m_acceptor->bind(ep,bec);
411  }
412  if (!bec) {
413  m_acceptor->listen(m_listen_backlog,bec);
414  }
415  if (bec) {
416  if (m_acceptor->is_open()) {
417  m_acceptor->close();
418  }
419  log_err(log::elevel::info,"asio listen",bec);
420  ec = make_error_code(error::pass_through);
421  } else {
422  m_state = LISTENING;
423  ec = lib::error_code();
424  }
425  }
426 
427  /// Set up endpoint for listening manually
428  /**
429  * Bind the internal acceptor using the settings specified by the endpoint e
430  *
431  * @param ep An endpoint to read settings from
432  */
433  void listen(lib::asio::ip::tcp::endpoint const & ep) {
434  lib::error_code ec;
435  listen(ep,ec);
436  if (ec) { throw exception(ec); }
437  }
438 
439  /// Set up endpoint for listening with protocol and port (exception free)
440  /**
441  * Bind the internal acceptor using the given internet protocol and port.
442  * The endpoint must have been initialized by calling init_asio before
443  * listening.
444  *
445  * Common options include:
446  * - IPv6 with mapped IPv4 for dual stack hosts lib::asio::ip::tcp::v6()
447  * - IPv4 only: lib::asio::ip::tcp::v4()
448  *
449  * @param internet_protocol The internet protocol to use.
450  * @param port The port to listen on.
451  * @param ec Set to indicate what error occurred, if any.
452  */
453  template <typename InternetProtocol>
454  void listen(InternetProtocol const & internet_protocol, uint16_t port,
455  lib::error_code & ec)
456  {
457  lib::asio::ip::tcp::endpoint ep(internet_protocol, port);
458  listen(ep,ec);
459  }
460 
461  /// Set up endpoint for listening with protocol and port
462  /**
463  * Bind the internal acceptor using the given internet protocol and port.
464  * The endpoint must have been initialized by calling init_asio before
465  * listening.
466  *
467  * Common options include:
468  * - IPv6 with mapped IPv4 for dual stack hosts lib::asio::ip::tcp::v6()
469  * - IPv4 only: lib::asio::ip::tcp::v4()
470  *
471  * @param internet_protocol The internet protocol to use.
472  * @param port The port to listen on.
473  */
474  template <typename InternetProtocol>
475  void listen(InternetProtocol const & internet_protocol, uint16_t port)
476  {
477  lib::asio::ip::tcp::endpoint ep(internet_protocol, port);
478  listen(ep);
479  }
480 
481  /// Set up endpoint for listening on a port (exception free)
482  /**
483  * Bind the internal acceptor using the given port. The IPv6 protocol with
484  * mapped IPv4 for dual stack hosts will be used. If you need IPv4 only use
485  * the overload that allows specifying the protocol explicitly.
486  *
487  * The endpoint must have been initialized by calling init_asio before
488  * listening.
489  *
490  * @param port The port to listen on.
491  * @param ec Set to indicate what error occurred, if any.
492  */
493  void listen(uint16_t port, lib::error_code & ec) {
494  listen(lib::asio::ip::tcp::v6(), port, ec);
495  }
496 
497  /// Set up endpoint for listening on a port
498  /**
499  * Bind the internal acceptor using the given port. The IPv6 protocol with
500  * mapped IPv4 for dual stack hosts will be used. If you need IPv4 only use
501  * the overload that allows specifying the protocol explicitly.
502  *
503  * The endpoint must have been initialized by calling init_asio before
504  * listening.
505  *
506  * @param port The port to listen on.
507  * @param ec Set to indicate what error occurred, if any.
508  */
509  void listen(uint16_t port) {
510  listen(lib::asio::ip::tcp::v6(), port);
511  }
512 
513  /// Set up endpoint for listening on a host and service (exception free)
514  /**
515  * Bind the internal acceptor using the given host and service. More details
516  * about what host and service can be are available in the Asio
517  * documentation for ip::basic_resolver_query::basic_resolver_query's
518  * constructors.
519  *
520  * The endpoint must have been initialized by calling init_asio before
521  * listening.
522  *
523  * @param host A string identifying a location. May be a descriptive name or
524  * a numeric address string.
525  * @param service A string identifying the requested service. This may be a
526  * descriptive name or a numeric string corresponding to a port number.
527  * @param ec Set to indicate what error occurred, if any.
528  */
529  void listen(std::string const & host, std::string const & service,
530  lib::error_code & ec)
531  {
532  using lib::asio::ip::tcp;
533  tcp::resolver r(*m_io_service);
534  tcp::resolver::query query(host, service);
535  tcp::resolver::iterator endpoint_iterator = r.resolve(query);
536  tcp::resolver::iterator end;
537  if (endpoint_iterator == end) {
538  m_elog->write(log::elevel::library,
539  "asio::listen could not resolve the supplied host or service");
540  ec = make_error_code(error::invalid_host_service);
541  return;
542  }
543  listen(*endpoint_iterator,ec);
544  }
545 
546  /// Set up endpoint for listening on a host and service
547  /**
548  * Bind the internal acceptor using the given host and service. More details
549  * about what host and service can be are available in the Asio
550  * documentation for ip::basic_resolver_query::basic_resolver_query's
551  * constructors.
552  *
553  * The endpoint must have been initialized by calling init_asio before
554  * listening.
555  *
556  * @param host A string identifying a location. May be a descriptive name or
557  * a numeric address string.
558  * @param service A string identifying the requested service. This may be a
559  * descriptive name or a numeric string corresponding to a port number.
560  * @param ec Set to indicate what error occurred, if any.
561  */
562  void listen(std::string const & host, std::string const & service)
563  {
564  lib::error_code ec;
565  listen(host,service,ec);
566  if (ec) { throw exception(ec); }
567  }
568 
569  /// Stop listening (exception free)
570  /**
571  * Stop listening and accepting new connections. This will not end any
572  * existing connections.
573  *
574  * @since 0.3.0-alpha4
575  * @param ec A status code indicating an error, if any.
576  */
577  void stop_listening(lib::error_code & ec) {
578  if (m_state != LISTENING) {
579  m_elog->write(log::elevel::library,
580  "asio::listen called from the wrong state");
581  using websocketpp::error::make_error_code;
582  ec = make_error_code(websocketpp::error::invalid_state);
583  return;
584  }
585 
586  m_acceptor->close();
587  m_state = READY;
588  ec = lib::error_code();
589  }
590 
591  /// Stop listening
592  /**
593  * Stop listening and accepting new connections. This will not end any
594  * existing connections.
595  *
596  * @since 0.3.0-alpha4
597  */
598  void stop_listening() {
599  lib::error_code ec;
600  stop_listening(ec);
601  if (ec) { throw exception(ec); }
602  }
603 
604  /// Check if the endpoint is listening
605  /**
606  * @return Whether or not the endpoint is listening.
607  */
608  bool is_listening() const {
609  return (m_state == LISTENING);
610  }
611 
612  /// wraps the run method of the internal io_service object
613  std::size_t run() {
614  return m_io_service->run();
615  }
616 
617  /// wraps the run_one method of the internal io_service object
618  /**
619  * @since 0.3.0-alpha4
620  */
621  std::size_t run_one() {
622  return m_io_service->run_one();
623  }
624 
625  /// wraps the stop method of the internal io_service object
626  void stop() {
627  m_io_service->stop();
628  }
629 
630  /// wraps the poll method of the internal io_service object
631  std::size_t poll() {
632  return m_io_service->poll();
633  }
634 
635  /// wraps the poll_one method of the internal io_service object
636  std::size_t poll_one() {
637  return m_io_service->poll_one();
638  }
639 
640  /// wraps the reset method of the internal io_service object
641  void reset() {
642  m_io_service->reset();
643  }
644 
645  /// wraps the stopped method of the internal io_service object
646  bool stopped() const {
647  return m_io_service->stopped();
648  }
649 
650  /// Marks the endpoint as perpetual, stopping it from exiting when empty
651  /**
652  * Marks the endpoint as perpetual. Perpetual endpoints will not
653  * automatically exit when they run out of connections to process. To stop
654  * a perpetual endpoint call `end_perpetual`.
655  *
656  * An endpoint may be marked perpetual at any time by any thread. It must be
657  * called either before the endpoint has run out of work or before it was
658  * started
659  *
660  * @since 0.3.0
661  */
662  void start_perpetual() {
663  m_work = lib::make_shared<lib::asio::io_service::work>(
664  lib::ref(*m_io_service)
665  );
666  }
667 
668  /// Clears the endpoint's perpetual flag, allowing it to exit when empty
669  /**
670  * Clears the endpoint's perpetual flag. This will cause the endpoint's run
671  * method to exit normally when it runs out of connections. If there are
672  * currently active connections it will not end until they are complete.
673  *
674  * @since 0.3.0
675  */
676  void stop_perpetual() {
677  m_work.reset();
678  }
679 
680  /// Call back a function after a period of time.
681  /**
682  * Sets a timer that calls back a function after the specified period of
683  * milliseconds. Returns a handle that can be used to cancel the timer.
684  * A cancelled timer will return the error code error::operation_aborted
685  * A timer that expired will return no error.
686  *
687  * @param duration Length of time to wait in milliseconds
688  * @param callback The function to call back when the timer has expired
689  * @return A handle that can be used to cancel the timer if it is no longer
690  * needed.
691  */
692  timer_ptr set_timer(long duration, timer_handler callback) {
693  timer_ptr new_timer = lib::make_shared<lib::asio::steady_timer>(
694  *m_io_service,
695  lib::asio::milliseconds(duration)
696  );
697 
698  new_timer->async_wait(
699  lib::bind(
700  &type::handle_timer,
701  this,
702  new_timer,
703  callback,
704  lib::placeholders::_1
705  )
706  );
707 
708  return new_timer;
709  }
710 
711  /// Timer handler
712  /**
713  * The timer pointer is included to ensure the timer isn't destroyed until
714  * after it has expired.
715  *
716  * @param t Pointer to the timer in question
717  * @param callback The function to call back
718  * @param ec A status code indicating an error, if any.
719  */
720  void handle_timer(timer_ptr, timer_handler callback,
721  lib::asio::error_code const & ec)
722  {
723  if (ec) {
724  if (ec == lib::asio::error::operation_aborted) {
725  callback(make_error_code(transport::error::operation_aborted));
726  } else {
727  m_elog->write(log::elevel::info,
728  "asio handle_timer error: "+ec.message());
729  log_err(log::elevel::info,"asio handle_timer",ec);
730  callback(make_error_code(error::pass_through));
731  }
732  } else {
733  callback(lib::error_code());
734  }
735  }
736 
737  /// Accept the next connection attempt and assign it to con (exception free)
738  /**
739  * @param tcon The connection to accept into.
740  * @param callback The function to call when the operation is complete.
741  * @param ec A status code indicating an error, if any.
742  */
743  void async_accept(transport_con_ptr tcon, accept_handler callback,
744  lib::error_code & ec)
745  {
746  if (m_state != LISTENING) {
747  using websocketpp::error::make_error_code;
748  ec = make_error_code(websocketpp::error::async_accept_not_listening);
749  return;
750  }
751 
752  m_alog->write(log::alevel::devel, "asio::async_accept");
753 
754  if (config::enable_multithreading) {
755  m_acceptor->async_accept(
756  tcon->get_raw_socket(),
757  tcon->get_strand()->wrap(lib::bind(
758  &type::handle_accept,
759  this,
760  callback,
761  lib::placeholders::_1
762  ))
763  );
764  } else {
765  m_acceptor->async_accept(
766  tcon->get_raw_socket(),
767  lib::bind(
768  &type::handle_accept,
769  this,
770  callback,
771  lib::placeholders::_1
772  )
773  );
774  }
775  }
776 
777  /// Accept the next connection attempt and assign it to con.
778  /**
779  * @param tcon The connection to accept into.
780  * @param callback The function to call when the operation is complete.
781  */
782  void async_accept(transport_con_ptr tcon, accept_handler callback) {
783  lib::error_code ec;
784  async_accept(tcon,callback,ec);
785  if (ec) { throw exception(ec); }
786  }
787 protected:
788  /// Initialize logging
789  /**
790  * The loggers are located in the main endpoint class. As such, the
791  * transport doesn't have direct access to them. This method is called
792  * by the endpoint constructor to allow shared logging from the transport
793  * component. These are raw pointers to member variables of the endpoint.
794  * In particular, they cannot be used in the transport constructor as they
795  * haven't been constructed yet, and cannot be used in the transport
796  * destructor as they will have been destroyed by then.
797  */
798  void init_logging(alog_type* a, elog_type* e) {
799  m_alog = a;
800  m_elog = e;
801  }
802 
803  void handle_accept(accept_handler callback, lib::asio::error_code const &
804  asio_ec)
805  {
806  lib::error_code ret_ec;
807 
808  m_alog->write(log::alevel::devel, "asio::handle_accept");
809 
810  if (asio_ec) {
811  if (asio_ec == lib::asio::errc::operation_canceled) {
812  ret_ec = make_error_code(websocketpp::error::operation_canceled);
813  } else {
814  log_err(log::elevel::info,"asio handle_accept",asio_ec);
815  ret_ec = make_error_code(error::pass_through);
816  }
817  }
818 
819  callback(ret_ec);
820  }
821 
822  /// Initiate a new connection
823  // TODO: there have to be some more failure conditions here
824  void async_connect(transport_con_ptr tcon, uri_ptr u, connect_handler cb) {
825  using namespace lib::asio::ip;
826 
827  // Create a resolver
828  if (!m_resolver) {
829  m_resolver = lib::make_shared<lib::asio::ip::tcp::resolver>(
830  lib::ref(*m_io_service));
831  }
832 
833  tcon->set_uri(u);
834 
835  std::string proxy = tcon->get_proxy();
836  std::string host;
837  std::string port;
838 
839  if (proxy.empty()) {
840  host = u->get_host();
841  port = u->get_port_str();
842  } else {
843  lib::error_code ec;
844 
845  uri_ptr pu = lib::make_shared<uri>(proxy);
846 
847  if (!pu->get_valid()) {
848  cb(make_error_code(error::proxy_invalid));
849  return;
850  }
851 
852  ec = tcon->proxy_init(u->get_authority());
853  if (ec) {
854  cb(ec);
855  return;
856  }
857 
858  host = pu->get_host();
859  port = pu->get_port_str();
860  }
861 
862  tcp::resolver::query query(host,port);
863 
864  if (m_alog->static_test(log::alevel::devel)) {
865  m_alog->write(log::alevel::devel,
866  "starting async DNS resolve for "+host+":"+port);
867  }
868 
869  timer_ptr dns_timer;
870 
871  dns_timer = tcon->set_timer(
872  config::timeout_dns_resolve,
873  lib::bind(
874  &type::handle_resolve_timeout,
875  this,
876  dns_timer,
877  cb,
878  lib::placeholders::_1
879  )
880  );
881 
882  if (config::enable_multithreading) {
883  m_resolver->async_resolve(
884  query,
885  tcon->get_strand()->wrap(lib::bind(
886  &type::handle_resolve,
887  this,
888  tcon,
889  dns_timer,
890  cb,
891  lib::placeholders::_1,
892  lib::placeholders::_2
893  ))
894  );
895  } else {
896  m_resolver->async_resolve(
897  query,
898  lib::bind(
899  &type::handle_resolve,
900  this,
901  tcon,
902  dns_timer,
903  cb,
904  lib::placeholders::_1,
905  lib::placeholders::_2
906  )
907  );
908  }
909  }
910 
911  /// DNS resolution timeout handler
912  /**
913  * The timer pointer is included to ensure the timer isn't destroyed until
914  * after it has expired.
915  *
916  * @param dns_timer Pointer to the timer in question
917  * @param callback The function to call back
918  * @param ec A status code indicating an error, if any.
919  */
920  void handle_resolve_timeout(timer_ptr, connect_handler callback,
921  lib::error_code const & ec)
922  {
923  lib::error_code ret_ec;
924 
925  if (ec) {
926  if (ec == transport::error::operation_aborted) {
927  m_alog->write(log::alevel::devel,
928  "asio handle_resolve_timeout timer cancelled");
929  return;
930  }
931 
932  log_err(log::elevel::devel,"asio handle_resolve_timeout",ec);
933  ret_ec = ec;
934  } else {
935  ret_ec = make_error_code(transport::error::timeout);
936  }
937 
938  m_alog->write(log::alevel::devel,"DNS resolution timed out");
939  m_resolver->cancel();
940  callback(ret_ec);
941  }
942 
943  void handle_resolve(transport_con_ptr tcon, timer_ptr dns_timer,
944  connect_handler callback, lib::asio::error_code const & ec,
945  lib::asio::ip::tcp::resolver::iterator iterator)
946  {
947  if (ec == lib::asio::error::operation_aborted ||
948  lib::asio::is_neg(dns_timer->expires_from_now()))
949  {
950  m_alog->write(log::alevel::devel,"async_resolve cancelled");
951  return;
952  }
953 
954  dns_timer->cancel();
955 
956  if (ec) {
957  log_err(log::elevel::info,"asio async_resolve",ec);
958  callback(make_error_code(error::pass_through));
959  return;
960  }
961 
962  if (m_alog->static_test(log::alevel::devel)) {
963  std::stringstream s;
964  s << "Async DNS resolve successful. Results: ";
965 
966  lib::asio::ip::tcp::resolver::iterator it, end;
967  for (it = iterator; it != end; ++it) {
968  s << (*it).endpoint() << " ";
969  }
970 
971  m_alog->write(log::alevel::devel,s.str());
972  }
973 
974  m_alog->write(log::alevel::devel,"Starting async connect");
975 
976  timer_ptr con_timer;
977 
978  con_timer = tcon->set_timer(
979  config::timeout_connect,
980  lib::bind(
981  &type::handle_connect_timeout,
982  this,
983  tcon,
984  con_timer,
985  callback,
986  lib::placeholders::_1
987  )
988  );
989 
990  if (config::enable_multithreading) {
991  lib::asio::async_connect(
992  tcon->get_raw_socket(),
993  iterator,
994  tcon->get_strand()->wrap(lib::bind(
995  &type::handle_connect,
996  this,
997  tcon,
998  con_timer,
999  callback,
1000  lib::placeholders::_1
1001  ))
1002  );
1003  } else {
1004  lib::asio::async_connect(
1005  tcon->get_raw_socket(),
1006  iterator,
1007  lib::bind(
1008  &type::handle_connect,
1009  this,
1010  tcon,
1011  con_timer,
1012  callback,
1013  lib::placeholders::_1
1014  )
1015  );
1016  }
1017  }
1018 
1019  /// Asio connect timeout handler
1020  /**
1021  * The timer pointer is included to ensure the timer isn't destroyed until
1022  * after it has expired.
1023  *
1024  * @param tcon Pointer to the transport connection that is being connected
1025  * @param con_timer Pointer to the timer in question
1026  * @param callback The function to call back
1027  * @param ec A status code indicating an error, if any.
1028  */
1029  void handle_connect_timeout(transport_con_ptr tcon, timer_ptr,
1030  connect_handler callback, lib::error_code const & ec)
1031  {
1032  lib::error_code ret_ec;
1033 
1034  if (ec) {
1035  if (ec == transport::error::operation_aborted) {
1036  m_alog->write(log::alevel::devel,
1037  "asio handle_connect_timeout timer cancelled");
1038  return;
1039  }
1040 
1041  log_err(log::elevel::devel,"asio handle_connect_timeout",ec);
1042  ret_ec = ec;
1043  } else {
1044  ret_ec = make_error_code(transport::error::timeout);
1045  }
1046 
1047  m_alog->write(log::alevel::devel,"TCP connect timed out");
1048  tcon->cancel_socket_checked();
1049  callback(ret_ec);
1050  }
1051 
1052  void handle_connect(transport_con_ptr tcon, timer_ptr con_timer,
1053  connect_handler callback, lib::asio::error_code const & ec)
1054  {
1055  if (ec == lib::asio::error::operation_aborted ||
1056  lib::asio::is_neg(con_timer->expires_from_now()))
1057  {
1058  m_alog->write(log::alevel::devel,"async_connect cancelled");
1059  return;
1060  }
1061 
1062  con_timer->cancel();
1063 
1064  if (ec) {
1065  log_err(log::elevel::info,"asio async_connect",ec);
1066  callback(make_error_code(error::pass_through));
1067  return;
1068  }
1069 
1070  if (m_alog->static_test(log::alevel::devel)) {
1071  m_alog->write(log::alevel::devel,
1072  "Async connect to "+tcon->get_remote_endpoint()+" successful.");
1073  }
1074 
1075  callback(lib::error_code());
1076  }
1077 
1078  /// Initialize a connection
1079  /**
1080  * init is called by an endpoint once for each newly created connection.
1081  * It's purpose is to give the transport policy the chance to perform any
1082  * transport specific initialization that couldn't be done via the default
1083  * constructor.
1084  *
1085  * @param tcon A pointer to the transport portion of the connection.
1086  *
1087  * @return A status code indicating the success or failure of the operation
1088  */
1089  lib::error_code init(transport_con_ptr tcon) {
1090  m_alog->write(log::alevel::devel, "transport::asio::init");
1091 
1092  // Initialize the connection socket component
1093  socket_type::init(lib::static_pointer_cast<socket_con_type,
1094  transport_con_type>(tcon));
1095 
1096  lib::error_code ec;
1097 
1098  ec = tcon->init_asio(m_io_service);
1099  if (ec) {return ec;}
1100 
1101  tcon->set_tcp_pre_init_handler(m_tcp_pre_init_handler);
1102  tcon->set_tcp_post_init_handler(m_tcp_post_init_handler);
1103 
1104  return lib::error_code();
1105  }
1106 private:
1107  /// Convenience method for logging the code and message for an error_code
1108  template <typename error_type>
1109  void log_err(log::level l, char const * msg, error_type const & ec) {
1110  std::stringstream s;
1111  s << msg << " error: " << ec << " (" << ec.message() << ")";
1112  m_elog->write(l,s.str());
1113  }
1114 
1115  enum state {
1116  UNINITIALIZED = 0,
1117  READY = 1,
1118  LISTENING = 2
1119  };
1120 
1121  // Handlers
1122  tcp_init_handler m_tcp_pre_init_handler;
1123  tcp_init_handler m_tcp_post_init_handler;
1124 
1125  // Network Resources
1126  io_service_ptr m_io_service;
1127  bool m_external_io_service;
1128  acceptor_ptr m_acceptor;
1129  resolver_ptr m_resolver;
1130  work_ptr m_work;
1131 
1132  // Network constants
1133  int m_listen_backlog;
1134  bool m_reuse_addr;
1135 
1136  elog_type* m_elog;
1137  alog_type* m_alog;
1138 
1139  // Transport state
1140  state m_state;
1141 };
1142 
1143 } // namespace asio
1144 } // namespace transport
1145 } // namespace websocketpp
1146 
1147 #endif // WEBSOCKETPP_TRANSPORT_ASIO_HPP