From 7d8f1ea163abf98da7dac56f655f88815a021ca0 Mon Sep 17 00:00:00 2001 From: Peter Thorson Date: Tue, 7 May 2013 07:02:17 -0500 Subject: [PATCH 01/14] revert ignore of TLS short read references #224 --- websocketpp/impl/connection_impl.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/websocketpp/impl/connection_impl.hpp b/websocketpp/impl/connection_impl.hpp index 1b0b0f02d4..314097d199 100644 --- a/websocketpp/impl/connection_impl.hpp +++ b/websocketpp/impl/connection_impl.hpp @@ -745,7 +745,8 @@ void connection::handle_read_frame(const lib::error_code& ec, } } if (ec == transport::error::tls_short_read) { - m_elog.write(log::elevel::rerror,"got TLS short read, ignore for the moment"); + m_elog.write(log::elevel::rerror,"got TLS short read, killing connection for now"); + this->terminate(); return; } From 42ca501f76012dcedc54062e84d93b8691011b47 Mon Sep 17 00:00:00 2001 From: Peter Thorson Date: Tue, 7 May 2013 09:35:13 -0500 Subject: [PATCH 02/14] adds async_shutdown interface to transport connections --- websocketpp/transport/asio/base.hpp | 8 +++- websocketpp/transport/asio/connection.hpp | 45 ++++++++++++++++--- websocketpp/transport/asio/security/none.hpp | 5 +-- websocketpp/transport/asio/security/tls.hpp | 17 ++++--- websocketpp/transport/base/connection.hpp | 1 + websocketpp/transport/iostream/connection.hpp | 4 +- 6 files changed, 60 insertions(+), 20 deletions(-) diff --git a/websocketpp/transport/asio/base.hpp b/websocketpp/transport/asio/base.hpp index 3796fae2f0..f313b916e4 100644 --- a/websocketpp/transport/asio/base.hpp +++ b/websocketpp/transport/asio/base.hpp @@ -28,8 +28,11 @@ #ifndef WEBSOCKETPP_TRANSPORT_ASIO_BASE_HPP #define WEBSOCKETPP_TRANSPORT_ASIO_BASE_HPP -#include #include +#include +#include + +#include #include @@ -37,6 +40,9 @@ namespace websocketpp { namespace transport { namespace asio { +typedef lib::function + socket_shutdown_handler; + /** * This policy uses a single boost::asio io_service to provide transport * services to a WebSocket++ endpoint. diff --git a/websocketpp/transport/asio/connection.hpp b/websocketpp/transport/asio/connection.hpp index 7cf155bf28..d5716eca54 100644 --- a/websocketpp/transport/asio/connection.hpp +++ b/websocketpp/transport/asio/connection.hpp @@ -503,9 +503,12 @@ protected: boost::system::error_code& ec) { m_bufs.clear(); - // TODO: translate this better if (ec) { - handler(make_error_code(error::pass_through)); + std::stringstream s; + s << "asio async_write error: " << ec + << " (" << ec.message() << ")"; + m_elog.write(log::elevel::info,s.str()); + handler(make_error_code(transport::error::pass_through)); } else { handler(lib::error_code()); } @@ -544,8 +547,37 @@ protected: }*/ /// close and clean up the underlying socket - void shutdown() { - socket_con_type::shutdown(); + void async_shutdown(shutdown_handler h) { + if (m_alog.static_test(log::alevel::devel)) { + m_alog.write(log::alevel::devel,"asio connection async_shutdown"); + } + + socket_con_type::async_shutdown( + lib::bind( + &type::handle_async_shutdown, + this, + h, + lib::placeholders::_1 + ) + ); + } + + void handle_async_shutdown(shutdown_handler h, const + boost::system::error_code & ec) + { + if (m_alog.static_test(log::alevel::devel)) { + m_alog.write(log::alevel::devel,"asio con handle_async_shutdown"); + } + + if (ec) { + std::stringstream s; + s << "asio async_shutdown error: " << ec + << " (" << ec.message() << ")"; + m_elog.write(log::elevel::info,s.str()); + h(make_error_code(transport::error::pass_through)); + } else { + h(lib::error_code()); + } } typedef lib::shared_ptr timer_ptr; @@ -563,9 +595,8 @@ protected: h(make_error_code(transport::error::operation_aborted)); } else if (ec) { std::stringstream s; - s << "asio async_wait error::pass_through" - << "Original Error: " << ec << " (" << ec.message() << ")"; - m_elog.write(log::elevel::devel,s.str()); + s << "asio async_wait error: " << ec << " (" << ec.message() << ")"; + m_elog.write(log::elevel::info,s.str()); h(make_error_code(transport::error::pass_through)); } else { h(lib::error_code()); diff --git a/websocketpp/transport/asio/security/none.hpp b/websocketpp/transport/asio/security/none.hpp index 373bdfb89c..68e77e4451 100644 --- a/websocketpp/transport/asio/security/none.hpp +++ b/websocketpp/transport/asio/security/none.hpp @@ -205,11 +205,10 @@ protected: m_hdl = hdl; } - void shutdown() { + void async_shutdown(socket_shutdown_handler h) { boost::system::error_code ec; m_socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both,ec); - - // TODO: handle errors + h(ec); } private: enum state { diff --git a/websocketpp/transport/asio/security/tls.hpp b/websocketpp/transport/asio/security/tls.hpp index 946c49c2de..682eb5e66b 100644 --- a/websocketpp/transport/asio/security/tls.hpp +++ b/websocketpp/transport/asio/security/tls.hpp @@ -291,20 +291,23 @@ protected: callback(lib::error_code()); } - void handle_shutdown(socket_ptr s, const boost::system::error_code& ec) { - // TODO: error handling? - } - - void shutdown() { + void async_shutdown(socket_shutdown_handler h) { m_socket->async_shutdown( - lib::bind( - &type::handle_shutdown, + lib::bind( + &type::handle_async_shutdown, this, m_socket, + h, lib::placeholders::_1 ) ); } + + void handle_async_shutdown(socket_ptr s, socket_shutdown_handler h, const + boost::system::error_code& ec) + { + h(ec); + } private: socket_type::handshake_type get_handshake_type() { if (m_is_server) { diff --git a/websocketpp/transport/base/connection.hpp b/websocketpp/transport/base/connection.hpp index d3ccaafb69..983fcb8393 100644 --- a/websocketpp/transport/base/connection.hpp +++ b/websocketpp/transport/base/connection.hpp @@ -70,6 +70,7 @@ typedef lib::function init_handler; typedef lib::function read_handler; typedef lib::function write_handler; typedef lib::function timer_handler; +typedef lib::function shutdown_handler; typedef lib::function inturrupt_handler; typedef lib::function dispatch_handler; diff --git a/websocketpp/transport/iostream/connection.hpp b/websocketpp/transport/iostream/connection.hpp index ed801e421c..ab2b19b01d 100644 --- a/websocketpp/transport/iostream/connection.hpp +++ b/websocketpp/transport/iostream/connection.hpp @@ -313,8 +313,8 @@ protected: return lib::error_code(); } - void shutdown() { - // TODO: + void async_shutdown(shutdown_handler h) { + h(lib::error_code()); } private: void read(std::istream &in) { From 75af01cda4deb08acff69dd7bef8e5440e5bfe74 Mon Sep 17 00:00:00 2001 From: Peter Thorson Date: Tue, 7 May 2013 09:35:48 -0500 Subject: [PATCH 03/14] updates asio transport base unit tests for new interface --- test/transport/asio/SConscript | 2 +- test/transport/asio/base.cpp | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/transport/asio/SConscript b/test/transport/asio/SConscript index 909a2f320f..5377a89095 100644 --- a/test/transport/asio/SConscript +++ b/test/transport/asio/SConscript @@ -18,7 +18,7 @@ prgs = env.Program('test_base_boost', ["base_boost.o"], LIBS = BOOST_LIBS) #prgs += env.Program('test_utility_boost', ["utilities_boost.o"], LIBS = BOOST_LIBS) if env_cpp11.has_key('WSPP_CPP11_ENABLED'): - BOOST_LIBS_CPP11 = boostlibs(['unit_test_framework'],env_cpp11) + [platform_libs] + [polyfill_libs] + BOOST_LIBS_CPP11 = boostlibs(['unit_test_framework','system'],env_cpp11) + [platform_libs] + [polyfill_libs] objs += env_cpp11.Object('base_stl.o', ["base.cpp"], LIBS = BOOST_LIBS_CPP11) prgs += env_cpp11.Program('test_base_stl', ["base_stl.o"], LIBS = BOOST_LIBS_CPP11) diff --git a/test/transport/asio/base.cpp b/test/transport/asio/base.cpp index 33052cacd3..44a13019de 100644 --- a/test/transport/asio/base.cpp +++ b/test/transport/asio/base.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, Peter Thorson. All rights reserved. + * Copyright (c) 2013, 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: @@ -33,17 +33,17 @@ #include BOOST_AUTO_TEST_CASE( blank_error ) { - websocketpp::lib::error_code ec; - + websocketpp::lib::error_code ec; + BOOST_CHECK( !ec ); } BOOST_AUTO_TEST_CASE( asio_error ) { - using websocketpp::transport::asio::error::make_error_code; - using websocketpp::transport::asio::error::general; - - websocketpp::lib::error_code ec = make_error_code(general); - + using websocketpp::transport::asio::error::make_error_code; + using websocketpp::transport::asio::error::general; + + websocketpp::lib::error_code ec = make_error_code(general); + BOOST_CHECK( ec == general ); BOOST_CHECK( ec.value() == 1 ); } \ No newline at end of file From 6bd62edb43e60a4103dda857a930b79221f87acd Mon Sep 17 00:00:00 2001 From: Peter Thorson Date: Tue, 7 May 2013 09:37:37 -0500 Subject: [PATCH 04/14] removes unused code & updates naming conventions --- websocketpp/connection.hpp | 10 ++-- websocketpp/impl/connection_impl.hpp | 78 +++------------------------- 2 files changed, 12 insertions(+), 76 deletions(-) diff --git a/websocketpp/connection.hpp b/websocketpp/connection.hpp index e54209de40..62b0e8ce53 100644 --- a/websocketpp/connection.hpp +++ b/websocketpp/connection.hpp @@ -773,14 +773,12 @@ public: void start(); - void read(size_t num_bytes); - void handle_read(const lib::error_code& ec, size_t bytes_transferred); - + void read_handshake(size_t num_bytes); - void write(std::string msg); - void handle_write(const lib::error_code& ec); + //void write(std::string msg); + //void handle_write(const lib::error_code& ec); - void handle_handshake_read(const lib::error_code& ec, + void handle_read_handshake(const lib::error_code& ec, size_t bytes_transferred); void handle_read_http_response(const lib::error_code& ec, size_t bytes_transferred); diff --git a/websocketpp/impl/connection_impl.hpp b/websocketpp/impl/connection_impl.hpp index 314097d199..21d56c68da 100644 --- a/websocketpp/impl/connection_impl.hpp +++ b/websocketpp/impl/connection_impl.hpp @@ -557,25 +557,18 @@ void connection::handle_transport_init(const lib::error_code& ec) { } // At this point the transport is ready to read and write bytes. - if (m_is_server) { - this->read(1); + this->read_handshake(1); } else { // We are a client. Set the processor to the version specified in the // config file and send a handshake request. m_processor = get_processor(config::client_version); this->send_http_request(); } - - // TODO: Begin websocket handshake - // server: read/process/write/go - // client: process/write/read/process/go - - //this->read(); } template -void connection::read(size_t num_bytes) { +void connection::read_handshake(size_t num_bytes) { m_alog.write(log::alevel::devel,"connection read"); transport_con_type::async_read_at_least( @@ -583,7 +576,7 @@ void connection::read(size_t num_bytes) { m_buf, config::connection_read_buffer_size, lib::bind( - &type::handle_handshake_read, + &type::handle_read_handshake, type::shared_from_this(), lib::placeholders::_1, lib::placeholders::_2 @@ -594,14 +587,14 @@ void connection::read(size_t num_bytes) { // All exit paths for this function need to call send_http_response() or submit // a new read request with this function as the handler. template -void connection::handle_handshake_read(const lib::error_code& ec, +void connection::handle_read_handshake(const lib::error_code& ec, size_t bytes_transferred) { - m_alog.write(log::alevel::devel,"connection handle_handshake_read"); + m_alog.write(log::alevel::devel,"connection handle_read_handshake"); this->atomic_state_check( istate::READ_HTTP_REQUEST, - "handle_handshake_read must be called from READ_HTTP_REQUEST state" + "handle_read_handshake must be called from READ_HTTP_REQUEST state" ); if (ec) { @@ -699,7 +692,7 @@ void connection::handle_handshake_read(const lib::error_code& ec, m_buf, config::connection_read_buffer_size, lib::bind( - &type::handle_handshake_read, + &type::handle_read_handshake, type::shared_from_this(), lib::placeholders::_1, lib::placeholders::_2 @@ -993,62 +986,7 @@ bool connection::process_handshake_request() { } return true; -} - -// TODO: does this function still need to be here? -template -void connection::handle_read(const lib::error_code& ec, - size_t bytes_transferred) -{ - if (ec) { - m_elog.write(log::elevel::rerror,"error in handle_read"+ec.message()); - return; - } - - // TODO: assert bytes_transferred < m_buf size. - - m_alog.write(log::alevel::devel,"connection handle_read"); - - std::string foo(m_buf,bytes_transferred); - - // process bytes - - if (foo == "close") { - this->terminate(); - return; - } - - //m_handler->on_message(type::shared_from_this(),foo); - - this->read(); -} - - -template -void connection::write(std::string msg) { - m_alog.write(log::alevel::devel,"connection write"); - - transport_con_type::async_write( - msg.data(), - msg.size(), - lib::bind( - &type::handle_write, - type::shared_from_this(), - lib::placeholders::_1 - ) - ); -} - -template -void connection::handle_write(const lib::error_code& ec) { - if (ec) { - m_elog.write(log::elevel::rerror, - "error in handle_write: "+ec.message()); - return; - } - - m_alog.write(log::alevel::devel,"connection handle_write"); -} +} template void connection::send_http_response() { From 8e40c53f97fc1861d6dafa344ffd224072299722 Mon Sep 17 00:00:00 2001 From: Peter Thorson Date: Tue, 7 May 2013 09:38:26 -0500 Subject: [PATCH 05/14] adds new async terminate interface which significantly improves error handling --- websocketpp/connection.hpp | 11 ++- websocketpp/error.hpp | 5 + websocketpp/impl/connection_impl.hpp | 133 +++++++++++++++++--------- websocketpp/roles/client_endpoint.hpp | 2 +- websocketpp/roles/server_endpoint.hpp | 2 +- 5 files changed, 103 insertions(+), 50 deletions(-) diff --git a/websocketpp/connection.hpp b/websocketpp/connection.hpp index 62b0e8ce53..55150b2b64 100644 --- a/websocketpp/connection.hpp +++ b/websocketpp/connection.hpp @@ -167,6 +167,14 @@ public: // Misc Convenience Types typedef session::internal_state::value istate_type; +private: + enum terminate_status { + failed = 1, + closed, + unknown + }; +public: + explicit connection(bool is_server, const std::string& ua, alog_type& alog, elog_type& elog, rng_type & rng) : transport_con_type(is_server,alog,elog) @@ -796,7 +804,8 @@ public: /// internally by the endpoint class. void set_termination_handler(termination_handler new_handler); - void terminate(); + void terminate(const lib::error_code & ec); + void handle_terminate(terminate_status tstat, const lib::error_code& ec); /// Checks if there are frames in the send queue and if there are sends one /** diff --git a/websocketpp/error.hpp b/websocketpp/error.hpp index e094b867f2..c7e49d901f 100644 --- a/websocketpp/error.hpp +++ b/websocketpp/error.hpp @@ -100,6 +100,9 @@ enum value { /// Attempted to use a server specific feature on a client endpoint server_only, + + /// HTTP connection ended + http_connection_ended }; // enum value @@ -153,6 +156,8 @@ public: return "Feature not available on server endpoints"; case error::server_only: return "Feature not available on client endpoints"; + case error::http_connection_ended: + return "HTTP connection ended"; default: return "Unknown"; } diff --git a/websocketpp/impl/connection_impl.hpp b/websocketpp/impl/connection_impl.hpp index 21d56c68da..9919afccdb 100644 --- a/websocketpp/impl/connection_impl.hpp +++ b/websocketpp/impl/connection_impl.hpp @@ -552,7 +552,7 @@ void connection::handle_transport_init(const lib::error_code& ec) { s << "handle_transport_init recieved error: "<< ec.message(); m_elog.write(log::elevel::fatal,s.str()); - this->terminate(); + this->terminate(ec); return; } @@ -601,14 +601,14 @@ void connection::handle_read_handshake(const lib::error_code& ec, std::stringstream s; s << "error in handle_read_handshake: "<< ec.message(); m_elog.write(log::elevel::fatal,s.str()); - this->terminate(); + this->terminate(ec); return; } // Boundaries checking. TODO: How much of this should be done? if (bytes_transferred > config::connection_read_buffer_size) { m_elog.write(log::elevel::fatal,"Fatal boundaries checking error."); - this->terminate(); + this->terminate(make_error_code(error::general)); return; } @@ -627,7 +627,7 @@ void connection::handle_read_handshake(const lib::error_code& ec, // TODO: Is this overkill? if (bytes_processed > config::connection_read_buffer_size) { m_elog.write(log::elevel::fatal,"Fatal boundaries checking error."); - this->terminate(); + this->terminate(make_error_code(error::general)); return; } @@ -739,21 +739,21 @@ void connection::handle_read_frame(const lib::error_code& ec, } if (ec == transport::error::tls_short_read) { m_elog.write(log::elevel::rerror,"got TLS short read, killing connection for now"); - this->terminate(); + this->terminate(ec); return; } std::stringstream s; s << "error in handle_read_frame: " << ec.message() << " (" << ec << ")"; m_elog.write(log::elevel::fatal,s.str()); - this->terminate(); + this->terminate(ec); return; } // Boundaries checking. TODO: How much of this should be done? if (bytes_transferred > config::connection_read_buffer_size) { m_elog.write(log::elevel::fatal,"Fatal boundaries checking error"); - this->terminate(); + this->terminate(make_error_code(error::general)); return; } @@ -789,7 +789,7 @@ void connection::handle_read_frame(const lib::error_code& ec, m_elog.write(log::elevel::rerror,"consume error: "+ec.message()); if (config::drop_on_protocol_error) { - this->terminate(); + this->terminate(ec); return; } else { lib::error_code close_ec; @@ -799,7 +799,7 @@ void connection::handle_read_frame(const lib::error_code& ec, m_elog.write(log::elevel::fatal, "Failed to send a close frame after protocol error: " +close_ec.message()); - this->terminate(); + this->terminate(close_ec); return; } } @@ -1045,7 +1045,7 @@ void connection::handle_send_http_response( if (ec) { m_elog.write(log::elevel::rerror, "error in handle_send_http_response: "+ec.message()); - this->terminate(); + this->terminate(ec); return; } @@ -1063,7 +1063,7 @@ void connection::handle_send_http_response( << m_response.get_status_code(); m_elog.write(log::elevel::rerror,s.str()); } - this->terminate(); + this->terminate(make_error_code(error::http_connection_ended)); return; } @@ -1143,7 +1143,7 @@ void connection::handle_send_http_request(const lib::error_code& ec) { if (ec) { m_elog.write(log::elevel::rerror, "error in handle_send_http_request: "+ec.message()); - this->terminate(); + this->terminate(ec); return; } @@ -1182,16 +1182,17 @@ void connection::handle_read_http_response(const lib::error_code& ec, if (ec) { m_elog.write(log::elevel::rerror, "error in handle_read_http_response: "+ec.message()); - this->terminate(); + this->terminate(ec); return; } size_t bytes_processed = 0; + // TODO: refactor this to use error codes rather than exceptions try { bytes_processed = m_response.consume(m_buf,bytes_transferred); } catch (http::exception & e) { m_elog.write(log::elevel::rerror, std::string("error in handle_read_http_response: ")+e.what()); - this->terminate(); + this->terminate(make_error_code(error::general)); return; } @@ -1207,7 +1208,7 @@ void connection::handle_read_http_response(const lib::error_code& ec, std::string("Server handshake response was invalid: ")+ ec.message() ); - this->terminate(); + this->terminate(ec); return; } @@ -1251,32 +1252,68 @@ void connection::handle_read_http_response(const lib::error_code& ec, } template -void connection::terminate() { - try { - m_alog.write(log::alevel::devel,"connection terminate"); - - transport_con_type::shutdown(); - - if (m_state == session::state::connecting) { - m_state = session::state::closed; - if (m_fail_handler) { - m_fail_handler(m_connection_hdl); - } - } else if (m_state != session::state::closed) { - m_state = session::state::closed; - if (m_close_handler) { - m_close_handler(m_connection_hdl); - } - } else { - m_alog.write(log::alevel::devel,"terminate called on connection that was already terminated"); - return; - } - - log_close_result(); - } catch (const std::exception& e) { - m_elog.write(log::elevel::warn, - std::string("terminate failed. Reason was: ") + e.what()); +void connection::terminate(const lib::error_code & ec) { + if (m_alog.static_test(log::alevel::devel)) { + m_alog.write(log::alevel::devel,"connection "); } + + terminate_status tstat = unknown; + if (ec) { + m_local_close_code = close::status::abnormal_close; + m_local_close_reason = ec.message(); + } + + if (m_state == session::state::connecting) { + m_state = session::state::closed; + tstat = failed; + } else if (m_state != session::state::closed) { + m_state = session::state::closed; + tstat = closed; + } else { + m_alog.write(log::alevel::devel, + "terminate called on connection that was already terminated"); + return; + } + + transport_con_type::async_shutdown( + lib::bind( + &type::handle_terminate, + type::shared_from_this(), + tstat, + lib::placeholders::_1 + ) + ); +} + +template +void connection::handle_terminate(terminate_status tstat, + const lib::error_code& ec) +{ + if (m_alog.static_test(log::alevel::devel)) { + m_alog.write(log::alevel::devel,"connection handle_terminate"); + } + + if (ec) { + // there was an error actually shutting down the connection + m_elog.write(log::elevel::rerror,ec.message()); + } + + // clean shutdown + if (tstat == failed) { + if (m_fail_handler) { + m_fail_handler(m_connection_hdl); + } + // TODO: custom fail output log format? + log_close_result(); + } else if (tstat == closed) { + if (m_close_handler) { + m_close_handler(m_connection_hdl); + } + log_close_result(); + } else { + m_elog.write(log::elevel::rerror,"Unknown terminate_status"); + } + // call the termination handler if it exists // if it exists it might (but shouldn't) refer to a bad memory location. // If it does, we don't care and should catch and ignore it. @@ -1358,19 +1395,21 @@ template void connection::handle_write_frame(bool terminate, const lib::error_code& ec) { + if (m_alog.static_test(log::alevel::devel)) { + m_alog.write(log::alevel::devel,"connection handle_write_frame"); + } + m_send_buffer.clear(); m_current_msg.reset(); if (ec) { m_elog.write(log::elevel::fatal,"error in handle_write_frame: "+ec.message()); - this->terminate(); + this->terminate(ec); return; } - m_alog.write(log::alevel::devel,"connection handle_write_frame"); - if (terminate) { - this->terminate(); + this->terminate(lib::error_code()); return; } @@ -1483,7 +1522,7 @@ void connection::process_control_frame(typename s << "Received invalid close code " << m_remote_close_code << " dropping connection per config."; m_elog.write(log::elevel::devel,s.str()); - this->terminate(); + this->terminate(ec); } else { s << "Received invalid close code " << m_remote_close_code << " sending acknowledgement and closing"; @@ -1503,7 +1542,7 @@ void connection::process_control_frame(typename if (config::drop_on_protocol_error) { m_elog.write(log::elevel::devel, "Received invalid close reason. Dropping connection per config"); - this->terminate(); + this->terminate(ec); } else { m_elog.write(log::elevel::devel, "Received invalid close reason. Sending acknowledgement and closing"); @@ -1531,7 +1570,7 @@ void connection::process_control_frame(typename } else if (m_state == session::state::closing) { // ack of our close m_alog.write(log::alevel::devel,"Got acknowledgement of close"); - this->terminate(); + this->terminate(lib::error_code()); } else { // spurious, ignore m_elog.write(log::elevel::devel,"Got close frame in wrong state"); diff --git a/websocketpp/roles/client_endpoint.hpp b/websocketpp/roles/client_endpoint.hpp index 90539682e8..e07ed57de6 100644 --- a/websocketpp/roles/client_endpoint.hpp +++ b/websocketpp/roles/client_endpoint.hpp @@ -155,7 +155,7 @@ private: } else if (ec) { // TODO // Set connection's failure reasons - con->terminate(); + con->terminate(ec); endpoint_type::m_elog.write(log::elevel::rerror, "handle_connect error: "+ec.message()); diff --git a/websocketpp/roles/server_endpoint.hpp b/websocketpp/roles/server_endpoint.hpp index 723417e7ba..aa081ae109 100644 --- a/websocketpp/roles/server_endpoint.hpp +++ b/websocketpp/roles/server_endpoint.hpp @@ -114,7 +114,7 @@ public: //con->terminate(); } else { if (ec) { - con->terminate(); + con->terminate(ec); endpoint_type::m_elog.write(log::elevel::rerror, "handle_accept error: "+ec.message()); From 215c9bcdaba9b5901358dd95e51e393db604da8f Mon Sep 17 00:00:00 2001 From: Peter Thorson Date: Mon, 6 May 2013 10:33:02 -0500 Subject: [PATCH 06/14] adds better debugging options for the scons build system --- SConstruct | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/SConstruct b/SConstruct index c9a6f8f85b..2873c125de 100644 --- a/SConstruct +++ b/SConstruct @@ -2,10 +2,12 @@ import os, sys, commands env = Environment(ENV = os.environ) # figure out a better way to configure this -#env["CXX"] = "clang++" if os.environ.has_key('CXX'): env['CXX'] = os.environ['CXX'] +if os.environ.has_key('DEBUG'): + env['DEBUG'] = os.environ['DEBUG'] + if os.environ.has_key('CXXFLAGS'): #env['CXXFLAGS'] = os.environ['CXXFLAGS'] env.Append(CXXFLAGS = os.environ['CXXFLAGS']) @@ -69,14 +71,20 @@ if env['PLATFORM'].startswith('win'): env['CCFLAGS'] = '%s /EHsc /GR /GS- /MD /nologo %s %s' % (warn_flags, arch_flags, opt_flags) env['LINKFLAGS'] = '/INCREMENTAL:NO /MANIFEST /NOLOGO /OPT:REF /OPT:ICF /MACHINE:X86' elif env['PLATFORM'] == 'posix': - env.Append(CPPDEFINES = ['NDEBUG']) + if env.has_key('DEBUG'): + env.Append(CCFLAGS = ['-g', '-O0']) + else: + env.Append(CPPDEFINES = ['NDEBUG']) + env.Append(CCFLAGS = ['-O3', '-fomit-frame-pointer']) env.Append(CCFLAGS = ['-Wall']) - #env.Append(CCFLAGS = ['-O3', '-fomit-frame-pointer']) #env['LINKFLAGS'] = '' elif env['PLATFORM'] == 'darwin': - #env.Append(CPPDEFINES = ['NDEBUG']) - env.Append(CCFLAGS = ['-Wall','-O0']) - #env.Append(CCFLAGS = ['-O3', '-fomit-frame-pointer']) + if env.has_key('DEBUG'): + env.Append(CCFLAGS = ['-g', '-O0']) + else: + env.Append(CPPDEFINES = ['NDEBUG']) + env.Append(CCFLAGS = ['-O3', '-fomit-frame-pointer']) + env.Append(CCFLAGS = ['-Wall']) #env['LINKFLAGS'] = '' if env['PLATFORM'].startswith('win'): From 96158ee8b5648e80eed5b54f3168852bb1dbc75b Mon Sep 17 00:00:00 2001 From: Peter Thorson Date: Mon, 6 May 2013 10:33:10 -0500 Subject: [PATCH 07/14] print get connection errors --- examples/utility_client/utility_client.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/utility_client/utility_client.cpp b/examples/utility_client/utility_client.cpp index 19f45c27d3..6d22f7b8de 100644 --- a/examples/utility_client/utility_client.cpp +++ b/examples/utility_client/utility_client.cpp @@ -43,6 +43,10 @@ public: websocketpp::lib::error_code ec; client::connection_ptr con = m_endpoint.get_connection(uri, ec); + if (ec) { + m_endpoint.get_alog().write(websocketpp::log::alevel::app,ec.message()); + } + //con->set_proxy("http://humupdates.uchicago.edu:8443"); m_endpoint.connect(con); From bda4cf3a46a617333cef301e9d3c4b669be84a81 Mon Sep 17 00:00:00 2001 From: Peter Thorson Date: Tue, 7 May 2013 17:46:17 -0500 Subject: [PATCH 08/14] updates derived configs to also derive transport_config from base --- websocketpp/config/asio_client.hpp | 21 +++++++++++---------- websocketpp/config/asio_no_tls.hpp | 21 +++++++++++---------- websocketpp/config/asio_no_tls_client.hpp | 21 +++++++++++---------- websocketpp/config/debug_asio.hpp | 2 +- websocketpp/config/debug_asio_no_tls.hpp | 2 +- 5 files changed, 35 insertions(+), 32 deletions(-) diff --git a/websocketpp/config/asio_client.hpp b/websocketpp/config/asio_client.hpp index e169537a38..0c03142ecb 100644 --- a/websocketpp/config/asio_client.hpp +++ b/websocketpp/config/asio_client.hpp @@ -41,22 +41,23 @@ namespace config { struct asio_tls_client : public core_client { typedef asio_tls_client type; + typedef core_client base; - typedef core_client::concurrency_type concurrency_type; + typedef base::concurrency_type concurrency_type; - typedef core_client::request_type request_type; - typedef core_client::response_type response_type; + typedef base::request_type request_type; + typedef base::response_type response_type; - typedef core_client::message_type message_type; - typedef core_client::con_msg_manager_type con_msg_manager_type; - typedef core_client::endpoint_msg_manager_type endpoint_msg_manager_type; + typedef base::message_type message_type; + typedef base::con_msg_manager_type con_msg_manager_type; + typedef base::endpoint_msg_manager_type endpoint_msg_manager_type; - typedef core_client::alog_type alog_type; - typedef core_client::elog_type elog_type; + typedef base::alog_type alog_type; + typedef base::elog_type elog_type; - typedef core_client::rng_type rng_type; + typedef base::rng_type rng_type; - struct transport_config { + struct transport_config : public base::transport_config { typedef type::concurrency_type concurrency_type; typedef type::alog_type alog_type; typedef type::elog_type elog_type; diff --git a/websocketpp/config/asio_no_tls.hpp b/websocketpp/config/asio_no_tls.hpp index f4c1d9ea15..6b7bebe3bd 100644 --- a/websocketpp/config/asio_no_tls.hpp +++ b/websocketpp/config/asio_no_tls.hpp @@ -36,22 +36,23 @@ namespace config { struct asio : public core { typedef asio type; + typedef core base; - typedef core::concurrency_type concurrency_type; + typedef base::concurrency_type concurrency_type; - typedef core::request_type request_type; - typedef core::response_type response_type; + typedef base::request_type request_type; + typedef base::response_type response_type; - typedef core::message_type message_type; - typedef core::con_msg_manager_type con_msg_manager_type; - typedef core::endpoint_msg_manager_type endpoint_msg_manager_type; + typedef base::message_type message_type; + typedef base::con_msg_manager_type con_msg_manager_type; + typedef base::endpoint_msg_manager_type endpoint_msg_manager_type; - typedef core::alog_type alog_type; - typedef core::elog_type elog_type; + typedef base::alog_type alog_type; + typedef base::elog_type elog_type; - typedef core::rng_type rng_type; + typedef base::rng_type rng_type; - struct transport_config { + struct transport_config : public base::transport_config { typedef type::concurrency_type concurrency_type; typedef type::alog_type alog_type; typedef type::elog_type elog_type; diff --git a/websocketpp/config/asio_no_tls_client.hpp b/websocketpp/config/asio_no_tls_client.hpp index 7c6f1a87da..8f866ff43a 100644 --- a/websocketpp/config/asio_no_tls_client.hpp +++ b/websocketpp/config/asio_no_tls_client.hpp @@ -36,22 +36,23 @@ namespace config { struct asio_client : public core_client { typedef asio_client type; + typedef core_client base; - typedef core_client::concurrency_type concurrency_type; + typedef base::concurrency_type concurrency_type; - typedef core_client::request_type request_type; - typedef core_client::response_type response_type; + typedef base::request_type request_type; + typedef base::response_type response_type; - typedef core_client::message_type message_type; - typedef core_client::con_msg_manager_type con_msg_manager_type; - typedef core_client::endpoint_msg_manager_type endpoint_msg_manager_type; + typedef base::message_type message_type; + typedef base::con_msg_manager_type con_msg_manager_type; + typedef base::endpoint_msg_manager_type endpoint_msg_manager_type; - typedef core_client::alog_type alog_type; - typedef core_client::elog_type elog_type; + typedef base::alog_type alog_type; + typedef base::elog_type elog_type; - typedef core_client::rng_type rng_type; + typedef base::rng_type rng_type; - struct transport_config { + struct transport_config : public base::transport_config { typedef type::concurrency_type concurrency_type; typedef type::alog_type alog_type; typedef type::elog_type elog_type; diff --git a/websocketpp/config/debug_asio.hpp b/websocketpp/config/debug_asio.hpp index 37f6c98503..1545f10a7e 100644 --- a/websocketpp/config/debug_asio.hpp +++ b/websocketpp/config/debug_asio.hpp @@ -57,7 +57,7 @@ struct debug_asio_tls : public debug_core { typedef base::rng_type rng_type; - struct transport_config { + struct transport_config : public base::transport_config { typedef type::concurrency_type concurrency_type; typedef type::alog_type alog_type; typedef type::elog_type elog_type; diff --git a/websocketpp/config/debug_asio_no_tls.hpp b/websocketpp/config/debug_asio_no_tls.hpp index 6942190153..81c7f40d5b 100644 --- a/websocketpp/config/debug_asio_no_tls.hpp +++ b/websocketpp/config/debug_asio_no_tls.hpp @@ -52,7 +52,7 @@ struct debug_asio : public debug_core { typedef base::rng_type rng_type; - struct transport_config { + struct transport_config : public base::transport_config { typedef type::concurrency_type concurrency_type; typedef type::alog_type alog_type; typedef type::elog_type elog_type; From 175cab8d22fe7cd2527141061db6e5ed762610d6 Mon Sep 17 00:00:00 2001 From: Peter Thorson Date: Tue, 7 May 2013 17:46:41 -0500 Subject: [PATCH 09/14] updates configs to add default timer wait times references #226 --- websocketpp/config/core.hpp | 20 ++++++++++++++++++++ websocketpp/config/core_client.hpp | 20 ++++++++++++++++++++ websocketpp/config/debug.hpp | 20 ++++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/websocketpp/config/core.hpp b/websocketpp/config/core.hpp index eacee08f8a..6450e7d2f7 100644 --- a/websocketpp/config/core.hpp +++ b/websocketpp/config/core.hpp @@ -97,6 +97,17 @@ struct core { typedef type::alog_type alog_type; typedef type::request_type request_type; typedef type::response_type response_type; + + /// Default timer values (in ms) + + /// Length of time to wait before a proxy handshake is aborted + static const long timeout_proxy = 5000; + /// Length of time to wait before a tls handshake is aborted + static const long timeout_tls_handshake = 5000; + /// Length of time to wait for dns resolution + static const long timeout_dns_resolve = 5000; + /// Length of time to wait for socket shutdown + static const long timeout_socket_shutdown = 5000; }; /// Transport Endpoint Component @@ -108,6 +119,15 @@ struct core { /// User overridable Connection base class typedef websocketpp::connection_base connection_base; + /// Default timer values (in ms) + + /// Length of time before an opening handshake is aborted + static const long timeout_open_handshake = 5000; + /// Length of time before a closing handshake is aborted + static const long timeout_close_handshake = 5000; + /// Length of time to wait for a pong after a ping + static const long timeout_pong = 5000; + /// WebSocket Protocol version to use as a client /** * What version of the WebSocket Protocol to use for outgoing client diff --git a/websocketpp/config/core_client.hpp b/websocketpp/config/core_client.hpp index 653167bb02..0d032502fb 100644 --- a/websocketpp/config/core_client.hpp +++ b/websocketpp/config/core_client.hpp @@ -96,6 +96,17 @@ struct core_client { typedef type::alog_type alog_type; typedef type::request_type request_type; typedef type::response_type response_type; + + /// Default timer values (in ms) + + /// Length of time to wait before a proxy handshake is aborted + static const long timeout_proxy = 5000; + /// Length of time to wait before a tls handshake is aborted + static const long timeout_tls_handshake = 5000; + /// Length of time to wait for dns resolution + static const long timeout_dns_resolve = 5000; + /// Length of time to wait for socket shutdown + static const long timeout_socket_shutdown = 5000; }; /// Transport Endpoint Component @@ -107,6 +118,15 @@ struct core_client { /// User overridable Connection base class typedef websocketpp::connection_base connection_base; + /// Default timer values (in ms) + + /// Length of time before an opening handshake is aborted + static const long timeout_open_handshake = 5000; + /// Length of time before a closing handshake is aborted + static const long timeout_close_handshake = 5000; + /// Length of time to wait for a pong after a ping + static const long timeout_pong = 5000; + /// WebSocket Protocol version to use as a client /** * What version of the WebSocket Protocol to use for outgoing client diff --git a/websocketpp/config/debug.hpp b/websocketpp/config/debug.hpp index 51b2ecbd85..eb3a11e7dc 100644 --- a/websocketpp/config/debug.hpp +++ b/websocketpp/config/debug.hpp @@ -97,6 +97,17 @@ struct debug_core { typedef type::alog_type alog_type; typedef type::request_type request_type; typedef type::response_type response_type; + + /// Default timer values (in ms) + + /// Length of time to wait before a proxy handshake is aborted + static const long timeout_proxy = 5000; + /// Length of time to wait before a tls handshake is aborted + static const long timeout_tls_handshake = 5000; + /// Length of time to wait for dns resolution + static const long timeout_dns_resolve = 5000; + /// Length of time to wait for socket shutdown + static const long timeout_socket_shutdown = 5000; }; /// Transport Endpoint Component @@ -108,6 +119,15 @@ struct debug_core { /// User overridable Connection base class typedef websocketpp::connection_base connection_base; + /// Default timer values (in ms) + + /// Length of time before an opening handshake is aborted + static const long timeout_open_handshake = 5000; + /// Length of time before a closing handshake is aborted + static const long timeout_close_handshake = 5000; + /// Length of time to wait for a pong after a ping + static const long timeout_pong = 5000; + /// WebSocket Protocol version to use as a client /** * What version of the WebSocket Protocol to use for outgoing client From 5d0d1379b6ced51998ed3e7a06b9060d9fefc873 Mon Sep 17 00:00:00 2001 From: Peter Thorson Date: Tue, 7 May 2013 17:49:02 -0500 Subject: [PATCH 10/14] adds timer support for asio transport and adds proxy connect timeout references #226 --- websocketpp/transport/asio/connection.hpp | 126 +++++++++++++++++----- websocketpp/transport/base/connection.hpp | 7 +- 2 files changed, 106 insertions(+), 27 deletions(-) diff --git a/websocketpp/transport/asio/connection.hpp b/websocketpp/transport/asio/connection.hpp index d5716eca54..af0552ef93 100644 --- a/websocketpp/transport/asio/connection.hpp +++ b/websocketpp/transport/asio/connection.hpp @@ -80,7 +80,9 @@ public: /// Type of a pointer to the ASIO io_service being used typedef boost::asio::io_service* io_service_ptr; - + /// Type of a pointer to the ASIO timer class + typedef lib::shared_ptr timer_ptr; + // generate and manage our own io_service explicit connection(bool is_server, alog_type& alog, elog_type& elog) : m_is_server(is_server) @@ -185,7 +187,68 @@ public: return lib::error_code(); } + + /// 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( + new boost::asio::deadline_timer( + *m_io_service, + boost::posix_time::milliseconds(duration) + ) + ); + new_timer->async_wait( + lib::bind( + &type::handle_timer, + this, + 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. + * + * @param t Pointer to the timer in question + * + * @param callback The function to call back + * + * @param ec The status code + */ + void handle_timer(timer_ptr t, timer_handler callback, const + boost::system::error_code& ec) + { + if (ec) { + if (ec == boost::asio::error::operation_aborted) { + callback(make_error_code(transport::error::operation_aborted)); + } else { + m_elog.write(log::elevel::info, + "asio handle_timer error: "+ec.message()); + callback(make_error_code(error::pass_through)); + } + } else { + callback(lib::error_code()); + } + } protected: /// Initialize transport for reading /** @@ -281,7 +344,19 @@ protected: 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, + this, + callback, + lib::placeholders::_1 + ) + ); + + // Send proxy request boost::asio::async_write( socket_con_type::get_next_layer(), m_bufs, @@ -294,6 +369,19 @@ protected: ); } + void handle_proxy_timeout(init_handler callback, const lib::error_code & ec) { + if (ec == transport::error::operation_aborted) { + m_alog.write(log::alevel::devel,"asio handle_proxy_write timer cancelled"); + return; + } else if (ec) { + m_alog.write(log::alevel::devel,"asio handle_proxy_write timer error: "+ec.message()); + callback(ec); + } else { + m_alog.write(log::alevel::devel,"asio handle_proxy_write timer expired"); + callback(make_error_code(transport::error::timeout)); + } + } + void handle_proxy_write(init_handler callback, const boost::system::error_code& ec) { @@ -306,6 +394,7 @@ protected: if (ec) { m_elog.write(log::elevel::info, "asio handle_proxy_write error: "+ec.message()); + m_proxy_data->timer->cancel(); callback(make_error_code(error::pass_through)); } else { proxy_read(callback); @@ -320,6 +409,7 @@ protected: 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; } @@ -345,6 +435,9 @@ protected: m_alog.write(log::alevel::devel,"asio connection handle_proxy_read"); } + // 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()); @@ -579,40 +672,21 @@ protected: h(lib::error_code()); } } - - typedef lib::shared_ptr timer_ptr; - - timer_ptr set_timer(long duration, timer_handler handler) { - timer_ptr timer(new boost::asio::deadline_timer(*m_io_service)); - timer->expires_from_now(boost::posix_time::milliseconds(duration)); - timer->async_wait(lib::bind(&type::timer_handler, this, handler, - lib::placeholders::_1)); - return timer; - } - - void timer_handler(timer_handler h, const boost::system::error_code& ec) { - if (ec == boost::asio::error::operation_aborted) { - h(make_error_code(transport::error::operation_aborted)); - } else if (ec) { - std::stringstream s; - s << "asio async_wait error: " << ec << " (" << ec.message() << ")"; - m_elog.write(log::elevel::info,s.str()); - h(make_error_code(transport::error::pass_through)); - } else { - h(lib::error_code()); - } - } private: // static settings - const bool m_is_server; + 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; boost::asio::streambuf read_buf; + long timeout_proxy; + timer_ptr timer; }; std::string m_proxy; diff --git a/websocketpp/transport/base/connection.hpp b/websocketpp/transport/base/connection.hpp index 983fcb8393..89464312dd 100644 --- a/websocketpp/transport/base/connection.hpp +++ b/websocketpp/transport/base/connection.hpp @@ -108,7 +108,10 @@ enum value { eof, /// TLS short read - tls_short_read + tls_short_read, + + /// Timer expired + timeout }; class category : public lib::error_category { @@ -135,6 +138,8 @@ class category : public lib::error_category { return "End of File"; case tls_short_read: return "TLS Short Read"; + case timeout: + return "Timer Expired"; default: return "Unknown"; } From fd46083c4a7c53413c23f386d3fe123c3b1310f0 Mon Sep 17 00:00:00 2001 From: Peter Thorson Date: Tue, 7 May 2013 20:34:17 -0500 Subject: [PATCH 11/14] updates frame unit tests to use more specific checks --- test/utility/frame.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/utility/frame.cpp b/test/utility/frame.cpp index f6adc5df19..d79637aa8a 100644 --- a/test/utility/frame.cpp +++ b/test/utility/frame.cpp @@ -407,7 +407,7 @@ BOOST_AUTO_TEST_CASE( continuous_word_mask ) { pkey_temp = frame::word_mask_circ(input+7,output+7,8,pkey_temp); BOOST_CHECK( std::equal(output,output+16,masked) ); - BOOST_CHECK( pkey_temp == frame::circshift_prepared_key(pkey,3) ); + BOOST_CHECK_EQUAL( pkey_temp, frame::circshift_prepared_key(pkey,3) ); } BOOST_AUTO_TEST_CASE( continuous_word_mask_inplace ) { @@ -437,11 +437,11 @@ BOOST_AUTO_TEST_CASE( continuous_word_mask_inplace ) { pkey_temp = frame::word_mask_circ(buffer,7,pkey); BOOST_CHECK( std::equal(buffer,buffer+7,masked) ); - BOOST_CHECK( pkey_temp == frame::circshift_prepared_key(pkey,3) ); + BOOST_CHECK_EQUAL( pkey_temp, frame::circshift_prepared_key(pkey,3) ); pkey_temp = frame::word_mask_circ(buffer+7,8,pkey_temp); BOOST_CHECK( std::equal(buffer,buffer+16,masked) ); - BOOST_CHECK( pkey_temp == frame::circshift_prepared_key(pkey,3) ); + BOOST_CHECK_EQUAL( pkey_temp, frame::circshift_prepared_key(pkey,3) ); } BOOST_AUTO_TEST_CASE( continuous_word_mask2 ) { From 747bc55bcf785bb2fa46a8d4a18bad3038c5cb6c Mon Sep 17 00:00:00 2001 From: Peter Thorson Date: Tue, 7 May 2013 20:45:04 -0500 Subject: [PATCH 12/14] adds documentation and error handling to proxy methods --- websocketpp/transport/asio/connection.hpp | 70 ++++++++++++++++++++--- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/websocketpp/transport/asio/connection.hpp b/websocketpp/transport/asio/connection.hpp index af0552ef93..feaf63a011 100644 --- a/websocketpp/transport/asio/connection.hpp +++ b/websocketpp/transport/asio/connection.hpp @@ -123,17 +123,69 @@ public: m_tcp_init_handler = h; } - void set_proxy(const std::string & proxy) { - m_proxy = proxy; + /// 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(const std::string & 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.reset(new proxy_data()); + ec = lib::error_code(); } - void set_proxy_basic_auth(const std::string & u, const std::string & p) { - if (m_proxy_data) { - std::string val = "Basic "+base64_encode(u + ":" + p); - m_proxy_data->req.replace_header("Proxy-Authorization",val); - } else { - // TODO: should we throw errors with invalid stuff here or just - // silently ignore? + + /// Set the proxy to connect through (exception) + void set_proxy(const std::string & uri) { + lib::error_code ec; + set_proxy(uri,ec); + if (ec) { throw 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(const std::string & username, const + std::string & 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(const std::string & username, const + std::string & password) + { + lib::error_code ec; + set_proxy_basic_auth(username,password,ec); + if (ec) { throw ec; } + } + } } From b408ab876e4d763a07736eb42f0c9174ce937820 Mon Sep 17 00:00:00 2001 From: Peter Thorson Date: Tue, 7 May 2013 20:45:28 -0500 Subject: [PATCH 13/14] adds method to set proxy timeout duration at runtime --- websocketpp/transport/asio/connection.hpp | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/websocketpp/transport/asio/connection.hpp b/websocketpp/transport/asio/connection.hpp index feaf63a011..f1ad0d4f64 100644 --- a/websocketpp/transport/asio/connection.hpp +++ b/websocketpp/transport/asio/connection.hpp @@ -186,7 +186,31 @@ public: if (ec) { throw 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 ec; } } const std::string & get_proxy() const { From d85ea328513813f8d6e365966a019f25cee3154e Mon Sep 17 00:00:00 2001 From: Peter Thorson Date: Wed, 8 May 2013 08:52:52 -0500 Subject: [PATCH 14/14] adds timer support to asio endpoints, impliments dns resolve timer references #226 --- websocketpp/transport/asio/connection.hpp | 220 +++++++++++----------- websocketpp/transport/asio/endpoint.hpp | 152 +++++++++++---- 2 files changed, 224 insertions(+), 148 deletions(-) diff --git a/websocketpp/transport/asio/connection.hpp b/websocketpp/transport/asio/connection.hpp index f1ad0d4f64..3ae90f886d 100644 --- a/websocketpp/transport/asio/connection.hpp +++ b/websocketpp/transport/asio/connection.hpp @@ -80,9 +80,9 @@ public: /// Type of a pointer to the ASIO io_service being used typedef boost::asio::io_service* io_service_ptr; - /// Type of a pointer to the ASIO timer class - typedef lib::shared_ptr timer_ptr; - + /// Type of a pointer to the ASIO timer class + typedef lib::shared_ptr timer_ptr; + // generate and manage our own io_service explicit connection(bool is_server, alog_type& alog, elog_type& elog) : m_is_server(is_server) @@ -136,8 +136,8 @@ public: * @param ec A status value */ void set_proxy(const std::string & uri, lib::error_code & ec) { - // TODO: return errors for illegal URIs here? - // TODO: should https urls be illegal for the moment? + // TODO: return errors for illegal URIs here? + // TODO: should https urls be illegal for the moment? m_proxy = uri; m_proxy_data.reset(new proxy_data()); ec = lib::error_code(); @@ -145,9 +145,9 @@ public: /// Set the proxy to connect through (exception) void set_proxy(const std::string & uri) { - lib::error_code ec; - set_proxy(uri,ec); - if (ec) { throw ec; } + lib::error_code ec; + set_proxy(uri,ec); + if (ec) { throw ec; } } /// Set the basic auth credentials to use (exception free) @@ -164,26 +164,26 @@ public: * @param ec A status value */ void set_proxy_basic_auth(const std::string & username, const - std::string & password, lib::error_code & ec) + std::string & password, lib::error_code & ec) { if (!m_proxy_data) { - ec = make_error_code(websocketpp::error::invalid_state); - return; + 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(); + + // 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(const std::string & username, const - std::string & password) + std::string & password) { - lib::error_code ec; - set_proxy_basic_auth(username,password,ec); - if (ec) { throw ec; } + lib::error_code ec; + set_proxy_basic_auth(username,password,ec); + if (ec) { throw ec; } } /// Set the proxy timeout duration (exception free) @@ -198,8 +198,8 @@ public: */ void set_proxy_timeout(long duration, lib::error_code & ec) { if (!m_proxy_data) { - ec = make_error_code(websocketpp::error::invalid_state); - return; + ec = make_error_code(websocketpp::error::invalid_state); + return; } m_proxy_data->timeout_proxy = duration; @@ -208,9 +208,9 @@ public: /// Set the proxy timeout duration (exception) void set_proxy_timeout(long duration) { - lib::error_code ec; - set_proxy_timeout(duration,ec); - if (ec) { throw ec; } + lib::error_code ec; + set_proxy_timeout(duration,ec); + if (ec) { throw ec; } } const std::string & get_proxy() const { @@ -253,7 +253,8 @@ public: */ lib::error_code proxy_init(const std::string & authority) { if (!m_proxy_data) { - return websocketpp::error::make_error_code(websocketpp::error::invalid_state); + 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"); @@ -263,28 +264,28 @@ public: return lib::error_code(); } - - /// 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( - new boost::asio::deadline_timer( - *m_io_service, - boost::posix_time::milliseconds(duration) - ) - ); + + /// 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( + new boost::asio::deadline_timer( + *m_io_service, + boost::posix_time::milliseconds(duration) + ) + ); new_timer->async_wait( lib::bind( @@ -297,34 +298,33 @@ public: ); return new_timer; - } - - /// Timer callback - /** - * The timer pointer is included to ensure the timer isn't destroyed until - * after it has expired. - * - * @param t Pointer to the timer in question - * - * @param callback The function to call back - * - * @param ec The status code - */ - void handle_timer(timer_ptr t, timer_handler callback, const + } + + /// Timer callback + /** + * The timer pointer is included to ensure the timer isn't destroyed until + * after it has expired. + * + * @param t Pointer to the timer in question + * + * @param callback The function to call back + * + * @param ec The status code + */ + void handle_timer(timer_ptr t, timer_handler callback, const boost::system::error_code& ec) { - if (ec) { + if (ec) { if (ec == boost::asio::error::operation_aborted) { - callback(make_error_code(transport::error::operation_aborted)); + callback(make_error_code(transport::error::operation_aborted)); } else { - m_elog.write(log::elevel::info, - "asio handle_timer error: "+ec.message()); - callback(make_error_code(error::pass_through)); + log_err(log::elevel::info,"asio handle_timer",ec); + callback(make_error_code(error::pass_through)); } } else { callback(lib::error_code()); } - } + } protected: /// Initialize transport for reading /** @@ -371,7 +371,8 @@ protected: callback(ec); } - // If we have a proxy set issue a proxy connect, otherwise skip to post_init + // If we have a proxy set issue a proxy connect, otherwise skip to + // post_init if (!m_proxy.empty()) { proxy_write(callback); } else { @@ -420,19 +421,19 @@ protected: 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, - this, - callback, - lib::placeholders::_1 - ) - ); - - // Send proxy request + + // 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, + this, + callback, + lib::placeholders::_1 + ) + ); + + // Send proxy request boost::asio::async_write( socket_con_type::get_next_layer(), m_bufs, @@ -446,16 +447,18 @@ protected: } void handle_proxy_timeout(init_handler callback, const lib::error_code & ec) { - if (ec == transport::error::operation_aborted) { - m_alog.write(log::alevel::devel,"asio handle_proxy_write timer cancelled"); - return; - } else if (ec) { - m_alog.write(log::alevel::devel,"asio handle_proxy_write timer error: "+ec.message()); - callback(ec); - } else { - m_alog.write(log::alevel::devel,"asio handle_proxy_write timer expired"); - callback(make_error_code(transport::error::timeout)); - } + 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"); + callback(make_error_code(transport::error::timeout)); + } } void handle_proxy_write(init_handler callback, const @@ -468,8 +471,7 @@ protected: m_bufs.clear(); if (ec) { - m_elog.write(log::elevel::info, - "asio handle_proxy_write error: "+ec.message()); + log_err(log::elevel::info,"asio handle_proxy_write",ec); m_proxy_data->timer->cancel(); callback(make_error_code(error::pass_through)); } else { @@ -622,13 +624,7 @@ protected: handler(make_error_code(transport::error::tls_short_read), bytes_transferred); } else { - // other error that we cannot translate into a WebSocket++ - // transport error. Use pass through and print an info warning - // with the original error. - std::stringstream s; - s << "asio async_read_at_least error: " - << ec << " (" << ec.message() << ")"; - m_elog.write(log::elevel::info,s.str()); + log_err(log::elevel::info,"asio async_read_at_least",ec); handler(make_error_code(transport::error::pass_through), bytes_transferred); } @@ -673,10 +669,7 @@ protected: { m_bufs.clear(); if (ec) { - std::stringstream s; - s << "asio async_write error: " << ec - << " (" << ec.message() << ")"; - m_elog.write(log::elevel::info,s.str()); + log_err(log::elevel::info,"asio async_write",ec); handler(make_error_code(transport::error::pass_through)); } else { handler(lib::error_code()); @@ -739,24 +732,29 @@ protected: } if (ec) { - std::stringstream s; - s << "asio async_shutdown error: " << ec - << " (" << ec.message() << ")"; - m_elog.write(log::elevel::info,s.str()); + log_err(log::elevel::info,"asio async_shutdown",ec); h(make_error_code(transport::error::pass_through)); } else { h(lib::error_code()); } } private: + /// Convenience method for logging the code and message for an error_code + std::string log_err(log::level l,const char * msg, lib::error_code & 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) {} - + proxy_data() : timeout_proxy(config::timeout_proxy) {} + request_type req; response_type res; std::string write_buf; diff --git a/websocketpp/transport/asio/endpoint.hpp b/websocketpp/transport/asio/endpoint.hpp index 709754580f..8a42311f84 100644 --- a/websocketpp/transport/asio/endpoint.hpp +++ b/websocketpp/transport/asio/endpoint.hpp @@ -74,14 +74,16 @@ public: /// Type of a shared pointer to the connection transport component /// associated with this endpoint transport component typedef typename transport_con_type::ptr transport_con_ptr; - + /// Type of a pointer to the ASIO io_service being used typedef boost::asio::io_service* io_service_ptr; /// Type of a shared pointer to the acceptor being used typedef lib::shared_ptr acceptor_ptr; /// Type of a shared pointer to the resolver being used typedef lib::shared_ptr resolver_ptr; - + /// Type of timer handle + typedef lib::shared_ptr timer_ptr; + // generate and manage our own io_service explicit endpoint() : m_external_io_service(false) @@ -293,28 +295,68 @@ public: listen(*endpoint_iterator); } - typedef lib::shared_ptr timer_ptr; - - timer_ptr set_timer(long duration, timer_handler handler) { - timer_ptr timer(new boost::asio::deadline_timer(*m_io_service)); - timer->expires_from_now(boost::posix_time::milliseconds(duration)); - timer->async_wait(lib::bind(&type::timer_handler, this, handler, - lib::placeholders::_1)); - return timer; - } - - void timer_handler(timer_handler h, const boost::system::error_code& ec) { - if (ec == boost::asio::error::operation_aborted) { - h(make_error_code(transport::error::operation_aborted)); - } else if (ec) { - std::stringstream s; - s << "asio async_wait error: " << ec << " (" << ec.message() << ")"; - m_elog->write(log::elevel::devel,s.str()); - h(make_error_code(transport::error::pass_through)); + /// 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( + new boost::asio::deadline_timer( + *m_io_service, + boost::posix_time::milliseconds(duration) + ) + ); + + new_timer->async_wait( + lib::bind( + &type::handle_timer, + this, + 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. + * + * @param t Pointer to the timer in question + * + * @param callback The function to call back + * + * @param ec The status code + */ + void handle_timer(timer_ptr t, timer_handler callback, const + boost::system::error_code& ec) + { + if (ec) { + if (ec == boost::asio::error::operation_aborted) { + callback(make_error_code(transport::error::operation_aborted)); + } else { + m_elog->write(log::elevel::info, + "asio handle_timer error: "+ec.message()); + log_err(log::elevel::info,"asio handle_timer",ec); + callback(make_error_code(error::pass_through)); + } } else { - h(lib::error_code()); + callback(lib::error_code()); } - } + } boost::asio::io_service& get_io_service() { return *m_io_service; @@ -396,12 +438,25 @@ protected: "starting async DNS resolve for "+host+":"+port); } + timer_ptr dns_timer = tcon->set_timer( + config::timeout_dns, + lib::bind( + &type::handle_resolve_timeout, + this, + tcon, + dns_timer, + cb, + lib::placeholders::_1 + ) + ); + m_resolver->async_resolve( query, lib::bind( &type::handle_resolve, this, tcon, + dns_timer, cb, lib::placeholders::_1, lib::placeholders::_2 @@ -409,28 +464,48 @@ protected: ); } + void handle_resolve_timeout(transport_con_ptr tcon, timer_ptr dns_timer, + connect_handler callback, const lib::error_code & ec) + { + m_resolver->cancel(); + + if (ec == transport::error::operation_aborted) { + m_alog->write(log::alevel::devel, + "asio handle_resolve_timeout timer cancelled"); + } else if (ec) { + log_err(log::elevel::devel,"asio handle_resolve_timeout",ec); + callback(tcon->get_handle(),ec); + } else { + m_alog->write(log::alevel::devel, + "asio handle_resolve_timeout timer expired"); + callback(tcon->get_handle(), + make_error_code(transport::error::timeout)); + } + } + void handle_resolve(transport_con_ptr tcon, connect_handler callback, - const boost::system::error_code& ec, + timer_ptr dns_timer, const boost::system::error_code& ec, boost::asio::ip::tcp::resolver::iterator iterator) { + dns_timer->cancel(); + if (ec) { - //con->terminate(); - // TODO: Better translation of errors at this point - std::stringstream s; - s << "asio async_resolve error:" - << ec << " (" << ec.message() << ")"; - m_elog->write(log::elevel::info,s.str()); + if (ec == boost::asio::error::operation_aborted) { + m_alog->write(log::alevel::devel, + "asio handle_resolve resolve cancelled"); + return; + } + + log_err(log::elevel::info,"asio async_resolve",ec); callback(tcon->get_handle(),make_error_code(error::pass_through)); return; } if (m_alog->static_test(log::alevel::devel)) { std::stringstream s; - s << "Async DNS resolve successful. Results: "; boost::asio::ip::tcp::resolver::iterator it, end; - for (it = iterator; it != end; ++it) { s << (*it).endpoint() << " "; } @@ -456,12 +531,7 @@ protected: const boost::system::error_code& ec) { if (ec) { - //con->terminate(); - // TODO: Better translation of errors at this point - std::stringstream s; - s << "asio async_connect error: " - << ec << " (" << ec.message() << ")"; - m_elog->write(log::elevel::info,s.str()); + log_err(log::elevel::info,"asio async_connect",ec); callback(tcon->get_handle(),make_error_code(error::pass_through)); return; } @@ -506,6 +576,14 @@ protected: return lib::error_code(); } private: + /// Convenience method for logging the code and message for an error_code + std::string log_err(log::level l,const char * msg, lib::error_code & ec) + { + std::stringstream s; + s << msg << " error: " << ec << " (" << ec.message() << ")"; + m_elog->write(l,s.str()); + } + enum state { UNINITIALIZED = 0, READY = 1,