/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace websocketpp { namespace transport { namespace asio { typedef lib::function 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 class connection : public config::socket_type::socket_con_type { public: /// Type of this connection transport component typedef connection type; /// Type of a shared pointer to this connection transport component typedef lib::shared_ptr 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 strand_ptr; /// Type of a pointer to the Asio timer class typedef lib::shared_ptr 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; // 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(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(); 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::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(make_error_code(error::pass_through)); } } 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::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(make_error_code(error::pass_through)); 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(make_error_code(error::pass_through)); } 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 const & bufs, write_handler handler) { std::vector::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 = make_error_code(transport::error::pass_through); } 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 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 m_proxy_data; // transport resources io_service_ptr m_io_service; strand_ptr m_strand; connection_hdl m_connection_hdl; std::vector 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