text: /*
   * Copyright (c) 2015, Peter Thorson. All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *     * Redistributions of source code must retain the above copyright
   *       notice, this list of conditions and the following disclaimer.
   *     * Redistributions in binary form must reproduce the above copyright
   *       notice, this list of conditions and the following disclaimer in the
   *       documentation and/or other materials provided with the distribution.
   *     * Neither the name of the WebSocket++ Project nor the
   *       names of its contributors may be used to endorse or promote products
   *       derived from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *
   */
  
  #ifndef WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP
  #define WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP
  
  #include <websocketpp/transport/asio/base.hpp>
  
  #include <websocketpp/transport/base/connection.hpp>
  
  #include <websocketpp/logger/levels.hpp>
  #include <websocketpp/http/constants.hpp>
  
  #include <websocketpp/base64/base64.hpp>
  #include <websocketpp/error.hpp>
  #include <websocketpp/uri.hpp>
  
  #include <websocketpp/common/asio.hpp>
  #include <websocketpp/common/chrono.hpp>
  #include <websocketpp/common/cpp11.hpp>
  #include <websocketpp/common/memory.hpp>
  #include <websocketpp/common/functional.hpp>
  #include <websocketpp/common/connection_hdl.hpp>
  
  #include <istream>
  #include <sstream>
  #include <string>
  #include <vector>
  
  namespace websocketpp {
  namespace transport {
  namespace asio {
  
  typedef lib::function<void(connection_hdl)> tcp_init_handler;
  
  /// Asio based connection transport component
  /**
   * transport::asio::connection implements a connection transport component using
   * Asio that works with the transport::asio::endpoint endpoint transport
   * component.
   */
  template <typename config>
  class connection : public config::socket_type::socket_con_type {
  public:
      /// Type of this connection transport component
      typedef connection<config> type;
      /// Type of a shared pointer to this connection transport component
      typedef lib::shared_ptr<type> ptr;
  
      /// Type of the socket connection component
      typedef typename config::socket_type::socket_con_type socket_con_type;
      /// Type of a shared pointer to the socket connection component
      typedef typename socket_con_type::ptr socket_con_ptr;
      /// Type of this transport's access logging policy
      typedef typename config::alog_type alog_type;
      /// Type of this transport's error logging policy
      typedef typename config::elog_type elog_type;
  
      typedef typename config::request_type request_type;
      typedef typename request_type::ptr request_ptr;
      typedef typename config::response_type response_type;
      typedef typename response_type::ptr response_ptr;
  
      /// Type of a pointer to the Asio io_service being used
      typedef lib::asio::io_service * io_service_ptr;
      /// Type of a pointer to the Asio io_service::strand being used
      typedef lib::shared_ptr<lib::asio::io_service::strand> strand_ptr;
      /// Type of a pointer to the Asio timer class
      typedef lib::shared_ptr<lib::asio::steady_timer> timer_ptr;
  
      // connection is friends with its associated endpoint to allow the endpoint
      // to call private/protected utility methods that we don't want to expose
      // to the public api.
      friend class endpoint<config>;
  
      // generate and manage our own io_service
      explicit connection(bool is_server, alog_type & alog, elog_type & elog)
        : m_is_server(is_server)
        , m_alog(alog)
        , m_elog(elog)
      {
          m_alog.write(log::alevel::devel,"asio con transport constructor");
      }
  
      /// Get a shared pointer to this component
      ptr get_shared() {
          return lib::static_pointer_cast<type>(socket_con_type::get_shared());
      }
  
      bool is_secure() const {
          return socket_con_type::is_secure();
      }
  
      /// Set uri hook
      /**
       * Called by the endpoint as a connection is being established to provide
       * the uri being connected to to the transport layer.
       *
       * This transport policy doesn't use the uri except to forward it to the 
       * socket layer.
       *
       * @since 0.6.0
       *
       * @param u The uri to set
       */
      void set_uri(uri_ptr u) {
          socket_con_type::set_uri(u);
      }
  
      /// Sets the tcp pre init handler
      /**
       * The tcp pre init handler is called after the raw tcp connection has been
       * established but before any additional wrappers (proxy connects, TLS
       * handshakes, etc) have been performed.
       *
       * @since 0.3.0
       *
       * @param h The handler to call on tcp pre init.
       */
      void set_tcp_pre_init_handler(tcp_init_handler h) {
          m_tcp_pre_init_handler = h;
      }
  
      /// Sets the tcp pre init handler (deprecated)
      /**
       * The tcp pre init handler is called after the raw tcp connection has been
       * established but before any additional wrappers (proxy connects, TLS
       * handshakes, etc) have been performed.
       *
       * @deprecated Use set_tcp_pre_init_handler instead
       *
       * @param h The handler to call on tcp pre init.
       */
      void set_tcp_init_handler(tcp_init_handler h) {
          set_tcp_pre_init_handler(h);
      }
  
      /// Sets the tcp post init handler
      /**
       * The tcp post init handler is called after the tcp connection has been
       * established and all additional wrappers (proxy connects, TLS handshakes,
       * etc have been performed. This is fired before any bytes are read or any
       * WebSocket specific handshake logic has been performed.
       *
       * @since 0.3.0
       *
       * @param h The handler to call on tcp post init.
       */
      void set_tcp_post_init_handler(tcp_init_handler h) {
          m_tcp_post_init_handler = h;
      }
  
      /// Set the proxy to connect through (exception free)
      /**
       * The URI passed should be a complete URI including scheme. For example:
       * http://proxy.example.com:8080/
       *
       * The proxy must be set up as an explicit (CONNECT) proxy allowed to
       * connect to the port you specify. Traffic to the proxy is not encrypted.
       *
       * @param uri The full URI of the proxy to connect to.
       *
       * @param ec A status value
       */
      void set_proxy(std::string const & uri, lib::error_code & ec) {
          // TODO: return errors for illegal URIs here?
          // TODO: should https urls be illegal for the moment?
          m_proxy = uri;
          m_proxy_data = lib::make_shared<proxy_data>();
          ec = lib::error_code();
      }
  
      /// Set the proxy to connect through (exception)
      void set_proxy(std::string const & uri) {
          lib::error_code ec;
          set_proxy(uri,ec);
          if (ec) { throw exception(ec); }
      }
  
      /// Set the basic auth credentials to use (exception free)
      /**
       * The URI passed should be a complete URI including scheme. For example:
       * http://proxy.example.com:8080/
       *
       * The proxy must be set up as an explicit proxy
       *
       * @param username The username to send
       *
       * @param password The password to send
       *
       * @param ec A status value
       */
      void set_proxy_basic_auth(std::string const & username, std::string const &
          password, lib::error_code & ec)
      {
          if (!m_proxy_data) {
              ec = make_error_code(websocketpp::error::invalid_state);
              return;
          }
  
          // TODO: username can't contain ':'
          std::string val = "Basic "+base64_encode(username + ":" + password);
          m_proxy_data->req.replace_header("Proxy-Authorization",val);
          ec = lib::error_code();
      }
  
      /// Set the basic auth credentials to use (exception)
      void set_proxy_basic_auth(std::string const & username, std::string const &
          password)
      {
          lib::error_code ec;
          set_proxy_basic_auth(username,password,ec);
          if (ec) { throw exception(ec); }
      }
  
      /// Set the proxy timeout duration (exception free)
      /**
       * Duration is in milliseconds. Default value is based on the transport
       * config
       *
       * @param duration The number of milliseconds to wait before aborting the
       * proxy connection.
       *
       * @param ec A status value
       */
      void set_proxy_timeout(long duration, lib::error_code & ec) {
          if (!m_proxy_data) {
              ec = make_error_code(websocketpp::error::invalid_state);
              return;
          }
  
          m_proxy_data->timeout_proxy = duration;
          ec = lib::error_code();
      }
  
      /// Set the proxy timeout duration (exception)
      void set_proxy_timeout(long duration) {
          lib::error_code ec;
          set_proxy_timeout(duration,ec);
          if (ec) { throw exception(ec); }
      }
  
      std::string const & get_proxy() const {
          return m_proxy;
      }
  
      /// Get the remote endpoint address
      /**
       * The iostream transport has no information about the ultimate remote
       * endpoint. It will return the string "iostream transport". To indicate
       * this.
       *
       * TODO: allow user settable remote endpoint addresses if this seems useful
       *
       * @return A string identifying the address of the remote endpoint
       */
      std::string get_remote_endpoint() const {
          lib::error_code ec;
  
          std::string ret = socket_con_type::get_remote_endpoint(ec);
  
          if (ec) {
              m_elog.write(log::elevel::info,ret);
              return "Unknown";
          } else {
              return ret;
          }
      }
  
      /// Get the connection handle
      connection_hdl get_handle() const {
          return m_connection_hdl;
      }
  
      /// Call back a function after a period of time.
      /**
       * Sets a timer that calls back a function after the specified period of
       * milliseconds. Returns a handle that can be used to cancel the timer.
       * A cancelled timer will return the error code error::operation_aborted
       * A timer that expired will return no error.
       *
       * @param duration Length of time to wait in milliseconds
       *
       * @param callback The function to call back when the timer has expired
       *
       * @return A handle that can be used to cancel the timer if it is no longer
       * needed.
       */
      timer_ptr set_timer(long duration, timer_handler callback) {
          timer_ptr new_timer = lib::make_shared<lib::asio::steady_timer>(
              lib::ref(*m_io_service),
              lib::asio::milliseconds(duration)
          );
  
          if (config::enable_multithreading) {
              new_timer->async_wait(m_strand->wrap(lib::bind(
                  &type::handle_timer, get_shared(),
                  new_timer,
                  callback,
                  lib::placeholders::_1
              )));
          } else {
              new_timer->async_wait(lib::bind(
                  &type::handle_timer, get_shared(),
                  new_timer,
                  callback,
                  lib::placeholders::_1
              ));
          }
  
          return new_timer;
      }
  
      /// Timer callback
      /**
       * The timer pointer is included to ensure the timer isn't destroyed until
       * after it has expired.
       *
       * TODO: candidate for protected status
       *
       * @param post_timer Pointer to the timer in question
       * @param callback The function to call back
       * @param ec The status code
       */
      void handle_timer(timer_ptr, timer_handler callback,
          lib::asio::error_code const & ec)
      {
          if (ec) {
              if (ec == lib::asio::error::operation_aborted) {
                  callback(make_error_code(transport::error::operation_aborted));
              } else {
                  log_err(log::elevel::info,"asio handle_timer",ec);
                  callback(ec);
              }
          } else {
              callback(lib::error_code());
          }
      }
  
      /// Get a pointer to this connection's strand
      strand_ptr get_strand() {
          return m_strand;
      }
  
      /// Get the internal transport error code for a closed/failed connection
      /**
       * Retrieves a machine readable detailed error code indicating the reason
       * that the connection was closed or failed. Valid only after the close or
       * fail handler is called.
       *
       * Primarily used if you are using mismatched asio / system_error
       * implementations such as `boost::asio` with `std::system_error`. In these
       * cases the transport error type is different than the library error type
       * and some WebSocket++ functions that return transport errors via the 
       * library error code type will be coerced into a catch all `pass_through`
       * or `tls_error` error. This method will return the original machine 
       * readable transport error in the native type.
       *
       * @since 0.7.0
       *
       * @return Error code indicating the reason the connection was closed or 
       * failed
       */
      lib::asio::error_code get_transport_ec() const {
          return m_tec;
      }
  
      /// Initialize transport for reading
      /**
       * init_asio is called once immediately after construction to initialize
       * Asio components to the io_service
       *
       * The transport initialization sequence consists of the following steps:
       * - Pre-init: the underlying socket is initialized to the point where
       * bytes may be written. No bytes are actually written in this stage
       * - Proxy negotiation: if a proxy is set, a request is made to it to start
       * a tunnel to the final destination. This stage ends when the proxy is
       * ready to forward the
       * next byte to the remote endpoint.
       * - Post-init: Perform any i/o with the remote endpoint, such as setting up
       * tunnels for encryption. This stage ends when the connection is ready to
       * read or write the WebSocket handshakes. At this point the original
       * callback function is called.
       */
  protected:
      void init(init_handler callback) {
          if (m_alog.static_test(log::alevel::devel)) {
              m_alog.write(log::alevel::devel,"asio connection init");
          }
  
          // TODO: pre-init timeout. Right now no implemented socket policies
          // actually have an asyncronous pre-init
  
          socket_con_type::pre_init(
              lib::bind(
                  &type::handle_pre_init,
                  get_shared(),
                  callback,
                  lib::placeholders::_1
              )
          );
      }
  
      /// initialize the proxy buffers and http parsers
      /**
       *
       * @param authority The address of the server we want the proxy to tunnel to
       * in the format of a URI authority (host:port)
       *
       * @return Status code indicating what errors occurred, if any
       */
      lib::error_code proxy_init(std::string const & authority) {
          if (!m_proxy_data) {
              return websocketpp::error::make_error_code(
                  websocketpp::error::invalid_state);
          }
          m_proxy_data->req.set_version("HTTP/1.1");
          m_proxy_data->req.set_method("CONNECT");
  
          m_proxy_data->req.set_uri(authority);
          m_proxy_data->req.replace_header("Host",authority);
  
          return lib::error_code();
      }
  
      /// Finish constructing the transport
      /**
       * init_asio is called once immediately after construction to initialize
       * Asio components to the io_service.
       *
       * @param io_service A pointer to the io_service to register with this
       * connection
       *
       * @return Status code for the success or failure of the initialization
       */
      lib::error_code init_asio (io_service_ptr io_service) {
          m_io_service = io_service;
  
          if (config::enable_multithreading) {
              m_strand = lib::make_shared<lib::asio::io_service::strand>(
                  lib::ref(*io_service));
          }
  
          lib::error_code ec = socket_con_type::init_asio(io_service, m_strand,
              m_is_server);
  
          return ec;
      }
  
      void handle_pre_init(init_handler callback, lib::error_code const & ec) {
          if (m_alog.static_test(log::alevel::devel)) {
              m_alog.write(log::alevel::devel,"asio connection handle pre_init");
          }
  
          if (m_tcp_pre_init_handler) {
              m_tcp_pre_init_handler(m_connection_hdl);
          }
  
          if (ec) {
              callback(ec);
          }
  
          // If we have a proxy set issue a proxy connect, otherwise skip to
          // post_init
          if (!m_proxy.empty()) {
              proxy_write(callback);
          } else {
              post_init(callback);
          }
      }
  
      void post_init(init_handler callback) {
          if (m_alog.static_test(log::alevel::devel)) {
              m_alog.write(log::alevel::devel,"asio connection post_init");
          }
  
          timer_ptr post_timer;
          
          if (config::timeout_socket_post_init > 0) {
              post_timer = set_timer(
                  config::timeout_socket_post_init,
                  lib::bind(
                      &type::handle_post_init_timeout,
                      get_shared(),
                      post_timer,
                      callback,
                      lib::placeholders::_1
                  )
              );
          }
  
          socket_con_type::post_init(
              lib::bind(
                  &type::handle_post_init,
                  get_shared(),
                  post_timer,
                  callback,
                  lib::placeholders::_1
              )
          );
      }
  
      /// Post init timeout callback
      /**
       * The timer pointer is included to ensure the timer isn't destroyed until
       * after it has expired.
       *
       * @param post_timer Pointer to the timer in question
       * @param callback The function to call back
       * @param ec The status code
       */
      void handle_post_init_timeout(timer_ptr, init_handler callback,
          lib::error_code const & ec)
      {
          lib::error_code ret_ec;
  
          if (ec) {
              if (ec == transport::error::operation_aborted) {
                  m_alog.write(log::alevel::devel,
                      "asio post init timer cancelled");
                  return;
              }
  
              log_err(log::elevel::devel,"asio handle_post_init_timeout",ec);
              ret_ec = ec;
          } else {
              if (socket_con_type::get_ec()) {
                  ret_ec = socket_con_type::get_ec();
              } else {
                  ret_ec = make_error_code(transport::error::timeout);
              }
          }
  
          m_alog.write(log::alevel::devel, "Asio transport post-init timed out");
          cancel_socket_checked();
          callback(ret_ec);
      }
  
      /// Post init timeout callback
      /**
       * The timer pointer is included to ensure the timer isn't destroyed until
       * after it has expired.
       *
       * @param post_timer Pointer to the timer in question
       * @param callback The function to call back
       * @param ec The status code
       */
      void handle_post_init(timer_ptr post_timer, init_handler callback,
          lib::error_code const & ec)
      {
          if (ec == transport::error::operation_aborted ||
              (post_timer && lib::asio::is_neg(post_timer->expires_from_now())))
          {
              m_alog.write(log::alevel::devel,"post_init cancelled");
              return;
          }
  
          if (post_timer) {
              post_timer->cancel();
          }
  
          if (m_alog.static_test(log::alevel::devel)) {
              m_alog.write(log::alevel::devel,"asio connection handle_post_init");
          }
  
          if (m_tcp_post_init_handler) {
              m_tcp_post_init_handler(m_connection_hdl);
          }
  
          callback(ec);
      }
  
      void proxy_write(init_handler callback) {
          if (m_alog.static_test(log::alevel::devel)) {
              m_alog.write(log::alevel::devel,"asio connection proxy_write");
          }
  
          if (!m_proxy_data) {
              m_elog.write(log::elevel::library,
                  "assertion failed: !m_proxy_data in asio::connection::proxy_write");
              callback(make_error_code(error::general));
              return;
          }
  
          m_proxy_data->write_buf = m_proxy_data->req.raw();
  
          m_bufs.push_back(lib::asio::buffer(m_proxy_data->write_buf.data(),
                                             m_proxy_data->write_buf.size()));
  
          m_alog.write(log::alevel::devel,m_proxy_data->write_buf);
  
          // Set a timer so we don't wait forever for the proxy to respond
          m_proxy_data->timer = this->set_timer(
              m_proxy_data->timeout_proxy,
              lib::bind(
                  &type::handle_proxy_timeout,
                  get_shared(),
                  callback,
                  lib::placeholders::_1
              )
          );
  
          // Send proxy request
          if (config::enable_multithreading) {
              lib::asio::async_write(
                  socket_con_type::get_next_layer(),
                  m_bufs,
                  m_strand->wrap(lib::bind(
                      &type::handle_proxy_write, get_shared(),
                      callback,
                      lib::placeholders::_1
                  ))
              );
          } else {
              lib::asio::async_write(
                  socket_con_type::get_next_layer(),
                  m_bufs,
                  lib::bind(
                      &type::handle_proxy_write, get_shared(),
                      callback,
                      lib::placeholders::_1
                  )
              );
          }
      }
  
      void handle_proxy_timeout(init_handler callback, lib::error_code const & ec)
      {
          if (ec == transport::error::operation_aborted) {
              m_alog.write(log::alevel::devel,
                  "asio handle_proxy_write timer cancelled");
              return;
          } else if (ec) {
              log_err(log::elevel::devel,"asio handle_proxy_write",ec);
              callback(ec);
          } else {
              m_alog.write(log::alevel::devel,
                  "asio handle_proxy_write timer expired");
              cancel_socket_checked();
              callback(make_error_code(transport::error::timeout));
          }
      }
  
      void handle_proxy_write(init_handler callback,
          lib::asio::error_code const & ec)
      {
          if (m_alog.static_test(log::alevel::devel)) {
              m_alog.write(log::alevel::devel,
                  "asio connection handle_proxy_write");
          }
  
          m_bufs.clear();
  
          // Timer expired or the operation was aborted for some reason.
          // Whatever aborted it will be issuing the callback so we are safe to
          // return
          if (ec == lib::asio::error::operation_aborted ||
              lib::asio::is_neg(m_proxy_data->timer->expires_from_now()))
          {
              m_elog.write(log::elevel::devel,"write operation aborted");
              return;
          }
  
          if (ec) {
              log_err(log::elevel::info,"asio handle_proxy_write",ec);
              m_proxy_data->timer->cancel();
              callback(ec);
              return;
          }
  
          proxy_read(callback);
      }
  
      void proxy_read(init_handler callback) {
          if (m_alog.static_test(log::alevel::devel)) {
              m_alog.write(log::alevel::devel,"asio connection proxy_read");
          }
  
          if (!m_proxy_data) {
              m_elog.write(log::elevel::library,
                  "assertion failed: !m_proxy_data in asio::connection::proxy_read");
              m_proxy_data->timer->cancel();
              callback(make_error_code(error::general));
              return;
          }
  
          if (config::enable_multithreading) {
              lib::asio::async_read_until(
                  socket_con_type::get_next_layer(),
                  m_proxy_data->read_buf,
                  "\r\n\r\n",
                  m_strand->wrap(lib::bind(
                      &type::handle_proxy_read, get_shared(),
                      callback,
                      lib::placeholders::_1, lib::placeholders::_2
                  ))
              );
          } else {
              lib::asio::async_read_until(
                  socket_con_type::get_next_layer(),
                  m_proxy_data->read_buf,
                  "\r\n\r\n",
                  lib::bind(
                      &type::handle_proxy_read, get_shared(),
                      callback,
                      lib::placeholders::_1, lib::placeholders::_2
                  )
              );
          }
      }
  
      /// Proxy read callback
      /**
       * @param init_handler The function to call back
       * @param ec The status code
       * @param bytes_transferred The number of bytes read
       */
      void handle_proxy_read(init_handler callback,
          lib::asio::error_code const & ec, size_t)
      {
          if (m_alog.static_test(log::alevel::devel)) {
              m_alog.write(log::alevel::devel,
                  "asio connection handle_proxy_read");
          }
  
          // Timer expired or the operation was aborted for some reason.
          // Whatever aborted it will be issuing the callback so we are safe to
          // return
          if (ec == lib::asio::error::operation_aborted ||
              lib::asio::is_neg(m_proxy_data->timer->expires_from_now()))
          {
              m_elog.write(log::elevel::devel,"read operation aborted");
              return;
          }
  
          // At this point there is no need to wait for the timer anymore
          m_proxy_data->timer->cancel();
  
          if (ec) {
              m_elog.write(log::elevel::info,
                  "asio handle_proxy_read error: "+ec.message());
              callback(ec);
          } else {
              if (!m_proxy_data) {
                  m_elog.write(log::elevel::library,
                      "assertion failed: !m_proxy_data in asio::connection::handle_proxy_read");
                  callback(make_error_code(error::general));
                  return;
              }
  
              std::istream input(&m_proxy_data->read_buf);
  
              m_proxy_data->res.consume(input);
  
              if (!m_proxy_data->res.headers_ready()) {
                  // we read until the headers were done in theory but apparently
                  // they aren't. Internal endpoint error.
                  callback(make_error_code(error::general));
                  return;
              }
  
              m_alog.write(log::alevel::devel,m_proxy_data->res.raw());
  
              if (m_proxy_data->res.get_status_code() != http::status_code::ok) {
                  // got an error response back
                  // TODO: expose this error in a programmatically accessible way?
                  // if so, see below for an option on how to do this.
                  std::stringstream s;
                  s << "Proxy connection error: "
                    << m_proxy_data->res.get_status_code()
                    << " ("
                    << m_proxy_data->res.get_status_msg()
                    << ")";
                  m_elog.write(log::elevel::info,s.str());
                  callback(make_error_code(error::proxy_failed));
                  return;
              }
  
              // we have successfully established a connection to the proxy, now
              // we can continue and the proxy will transparently forward the
              // WebSocket connection.
  
              // TODO: decide if we want an on_proxy callback that would allow
              // access to the proxy response.
  
              // free the proxy buffers and req/res objects as they aren't needed
              // anymore
              m_proxy_data.reset();
  
              // Continue with post proxy initialization
              post_init(callback);
          }
      }
  
      /// read at least num_bytes bytes into buf and then call handler.
      void async_read_at_least(size_t num_bytes, char *buf, size_t len,
          read_handler handler)
      {
          if (m_alog.static_test(log::alevel::devel)) {
              std::stringstream s;
              s << "asio async_read_at_least: " << num_bytes;
              m_alog.write(log::alevel::devel,s.str());
          }
  
          // TODO: safety vs speed ?
          // maybe move into an if devel block
          /*if (num_bytes > len) {
              m_elog.write(log::elevel::devel,
                  "asio async_read_at_least error::invalid_num_bytes");
              handler(make_error_code(transport::error::invalid_num_bytes),
                  size_t(0));
              return;
          }*/
  
          if (config::enable_multithreading) {
              lib::asio::async_read(
                  socket_con_type::get_socket(),
                  lib::asio::buffer(buf,len),
                  lib::asio::transfer_at_least(num_bytes),
                  m_strand->wrap(make_custom_alloc_handler(
                      m_read_handler_allocator,
                      lib::bind(
                          &type::handle_async_read, get_shared(),
                          handler,
                          lib::placeholders::_1, lib::placeholders::_2
                      )
                  ))
              );
          } else {
              lib::asio::async_read(
                  socket_con_type::get_socket(),
                  lib::asio::buffer(buf,len),
                  lib::asio::transfer_at_least(num_bytes),
                  make_custom_alloc_handler(
                      m_read_handler_allocator,
                      lib::bind(
                          &type::handle_async_read, get_shared(),
                          handler,
                          lib::placeholders::_1, lib::placeholders::_2
                      )
                  )
              );    
          }
          
      }
  
      void handle_async_read(read_handler handler, lib::asio::error_code const & ec,
          size_t bytes_transferred)
      {
          m_alog.write(log::alevel::devel, "asio con handle_async_read");
  
          // translate asio error codes into more lib::error_codes
          lib::error_code tec;
          if (ec == lib::asio::error::eof) {
              tec = make_error_code(transport::error::eof);
          } else if (ec) {
              // We don't know much more about the error at this point. As our
              // socket/security policy if it knows more:
              tec = socket_con_type::translate_ec(ec);
              m_tec = ec;
  
              if (tec == transport::error::tls_error ||
                  tec == transport::error::pass_through)
              {
                  // These are aggregate/catch all errors. Log some human readable
                  // information to the info channel to give library users some
                  // more details about why the upstream method may have failed.
                  log_err(log::elevel::info,"asio async_read_at_least",ec);
              }
          }
          if (handler) {
              handler(tec,bytes_transferred);
          } else {
              // This can happen in cases where the connection is terminated while
              // the transport is waiting on a read.
              m_alog.write(log::alevel::devel,
                  "handle_async_read called with null read handler");
          }
      }
  
      /// Initiate a potentially asyncronous write of the given buffer
      void async_write(const char* buf, size_t len, write_handler handler) {
          m_bufs.push_back(lib::asio::buffer(buf,len));
  
          if (config::enable_multithreading) {
              lib::asio::async_write(
                  socket_con_type::get_socket(),
                  m_bufs,
                  m_strand->wrap(make_custom_alloc_handler(
                      m_write_handler_allocator,
                      lib::bind(
                          &type::handle_async_write, get_shared(),
                          handler,
                          lib::placeholders::_1, lib::placeholders::_2
                      )
                  ))
              );
          } else {
              lib::asio::async_write(
                  socket_con_type::get_socket(),
                  m_bufs,
                  make_custom_alloc_handler(
                      m_write_handler_allocator,
                      lib::bind(
                          &type::handle_async_write, get_shared(),
                          handler,
                          lib::placeholders::_1, lib::placeholders::_2
                      )
                  )
              );
          }
      }
  
      /// Initiate a potentially asyncronous write of the given buffers
      void async_write(std::vector<buffer> const & bufs, write_handler handler) {
          std::vector<buffer>::const_iterator it;
  
          for (it = bufs.begin(); it != bufs.end(); ++it) {
              m_bufs.push_back(lib::asio::buffer((*it).buf,(*it).len));
          }
  
          if (config::enable_multithreading) {
              lib::asio::async_write(
                  socket_con_type::get_socket(),
                  m_bufs,
                  m_strand->wrap(make_custom_alloc_handler(
                      m_write_handler_allocator,
                      lib::bind(
                          &type::handle_async_write, get_shared(),
                          handler,
                          lib::placeholders::_1, lib::placeholders::_2
                      )
                  ))
              );
          } else {
              lib::asio::async_write(
                  socket_con_type::get_socket(),
                  m_bufs,
                  make_custom_alloc_handler(
                      m_write_handler_allocator,
                      lib::bind(
                          &type::handle_async_write, get_shared(),
                          handler,
                          lib::placeholders::_1, lib::placeholders::_2
                      )
                  )
              );
          }
      }
  
      /// Async write callback
      /**
       * @param ec The status code
       * @param bytes_transferred The number of bytes read
       */
      void handle_async_write(write_handler handler, lib::asio::error_code const & ec, size_t) {
          m_bufs.clear();
          lib::error_code tec;
          if (ec) {
              log_err(log::elevel::info,"asio async_write",ec);
              tec = ec;
          }
          if (handler) {
              handler(tec);
          } else {
              // This can happen in cases where the connection is terminated while
              // the transport is waiting on a read.
              m_alog.write(log::alevel::devel,
                  "handle_async_write called with null write handler");
          }
      }
  
      /// Set Connection Handle
      /**
       * See common/connection_hdl.hpp for information
       *
       * @param hdl A connection_hdl that the transport will use to refer
       * to itself
       */
      void set_handle(connection_hdl hdl) {
          m_connection_hdl = hdl;
          socket_con_type::set_handle(hdl);
      }
  
      /// Trigger the on_interrupt handler
      /**
       * This needs to be thread safe
       */
      lib::error_code interrupt(interrupt_handler handler) {
          if (config::enable_multithreading) {
              m_io_service->post(m_strand->wrap(handler));
          } else {
              m_io_service->post(handler);
          }
          return lib::error_code();
      }
  
      lib::error_code dispatch(dispatch_handler handler) {
          if (config::enable_multithreading) {
              m_io_service->post(m_strand->wrap(handler));
          } else {
              m_io_service->post(handler);
          }
          return lib::error_code();
      }
  
      /*void handle_interrupt(interrupt_handler handler) {
          handler();
      }*/
  
      /// close and clean up the underlying socket
      void async_shutdown(shutdown_handler callback) {
          if (m_alog.static_test(log::alevel::devel)) {
              m_alog.write(log::alevel::devel,"asio connection async_shutdown");
          }
  
          timer_ptr shutdown_timer;
          shutdown_timer = set_timer(
              config::timeout_socket_shutdown,
              lib::bind(
                  &type::handle_async_shutdown_timeout,
                  get_shared(),
                  shutdown_timer,
                  callback,
                  lib::placeholders::_1
              )
          );
  
          socket_con_type::async_shutdown(
              lib::bind(
                  &type::handle_async_shutdown,
                  get_shared(),
                  shutdown_timer,
                  callback,
                  lib::placeholders::_1
              )
          );
      }
  
      /// Async shutdown timeout handler
      /**
       * @param shutdown_timer A pointer to the timer to keep it in scope
       * @param callback The function to call back
       * @param ec The status code
       */
      void handle_async_shutdown_timeout(timer_ptr, init_handler callback, 
          lib::error_code const & ec)
      {
          lib::error_code ret_ec;
  
          if (ec) {
              if (ec == transport::error::operation_aborted) {
                  m_alog.write(log::alevel::devel,
                      "asio socket shutdown timer cancelled");
                  return;
              }
  
              log_err(log::elevel::devel,"asio handle_async_shutdown_timeout",ec);
              ret_ec = ec;
          } else {
              ret_ec = make_error_code(transport::error::timeout);
          }
  
          m_alog.write(log::alevel::devel,
              "Asio transport socket shutdown timed out");
          cancel_socket_checked();
          callback(ret_ec);
      }
  
      void handle_async_shutdown(timer_ptr shutdown_timer, shutdown_handler
          callback, lib::asio::error_code const & ec)
      {
          if (ec == lib::asio::error::operation_aborted ||
              lib::asio::is_neg(shutdown_timer->expires_from_now()))
          {
              m_alog.write(log::alevel::devel,"async_shutdown cancelled");
              return;
          }
  
          shutdown_timer->cancel();
  
          lib::error_code tec;
          if (ec) {
              if (ec == lib::asio::error::not_connected) {
                  // The socket was already closed when we tried to close it. This
                  // happens periodically (usually if a read or write fails
                  // earlier and if it is a real error will be caught at another
                  // level of the stack.
              } else {
                  // We don't know anything more about this error, give our
                  // socket/security policy a crack at it.
                  tec = socket_con_type::translate_ec(ec);
                  m_tec = ec;
  
                  if (tec == transport::error::tls_short_read) {
                      // TLS short read at this point is somewhat expected if both
                      // sides try and end the connection at the same time or if
                      // SSLv2 is being used. In general there is nothing that can
                      // be done here other than a low level development log.
                  } else {
                      // all other errors are effectively pass through errors of
                      // some sort so print some detail on the info channel for
                      // library users to look up if needed.
                      log_err(log::elevel::info,"asio async_shutdown",ec);
                  }
              }
          } else {
              if (m_alog.static_test(log::alevel::devel)) {
                  m_alog.write(log::alevel::devel,
                      "asio con handle_async_shutdown");
              }
          }
          callback(tec);
      }
  
      /// Cancel the underlying socket and log any errors
      void cancel_socket_checked() {
          lib::asio::error_code cec = socket_con_type::cancel_socket();
          if (cec) {
              if (cec == lib::asio::error::operation_not_supported) {
                  // cancel not supported on this OS, ignore and log at dev level
                  m_alog.write(log::alevel::devel, "socket cancel not supported");
              } else {
                  log_err(log::elevel::warn, "socket cancel failed", cec);
              }
          }
      }
  
  private:
      /// Convenience method for logging the code and message for an error_code
      template <typename error_type>
      void log_err(log::level l, const char * msg, const error_type & ec) {
          std::stringstream s;
          s << msg << " error: " << ec << " (" << ec.message() << ")";
          m_elog.write(l,s.str());
      }
  
      // static settings
      const bool m_is_server;
      alog_type& m_alog;
      elog_type& m_elog;
  
      struct proxy_data {
          proxy_data() : timeout_proxy(config::timeout_proxy) {}
  
          request_type req;
          response_type res;
          std::string write_buf;
          lib::asio::streambuf read_buf;
          long timeout_proxy;
          timer_ptr timer;
      };
  
      std::string m_proxy;
      lib::shared_ptr<proxy_data> m_proxy_data;
  
      // transport resources
      io_service_ptr  m_io_service;
      strand_ptr      m_strand;
      connection_hdl  m_connection_hdl;
  
      std::vector<lib::asio::const_buffer> m_bufs;
  
      /// Detailed internal error code
      lib::asio::error_code m_tec;
  
      // Handlers
      tcp_init_handler    m_tcp_pre_init_handler;
      tcp_init_handler    m_tcp_post_init_handler;
  
      handler_allocator   m_read_handler_allocator;
      handler_allocator   m_write_handler_allocator;
  };
  
  
  } // namespace asio
  } // namespace transport
  } // namespace websocketpp
  
  #endif // WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP
  