diff --git a/SConstruct b/SConstruct index b104241a14..b930d4d3ab 100644 --- a/SConstruct +++ b/SConstruct @@ -198,7 +198,7 @@ Export('polyfill_libs') if not env['PLATFORM'].startswith('win'): # Unit tests, add test folders with SConscript files to to_test list. - to_test = ['utility','http','logger','random','processors','message_buffer','extension','transport/iostream','transport/asio','roles','endpoint','connection'] #,'http','processors','connection' + to_test = ['utility','http','logger','random','processors','message_buffer','extension','transport/iostream','transport/asio','roles','endpoint','connection','transport'] #,'http','processors','connection' for t in to_test: new_tests = SConscript('#/test/'+t+'/SConscript',variant_dir = testdir + t, duplicate = 0) diff --git a/test/transport/SConscript b/test/transport/SConscript new file mode 100644 index 0000000000..b31a190059 --- /dev/null +++ b/test/transport/SConscript @@ -0,0 +1,24 @@ +## transport integration tests +## + +Import('env') +Import('env_cpp11') +Import('boostlibs') +Import('platform_libs') +Import('polyfill_libs') +Import('tls_libs') + +env = env.Clone () +env_cpp11 = env_cpp11.Clone () + +BOOST_LIBS = boostlibs(['unit_test_framework','system','thread','regex','random'],env) + [platform_libs] + [tls_libs] + +objs = env.Object('boost_integration.o', ["integration.cpp"], LIBS = BOOST_LIBS) +prgs = env.Program('test_boost_integration', ["boost_integration.o"], LIBS = BOOST_LIBS) + +if env_cpp11.has_key('WSPP_CPP11_ENABLED'): + BOOST_LIBS_CPP11 = boostlibs(['unit_test_framework','system'],env_cpp11) + [platform_libs] + [polyfill_libs] + [tls_libs] + objs += env_cpp11.Object('stl_integration.o', ["integration.cpp"], LIBS = BOOST_LIBS_CPP11) + prgs += env_cpp11.Program('test_stl_integration', ["stl_integration.o"], LIBS = BOOST_LIBS_CPP11) + +Return('prgs') diff --git a/test/transport/integration.cpp b/test/transport/integration.cpp new file mode 100644 index 0000000000..369688d191 --- /dev/null +++ b/test/transport/integration.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2011, 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. + * + */ +//#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE transport_integration +#include + +#include + +#include +#include +#include +#include + +typedef websocketpp::server server; +typedef websocketpp::client client; + +using websocketpp::lib::placeholders::_1; +using websocketpp::lib::placeholders::_2; +using websocketpp::lib::bind; + +void run_server(server * s, int port) { + try { + s->clear_access_channels(websocketpp::log::alevel::all); + s->clear_error_channels(websocketpp::log::elevel::all); + + s->init_asio(); + + s->listen(port); + s->start_accept(); + s->run(); + } catch (std::exception & e) { + std::cout << e.what() << std::endl; + } catch (boost::system::error_code & ec) { + std::cout << ec.message() << std::endl; + } +} + +void run_client(client & c, std::string uri) { + c.clear_access_channels(websocketpp::log::alevel::all); + c.clear_error_channels(websocketpp::log::elevel::all); + + c.init_asio(); + + websocketpp::lib::error_code ec; + client::connection_ptr con = c.get_connection(uri,ec); + BOOST_CHECK( !ec ); + c.connect(con); + + c.run(); +} + +bool on_ping(websocketpp::connection_hdl, std::string payload) { + return false; +} + +void cancel_on_open(server * s, websocketpp::connection_hdl hdl) { + s->cancel(); +} + +template +void ping_on_open(T * c, std::string payload, websocketpp::connection_hdl hdl) { + typename T::connection_ptr con = c->get_con_from_hdl(hdl); + con->ping(payload); +} + +void fail_on_pong(websocketpp::connection_hdl hdl, std::string payload) { + BOOST_FAIL( "expected no pong" ); +} + +template +void req_pong_timeout(T * c, std::string expected_payload, + websocketpp::connection_hdl hdl, std::string payload) +{ + typename T::connection_ptr con = c->get_con_from_hdl(hdl); + BOOST_CHECK_EQUAL( payload, expected_payload ); + con->close(websocketpp::close::status::normal,""); +} + +// Wait for the specified time period then fail the test +void run_test_timer(long value) { + /*boost::asio::io_service ios; + boost::asio::deadline_timer t(ios,boost::posix_time::milliseconds(value)); + boost::system::error_code ec; + t.wait(ec);*/ + sleep(value); + BOOST_FAIL( "Test timed out" ); +} + +BOOST_AUTO_TEST_CASE( pong_timeout ) { + server s; + client c; + + s.set_ping_handler(on_ping); + s.set_open_handler(bind(&cancel_on_open,&s,::_1)); + + c.set_pong_handler(bind(&fail_on_pong,::_1,::_2)); + c.set_open_handler(bind(&ping_on_open,&c,"foo",::_1)); + c.set_pong_timeout_handler(bind(&req_pong_timeout,&c,"foo",::_1,::_2)); + + websocketpp::lib::thread sthread(websocketpp::lib::bind(&run_server,&s,9005)); + websocketpp::lib::thread tthread(websocketpp::lib::bind(&run_test_timer,6)); + sthread.detach(); + tthread.detach(); + + run_client(c, "http://localhost:9005"); +} + diff --git a/websocketpp/connection.hpp b/websocketpp/connection.hpp index 55150b2b64..d43408135c 100644 --- a/websocketpp/connection.hpp +++ b/websocketpp/connection.hpp @@ -163,7 +163,10 @@ public: // Message handler (needs to know message type) typedef lib::function message_handler; - + + /// Type of a pointer to a transport timer handle + typedef typename transport_con_type::timer_ptr timer_ptr; + // Misc Convenience Types typedef session::internal_state::value istate_type; @@ -263,11 +266,18 @@ public: * If the transport component being used supports timers, the pong timeout * handler is called whenever a pong control frame is not received with the * configured timeout period after the application sends a ping. - * + * + * The config setting `timeout_pong` controls the length of the timeout + * period. It is specified in milliseconds. + * * This can be used to probe the health of the remote endpoint's WebSocket * implimentation. This does not guarantee that the remote application * itself is still healthy but can be a useful diagnostic. * + * Note: receipt of this callback doesn't mean the pong will never come. + * This functionality will not suppress delivery of the pong in question + * should it arrive after the timeout. + * * @param h The new pong_timeout_handler */ void set_pong_timeout_handler(pong_timeout_handler h) { @@ -428,7 +438,13 @@ public: * @param payload Payload to be used for the ping */ void ping(const std::string& payload); - + + /// exception free variant of ping + void ping(const std::string & payload, lib::error_code & ec); + + /// Utility method that gets called back when the ping timer expires + void handle_pong_timeout(std::string payload, const lib::error_code & ec); + /// Send a pong /** * Initiates a pong with the given payload. @@ -1027,6 +1043,7 @@ private: size_t m_buf_cursor; termination_handler m_termination_handler; con_msg_manager_ptr m_msg_manager; + timer_ptr m_ping_timer; // TODO: this is not memory efficient. this value is not used after the // handshake. diff --git a/websocketpp/impl/connection_impl.hpp b/websocketpp/impl/connection_impl.hpp index 9919afccdb..7d0f7370cd 100644 --- a/websocketpp/impl/connection_impl.hpp +++ b/websocketpp/impl/connection_impl.hpp @@ -139,21 +139,45 @@ lib::error_code connection::send(typename config::message_type::ptr msg) } template -void connection::ping(const std::string& payload) { +void connection::ping(const std::string& payload, lib::error_code& ec) { m_alog.write(log::alevel::devel,"connection ping"); if (m_state != session::state::open) { - throw error::make_error_code(error::invalid_state); + ec = error::make_error_code(error::invalid_state); + return; } message_ptr msg = m_msg_manager->get_message(); if (!msg) { - throw error::make_error_code(error::no_outgoing_buffers); + ec = error::make_error_code(error::no_outgoing_buffers); + return; } - lib::error_code ec = m_processor->prepare_ping(payload,msg); - if (ec) { - throw ec; + ec = m_processor->prepare_ping(payload,msg); + if (ec) {return;} + + // set ping timer if we are listening for one + if (m_pong_timeout_handler) { + // Cancel any existing timers + if (m_ping_timer) { + m_ping_timer->cancel(); + } + + m_ping_timer = transport_con_type::set_timer( + config::timeout_pong, + lib::bind( + &type::handle_pong_timeout, + type::shared_from_this(), + payload, + lib::placeholders::_1 + ) + ); + + if (!m_ping_timer) { + // Our transport doesn't support timers + m_elog.write(log::elevel::warn,"Warning: a pong_timeout_handler is \ + set but the transport in use does not support timeouts."); + } } bool needs_writing = false; @@ -169,6 +193,36 @@ void connection::ping(const std::string& payload) { type::shared_from_this() )); } + + ec = lib::error_code(); +} + +template +void connection::ping(const std::string& payload) { + lib::error_code ec; + ping(payload,ec); + if (ec) { + throw ec; + } +} + +template +void connection::handle_pong_timeout(std::string payload, const lib::error_code & + ec) +{ + if (ec) { + if (ec == transport::error::operation_aborted) { + // ignore, this is expected + return; + } + + m_elog.write(log::elevel::devel,"pong_timeout error: "+ec.message()); + return; + } + + if (m_pong_timeout_handler) { + m_pong_timeout_handler(m_connection_hdl,payload); + } } template @@ -187,9 +241,7 @@ void connection::pong(const std::string& payload, lib::error_code& ec) { } ec = m_processor->prepare_pong(payload,msg); - if (ec) { - return; - } + if (ec) {return;} bool needs_writing = false; {