adds pong timer support and new full stack automated integration testing for it

This commit is contained in:
Peter Thorson
2013-05-26 22:04:28 -05:00
parent 5677b5f400
commit 10a8af1ac3
5 changed files with 237 additions and 13 deletions

View File

@@ -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)

24
test/transport/SConscript Normal file
View File

@@ -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')

View File

@@ -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 <boost/test/unit_test.hpp>
#include <websocketpp/common/thread.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/config/asio_no_tls_client.hpp>
#include <websocketpp/server.hpp>
#include <websocketpp/client.hpp>
typedef websocketpp::server<websocketpp::config::asio> server;
typedef websocketpp::client<websocketpp::config::asio_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 <typename T>
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 <typename T>
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<client>,&c,"foo",::_1));
c.set_pong_timeout_handler(bind(&req_pong_timeout<client>,&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");
}

View File

@@ -163,7 +163,10 @@ public:
// Message handler (needs to know message type)
typedef lib::function<void(connection_hdl,message_ptr)> 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.

View File

@@ -139,21 +139,45 @@ lib::error_code connection<config>::send(typename config::message_type::ptr msg)
}
template <typename config>
void connection<config>::ping(const std::string& payload) {
void connection<config>::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<config>::ping(const std::string& payload) {
type::shared_from_this()
));
}
ec = lib::error_code();
}
template<typename config>
void connection<config>::ping(const std::string& payload) {
lib::error_code ec;
ping(payload,ec);
if (ec) {
throw ec;
}
}
template<typename config>
void connection<config>::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 <typename config>
@@ -187,9 +241,7 @@ void connection<config>::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;
{