WebSocket++  0.8.0-dev
C++ websocket client/server library
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Pages
connection.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_CON_HPP
29 #define WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP
30 
31 #include <websocketpp/transport/asio/base.hpp>
32 
33 #include <websocketpp/transport/base/connection.hpp>
34 
35 #include <websocketpp/logger/levels.hpp>
36 #include <websocketpp/http/constants.hpp>
37 
38 #include <websocketpp/base64/base64.hpp>
39 #include <websocketpp/error.hpp>
40 #include <websocketpp/uri.hpp>
41 
42 #include <websocketpp/common/asio.hpp>
43 #include <websocketpp/common/chrono.hpp>
44 #include <websocketpp/common/cpp11.hpp>
45 #include <websocketpp/common/memory.hpp>
46 #include <websocketpp/common/functional.hpp>
47 #include <websocketpp/common/connection_hdl.hpp>
48 
49 #include <istream>
50 #include <sstream>
51 #include <string>
52 #include <vector>
53 
54 namespace websocketpp {
55 namespace transport {
56 namespace asio {
57 
58 typedef lib::function<void(connection_hdl)> tcp_init_handler;
59 
60 /// Asio based connection transport component
61 /**
62  * transport::asio::connection implements a connection transport component using
63  * Asio that works with the transport::asio::endpoint endpoint transport
64  * component.
65  */
66 template <typename config>
67 class connection : public config::socket_type::socket_con_type {
68 public:
69  /// Type of this connection transport component
70  typedef connection<config> type;
71  /// Type of a shared pointer to this connection transport component
72  typedef lib::shared_ptr<type> ptr;
73 
74  /// Type of the socket connection component
75  typedef typename config::socket_type::socket_con_type socket_con_type;
76  /// Type of a shared pointer to the socket connection component
77  typedef typename socket_con_type::ptr socket_con_ptr;
78  /// Type of this transport's access logging policy
79  typedef typename config::alog_type alog_type;
80  /// Type of this transport's error logging policy
81  typedef typename config::elog_type elog_type;
82 
83  typedef typename config::request_type request_type;
84  typedef typename request_type::ptr request_ptr;
85  typedef typename config::response_type response_type;
86  typedef typename response_type::ptr response_ptr;
87 
88  /// Type of a pointer to the Asio io_service being used
90  /// Type of a pointer to the Asio io_service::strand being used
91  typedef lib::shared_ptr<lib::asio::io_service::strand> strand_ptr;
92  /// Type of a pointer to the Asio timer class
93  typedef lib::shared_ptr<lib::asio::steady_timer> timer_ptr;
94 
95  // connection is friends with its associated endpoint to allow the endpoint
96  // to call private/protected utility methods that we don't want to expose
97  // to the public api.
98  friend class endpoint<config>;
99 
100  // generate and manage our own io_service
101  explicit connection(bool is_server, const lib::shared_ptr<alog_type> & alog, const lib::shared_ptr<elog_type> & elog)
102  : m_is_server(is_server)
103  , m_alog(alog)
104  , m_elog(elog)
105  {
106  m_alog->write(log::alevel::devel,"asio con transport constructor");
107  }
108 
109  /// Get a shared pointer to this component
111  return lib::static_pointer_cast<type>(socket_con_type::get_shared());
112  }
113 
114  bool is_secure() const {
115  return socket_con_type::is_secure();
116  }
117 
118  /// Set uri hook
119  /**
120  * Called by the endpoint as a connection is being established to provide
121  * the uri being connected to to the transport layer.
122  *
123  * This transport policy doesn't use the uri except to forward it to the
124  * socket layer.
125  *
126  * @since 0.6.0
127  *
128  * @param u The uri to set
129  */
130  void set_uri(uri_ptr u) {
131  socket_con_type::set_uri(u);
132  }
133 
134  /// Sets the tcp pre init handler
135  /**
136  * The tcp pre init handler is called after the raw tcp connection has been
137  * established but before any additional wrappers (proxy connects, TLS
138  * handshakes, etc) have been performed.
139  *
140  * @since 0.3.0
141  *
142  * @param h The handler to call on tcp pre init.
143  */
144  void set_tcp_pre_init_handler(tcp_init_handler h) {
145  m_tcp_pre_init_handler = h;
146  }
147 
148  /// Sets the tcp pre init handler (deprecated)
149  /**
150  * The tcp pre init handler is called after the raw tcp connection has been
151  * established but before any additional wrappers (proxy connects, TLS
152  * handshakes, etc) have been performed.
153  *
154  * @deprecated Use set_tcp_pre_init_handler instead
155  *
156  * @param h The handler to call on tcp pre init.
157  */
158  void set_tcp_init_handler(tcp_init_handler h) {
159  set_tcp_pre_init_handler(h);
160  }
161 
162  /// Sets the tcp post init handler
163  /**
164  * The tcp post init handler is called after the tcp connection has been
165  * established and all additional wrappers (proxy connects, TLS handshakes,
166  * etc have been performed. This is fired before any bytes are read or any
167  * WebSocket specific handshake logic has been performed.
168  *
169  * @since 0.3.0
170  *
171  * @param h The handler to call on tcp post init.
172  */
173  void set_tcp_post_init_handler(tcp_init_handler h) {
174  m_tcp_post_init_handler = h;
175  }
176 
177  /// Set the proxy to connect through (exception free)
178  /**
179  * The URI passed should be a complete URI including scheme. For example:
180  * http://proxy.example.com:8080/
181  *
182  * The proxy must be set up as an explicit (CONNECT) proxy allowed to
183  * connect to the port you specify. Traffic to the proxy is not encrypted.
184  *
185  * @param uri The full URI of the proxy to connect to.
186  *
187  * @param ec A status value
188  */
189  void set_proxy(std::string const & uri, lib::error_code & ec) {
190  // TODO: return errors for illegal URIs here?
191  // TODO: should https urls be illegal for the moment?
192  m_proxy = uri;
193  m_proxy_data = lib::make_shared<proxy_data>();
194  ec = lib::error_code();
195  }
196 
197  /// Set the proxy to connect through (exception)
198  void set_proxy(std::string const & uri) {
199  lib::error_code ec;
200  set_proxy(uri,ec);
201  if (ec) { throw exception(ec); }
202  }
203 
204  /// Set the basic auth credentials to use (exception free)
205  /**
206  * The URI passed should be a complete URI including scheme. For example:
207  * http://proxy.example.com:8080/
208  *
209  * The proxy must be set up as an explicit proxy
210  *
211  * @param username The username to send
212  *
213  * @param password The password to send
214  *
215  * @param ec A status value
216  */
217  void set_proxy_basic_auth(std::string const & username, std::string const &
218  password, lib::error_code & ec)
219  {
220  if (!m_proxy_data) {
221  ec = make_error_code(websocketpp::error::invalid_state);
222  return;
223  }
224 
225  // TODO: username can't contain ':'
226  std::string val = "Basic "+base64_encode(username + ":" + password);
227  m_proxy_data->req.replace_header("Proxy-Authorization",val);
228  ec = lib::error_code();
229  }
230 
231  /// Set the basic auth credentials to use (exception)
232  void set_proxy_basic_auth(std::string const & username, std::string const &
233  password)
234  {
235  lib::error_code ec;
236  set_proxy_basic_auth(username,password,ec);
237  if (ec) { throw exception(ec); }
238  }
239 
240  /// Set the proxy timeout duration (exception free)
241  /**
242  * Duration is in milliseconds. Default value is based on the transport
243  * config
244  *
245  * @param duration The number of milliseconds to wait before aborting the
246  * proxy connection.
247  *
248  * @param ec A status value
249  */
250  void set_proxy_timeout(long duration, lib::error_code & ec) {
251  if (!m_proxy_data) {
252  ec = make_error_code(websocketpp::error::invalid_state);
253  return;
254  }
255 
256  m_proxy_data->timeout_proxy = duration;
257  ec = lib::error_code();
258  }
259 
260  /// Set the proxy timeout duration (exception)
261  void set_proxy_timeout(long duration) {
262  lib::error_code ec;
263  set_proxy_timeout(duration,ec);
264  if (ec) { throw exception(ec); }
265  }
266 
267  std::string const & get_proxy() const {
268  return m_proxy;
269  }
270 
271  /// Get the remote endpoint address
272  /**
273  * The iostream transport has no information about the ultimate remote
274  * endpoint. It will return the string "iostream transport". To indicate
275  * this.
276  *
277  * TODO: allow user settable remote endpoint addresses if this seems useful
278  *
279  * @return A string identifying the address of the remote endpoint
280  */
281  std::string get_remote_endpoint() const {
282  lib::error_code ec;
283 
284  std::string ret = socket_con_type::get_remote_endpoint(ec);
285 
286  if (ec) {
287  m_elog->write(log::elevel::info,ret);
288  return "Unknown";
289  } else {
290  return ret;
291  }
292  }
293 
294  /// Get the connection handle
296  return m_connection_hdl;
297  }
298 
299  /// Call back a function after a period of time.
300  /**
301  * Sets a timer that calls back a function after the specified period of
302  * milliseconds. Returns a handle that can be used to cancel the timer.
303  * A cancelled timer will return the error code error::operation_aborted
304  * A timer that expired will return no error.
305  *
306  * @param duration Length of time to wait in milliseconds
307  *
308  * @param callback The function to call back when the timer has expired
309  *
310  * @return A handle that can be used to cancel the timer if it is no longer
311  * needed.
312  */
314  timer_ptr new_timer = lib::make_shared<lib::asio::steady_timer>(
315  lib::ref(*m_io_service),
316  lib::asio::milliseconds(duration)
317  );
318 
319  if (config::enable_multithreading) {
320  new_timer->async_wait(m_strand->wrap(lib::bind(
321  &type::handle_timer, get_shared(),
322  new_timer,
323  callback,
324  lib::placeholders::_1
325  )));
326  } else {
327  new_timer->async_wait(lib::bind(
328  &type::handle_timer, get_shared(),
329  new_timer,
330  callback,
331  lib::placeholders::_1
332  ));
333  }
334 
335  return new_timer;
336  }
337 
338  /// Timer callback
339  /**
340  * The timer pointer is included to ensure the timer isn't destroyed until
341  * after it has expired.
342  *
343  * TODO: candidate for protected status
344  *
345  * @param post_timer Pointer to the timer in question
346  * @param callback The function to call back
347  * @param ec The status code
348  */
349  void handle_timer(timer_ptr, timer_handler callback,
350  lib::asio::error_code const & ec)
351  {
352  if (ec) {
353  if (ec == lib::asio::error::operation_aborted) {
354  callback(make_error_code(transport::error::operation_aborted));
355  } else {
356  log_err(log::elevel::info,"asio handle_timer",ec);
357  callback(make_error_code(error::pass_through));
358  }
359  } else {
360  callback(lib::error_code());
361  }
362  }
363 
364  /// Get a pointer to this connection's strand
366  return m_strand;
367  }
368 
369  /// Get the internal transport error code for a closed/failed connection
370  /**
371  * Retrieves a machine readable detailed error code indicating the reason
372  * that the connection was closed or failed. Valid only after the close or
373  * fail handler is called.
374  *
375  * Primarily used if you are using mismatched asio / system_error
376  * implementations such as `boost::asio` with `std::system_error`. In these
377  * cases the transport error type is different than the library error type
378  * and some WebSocket++ functions that return transport errors via the
379  * library error code type will be coerced into a catch all `pass_through`
380  * or `tls_error` error. This method will return the original machine
381  * readable transport error in the native type.
382  *
383  * @since 0.7.0
384  *
385  * @return Error code indicating the reason the connection was closed or
386  * failed
387  */
389  return m_tec;
390  }
391 
392  /// Initialize transport for reading
393  /**
394  * init_asio is called once immediately after construction to initialize
395  * Asio components to the io_service
396  *
397  * The transport initialization sequence consists of the following steps:
398  * - Pre-init: the underlying socket is initialized to the point where
399  * bytes may be written. No bytes are actually written in this stage
400  * - Proxy negotiation: if a proxy is set, a request is made to it to start
401  * a tunnel to the final destination. This stage ends when the proxy is
402  * ready to forward the
403  * next byte to the remote endpoint.
404  * - Post-init: Perform any i/o with the remote endpoint, such as setting up
405  * tunnels for encryption. This stage ends when the connection is ready to
406  * read or write the WebSocket handshakes. At this point the original
407  * callback function is called.
408  */
409 
410 
411 
412 
413 
414  void test() {
415  std::cout << "m_is_server: " << sizeof(m_is_server) << std::endl;
416  std::cout << "m_alog: " << sizeof(m_alog) << std::endl;
417  std::cout << "m_elog: " << sizeof(m_elog) << std::endl;
418  std::cout << "m_proxy: " << sizeof(m_proxy) << std::endl;
419 
420  std::cout << "m_proxy_data: " << sizeof(m_proxy_data) << std::endl;
421  std::cout << "m_io_service: " << sizeof(m_io_service) << std::endl;
422  std::cout << "m_strand: " << sizeof(m_strand) << std::endl;
423  std::cout << "m_connection_hdl: " << sizeof(m_connection_hdl) << std::endl;
424  std::cout << "m_bufs: " << sizeof(m_bufs) << std::endl;
425  std::cout << "m_tec: " << sizeof(m_tec) << std::endl;
426  std::cout << "m_tcp_pre_init_handler: " << sizeof(m_tcp_pre_init_handler) << std::endl;
427  std::cout << "m_tcp_post_init_handler: " << sizeof(m_tcp_post_init_handler) << std::endl;
428  std::cout << "m_read_handler_allocator: " << sizeof(m_read_handler_allocator) << std::endl;
429  std::cout << "m_write_handler_allocator: " << sizeof(m_write_handler_allocator) << std::endl;
430 
431  std::cout << "socket_con_type: " << sizeof(socket_con_type) << std::endl;
432  }
433 protected:
434  void init(init_handler callback) {
435  if (m_alog->static_test(log::alevel::devel)) {
436  m_alog->write(log::alevel::devel,"asio connection init");
437  }
438 
439  // TODO: pre-init timeout. Right now no implemented socket policies
440  // actually have an asyncronous pre-init
441 
442  socket_con_type::pre_init(
443  lib::bind(
444  &type::handle_pre_init,
445  get_shared(),
446  callback,
447  lib::placeholders::_1
448  )
449  );
450  }
451 
452  /// initialize the proxy buffers and http parsers
453  /**
454  *
455  * @param authority The address of the server we want the proxy to tunnel to
456  * in the format of a URI authority (host:port)
457  *
458  * @return Status code indicating what errors occurred, if any
459  */
461  if (!m_proxy_data) {
462  return websocketpp::error::make_error_code(
463  websocketpp::error::invalid_state);
464  }
465  m_proxy_data->req.set_version("HTTP/1.1");
466  m_proxy_data->req.set_method("CONNECT");
467 
468  m_proxy_data->req.set_uri(authority);
469  m_proxy_data->req.replace_header("Host",authority);
470 
471  return lib::error_code();
472  }
473 
474  /// Finish constructing the transport
475  /**
476  * init_asio is called once immediately after construction to initialize
477  * Asio components to the io_service.
478  *
479  * @param io_service A pointer to the io_service to register with this
480  * connection
481  *
482  * @return Status code for the success or failure of the initialization
483  */
485  m_io_service = io_service;
486 
487  if (config::enable_multithreading) {
488  m_strand = lib::make_shared<lib::asio::io_service::strand>(
489  lib::ref(*io_service));
490  }
491 
492  lib::error_code ec = socket_con_type::init_asio(io_service, m_strand,
493  m_is_server);
494 
495  return ec;
496  }
497 
498  void handle_pre_init(init_handler callback, lib::error_code const & ec) {
499  if (m_alog->static_test(log::alevel::devel)) {
500  m_alog->write(log::alevel::devel,"asio connection handle pre_init");
501  }
502 
503  if (m_tcp_pre_init_handler) {
504  m_tcp_pre_init_handler(m_connection_hdl);
505  }
506 
507  if (ec) {
508  callback(ec);
509  }
510 
511  // If we have a proxy set issue a proxy connect, otherwise skip to
512  // post_init
513  if (!m_proxy.empty()) {
514  proxy_write(callback);
515  } else {
516  post_init(callback);
517  }
518  }
519 
520  void post_init(init_handler callback) {
521  if (m_alog->static_test(log::alevel::devel)) {
522  m_alog->write(log::alevel::devel,"asio connection post_init");
523  }
524 
525  timer_ptr post_timer;
526 
527  if (config::timeout_socket_post_init > 0) {
528  post_timer = set_timer(
529  config::timeout_socket_post_init,
530  lib::bind(
531  &type::handle_post_init_timeout,
532  get_shared(),
533  post_timer,
534  callback,
535  lib::placeholders::_1
536  )
537  );
538  }
539 
540  socket_con_type::post_init(
541  lib::bind(
542  &type::handle_post_init,
543  get_shared(),
544  post_timer,
545  callback,
546  lib::placeholders::_1
547  )
548  );
549  }
550 
551  /// Post init timeout callback
552  /**
553  * The timer pointer is included to ensure the timer isn't destroyed until
554  * after it has expired.
555  *
556  * @param post_timer Pointer to the timer in question
557  * @param callback The function to call back
558  * @param ec The status code
559  */
560  void handle_post_init_timeout(timer_ptr, init_handler callback,
561  lib::error_code const & ec)
562  {
563  lib::error_code ret_ec;
564 
565  if (ec) {
566  if (ec == transport::error::operation_aborted) {
567  m_alog->write(log::alevel::devel,
568  "asio post init timer cancelled");
569  return;
570  }
571 
572  log_err(log::elevel::devel,"asio handle_post_init_timeout",ec);
573  ret_ec = ec;
574  } else {
575  if (socket_con_type::get_ec()) {
576  ret_ec = socket_con_type::get_ec();
577  } else {
578  ret_ec = make_error_code(transport::error::timeout);
579  }
580  }
581 
582  m_alog->write(log::alevel::devel, "Asio transport post-init timed out");
584  callback(ret_ec);
585  }
586 
587  /// Post init timeout callback
588  /**
589  * The timer pointer is included to ensure the timer isn't destroyed until
590  * after it has expired.
591  *
592  * @param post_timer Pointer to the timer in question
593  * @param callback The function to call back
594  * @param ec The status code
595  */
596  void handle_post_init(timer_ptr post_timer, init_handler callback,
597  lib::error_code const & ec)
598  {
599  if (ec == transport::error::operation_aborted ||
600  (post_timer && lib::asio::is_neg(post_timer->expires_from_now())))
601  {
602  m_alog->write(log::alevel::devel,"post_init cancelled");
603  return;
604  }
605 
606  if (post_timer) {
607  post_timer->cancel();
608  }
609 
610  if (m_alog->static_test(log::alevel::devel)) {
611  m_alog->write(log::alevel::devel,"asio connection handle_post_init");
612  }
613 
614  if (m_tcp_post_init_handler) {
615  m_tcp_post_init_handler(m_connection_hdl);
616  }
617 
618  callback(ec);
619  }
620 
621  void proxy_write(init_handler callback) {
622  if (m_alog->static_test(log::alevel::devel)) {
623  m_alog->write(log::alevel::devel,"asio connection proxy_write");
624  }
625 
626  if (!m_proxy_data) {
627  m_elog->write(log::elevel::library,
628  "assertion failed: !m_proxy_data in asio::connection::proxy_write");
629  callback(make_error_code(error::general));
630  return;
631  }
632 
633  m_proxy_data->write_buf = m_proxy_data->req.raw();
634 
635  m_bufs.push_back(lib::asio::buffer(m_proxy_data->write_buf.data(),
636  m_proxy_data->write_buf.size()));
637 
638  m_alog->write(log::alevel::devel,m_proxy_data->write_buf);
639 
640  // Set a timer so we don't wait forever for the proxy to respond
641  m_proxy_data->timer = this->set_timer(
642  m_proxy_data->timeout_proxy,
643  lib::bind(
644  &type::handle_proxy_timeout,
645  get_shared(),
646  callback,
647  lib::placeholders::_1
648  )
649  );
650 
651  // Send proxy request
652  if (config::enable_multithreading) {
653  lib::asio::async_write(
654  socket_con_type::get_next_layer(),
655  m_bufs,
656  m_strand->wrap(lib::bind(
657  &type::handle_proxy_write, get_shared(),
658  callback,
659  lib::placeholders::_1
660  ))
661  );
662  } else {
663  lib::asio::async_write(
664  socket_con_type::get_next_layer(),
665  m_bufs,
666  lib::bind(
667  &type::handle_proxy_write, get_shared(),
668  callback,
669  lib::placeholders::_1
670  )
671  );
672  }
673  }
674 
675  void handle_proxy_timeout(init_handler callback, lib::error_code const & ec)
676  {
677  if (ec == transport::error::operation_aborted) {
678  m_alog->write(log::alevel::devel,
679  "asio handle_proxy_write timer cancelled");
680  return;
681  } else if (ec) {
682  log_err(log::elevel::devel,"asio handle_proxy_write",ec);
683  callback(ec);
684  } else {
685  m_alog->write(log::alevel::devel,
686  "asio handle_proxy_write timer expired");
687  cancel_socket_checked();
688  callback(make_error_code(transport::error::timeout));
689  }
690  }
691 
692  void handle_proxy_write(init_handler callback,
693  lib::asio::error_code const & ec)
694  {
695  if (m_alog->static_test(log::alevel::devel)) {
696  m_alog->write(log::alevel::devel,
697  "asio connection handle_proxy_write");
698  }
699 
700  m_bufs.clear();
701 
702  // Timer expired or the operation was aborted for some reason.
703  // Whatever aborted it will be issuing the callback so we are safe to
704  // return
705  if (ec == lib::asio::error::operation_aborted ||
706  lib::asio::is_neg(m_proxy_data->timer->expires_from_now()))
707  {
708  m_elog->write(log::elevel::devel,"write operation aborted");
709  return;
710  }
711 
712  if (ec) {
713  log_err(log::elevel::info,"asio handle_proxy_write",ec);
714  m_proxy_data->timer->cancel();
715  callback(make_error_code(error::pass_through));
716  return;
717  }
718 
719  proxy_read(callback);
720  }
721 
722  void proxy_read(init_handler callback) {
723  if (m_alog->static_test(log::alevel::devel)) {
724  m_alog->write(log::alevel::devel,"asio connection proxy_read");
725  }
726 
727  if (!m_proxy_data) {
728  m_elog->write(log::elevel::library,
729  "assertion failed: !m_proxy_data in asio::connection::proxy_read");
730  m_proxy_data->timer->cancel();
731  callback(make_error_code(error::general));
732  return;
733  }
734 
735  if (config::enable_multithreading) {
736  lib::asio::async_read_until(
737  socket_con_type::get_next_layer(),
738  m_proxy_data->read_buf,
739  "\r\n\r\n",
740  m_strand->wrap(lib::bind(
741  &type::handle_proxy_read, get_shared(),
742  callback,
743  lib::placeholders::_1, lib::placeholders::_2
744  ))
745  );
746  } else {
747  lib::asio::async_read_until(
748  socket_con_type::get_next_layer(),
749  m_proxy_data->read_buf,
750  "\r\n\r\n",
751  lib::bind(
752  &type::handle_proxy_read, get_shared(),
753  callback,
754  lib::placeholders::_1, lib::placeholders::_2
755  )
756  );
757  }
758  }
759 
760  /// Proxy read callback
761  /**
762  * @param init_handler The function to call back
763  * @param ec The status code
764  * @param bytes_transferred The number of bytes read
765  */
766  void handle_proxy_read(init_handler callback,
767  lib::asio::error_code const & ec, size_t)
768  {
769  if (m_alog->static_test(log::alevel::devel)) {
770  m_alog->write(log::alevel::devel,
771  "asio connection handle_proxy_read");
772  }
773 
774  // Timer expired or the operation was aborted for some reason.
775  // Whatever aborted it will be issuing the callback so we are safe to
776  // return
777  if (ec == lib::asio::error::operation_aborted ||
778  lib::asio::is_neg(m_proxy_data->timer->expires_from_now()))
779  {
780  m_elog->write(log::elevel::devel,"read operation aborted");
781  return;
782  }
783 
784  // At this point there is no need to wait for the timer anymore
785  m_proxy_data->timer->cancel();
786 
787  if (ec) {
788  m_elog->write(log::elevel::info,
789  "asio handle_proxy_read error: "+ec.message());
790  callback(make_error_code(error::pass_through));
791  } else {
792  if (!m_proxy_data) {
793  m_elog->write(log::elevel::library,
794  "assertion failed: !m_proxy_data in asio::connection::handle_proxy_read");
795  callback(make_error_code(error::general));
796  return;
797  }
798 
799  std::istream input(&m_proxy_data->read_buf);
800 
801  m_proxy_data->res.consume(input);
802 
803  if (!m_proxy_data->res.headers_ready()) {
804  // we read until the headers were done in theory but apparently
805  // they aren't. Internal endpoint error.
806  callback(make_error_code(error::general));
807  return;
808  }
809 
810  m_alog->write(log::alevel::devel,m_proxy_data->res.raw());
811 
812  if (m_proxy_data->res.get_status_code() != http::status_code::ok) {
813  // got an error response back
814  // TODO: expose this error in a programmatically accessible way?
815  // if so, see below for an option on how to do this.
816  std::stringstream s;
817  s << "Proxy connection error: "
818  << m_proxy_data->res.get_status_code()
819  << " ("
820  << m_proxy_data->res.get_status_msg()
821  << ")";
822  m_elog->write(log::elevel::info,s.str());
823  callback(make_error_code(error::proxy_failed));
824  return;
825  }
826 
827  // we have successfully established a connection to the proxy, now
828  // we can continue and the proxy will transparently forward the
829  // WebSocket connection.
830 
831  // TODO: decide if we want an on_proxy callback that would allow
832  // access to the proxy response.
833 
834  // free the proxy buffers and req/res objects as they aren't needed
835  // anymore
836  m_proxy_data.reset();
837 
838  // Continue with post proxy initialization
839  post_init(callback);
840  }
841  }
842 
843  /// read at least num_bytes bytes into buf and then call handler.
844  void async_read_at_least(size_t num_bytes, char *buf, size_t len,
845  read_handler handler)
846  {
847  if (m_alog->static_test(log::alevel::devel)) {
848  std::stringstream s;
849  s << "asio async_read_at_least: " << num_bytes;
850  m_alog->write(log::alevel::devel,s.str());
851  }
852 
853  // TODO: safety vs speed ?
854  // maybe move into an if devel block
855  /*if (num_bytes > len) {
856  m_elog->write(log::elevel::devel,
857  "asio async_read_at_least error::invalid_num_bytes");
858  handler(make_error_code(transport::error::invalid_num_bytes),
859  size_t(0));
860  return;
861  }*/
862 
863  if (config::enable_multithreading) {
864  lib::asio::async_read(
865  socket_con_type::get_socket(),
866  lib::asio::buffer(buf,len),
867  lib::asio::transfer_at_least(num_bytes),
868  m_strand->wrap(make_custom_alloc_handler(
869  m_read_handler_allocator,
870  lib::bind(
871  &type::handle_async_read, get_shared(),
872  handler,
873  lib::placeholders::_1, lib::placeholders::_2
874  )
875  ))
876  );
877  } else {
878  lib::asio::async_read(
879  socket_con_type::get_socket(),
880  lib::asio::buffer(buf,len),
881  lib::asio::transfer_at_least(num_bytes),
882  make_custom_alloc_handler(
883  m_read_handler_allocator,
884  lib::bind(
885  &type::handle_async_read, get_shared(),
886  handler,
887  lib::placeholders::_1, lib::placeholders::_2
888  )
889  )
890  );
891  }
892 
893  }
894 
895  void handle_async_read(read_handler handler, lib::asio::error_code const & ec,
896  size_t bytes_transferred)
897  {
898  m_alog->write(log::alevel::devel, "asio con handle_async_read");
899 
900  // translate asio error codes into more lib::error_codes
901  lib::error_code tec;
902  if (ec == lib::asio::error::eof) {
903  tec = make_error_code(transport::error::eof);
904  } else if (ec) {
905  // We don't know much more about the error at this point. As our
906  // socket/security policy if it knows more:
907  tec = socket_con_type::translate_ec(ec);
908  m_tec = ec;
909 
910  if (tec == transport::error::tls_error ||
911  tec == transport::error::pass_through)
912  {
913  // These are aggregate/catch all errors. Log some human readable
914  // information to the info channel to give library users some
915  // more details about why the upstream method may have failed.
916  log_err(log::elevel::info,"asio async_read_at_least",ec);
917  }
918  }
919  if (handler) {
920  handler(tec,bytes_transferred);
921  } else {
922  // This can happen in cases where the connection is terminated while
923  // the transport is waiting on a read.
924  m_alog->write(log::alevel::devel,
925  "handle_async_read called with null read handler");
926  }
927  }
928 
929  /// Initiate a potentially asyncronous write of the given buffer
930  void async_write(const char* buf, size_t len, write_handler handler) {
931  m_bufs.push_back(lib::asio::buffer(buf,len));
932 
933  if (config::enable_multithreading) {
934  lib::asio::async_write(
935  socket_con_type::get_socket(),
936  m_bufs,
937  m_strand->wrap(make_custom_alloc_handler(
938  m_write_handler_allocator,
939  lib::bind(
940  &type::handle_async_write, get_shared(),
941  handler,
942  lib::placeholders::_1, lib::placeholders::_2
943  )
944  ))
945  );
946  } else {
947  lib::asio::async_write(
948  socket_con_type::get_socket(),
949  m_bufs,
950  make_custom_alloc_handler(
951  m_write_handler_allocator,
952  lib::bind(
953  &type::handle_async_write, get_shared(),
954  handler,
955  lib::placeholders::_1, lib::placeholders::_2
956  )
957  )
958  );
959  }
960  }
961 
962  /// Initiate a potentially asyncronous write of the given buffers
963  void async_write(std::vector<buffer> const & bufs, write_handler handler) {
964  std::vector<buffer>::const_iterator it;
965 
966  for (it = bufs.begin(); it != bufs.end(); ++it) {
967  m_bufs.push_back(lib::asio::buffer((*it).buf,(*it).len));
968  }
969 
970  if (config::enable_multithreading) {
971  lib::asio::async_write(
972  socket_con_type::get_socket(),
973  m_bufs,
974  m_strand->wrap(make_custom_alloc_handler(
975  m_write_handler_allocator,
976  lib::bind(
977  &type::handle_async_write, get_shared(),
978  handler,
979  lib::placeholders::_1, lib::placeholders::_2
980  )
981  ))
982  );
983  } else {
984  lib::asio::async_write(
985  socket_con_type::get_socket(),
986  m_bufs,
987  make_custom_alloc_handler(
988  m_write_handler_allocator,
989  lib::bind(
990  &type::handle_async_write, get_shared(),
991  handler,
992  lib::placeholders::_1, lib::placeholders::_2
993  )
994  )
995  );
996  }
997  }
998 
999  /// Async write callback
1000  /**
1001  * @param ec The status code
1002  * @param bytes_transferred The number of bytes read
1003  */
1004  void handle_async_write(write_handler handler, lib::asio::error_code const & ec, size_t) {
1005  m_bufs.clear();
1006  lib::error_code tec;
1007  if (ec) {
1008  log_err(log::elevel::info,"asio async_write",ec);
1009  tec = make_error_code(transport::error::pass_through);
1010  }
1011  if (handler) {
1012  handler(tec);
1013  } else {
1014  // This can happen in cases where the connection is terminated while
1015  // the transport is waiting on a read.
1016  m_alog->write(log::alevel::devel,
1017  "handle_async_write called with null write handler");
1018  }
1019  }
1020 
1021  /// Set Connection Handle
1022  /**
1023  * See common/connection_hdl.hpp for information
1024  *
1025  * @param hdl A connection_hdl that the transport will use to refer
1026  * to itself
1027  */
1028  void set_handle(connection_hdl hdl) {
1029  m_connection_hdl = hdl;
1030  socket_con_type::set_handle(hdl);
1031  }
1032 
1033  /// Trigger the on_interrupt handler
1034  /**
1035  * This needs to be thread safe
1036  */
1038  if (config::enable_multithreading) {
1039  m_io_service->post(m_strand->wrap(handler));
1040  } else {
1041  m_io_service->post(handler);
1042  }
1043  return lib::error_code();
1044  }
1045 
1046  lib::error_code dispatch(dispatch_handler handler) {
1047  if (config::enable_multithreading) {
1048  m_io_service->post(m_strand->wrap(handler));
1049  } else {
1050  m_io_service->post(handler);
1051  }
1052  return lib::error_code();
1053  }
1054 
1055  /*void handle_interrupt(interrupt_handler handler) {
1056  handler();
1057  }*/
1058 
1059  /// close and clean up the underlying socket
1060  void async_shutdown(shutdown_handler callback) {
1061  if (m_alog->static_test(log::alevel::devel)) {
1062  m_alog->write(log::alevel::devel,"asio connection async_shutdown");
1063  }
1064 
1065  timer_ptr shutdown_timer;
1066  shutdown_timer = set_timer(
1067  config::timeout_socket_shutdown,
1068  lib::bind(
1069  &type::handle_async_shutdown_timeout,
1070  get_shared(),
1071  shutdown_timer,
1072  callback,
1073  lib::placeholders::_1
1074  )
1075  );
1076 
1077  socket_con_type::async_shutdown(
1078  lib::bind(
1079  &type::handle_async_shutdown,
1080  get_shared(),
1081  shutdown_timer,
1082  callback,
1083  lib::placeholders::_1
1084  )
1085  );
1086  }
1087 
1088  /// Async shutdown timeout handler
1089  /**
1090  * @param shutdown_timer A pointer to the timer to keep it in scope
1091  * @param callback The function to call back
1092  * @param ec The status code
1093  */
1094  void handle_async_shutdown_timeout(timer_ptr, init_handler callback,
1095  lib::error_code const & ec)
1096  {
1097  lib::error_code ret_ec;
1098 
1099  if (ec) {
1100  if (ec == transport::error::operation_aborted) {
1101  m_alog->write(log::alevel::devel,
1102  "asio socket shutdown timer cancelled");
1103  return;
1104  }
1105 
1106  log_err(log::elevel::devel,"asio handle_async_shutdown_timeout",ec);
1107  ret_ec = ec;
1108  } else {
1109  ret_ec = make_error_code(transport::error::timeout);
1110  }
1111 
1112  m_alog->write(log::alevel::devel,
1113  "Asio transport socket shutdown timed out");
1115  callback(ret_ec);
1116  }
1117 
1118  void handle_async_shutdown(timer_ptr shutdown_timer, shutdown_handler
1119  callback, lib::asio::error_code const & ec)
1120  {
1121  if (ec == lib::asio::error::operation_aborted ||
1122  lib::asio::is_neg(shutdown_timer->expires_from_now()))
1123  {
1124  m_alog->write(log::alevel::devel,"async_shutdown cancelled");
1125  return;
1126  }
1127 
1128  shutdown_timer->cancel();
1129 
1130  lib::error_code tec;
1131  if (ec) {
1132  if (ec == lib::asio::error::not_connected) {
1133  // The socket was already closed when we tried to close it. This
1134  // happens periodically (usually if a read or write fails
1135  // earlier and if it is a real error will be caught at another
1136  // level of the stack.
1137  } else {
1138  // We don't know anything more about this error, give our
1139  // socket/security policy a crack at it.
1140  tec = socket_con_type::translate_ec(ec);
1141  m_tec = ec;
1142 
1143  if (tec == transport::error::tls_short_read) {
1144  // TLS short read at this point is somewhat expected if both
1145  // sides try and end the connection at the same time or if
1146  // SSLv2 is being used. In general there is nothing that can
1147  // be done here other than a low level development log.
1148  } else {
1149  // all other errors are effectively pass through errors of
1150  // some sort so print some detail on the info channel for
1151  // library users to look up if needed.
1152  log_err(log::elevel::info,"asio async_shutdown",ec);
1153  }
1154  }
1155  } else {
1156  if (m_alog->static_test(log::alevel::devel)) {
1157  m_alog->write(log::alevel::devel,
1158  "asio con handle_async_shutdown");
1159  }
1160  }
1161  callback(tec);
1162  }
1163 
1164  /// Cancel the underlying socket and log any errors
1166  lib::asio::error_code cec = socket_con_type::cancel_socket();
1167  if (cec) {
1168  if (cec == lib::asio::error::operation_not_supported) {
1169  // cancel not supported on this OS, ignore and log at dev level
1170  m_alog->write(log::alevel::devel, "socket cancel not supported");
1171  } else {
1172  log_err(log::elevel::warn, "socket cancel failed", cec);
1173  }
1174  }
1175  }
1176 
1177 private:
1178  /// Convenience method for logging the code and message for an error_code
1179  template <typename error_type>
1180  void log_err(log::level l, const char * msg, const error_type & ec) {
1181  std::stringstream s;
1182  s << msg << " error: " << ec << " (" << ec.message() << ")";
1183  m_elog->write(l,s.str());
1184  }
1185 
1186  // static settings
1187  const bool m_is_server;
1188  lib::shared_ptr<alog_type> m_alog;
1189  lib::shared_ptr<elog_type> m_elog;
1190 
1191  struct proxy_data {
1192  proxy_data() : timeout_proxy(config::timeout_proxy) {}
1193 
1194  request_type req;
1195  response_type res;
1196  std::string write_buf;
1197  lib::asio::streambuf read_buf;
1198  long timeout_proxy;
1199  timer_ptr timer;
1200  };
1201 
1202  std::string m_proxy;
1203  lib::shared_ptr<proxy_data> m_proxy_data;
1204 
1205  // transport resources
1206  io_service_ptr m_io_service;
1207  strand_ptr m_strand;
1208  connection_hdl m_connection_hdl;
1209 
1210  std::vector<lib::asio::const_buffer> m_bufs;
1211 
1212  /// Detailed internal error code
1213  lib::asio::error_code m_tec;
1214 
1215  // Handlers
1216  tcp_init_handler m_tcp_pre_init_handler;
1217  tcp_init_handler m_tcp_post_init_handler;
1218 
1219  handler_allocator m_read_handler_allocator;
1220  handler_allocator m_write_handler_allocator;
1221 };
1222 
1223 
1224 } // namespace asio
1225 } // namespace transport
1226 } // namespace websocketpp
1227 
1228 #endif // WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP
lib::asio::error_code get_transport_ec() const
Get the internal transport error code for a closed/failed connection.
Definition: connection.hpp:388
config::elog_type elog_type
Type of this transport's error logging policy.
Definition: connection.hpp:81
void cancel_socket_checked()
Cancel the underlying socket and log any errors.
void async_write(const char *buf, size_t len, write_handler handler)
Initiate a potentially asyncronous write of the given buffer.
Definition: connection.hpp:930
void set_proxy_basic_auth(std::string const &username, std::string const &password, lib::error_code &ec)
Set the basic auth credentials to use (exception free)
Definition: connection.hpp:217
friend std::istream & operator>>(std::istream &in, type &t)
Overloaded stream input operator.
Definition: connection.hpp:142
lib::asio::io_service * io_service_ptr
Type of a pointer to the Asio io_service being used.
Definition: connection.hpp:89
void set_tcp_pre_init_handler(tcp_init_handler h)
Sets the tcp pre init handler.
Definition: connection.hpp:144
void test()
Initialize transport for reading.
Definition: connection.hpp:414
strand_ptr get_strand()
Get a pointer to this connection's strand.
Definition: connection.hpp:365
socket_con_type::ptr socket_con_ptr
Type of a shared pointer to the socket connection component.
Definition: connection.hpp:77
void handle_post_init_timeout(timer_ptr, init_handler callback, lib::error_code const &ec)
Post init timeout callback.
Definition: connection.hpp:560
void async_write(std::vector< buffer > const &bufs, write_handler handler)
Initiate a potentially asyncronous write of the given buffers.
Definition: connection.hpp:963
connection_hdl get_handle() const
Get the connection handle.
Definition: connection.hpp:295
void handle_async_shutdown_timeout(timer_ptr, init_handler callback, lib::error_code const &ec)
Async shutdown timeout handler.
void handle_async_write(write_handler handler, lib::asio::error_code const &ec, size_t)
Async write callback.
void set_tcp_post_init_handler(tcp_init_handler h)
Sets the tcp post init handler.
Definition: connection.hpp:173
std::string get_remote_endpoint() const
Get the remote endpoint address.
Definition: connection.hpp:281
void handle_timer(timer_ptr, timer_handler callback, lib::asio::error_code const &ec)
Timer callback.
Definition: connection.hpp:349
timer_ptr set_timer(long duration, timer_handler callback)
Call back a function after a period of time.
Definition: connection.hpp:313
void set_proxy(std::string const &uri, lib::error_code &ec)
Set the proxy to connect through (exception free)
Definition: connection.hpp:189
config::alog_type alog_type
Type of this transport's access logging policy.
Definition: connection.hpp:79
void handle_proxy_read(init_handler callback, lib::asio::error_code const &ec, size_t)
Proxy read callback.
Definition: connection.hpp:766
lib::error_code interrupt(interrupt_handler handler)
Trigger the on_interrupt handler.
void set_proxy_timeout(long duration, lib::error_code &ec)
Set the proxy timeout duration (exception free)
Definition: connection.hpp:250
void set_proxy_timeout(long duration)
Set the proxy timeout duration (exception)
Definition: connection.hpp:261
void handle_accept(connection_ptr con, lib::error_code const &ec)
Handler callback for start_accept.
ptr get_shared()
Get a shared pointer to this component.
Definition: connection.hpp:110
connection< config > type
Type of this connection transport component.
Definition: connection.hpp:70
void handle_post_init(timer_ptr post_timer, init_handler callback, lib::error_code const &ec)
Post init timeout callback.
Definition: connection.hpp:596
void async_shutdown(shutdown_handler callback)
close and clean up the underlying socket
lib::error_code proxy_init(std::string const &authority)
initialize the proxy buffers and http parsers
Definition: connection.hpp:460
lib::error_code init_asio(io_service_ptr io_service)
Finish constructing the transport.
Definition: connection.hpp:484
config::socket_type::socket_con_type socket_con_type
Type of the socket connection component.
Definition: connection.hpp:75
void set_proxy(std::string const &uri)
Set the proxy to connect through (exception)
Definition: connection.hpp:198
void async_read_at_least(size_t num_bytes, char *buf, size_t len, read_handler handler)
read at least num_bytes bytes into buf and then call handler.
Definition: connection.hpp:844
void set_tcp_init_handler(tcp_init_handler h)
Sets the tcp pre init handler (deprecated)
Definition: connection.hpp:158
void set_handle(connection_hdl hdl)
Set Connection Handle.
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
void set_uri(uri_ptr u)
Set uri hook.
Definition: connection.hpp:130
void set_proxy_basic_auth(std::string const &username, std::string const &password)
Set the basic auth credentials to use (exception)
Definition: connection.hpp:232