WebSocket++  0.8.3-dev
C++ websocket client/server library
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
92  /// Type of a pointer to the Asio timer class
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) {
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  */
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  */
313  timer_ptr set_timer(long duration, timer_handler callback) {
314  timer_ptr new_timer(
315  new lib::asio::steady_timer(
316  *m_io_service,
317  lib::asio::milliseconds(duration))
318  );
319 
320  if (config::enable_multithreading) {
321  new_timer->async_wait(m_strand->wrap(lib::bind(
322  &type::handle_timer, get_shared(),
323  new_timer,
324  callback,
325  lib::placeholders::_1
326  )));
327  } else {
328  new_timer->async_wait(lib::bind(
329  &type::handle_timer, get_shared(),
330  new_timer,
331  callback,
332  lib::placeholders::_1
333  ));
334  }
335 
336  return new_timer;
337  }
338 
339  /// Timer callback
340  /**
341  * The timer pointer is included to ensure the timer isn't destroyed until
342  * after it has expired.
343  *
344  * TODO: candidate for protected status
345  *
346  * @param post_timer Pointer to the timer in question
347  * @param callback The function to call back
348  * @param ec The status code
349  */
350  void handle_timer(timer_ptr, timer_handler callback,
351  lib::asio::error_code const & ec)
352  {
353  if (ec) {
354  if (ec == lib::asio::error::operation_aborted) {
355  callback(make_error_code(transport::error::operation_aborted));
356  } else {
357  log_err(log::elevel::info,"asio handle_timer",ec);
358  callback(make_error_code(error::pass_through));
359  }
360  } else {
361  callback(lib::error_code());
362  }
363  }
364 
365  /// Get a pointer to this connection's strand
367  return m_strand;
368  }
369 
370  /// Get the internal transport error code for a closed/failed connection
371  /**
372  * Retrieves a machine readable detailed error code indicating the reason
373  * that the connection was closed or failed. Valid only after the close or
374  * fail handler is called.
375  *
376  * Primarily used if you are using mismatched asio / system_error
377  * implementations such as `boost::asio` with `std::system_error`. In these
378  * cases the transport error type is different than the library error type
379  * and some WebSocket++ functions that return transport errors via the
380  * library error code type will be coerced into a catch all `pass_through`
381  * or `tls_error` error. This method will return the original machine
382  * readable transport error in the native type.
383  *
384  * @since 0.7.0
385  *
386  * @return Error code indicating the reason the connection was closed or
387  * failed
388  */
390  return m_tec;
391  }
392 
393  /// Initialize transport for reading
394  /**
395  * init_asio is called once immediately after construction to initialize
396  * Asio components to the io_service
397  *
398  * The transport initialization sequence consists of the following steps:
399  * - Pre-init: the underlying socket is initialized to the point where
400  * bytes may be written. No bytes are actually written in this stage
401  * - Proxy negotiation: if a proxy is set, a request is made to it to start
402  * a tunnel to the final destination. This stage ends when the proxy is
403  * ready to forward the
404  * next byte to the remote endpoint.
405  * - Post-init: Perform any i/o with the remote endpoint, such as setting up
406  * tunnels for encryption. This stage ends when the connection is ready to
407  * read or write the WebSocket handshakes. At this point the original
408  * callback function is called.
409  */
410 protected:
411  void init(init_handler callback) {
412  if (m_alog->static_test(log::alevel::devel)) {
413  m_alog->write(log::alevel::devel,"asio connection init");
414  }
415 
416  // TODO: pre-init timeout. Right now no implemented socket policies
417  // actually have an asyncronous pre-init
418 
419  socket_con_type::pre_init(
420  lib::bind(
421  &type::handle_pre_init,
422  get_shared(),
423  callback,
424  lib::placeholders::_1
425  )
426  );
427  }
428 
429  /// initialize the proxy buffers and http parsers
430  /**
431  *
432  * @param authority The address of the server we want the proxy to tunnel to
433  * in the format of a URI authority (host:port)
434  *
435  * @return Status code indicating what errors occurred, if any
436  */
438  if (!m_proxy_data) {
439  return websocketpp::error::make_error_code(
440  websocketpp::error::invalid_state);
441  }
442  m_proxy_data->req.set_version("HTTP/1.1");
443  m_proxy_data->req.set_method("CONNECT");
444 
445  m_proxy_data->req.set_uri(authority);
446  m_proxy_data->req.replace_header("Host",authority);
447 
448  return lib::error_code();
449  }
450 
451  /// Finish constructing the transport
452  /**
453  * init_asio is called once immediately after construction to initialize
454  * Asio components to the io_service.
455  *
456  * @param io_service A pointer to the io_service to register with this
457  * connection
458  *
459  * @return Status code for the success or failure of the initialization
460  */
462  m_io_service = io_service;
463 
464  if (config::enable_multithreading) {
465  m_strand.reset(new lib::asio::io_service::strand(*io_service));
466  }
467 
468  lib::error_code ec = socket_con_type::init_asio(io_service, m_strand,
469  m_is_server);
470 
471  return ec;
472  }
473 
474  void handle_pre_init(init_handler callback, lib::error_code const & ec) {
475  if (m_alog->static_test(log::alevel::devel)) {
476  m_alog->write(log::alevel::devel,"asio connection handle pre_init");
477  }
478 
479  if (m_tcp_pre_init_handler) {
480  m_tcp_pre_init_handler(m_connection_hdl);
481  }
482 
483  if (ec) {
484  callback(ec);
485  }
486 
487  // If we have a proxy set issue a proxy connect, otherwise skip to
488  // post_init
489  if (!m_proxy.empty()) {
490  proxy_write(callback);
491  } else {
492  post_init(callback);
493  }
494  }
495 
496  void post_init(init_handler callback) {
497  if (m_alog->static_test(log::alevel::devel)) {
498  m_alog->write(log::alevel::devel,"asio connection post_init");
499  }
500 
501  timer_ptr post_timer;
502 
503  if (config::timeout_socket_post_init > 0) {
504  post_timer = set_timer(
505  config::timeout_socket_post_init,
506  lib::bind(
507  &type::handle_post_init_timeout,
508  get_shared(),
509  post_timer,
510  callback,
511  lib::placeholders::_1
512  )
513  );
514  }
515 
516  socket_con_type::post_init(
517  lib::bind(
518  &type::handle_post_init,
519  get_shared(),
520  post_timer,
521  callback,
522  lib::placeholders::_1
523  )
524  );
525  }
526 
527  /// Post init timeout callback
528  /**
529  * The timer pointer is included to ensure the timer isn't destroyed until
530  * after it has expired.
531  *
532  * @param post_timer Pointer to the timer in question
533  * @param callback The function to call back
534  * @param ec The status code
535  */
536  void handle_post_init_timeout(timer_ptr, init_handler callback,
537  lib::error_code const & ec)
538  {
539  lib::error_code ret_ec;
540 
541  if (ec) {
542  if (ec == transport::error::operation_aborted) {
543  m_alog->write(log::alevel::devel,
544  "asio post init timer cancelled");
545  return;
546  }
547 
548  log_err(log::elevel::devel,"asio handle_post_init_timeout",ec);
549  ret_ec = ec;
550  } else {
551  if (socket_con_type::get_ec()) {
552  ret_ec = socket_con_type::get_ec();
553  } else {
554  ret_ec = make_error_code(transport::error::timeout);
555  }
556  }
557 
558  m_alog->write(log::alevel::devel, "Asio transport post-init timed out");
560  callback(ret_ec);
561  }
562 
563  /// Post init timeout callback
564  /**
565  * The timer pointer is included to ensure the timer isn't destroyed until
566  * after it has expired.
567  *
568  * @param post_timer Pointer to the timer in question
569  * @param callback The function to call back
570  * @param ec The status code
571  */
572  void handle_post_init(timer_ptr post_timer, init_handler callback,
573  lib::error_code const & ec)
574  {
575  if (ec == transport::error::operation_aborted ||
576  (post_timer && lib::asio::is_neg(post_timer->expires_from_now())))
577  {
578  m_alog->write(log::alevel::devel,"post_init cancelled");
579  return;
580  }
581 
582  if (post_timer) {
583  post_timer->cancel();
584  }
585 
586  if (m_alog->static_test(log::alevel::devel)) {
587  m_alog->write(log::alevel::devel,"asio connection handle_post_init");
588  }
589 
590  if (m_tcp_post_init_handler) {
591  m_tcp_post_init_handler(m_connection_hdl);
592  }
593 
594  callback(ec);
595  }
596 
597  void proxy_write(init_handler callback) {
598  if (m_alog->static_test(log::alevel::devel)) {
599  m_alog->write(log::alevel::devel,"asio connection proxy_write");
600  }
601 
602  if (!m_proxy_data) {
603  m_elog->write(log::elevel::library,
604  "assertion failed: !m_proxy_data in asio::connection::proxy_write");
605  callback(make_error_code(error::general));
606  return;
607  }
608 
609  m_proxy_data->write_buf = m_proxy_data->req.raw();
610 
611  m_bufs.push_back(lib::asio::buffer(m_proxy_data->write_buf.data(),
612  m_proxy_data->write_buf.size()));
613 
614  m_alog->write(log::alevel::devel,m_proxy_data->write_buf);
615 
616  // Set a timer so we don't wait forever for the proxy to respond
617  m_proxy_data->timer = this->set_timer(
618  m_proxy_data->timeout_proxy,
619  lib::bind(
620  &type::handle_proxy_timeout,
621  get_shared(),
622  callback,
623  lib::placeholders::_1
624  )
625  );
626 
627  // Send proxy request
628  if (config::enable_multithreading) {
629  lib::asio::async_write(
630  socket_con_type::get_next_layer(),
631  m_bufs,
632  m_strand->wrap(lib::bind(
633  &type::handle_proxy_write, get_shared(),
634  callback,
635  lib::placeholders::_1
636  ))
637  );
638  } else {
639  lib::asio::async_write(
640  socket_con_type::get_next_layer(),
641  m_bufs,
642  lib::bind(
643  &type::handle_proxy_write, get_shared(),
644  callback,
645  lib::placeholders::_1
646  )
647  );
648  }
649  }
650 
651  void handle_proxy_timeout(init_handler callback, lib::error_code const & ec)
652  {
653  if (ec == transport::error::operation_aborted) {
654  m_alog->write(log::alevel::devel,
655  "asio handle_proxy_write timer cancelled");
656  return;
657  } else if (ec) {
658  log_err(log::elevel::devel,"asio handle_proxy_write",ec);
659  callback(ec);
660  } else {
661  m_alog->write(log::alevel::devel,
662  "asio handle_proxy_write timer expired");
664  callback(make_error_code(transport::error::timeout));
665  }
666  }
667 
668  void handle_proxy_write(init_handler callback,
669  lib::asio::error_code const & ec)
670  {
671  if (m_alog->static_test(log::alevel::devel)) {
672  m_alog->write(log::alevel::devel,
673  "asio connection handle_proxy_write");
674  }
675 
676  m_bufs.clear();
677 
678  // Timer expired or the operation was aborted for some reason.
679  // Whatever aborted it will be issuing the callback so we are safe to
680  // return
681  if (ec == lib::asio::error::operation_aborted ||
682  lib::asio::is_neg(m_proxy_data->timer->expires_from_now()))
683  {
684  m_elog->write(log::elevel::devel,"write operation aborted");
685  return;
686  }
687 
688  if (ec) {
689  log_err(log::elevel::info,"asio handle_proxy_write",ec);
690  m_proxy_data->timer->cancel();
691  callback(make_error_code(error::pass_through));
692  return;
693  }
694 
695  proxy_read(callback);
696  }
697 
698  void proxy_read(init_handler callback) {
699  if (m_alog->static_test(log::alevel::devel)) {
700  m_alog->write(log::alevel::devel,"asio connection proxy_read");
701  }
702 
703  if (!m_proxy_data) {
704  m_elog->write(log::elevel::library,
705  "assertion failed: !m_proxy_data in asio::connection::proxy_read");
706  m_proxy_data->timer->cancel();
707  callback(make_error_code(error::general));
708  return;
709  }
710 
711  if (config::enable_multithreading) {
712  lib::asio::async_read_until(
713  socket_con_type::get_next_layer(),
714  m_proxy_data->read_buf,
715  "\r\n\r\n",
716  m_strand->wrap(lib::bind(
717  &type::handle_proxy_read, get_shared(),
718  callback,
719  lib::placeholders::_1, lib::placeholders::_2
720  ))
721  );
722  } else {
723  lib::asio::async_read_until(
724  socket_con_type::get_next_layer(),
725  m_proxy_data->read_buf,
726  "\r\n\r\n",
727  lib::bind(
728  &type::handle_proxy_read, get_shared(),
729  callback,
730  lib::placeholders::_1, lib::placeholders::_2
731  )
732  );
733  }
734  }
735 
736  /// Proxy read callback
737  /**
738  * @param init_handler The function to call back
739  * @param ec The status code
740  * @param bytes_transferred The number of bytes read
741  */
742  void handle_proxy_read(init_handler callback,
743  lib::asio::error_code const & ec, size_t)
744  {
745  if (m_alog->static_test(log::alevel::devel)) {
746  m_alog->write(log::alevel::devel,
747  "asio connection handle_proxy_read");
748  }
749 
750  // Timer expired or the operation was aborted for some reason.
751  // Whatever aborted it will be issuing the callback so we are safe to
752  // return
753  if (ec == lib::asio::error::operation_aborted ||
754  lib::asio::is_neg(m_proxy_data->timer->expires_from_now()))
755  {
756  m_elog->write(log::elevel::devel,"read operation aborted");
757  return;
758  }
759 
760  // At this point there is no need to wait for the timer anymore
761  m_proxy_data->timer->cancel();
762 
763  if (ec) {
764  m_elog->write(log::elevel::info,
765  "asio handle_proxy_read error: "+ec.message());
766  callback(make_error_code(error::pass_through));
767  } else {
768  if (!m_proxy_data) {
769  m_elog->write(log::elevel::library,
770  "assertion failed: !m_proxy_data in asio::connection::handle_proxy_read");
771  callback(make_error_code(error::general));
772  return;
773  }
774 
775  std::istream input(&m_proxy_data->read_buf);
776 
777  m_proxy_data->res.consume(input);
778 
779  if (!m_proxy_data->res.headers_ready()) {
780  // we read until the headers were done in theory but apparently
781  // they aren't. Internal endpoint error.
782  callback(make_error_code(error::general));
783  return;
784  }
785 
786  m_alog->write(log::alevel::devel,m_proxy_data->res.raw());
787 
788  if (m_proxy_data->res.get_status_code() != http::status_code::ok) {
789  // got an error response back
790  // TODO: expose this error in a programmatically accessible way?
791  // if so, see below for an option on how to do this.
792  std::stringstream s;
793  s << "Proxy connection error: "
794  << m_proxy_data->res.get_status_code()
795  << " ("
796  << m_proxy_data->res.get_status_msg()
797  << ")";
798  m_elog->write(log::elevel::info,s.str());
799  callback(make_error_code(error::proxy_failed));
800  return;
801  }
802 
803  // we have successfully established a connection to the proxy, now
804  // we can continue and the proxy will transparently forward the
805  // WebSocket connection.
806 
807  // TODO: decide if we want an on_proxy callback that would allow
808  // access to the proxy response.
809 
810  // free the proxy buffers and req/res objects as they aren't needed
811  // anymore
812  m_proxy_data.reset();
813 
814  // Continue with post proxy initialization
815  post_init(callback);
816  }
817  }
818 
819  /// read at least num_bytes bytes into buf and then call handler.
820  void async_read_at_least(size_t num_bytes, char *buf, size_t len,
821  read_handler handler)
822  {
823  if (m_alog->static_test(log::alevel::devel)) {
824  std::stringstream s;
825  s << "asio async_read_at_least: " << num_bytes;
826  m_alog->write(log::alevel::devel,s.str());
827  }
828 
829  // TODO: safety vs speed ?
830  // maybe move into an if devel block
831  /*if (num_bytes > len) {
832  m_elog->write(log::elevel::devel,
833  "asio async_read_at_least error::invalid_num_bytes");
834  handler(make_error_code(transport::error::invalid_num_bytes),
835  size_t(0));
836  return;
837  }*/
838 
839  if (config::enable_multithreading) {
840  lib::asio::async_read(
841  socket_con_type::get_socket(),
842  lib::asio::buffer(buf,len),
843  lib::asio::transfer_at_least(num_bytes),
844  m_strand->wrap(make_custom_alloc_handler(
845  m_read_handler_allocator,
846  lib::bind(
847  &type::handle_async_read, get_shared(),
848  handler,
849  lib::placeholders::_1, lib::placeholders::_2
850  )
851  ))
852  );
853  } else {
854  lib::asio::async_read(
855  socket_con_type::get_socket(),
856  lib::asio::buffer(buf,len),
857  lib::asio::transfer_at_least(num_bytes),
858  make_custom_alloc_handler(
859  m_read_handler_allocator,
860  lib::bind(
861  &type::handle_async_read, get_shared(),
862  handler,
863  lib::placeholders::_1, lib::placeholders::_2
864  )
865  )
866  );
867  }
868 
869  }
870 
871  void handle_async_read(read_handler handler, lib::asio::error_code const & ec,
872  size_t bytes_transferred)
873  {
874  m_alog->write(log::alevel::devel, "asio con handle_async_read");
875 
876  // translate asio error codes into more lib::error_codes
877  lib::error_code tec;
878  if (ec == lib::asio::error::eof) {
879  tec = make_error_code(transport::error::eof);
880  } else if (ec) {
881  // We don't know much more about the error at this point. As our
882  // socket/security policy if it knows more:
883  tec = socket_con_type::translate_ec(ec);
884  m_tec = ec;
885 
886  if (tec == transport::error::tls_error ||
887  tec == transport::error::pass_through)
888  {
889  // These are aggregate/catch all errors. Log some human readable
890  // information to the info channel to give library users some
891  // more details about why the upstream method may have failed.
892  log_err(log::elevel::info,"asio async_read_at_least",ec);
893  }
894  }
895  if (handler) {
896  handler(tec,bytes_transferred);
897  } else {
898  // This can happen in cases where the connection is terminated while
899  // the transport is waiting on a read.
900  m_alog->write(log::alevel::devel,
901  "handle_async_read called with null read handler");
902  }
903  }
904 
905  /// Initiate a potentially asyncronous write of the given buffer
906  void async_write(const char* buf, size_t len, write_handler handler) {
907  m_bufs.push_back(lib::asio::buffer(buf,len));
908 
909  if (config::enable_multithreading) {
910  lib::asio::async_write(
911  socket_con_type::get_socket(),
912  m_bufs,
913  m_strand->wrap(make_custom_alloc_handler(
914  m_write_handler_allocator,
915  lib::bind(
916  &type::handle_async_write, get_shared(),
917  handler,
918  lib::placeholders::_1, lib::placeholders::_2
919  )
920  ))
921  );
922  } else {
923  lib::asio::async_write(
924  socket_con_type::get_socket(),
925  m_bufs,
926  make_custom_alloc_handler(
927  m_write_handler_allocator,
928  lib::bind(
929  &type::handle_async_write, get_shared(),
930  handler,
931  lib::placeholders::_1, lib::placeholders::_2
932  )
933  )
934  );
935  }
936  }
937 
938  /// Initiate a potentially asyncronous write of the given buffers
940  std::vector<buffer>::const_iterator it;
941 
942  for (it = bufs.begin(); it != bufs.end(); ++it) {
943  m_bufs.push_back(lib::asio::buffer((*it).buf,(*it).len));
944  }
945 
946  if (config::enable_multithreading) {
947  lib::asio::async_write(
948  socket_con_type::get_socket(),
949  m_bufs,
950  m_strand->wrap(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  } else {
960  lib::asio::async_write(
961  socket_con_type::get_socket(),
962  m_bufs,
963  make_custom_alloc_handler(
964  m_write_handler_allocator,
965  lib::bind(
966  &type::handle_async_write, get_shared(),
967  handler,
968  lib::placeholders::_1, lib::placeholders::_2
969  )
970  )
971  );
972  }
973  }
974 
975  /// Async write callback
976  /**
977  * @param ec The status code
978  * @param bytes_transferred The number of bytes read
979  */
980  void handle_async_write(write_handler handler, lib::asio::error_code const & ec, size_t) {
981  m_bufs.clear();
982  lib::error_code tec;
983  if (ec) {
984  log_err(log::elevel::info,"asio async_write",ec);
985  tec = make_error_code(transport::error::pass_through);
986  }
987  if (handler) {
988  handler(tec);
989  } else {
990  // This can happen in cases where the connection is terminated while
991  // the transport is waiting on a read.
992  m_alog->write(log::alevel::devel,
993  "handle_async_write called with null write handler");
994  }
995  }
996 
997  /// Set Connection Handle
998  /**
999  * See common/connection_hdl.hpp for information
1000  *
1001  * @param hdl A connection_hdl that the transport will use to refer
1002  * to itself
1003  */
1004  void set_handle(connection_hdl hdl) {
1005  m_connection_hdl = hdl;
1006  socket_con_type::set_handle(hdl);
1007  }
1008 
1009  /// Trigger the on_interrupt handler
1010  /**
1011  * This needs to be thread safe
1012  */
1014  if (config::enable_multithreading) {
1015  m_io_service->post(m_strand->wrap(handler));
1016  } else {
1017  m_io_service->post(handler);
1018  }
1019  return lib::error_code();
1020  }
1021 
1022  lib::error_code dispatch(dispatch_handler handler) {
1023  if (config::enable_multithreading) {
1024  m_io_service->post(m_strand->wrap(handler));
1025  } else {
1026  m_io_service->post(handler);
1027  }
1028  return lib::error_code();
1029  }
1030 
1031  /*void handle_interrupt(interrupt_handler handler) {
1032  handler();
1033  }*/
1034 
1035  /// close and clean up the underlying socket
1036  void async_shutdown(shutdown_handler callback) {
1037  if (m_alog->static_test(log::alevel::devel)) {
1038  m_alog->write(log::alevel::devel,"asio connection async_shutdown");
1039  }
1040 
1041  timer_ptr shutdown_timer;
1042  shutdown_timer = set_timer(
1043  config::timeout_socket_shutdown,
1044  lib::bind(
1045  &type::handle_async_shutdown_timeout,
1046  get_shared(),
1047  shutdown_timer,
1048  callback,
1049  lib::placeholders::_1
1050  )
1051  );
1052 
1053  socket_con_type::async_shutdown(
1054  lib::bind(
1055  &type::handle_async_shutdown,
1056  get_shared(),
1057  shutdown_timer,
1058  callback,
1059  lib::placeholders::_1
1060  )
1061  );
1062  }
1063 
1064  /// Async shutdown timeout handler
1065  /**
1066  * @param shutdown_timer A pointer to the timer to keep it in scope
1067  * @param callback The function to call back
1068  * @param ec The status code
1069  */
1070  void handle_async_shutdown_timeout(timer_ptr, init_handler callback,
1071  lib::error_code const & ec)
1072  {
1073  lib::error_code ret_ec;
1074 
1075  if (ec) {
1076  if (ec == transport::error::operation_aborted) {
1077  m_alog->write(log::alevel::devel,
1078  "asio socket shutdown timer cancelled");
1079  return;
1080  }
1081 
1082  log_err(log::elevel::devel,"asio handle_async_shutdown_timeout",ec);
1083  ret_ec = ec;
1084  } else {
1085  ret_ec = make_error_code(transport::error::timeout);
1086  }
1087 
1088  m_alog->write(log::alevel::devel,
1089  "Asio transport socket shutdown timed out");
1091  callback(ret_ec);
1092  }
1093 
1094  void handle_async_shutdown(timer_ptr shutdown_timer, shutdown_handler
1095  callback, lib::asio::error_code const & ec)
1096  {
1097  if (ec == lib::asio::error::operation_aborted ||
1098  lib::asio::is_neg(shutdown_timer->expires_from_now()))
1099  {
1100  m_alog->write(log::alevel::devel,"async_shutdown cancelled");
1101  return;
1102  }
1103 
1104  shutdown_timer->cancel();
1105 
1106  lib::error_code tec;
1107  if (ec) {
1108  if (ec == lib::asio::error::not_connected) {
1109  // The socket was already closed when we tried to close it. This
1110  // happens periodically (usually if a read or write fails
1111  // earlier and if it is a real error will be caught at another
1112  // level of the stack.
1113  } else {
1114  // We don't know anything more about this error, give our
1115  // socket/security policy a crack at it.
1116  tec = socket_con_type::translate_ec(ec);
1117  m_tec = ec;
1118 
1119  // all other errors are effectively pass through errors of
1120  // some sort so print some detail on the info channel for
1121  // library users to look up if needed.
1122  log_err(log::elevel::info,"asio async_shutdown",ec);
1123  }
1124  } else {
1125  if (m_alog->static_test(log::alevel::devel)) {
1126  m_alog->write(log::alevel::devel,
1127  "asio con handle_async_shutdown");
1128  }
1129  }
1130  callback(tec);
1131  }
1132 
1133  /// Cancel the underlying socket and log any errors
1135  lib::asio::error_code cec = socket_con_type::cancel_socket();
1136  if (cec) {
1137  if (cec == lib::asio::error::operation_not_supported) {
1138  // cancel not supported on this OS, ignore and log at dev level
1139  m_alog->write(log::alevel::devel, "socket cancel not supported");
1140  } else {
1141  log_err(log::elevel::warn, "socket cancel failed", cec);
1142  }
1143  }
1144  }
1145 
1146 private:
1147  /// Convenience method for logging the code and message for an error_code
1148  template <typename error_type>
1149  void log_err(log::level l, const char * msg, const error_type & ec) {
1150  std::stringstream s;
1151  s << msg << " error: " << ec << " (" << ec.message() << ")";
1152  m_elog->write(l,s.str());
1153  }
1154 
1155  // static settings
1156  const bool m_is_server;
1157  lib::shared_ptr<alog_type> m_alog;
1158  lib::shared_ptr<elog_type> m_elog;
1159 
1160  struct proxy_data {
1161  proxy_data() : timeout_proxy(config::timeout_proxy) {}
1162 
1163  request_type req;
1164  response_type res;
1165  std::string write_buf;
1166  lib::asio::streambuf read_buf;
1167  long timeout_proxy;
1168  timer_ptr timer;
1169  };
1170 
1171  std::string m_proxy;
1172  lib::shared_ptr<proxy_data> m_proxy_data;
1173 
1174  // transport resources
1175  io_service_ptr m_io_service;
1176  strand_ptr m_strand;
1177  connection_hdl m_connection_hdl;
1178 
1179  std::vector<lib::asio::const_buffer> m_bufs;
1180 
1181  /// Detailed internal error code
1182  lib::asio::error_code m_tec;
1183 
1184  // Handlers
1185  tcp_init_handler m_tcp_pre_init_handler;
1186  tcp_init_handler m_tcp_post_init_handler;
1187 
1188  handler_allocator m_read_handler_allocator;
1189  handler_allocator m_write_handler_allocator;
1190 };
1191 
1192 
1193 } // namespace asio
1194 } // namespace transport
1195 } // namespace websocketpp
1196 
1197 #endif // WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP
websocketpp::transport::asio::connection::alog_type
config::alog_type alog_type
Type of this transport's access logging policy.
Definition: connection.hpp:79
websocketpp::transport::asio::connection::strand_ptr
lib::shared_ptr< lib::asio::io_service::strand > strand_ptr
Type of a pointer to the Asio io_service::strand being used.
Definition: connection.hpp:91
websocketpp::transport::asio::connection::handle_proxy_read
void handle_proxy_read(init_handler callback, lib::asio::error_code const &ec, size_t)
Proxy read callback.
Definition: connection.hpp:742
websocketpp::transport::asio::connection::interrupt
lib::error_code interrupt(interrupt_handler handler)
Trigger the on_interrupt handler.
Definition: connection.hpp:1013
websocketpp::transport::asio::connection::set_proxy_timeout
void set_proxy_timeout(long duration, lib::error_code &ec)
Set the proxy timeout duration (exception free)
Definition: connection.hpp:250
websocketpp::transport::asio::connection::timer_ptr
lib::shared_ptr< lib::asio::steady_timer > timer_ptr
Type of a pointer to the Asio timer class.
Definition: connection.hpp:93
websocketpp::transport::asio::connection::set_proxy_timeout
void set_proxy_timeout(long duration)
Set the proxy timeout duration (exception)
Definition: connection.hpp:261
websocketpp::transport::asio::connection::type
connection< config > type
Type of this connection transport component.
Definition: connection.hpp:70
websocketpp::transport::asio::connection::handle_post_init
void handle_post_init(timer_ptr post_timer, init_handler callback, lib::error_code const &ec)
Post init timeout callback.
Definition: connection.hpp:572
websocketpp::transport::asio::connection::async_shutdown
void async_shutdown(shutdown_handler callback)
close and clean up the underlying socket
Definition: connection.hpp:1036
websocketpp::transport::asio::connection::get_shared
ptr get_shared()
Get a shared pointer to this component.
Definition: connection.hpp:110
websocketpp::transport::asio
Transport policy that uses asio.
Definition: base.hpp:46
websocketpp::transport::asio::connection::init_asio
lib::error_code init_asio(io_service_ptr io_service)
Finish constructing the transport.
Definition: connection.hpp:461
websocketpp::transport::asio::connection::socket_con_type
config::socket_type::socket_con_type socket_con_type
Type of the socket connection component.
Definition: connection.hpp:75
websocketpp::transport::asio::connection::set_proxy
void set_proxy(std::string const &uri)
Set the proxy to connect through (exception)
Definition: connection.hpp:198
websocketpp::transport::asio::connection::proxy_init
lib::error_code proxy_init(std::string const &authority)
initialize the proxy buffers and http parsers
Definition: connection.hpp:437
websocketpp::transport::asio::connection::set_handle
void set_handle(connection_hdl hdl)
Set Connection Handle.
Definition: connection.hpp:1004
websocketpp::versions_supported
static std::vector< int > const versions_supported(helper, helper+4)
Container that stores the list of protocol versions supported.
websocketpp::transport::asio::connection::async_read_at_least
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:820
websocketpp::transport::asio::connection::set_tcp_init_handler
void set_tcp_init_handler(tcp_init_handler h)
Sets the tcp pre init handler (deprecated)
Definition: connection.hpp:158
websocketpp::transport::dispatch_handler
lib::function< void()> dispatch_handler
The type and signature of the callback passed to the dispatch method.
Definition: connection.hpp:135
websocketpp::transport::asio::connection::elog_type
config::elog_type elog_type
Type of this transport's error logging policy.
Definition: connection.hpp:81
websocketpp::transport::asio::connection::set_uri
void set_uri(uri_ptr u)
Set uri hook.
Definition: connection.hpp:130
websocketpp::transport::asio::connection::set_proxy_basic_auth
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
websocketpp::transport::asio::connection::async_write
void async_write(const char *buf, size_t len, write_handler handler)
Initiate a potentially asyncronous write of the given buffer.
Definition: connection.hpp:906
websocketpp::transport::asio::connection::set_proxy_basic_auth
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
websocketpp::transport::asio::connection::cancel_socket_checked
void cancel_socket_checked()
Cancel the underlying socket and log any errors.
Definition: connection.hpp:1134
websocketpp::transport::asio::connection::get_handle
connection_hdl get_handle() const
Get the connection handle.
Definition: connection.hpp:295
websocketpp::transport::asio::connection::set_tcp_pre_init_handler
void set_tcp_pre_init_handler(tcp_init_handler h)
Sets the tcp pre init handler.
Definition: connection.hpp:144
websocketpp::transport::asio::connection::get_strand
strand_ptr get_strand()
Get a pointer to this connection's strand.
Definition: connection.hpp:366
websocketpp::transport::asio::connection::get_remote_endpoint
std::string get_remote_endpoint() const
Get the remote endpoint address.
Definition: connection.hpp:281
websocketpp::transport::asio::connection::handle_post_init_timeout
void handle_post_init_timeout(timer_ptr, init_handler callback, lib::error_code const &ec)
Post init timeout callback.
Definition: connection.hpp:536
websocketpp::transport::asio::connection::io_service_ptr
lib::asio::io_service * io_service_ptr
Type of a pointer to the Asio io_service being used.
Definition: connection.hpp:89
websocketpp::transport::asio::connection::socket_con_ptr
socket_con_type::ptr socket_con_ptr
Type of a shared pointer to the socket connection component.
Definition: connection.hpp:77
websocketpp::transport::asio::connection::handle_async_shutdown_timeout
void handle_async_shutdown_timeout(timer_ptr, init_handler callback, lib::error_code const &ec)
Async shutdown timeout handler.
Definition: connection.hpp:1070
websocketpp::transport::asio::connection::handle_async_write
void handle_async_write(write_handler handler, lib::asio::error_code const &ec, size_t)
Async write callback.
Definition: connection.hpp:980
websocketpp::transport::asio::connection::set_tcp_post_init_handler
void set_tcp_post_init_handler(tcp_init_handler h)
Sets the tcp post init handler.
Definition: connection.hpp:173
websocketpp::transport::asio::connection::async_write
void async_write(std::vector< buffer > const &bufs, write_handler handler)
Initiate a potentially asyncronous write of the given buffers.
Definition: connection.hpp:939
websocketpp::transport::asio::connection::get_transport_ec
lib::asio::error_code get_transport_ec() const
Get the internal transport error code for a closed/failed connection.
Definition: connection.hpp:389
websocketpp::transport::asio::connection::set_timer
timer_ptr set_timer(long duration, timer_handler callback)
Call back a function after a period of time.
Definition: connection.hpp:313
websocketpp::transport::asio::connection::set_proxy
void set_proxy(std::string const &uri, lib::error_code &ec)
Set the proxy to connect through (exception free)
Definition: connection.hpp:189
websocketpp::transport::asio::connection::init
void init(init_handler callback)
Initialize transport for reading.
Definition: connection.hpp:411
websocketpp::transport::asio::connection::handle_timer
void handle_timer(timer_ptr, timer_handler callback, lib::asio::error_code const &ec)
Timer callback.
Definition: connection.hpp:350