diff --git a/.gitignore b/.gitignore index 249c560a1..118b21487 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,5 @@ build/ examples/wsperf/wsperf_client *.out + +*.log diff --git a/SConstruct b/SConstruct index 45ea5b619..96026a449 100644 --- a/SConstruct +++ b/SConstruct @@ -20,6 +20,9 @@ if os.environ.has_key('LINKFLAGS'): ## or set BOOST_INCLUDES and BOOST_LIBS if Boost comes with your OS distro e.g. and ## needs BOOST_INCLUDES=/usr/include/boost and BOOST_LIBS=/usr/lib like Ubuntu. ## +if os.environ.has_key('BOOSTROOT'): + os.environ['BOOST_ROOT'] = os.environ['BOOSTROOT'] + if os.environ.has_key('BOOST_ROOT'): env['BOOST_INCLUDES'] = os.environ['BOOST_ROOT'] env['BOOST_LIBS'] = os.path.join(os.environ['BOOST_ROOT'], 'stage', 'lib') @@ -56,7 +59,10 @@ if env['PLATFORM'].startswith('win'): 'WIN32_LEAN_AND_MEAN', '_WIN32_WINNT=0x0600', '_CONSOLE', - '_WEBSOCKETPP_CPP11_FRIEND_']) + 'BOOST_TEST_DYN_LINK', + 'NOMINMAX', + '_WEBSOCKETPP_CPP11_MEMORY_', + '_WEBSOCKETPP_CPP11_FUNCTIONAL_']) arch_flags = '/arch:SSE2' opt_flags = '/Ox /Oi /fp:fast' warn_flags = '/W3 /wd4996 /wd4995 /wd4355' @@ -82,12 +88,14 @@ else: # Compiler specific warning flags if env['CXX'].startswith('g++'): + #env.Append(CCFLAGS = ['-Wconversion']) env.Append(CCFLAGS = ['-Wcast-align']) elif env['CXX'].startswith('clang++'): #env.Append(CCFLAGS = ['-Wcast-align']) #env.Append(CCFLAGS = ['-Wglobal-constructors']) + #env.Append(CCFLAGS = ['-Wconversion']) env.Append(CCFLAGS = ['-Wno-padded']) - + # Wpadded # Wsign-conversion @@ -117,11 +125,11 @@ env_cpp11 = env.Clone () if env_cpp11['CXX'].startswith('g++'): # TODO: check g++ version - + # g++ STL lacks support for - + GCC_VERSION = commands.getoutput(env_cpp11['CXX'] + ' -dumpversion') - + if GCC_VERSION > "4.4.0": print "C++11 build environment partially enabled" env_cpp11.Append(WSPP_CPP11_ENABLED = "true",CXXFLAGS = ['-std=c++0x'],TOOLSET = ['g++'],CPPDEFINES = ['_WEBSOCKETPP_CPP11_STL_','_WEBSOCKETPP_NO_CPP11_REGEX_']) @@ -133,7 +141,7 @@ if env_cpp11['CXX'].startswith('g++'): elif env_cpp11['CXX'].startswith('clang++'): print "C++11 build environment enabled" env_cpp11.Append(WSPP_CPP11_ENABLED = "true",CXXFLAGS = ['-std=c++0x','-stdlib=libc++'],LINKFLAGS = ['-stdlib=libc++'],TOOLSET = ['clang++'],CPPDEFINES = ['_WEBSOCKETPP_CPP11_STL_']) - + # look for optional second boostroot compiled with clang's libc++ STL library # this prevents warnings/errors when linking code built with two different # incompatible STL libraries. @@ -146,12 +154,22 @@ elif env_cpp11['CXX'].startswith('clang++'): else: print "C++11 build environment disabled" -#env.Append(CPPPATH = [env['BOOST_INCLUDES']]) -env.Append(CPPFLAGS = '-isystem '+env['BOOST_INCLUDES']) +# if the build system is known to allow the isystem modifier for library include +# values then use it for the boost libraries. Otherwise just add them to the +# regular CPPPATH values. +if env['CXX'].startswith('g++') or env['CXX'].startswith('clang'): + env.Append(CPPFLAGS = '-isystem ' + env['BOOST_INCLUDES']) +else: + env.Append(CPPPATH = [env['BOOST_INCLUDES']]) env.Append(LIBPATH = [env['BOOST_LIBS']]) -#env_cpp11.Append(CPPPATH = [env_cpp11['BOOST_INCLUDES']]) -env_cpp11.Append(CPPFLAGS = '-isystem ' + env_cpp11['BOOST_INCLUDES']) +# if the build system is known to allow the isystem modifier for library include +# values then use it for the boost libraries. Otherwise just add them to the +# regular CPPPATH values. +if env_cpp11['CXX'].startswith('g++') or env_cpp11['CXX'].startswith('clang'): + env_cpp11.Append(CPPFLAGS = '-isystem ' + env_cpp11['BOOST_INCLUDES']) +else: + env_cpp11.Append(CPPPATH = [env_cpp11['BOOST_INCLUDES']]) env_cpp11.Append(LIBPATH = [env_cpp11['BOOST_LIBS']]) releasedir = 'build/release/' @@ -170,23 +188,28 @@ Export('polyfill_libs') ## TARGETS: -# 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' +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' -for t in to_test: - new_tests = SConscript('#/test/'+t+'/SConscript',variant_dir = testdir + t, duplicate = 0) - for a in new_tests: - new_alias = Alias('test', [a], a.abspath) - AlwaysBuild(new_alias) + for t in to_test: + new_tests = SConscript('#/test/'+t+'/SConscript',variant_dir = testdir + t, duplicate = 0) + for a in new_tests: + new_alias = Alias('test', [a], a.abspath) + AlwaysBuild(new_alias) # Main test application #main = SConscript('#/examples/dev/SConscript',variant_dir = builddir + 'dev',duplicate = 0) +# testee_server +testee_server = SConscript('#/examples/testee_server/SConscript',variant_dir = builddir + 'testee_server',duplicate = 0) + # echo_server echo_server = SConscript('#/examples/echo_server/SConscript',variant_dir = builddir + 'echo_server',duplicate = 0) # echo_server_tls -echo_server_tls = SConscript('#/examples/echo_server_tls/SConscript',variant_dir = builddir + 'echo_server_tls',duplicate = 0) +if not env['PLATFORM'].startswith('win'): + echo_server_tls = SConscript('#/examples/echo_server_tls/SConscript',variant_dir = builddir + 'echo_server_tls',duplicate = 0) # broadcast_server broadcast_server = SConscript('#/examples/broadcast_server/SConscript',variant_dir = builddir + 'broadcast_server',duplicate = 0) @@ -194,6 +217,19 @@ broadcast_server = SConscript('#/examples/broadcast_server/SConscript',variant_d # echo_client echo_client = SConscript('#/examples/echo_client/SConscript',variant_dir = builddir + 'echo_client',duplicate = 0) +# subprotocol_server +subprotocol_server = SConscript('#/examples/subprotocol_server/SConscript',variant_dir = builddir + 'subprotocol_server',duplicate = 0) + +if not env['PLATFORM'].startswith('win'): + # iostream_server + iostream_server = SConscript('#/examples/iostream_server/SConscript',variant_dir = builddir + 'iostream_server',duplicate = 0) + + # telemetry_client + telemetry_client = SConscript('#/examples/telemetry_client/SConscript',variant_dir = builddir + 'telemetry_client',duplicate = 0) + + # print_server + print_server = SConscript('#/examples/print_server/SConscript',variant_dir = builddir + 'print_server',duplicate = 0) + # #wsperf = SConscript('#/examples/wsperf/SConscript', # variant_dir = builddir + 'wsperf', diff --git a/examples/echo_server/echo_server.cpp b/examples/echo_server/echo_server.cpp index 1a36534c7..db8623421 100644 --- a/examples/echo_server/echo_server.cpp +++ b/examples/echo_server/echo_server.cpp @@ -11,7 +11,7 @@ using websocketpp::lib::placeholders::_2; using websocketpp::lib::bind; // pull out the type of messages sent by our config -typedef websocketpp::config::asio::message_type::ptr message_ptr; +typedef server::message_ptr message_ptr; // Define a callback to handle incoming messages void on_message(server* s, websocketpp::connection_hdl hdl, message_ptr msg) { diff --git a/examples/iostream_server/SConscript b/examples/iostream_server/SConscript new file mode 100644 index 000000000..3ea3f68e3 --- /dev/null +++ b/examples/iostream_server/SConscript @@ -0,0 +1,23 @@ +## iostream server example +## + +Import('env') +Import('env_cpp11') +Import('boostlibs') +Import('platform_libs') +Import('polyfill_libs') + +env = env.Clone () +env_cpp11 = env_cpp11.Clone () + +prgs = [] + +# if a C++11 environment is avaliable build using that, otherwise use boost +if env_cpp11.has_key('WSPP_CPP11_ENABLED'): + ALL_LIBS = boostlibs(['system'],env_cpp11) + [platform_libs] + [polyfill_libs] + prgs += env_cpp11.Program('iostream_server', ["iostream_server.cpp"], LIBS = ALL_LIBS) +else: + ALL_LIBS = boostlibs(['system','regex'],env) + [platform_libs] + [polyfill_libs] + prgs += env.Program('iostream_server', ["iostream_server.cpp"], LIBS = ALL_LIBS) + +Return('prgs') diff --git a/examples/iostream_server/iostream_server.cpp b/examples/iostream_server/iostream_server.cpp new file mode 100644 index 000000000..b1c3f0769 --- /dev/null +++ b/examples/iostream_server/iostream_server.cpp @@ -0,0 +1,91 @@ +#include + +#include + +#include +#include + +typedef websocketpp::server server; + +using websocketpp::lib::placeholders::_1; +using websocketpp::lib::placeholders::_2; +using websocketpp::lib::bind; + +// pull out the type of messages sent by our config +typedef server::message_ptr message_ptr; + +// Define a callback to handle incoming messages +void on_message(server* s, websocketpp::connection_hdl hdl, message_ptr msg) { + if (msg->get_opcode() == websocketpp::frame::opcode::text) { + s->get_alog().write(websocketpp::log::alevel::app, + "Text Message Received: "+msg->get_payload()); + } else { + s->get_alog().write(websocketpp::log::alevel::app, + "Binary Message Received: "+websocketpp::utility::to_hex(msg->get_payload())); + } + + try { + s->send(hdl, msg->get_payload(), msg->get_opcode()); + } catch (const websocketpp::lib::error_code& e) { + s->get_alog().write(websocketpp::log::alevel::app, + "Echo Failed: "+e.message()); + } +} + +int main() { + server s; + std::ofstream log; + + try { + // set up access channels to only log interesting things + s.clear_access_channels(websocketpp::log::alevel::all); + s.set_access_channels(websocketpp::log::alevel::connect); + s.set_access_channels(websocketpp::log::alevel::disconnect); + s.set_access_channels(websocketpp::log::alevel::app); + + // Log to a file rather than stdout, as we are using stdout for real + // output + log.open("output.log"); + s.get_alog().set_ostream(&log); + s.get_elog().set_ostream(&log); + + // print all output to stdout + s.register_ostream(&std::cout); + + // Register our message handler + s.set_message_handler(bind(&on_message,&s,::_1,::_2)); + + server::connection_ptr con = s.get_connection(); + + con->start(); + + // C++ iostream's don't support the idea of asynchronous i/o. As such + // there are two input strategies demonstrated here. Buffered I/O will + // read from stdin in chunks until EOF. This works very well for + // replaying canned connections as would be done in automated testing. + // + // If the server is being used live however, assuming input is being + // piped from elsewhere in realtime, this strategy will result in small + // messages being buffered forever. The non-buffered strategy below + // reads characters from stdin one at a time. This is inefficient and + // for more serious uses should be replaced with a platform specific + // asyncronous i/o technique like select, poll, IOCP, etc + bool buffered_io = false; + + if (buffered_io) { + std::cin >> *con; + } else { + char a; + while(std::cin.get(a)) { + con->readsome(&a,1); + } + } + } catch (const std::exception & e) { + std::cout << e.what() << std::endl; + } catch (websocketpp::lib::error_code e) { + std::cout << e.message() << std::endl; + } catch (...) { + std::cout << "other exception" << std::endl; + } + log.close(); +} \ No newline at end of file diff --git a/examples/print_server/SConscript b/examples/print_server/SConscript new file mode 100644 index 000000000..6e0724e5f --- /dev/null +++ b/examples/print_server/SConscript @@ -0,0 +1,23 @@ +## Print server example +## + +Import('env') +Import('env_cpp11') +Import('boostlibs') +Import('platform_libs') +Import('polyfill_libs') + +env = env.Clone () +env_cpp11 = env_cpp11.Clone () + +prgs = [] + +# if a C++11 environment is avaliable build using that, otherwise use boost +if env_cpp11.has_key('WSPP_CPP11_ENABLED'): + ALL_LIBS = boostlibs(['system'],env_cpp11) + [platform_libs] + [polyfill_libs] + prgs += env_cpp11.Program('print_server', ["print_server.cpp"], LIBS = ALL_LIBS) +else: + ALL_LIBS = boostlibs(['system','regex'],env) + [platform_libs] + [polyfill_libs] + prgs += env.Program('print_server', ["print_server.cpp"], LIBS = ALL_LIBS) + +Return('prgs') diff --git a/examples/print_server/print_server.cpp b/examples/print_server/print_server.cpp new file mode 100644 index 000000000..962ec45ae --- /dev/null +++ b/examples/print_server/print_server.cpp @@ -0,0 +1,22 @@ +#include + +#include +#include + +typedef websocketpp::server server; + +void on_message(websocketpp::connection_hdl hdl, server::message_ptr msg) { + std::cout << msg->get_payload() << std::endl; +} + +int main() { + server print_server; + + print_server.set_message_handler(&on_message); + + print_server.init_asio(); + print_server.listen(9002); + print_server.start_accept(); + + print_server.run(); +} diff --git a/examples/subprotocol_server/SConscript b/examples/subprotocol_server/SConscript new file mode 100644 index 000000000..2ce12359f --- /dev/null +++ b/examples/subprotocol_server/SConscript @@ -0,0 +1,23 @@ +## Main development example +## + +Import('env') +Import('env_cpp11') +Import('boostlibs') +Import('platform_libs') +Import('polyfill_libs') + +env = env.Clone () +env_cpp11 = env_cpp11.Clone () + +prgs = [] + +# if a C++11 environment is avaliable build using that, otherwise use boost +if env_cpp11.has_key('WSPP_CPP11_ENABLED'): + ALL_LIBS = boostlibs(['system'],env_cpp11) + [platform_libs] + [polyfill_libs] + prgs += env_cpp11.Program('subprotocol_server', ["subprotocol_server.cpp"], LIBS = ALL_LIBS) +else: + ALL_LIBS = boostlibs(['system','regex'],env) + [platform_libs] + [polyfill_libs] + prgs += env.Program('subprotocol_server', ["subprotocol_server.cpp"], LIBS = ALL_LIBS) + +Return('prgs') diff --git a/examples/subprotocol_server/subprotocol_server.cpp b/examples/subprotocol_server/subprotocol_server.cpp new file mode 100644 index 000000000..0cdc1d0cd --- /dev/null +++ b/examples/subprotocol_server/subprotocol_server.cpp @@ -0,0 +1,52 @@ +#include + +#include +#include + +typedef websocketpp::server server; + +using websocketpp::connection_hdl; +using websocketpp::lib::placeholders::_1; +using websocketpp::lib::placeholders::_2; +using websocketpp::lib::bind; +using websocketpp::lib::ref; + + +bool validate(server & s, connection_hdl hdl) { + server::connection_ptr con = s.get_con_from_hdl(hdl); + + std::cout << "Cache-Control: " << con->get_request_header("Cache-Control") << std::endl; + + const std::vector & subp_requests = con->get_requested_subprotocols(); + std::vector::const_iterator it; + + for (it = subp_requests.begin(); it != subp_requests.end(); ++it) { + std::cout << "Requested: " << *it << std::endl; + } + + if (subp_requests.size() > 0) { + con->select_subprotocol(subp_requests[0]); + } + + return true; +} + +int main() { + try { + server s; + + s.set_validate_handler(bind(&validate,ref(s),::_1)); + + s.init_asio(); + s.listen(9005); + s.start_accept(); + + s.run(); + } catch (const std::exception & e) { + std::cout << e.what() << std::endl; + } catch (websocketpp::lib::error_code e) { + std::cout << e.message() << std::endl; + } catch (...) { + std::cout << "other exception" << std::endl; + } +} \ No newline at end of file diff --git a/examples/telemetry_client/SConscript b/examples/telemetry_client/SConscript new file mode 100644 index 000000000..41a1bb067 --- /dev/null +++ b/examples/telemetry_client/SConscript @@ -0,0 +1,23 @@ +## Telemetry client example +## + +Import('env') +Import('env_cpp11') +Import('boostlibs') +Import('platform_libs') +Import('polyfill_libs') + +env = env.Clone () +env_cpp11 = env_cpp11.Clone () + +prgs = [] + +# if a C++11 environment is avaliable build using that, otherwise use boost +if env_cpp11.has_key('WSPP_CPP11_ENABLED'): + ALL_LIBS = boostlibs(['system'],env_cpp11) + [platform_libs] + [polyfill_libs] + prgs += env_cpp11.Program('telemetry_client', ["telemetry_client.cpp"], LIBS = ALL_LIBS) +else: + ALL_LIBS = boostlibs(['system','regex','random'],env) + [platform_libs] + [polyfill_libs] + prgs += env.Program('telemetry_client', ["telemetry_client.cpp"], LIBS = ALL_LIBS) + +Return('prgs') diff --git a/examples/telemetry_client/telemetry_client.cpp b/examples/telemetry_client/telemetry_client.cpp new file mode 100644 index 000000000..700ca578a --- /dev/null +++ b/examples/telemetry_client/telemetry_client.cpp @@ -0,0 +1,150 @@ +#include +#include + +// This header pulls in the WebSocket++ abstracted thread support that will +// select between boost::thread and std::thread based on how the build system +// is configured. +#include + +/** + * The telemetry client connects to a WebSocket server and sends a message every + * second containing an integer count. This example can be used as the basis for + * programs where a client connects and pushes data for logging, stress/load + * testing, etc. + */ +class telemetry_client { +public: + typedef websocketpp::client client; + typedef websocketpp::lib::lock_guard scoped_lock; + + telemetry_client() : m_open(false),m_done(false) { + // set up access channels to only log interesting things + m_client.clear_access_channels(websocketpp::log::alevel::all); + m_client.set_access_channels(websocketpp::log::alevel::connect); + m_client.set_access_channels(websocketpp::log::alevel::disconnect); + m_client.set_access_channels(websocketpp::log::alevel::app); + + // Initialize the Asio transport policy + m_client.init_asio(); + + // Bind the handlers we are using + using websocketpp::lib::placeholders::_1; + using websocketpp::lib::bind; + m_client.set_open_handler(bind(&telemetry_client::on_open,this,::_1)); + m_client.set_close_handler(bind(&telemetry_client::on_close,this,::_1)); + m_client.set_fail_handler(bind(&telemetry_client::on_fail,this,::_1)); + } + + // This method will block until the connection is complete + void run(const std::string & uri) { + // Create a new connection to the given URI + websocketpp::lib::error_code ec; + client::connection_ptr con = m_client.get_connection(uri, ec); + if (ec) { + m_client.get_alog().write(websocketpp::log::alevel::app, + "Get Connection Error: "+ec.message()); + return; + } + + // Grab a handle for this connection so we can talk to it in a thread + // safe manor after the event loop starts. + m_hdl = con->get_handle(); + + // Queue the connection. No DNS queries or network connections will be + // made until the io_service event loop is run. + m_client.connect(con); + + // Create a thread to run the ASIO io_service event loop + websocketpp::lib::thread asio_thread(&client::run, &m_client); + + // Create a thread to run the telemetry loop + websocketpp::lib::thread telemetry_thread(&telemetry_client::telemetry_loop,this); + + asio_thread.join(); + telemetry_thread.join(); + } + + // The open handler will signal that we are ready to start sending telemetry + void on_open(websocketpp::connection_hdl hdl) { + m_client.get_alog().write(websocketpp::log::alevel::app, + "Connection opened, starting telemetry!"); + + scoped_lock guard(m_lock); + m_open = true; + } + + // The close handler will signal that we should stop sending telemetry + void on_close(websocketpp::connection_hdl hdl) { + m_client.get_alog().write(websocketpp::log::alevel::app, + "Connection closed, stopping telemetry!"); + + scoped_lock guard(m_lock); + m_done = true; + } + + // The fail handler will signal that we should stop sending telemetry + void on_fail(websocketpp::connection_hdl hdl) { + m_client.get_alog().write(websocketpp::log::alevel::app, + "Connection failed, stopping telemetry!"); + + scoped_lock guard(m_lock); + m_done = true; + } + + void telemetry_loop() { + uint64_t count = 0; + std::stringstream val; + websocketpp::lib::error_code ec; + + while(1) { + { + scoped_lock guard(m_lock); + // If the connection has been closed, stop generating telemetry + if (m_done) {break;} + + // If the connection hasn't been opened yet wait a bit and retry + if (!m_open) { + sleep(1); + continue; + } + } + + val.str(""); + val << "count is " << count++; + + m_client.get_alog().write(websocketpp::log::alevel::app, val.str()); + m_client.send(m_hdl,val.str(),websocketpp::frame::opcode::text,ec); + + // The most likely error that we will get is that the connection is + // not in the right state. Usually this means we tried to send a + // message to a connection that was closed or in the process of + // closing. While many errors here can be easily recovered from, + // in this simple example, we'll stop the telemetry loop. + if (ec) { + m_client.get_alog().write(websocketpp::log::alevel::app, + "Send Error: "+ec.message()); + break; + } + + sleep(1); + } + } +private: + client m_client; + websocketpp::connection_hdl m_hdl; + websocketpp::lib::mutex m_lock; + bool m_open; + bool m_done; +}; + +int main(int argc, char* argv[]) { + telemetry_client c; + + std::string uri = "ws://localhost:9002"; + + if (argc == 2) { + uri = argv[1]; + } + + c.run(uri); +} diff --git a/examples/testee_server/SConscript b/examples/testee_server/SConscript new file mode 100644 index 000000000..269420106 --- /dev/null +++ b/examples/testee_server/SConscript @@ -0,0 +1,23 @@ +## Autobahn Testee Server +## + +Import('env') +Import('env_cpp11') +Import('boostlibs') +Import('platform_libs') +Import('polyfill_libs') + +env = env.Clone () +env_cpp11 = env_cpp11.Clone () + +prgs = [] + +# if a C++11 environment is avaliable build using that, otherwise use boost +if env_cpp11.has_key('WSPP_CPP11_ENABLED'): + ALL_LIBS = boostlibs(['system'],env_cpp11) + [platform_libs] + [polyfill_libs] + prgs += env_cpp11.Program('testee_server', ["testee_server.cpp"], LIBS = ALL_LIBS) +else: + ALL_LIBS = boostlibs(['system','regex'],env) + [platform_libs] + [polyfill_libs] + prgs += env.Program('testee_server', ["testee_server.cpp"], LIBS = ALL_LIBS) + +Return('prgs') diff --git a/examples/testee_server/testee_server.cpp b/examples/testee_server/testee_server.cpp new file mode 100644 index 000000000..09e8091ae --- /dev/null +++ b/examples/testee_server/testee_server.cpp @@ -0,0 +1,76 @@ +/* + * 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: + * * 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. + * + */ + +#include +#include +#include + +typedef websocketpp::server server; + +using websocketpp::lib::placeholders::_1; +using websocketpp::lib::placeholders::_2; +using websocketpp::lib::bind; + +// pull out the type of messages sent by our config +typedef server::message_ptr message_ptr; + +// Define a callback to handle incoming messages +void on_message(server* s, websocketpp::connection_hdl hdl, message_ptr msg) { + s->send(hdl, msg->get_payload(), msg->get_opcode()); +} + +int main() { + // Create a server endpoint + server testee_server; + + try { + // Total silence + testee_server.clear_access_channels(websocketpp::log::alevel::all); + testee_server.clear_error_channels(websocketpp::log::alevel::all); + + // Initialize ASIO + testee_server.init_asio(); + + // Register our message handler + testee_server.set_message_handler(bind(&on_message,&testee_server,::_1,::_2)); + + // Listen on port 9002 + testee_server.listen(9002); + + // Start the server accept loop + testee_server.start_accept(); + + // Start the ASIO io_service run loop + testee_server.run(); + } catch (const std::exception & e) { + std::cout << e.what() << std::endl; + } catch (websocketpp::lib::error_code e) { + std::cout << e.message() << std::endl; + } catch (...) { + std::cout << "other exception" << std::endl; + } +} diff --git a/init.txt b/init.txt index efc3c59a2..14a2bf56b 100644 --- a/init.txt +++ b/init.txt @@ -105,3 +105,75 @@ start_accept ################## send message + + + + +########### Other development notes previously from websocketpp.hpp ############ +/* + +Endpoint +- container for connections +- Store and forward default connection settings + +Connection +- Represents the state and functionality of a single WebSocket session starting + with the opening handshake and completing with the closing one. +- After a connection is created settings may be applied that will be used for + this connection. +- Once setup is complete a start method is run and the connection enters its + event loop. The connection requests bytes from its transport, then runs those + bytes through the appropriate websocket frame processor, and calls handler + methods appropriate for the types of frames recieved. + + + + + +Policies: + +Concurrency + + + + + + +Concurrency Models +Single Thread Async (lock free) +- WS++ runs lock free (Access to endpoint and connection from non-WS++ threads is unsafe) +- All handlers and networking operations run in a single thread. +- Handlers can block each other and network operations +- Good for low traffic workflows where connections are independent and requests are short. + +Single Thread Async +- Same as lock free version except access to endpoint and connection from non-WS++ threads is safe +- Good for workflows where any long running handler job is deferred to a non-WS++ thread for processing. + +Thread Pool (lock free) +- WS++ runs lock free (access to endpoint and connection from non-WS++ threads is unsafe) +- Handlers and networking operations invoked by multiple threads. Individual connections are serialized. +- n handlers will block network operations (n=num_threads) +- Allows much better multi-core utilization, does not require end user syncronization as long as all work is performed inside handlers and handlers only reference their own connection. Handler local data must be syncronized. + +Thread pool +- Same as lock free version except access to endpoint and connection from non-WS++ threads is safe. + +Thread per connection +- + +io_service policies +- external vs internal +- per endpoint or per connection + + +message policies? +- Control Messages: +-- Each connection should have a single control message permanently allocated +- Data Messages +-- Dynamically allocate a new data message as needed. +-- Re-usable pool of data messages per endpoint +-- Re-usable pool of data messages per connection + + +*/ diff --git a/readme.txt b/readme.txt index c6f46c123..fe1a9c42e 100644 --- a/readme.txt +++ b/readme.txt @@ -36,6 +36,12 @@ Implimented, needs more testing - exception/error handling - Logging - Client role +- Subprotocol negotiation +- Hybi 00/Hixie 76 legacy protocol support +- Performance tuning +- Outgoing Proxy Support +- PowerPC support +- Visual Studio / Windows support Implimented, API not finalized - open_handler @@ -44,11 +50,9 @@ Implimented, API not finalized - http_handler Needs work: -- PowerPC support -- Subprotocol negotiation +- Timeouts + +Non-release blocking feature roadmap - Extension support - permessage_compress extension -- Visual Studio / Windows support -- Hybi 00/Hixie 76 legacy protocol support -- Message buffer pool -- Performance tuning \ No newline at end of file +- Message buffer pool \ No newline at end of file diff --git a/test/connection/connection.cpp b/test/connection/connection.cpp index 88f3bab38..5cbcea6ca 100644 --- a/test/connection/connection.cpp +++ b/test/connection/connection.cpp @@ -81,40 +81,49 @@ struct stub_config : public websocketpp::config::core { typedef connection_extension connection_base; }; -BOOST_AUTO_TEST_CASE( connection_extensions ) { - stub_config::alog_type alog; +struct connection_setup { + connection_setup(bool server) + : c(server,"",alog,elog,rng) {} + + websocketpp::lib::error_code ec; + stub_config::alog_type alog; stub_config::elog_type elog; - stub_config::rng_type rng; - websocketpp::connection s(true,"",alog,elog,rng); + stub_config::rng_type rng; + websocketpp::connection c; +}; + +/*void echo_func(server* s, websocketpp::connection_hdl hdl, message_ptr msg) { + s->send(hdl, msg->get_payload(), msg->get_opcode()); +}*/ + +void validate_func(server* s, websocketpp::connection_hdl hdl, message_ptr msg) { + s->send(hdl, msg->get_payload(), msg->get_opcode()); +} + + + +BOOST_AUTO_TEST_CASE( connection_extensions ) { + connection_setup env(true); - BOOST_CHECK( s.extension_value == 5 ); - BOOST_CHECK( s.extension_method() == 5 ); + BOOST_CHECK( env.c.extension_value == 5 ); + BOOST_CHECK( env.c.extension_method() == 5 ); - BOOST_CHECK( s.is_server() == true ); + BOOST_CHECK( env.c.is_server() == true ); } -/* BOOST_AUTO_TEST_CASE( basic_websocket_request ) { std::string input = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example.com\r\n\r\n"; - std::string output = "HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\nServer: "+websocketpp::USER_AGENT+"\r\nUpgrade: websocket\r\n\r\n"; - - BOOST_CHECK(run_server_test(input) == output); -} - -BOOST_AUTO_TEST_CASE( invalid_websocket_version ) { - std::string input = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: a\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example.com\r\n\r\n"; - std::string output = "HTTP/1.1 400 Bad Request\r\nServer: "+websocketpp::USER_AGENT+"\r\n\r\n"; - - BOOST_CHECK(run_server_test(input) == output); + std::string output = "HTTP/1.1 101 Switching Protocols\r\nConnection: upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\nServer: "; + output+=websocketpp::user_agent; + output+="\r\nUpgrade: websocket\r\n\r\n"; + + server s; + s.set_message_handler(bind(&echo_func,&s,::_1,::_2)); + + BOOST_CHECK(run_server_test(s,input) == output); } -BOOST_AUTO_TEST_CASE( unimplimented_websocket_version ) { - std::string input = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 14\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example.com\r\n\r\n"; - - std::string output = "HTTP/1.1 400 Bad Request\r\nSec-WebSocket-Version: 0,7,8,13\r\nServer: "+websocketpp::USER_AGENT+"\r\n\r\n"; - - BOOST_CHECK(run_server_test(input) == output); -} +/* BOOST_AUTO_TEST_CASE( user_reject_origin ) { std::string input = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example2.com\r\n\r\n"; diff --git a/test/connection/connection_tu2.cpp b/test/connection/connection_tu2.cpp index 76f4ddd83..9abd009af 100644 --- a/test/connection/connection_tu2.cpp +++ b/test/connection/connection_tu2.cpp @@ -27,7 +27,7 @@ #include "connection_tu2.hpp" -void on_message(server* s, websocketpp::connection_hdl hdl, message_ptr msg) { +void echo_func(server* s, websocketpp::connection_hdl hdl, message_ptr msg) { s->send(hdl, msg->get_payload(), msg->get_opcode()); } @@ -35,7 +35,7 @@ std::string run_server_test(std::string input) { server test_server; server::connection_ptr con; - test_server.set_message_handler(bind(&on_message,&test_server,::_1,::_2)); + test_server.set_message_handler(bind(&echo_func,&test_server,::_1,::_2)); std::stringstream output; @@ -50,5 +50,22 @@ std::string run_server_test(std::string input) { channel << input; channel >> *con; + return output.str(); +} + +std::string run_server_test(server& s, std::string input) { + server::connection_ptr con; + std::stringstream output; + + s.register_ostream(&output); + + con = s.get_connection(); + con->start(); + + std::stringstream channel; + + channel << input; + channel >> *con; + return output.str(); } \ No newline at end of file diff --git a/test/connection/connection_tu2.hpp b/test/connection/connection_tu2.hpp index bbc1cb738..09ad74eee 100644 --- a/test/connection/connection_tu2.hpp +++ b/test/connection/connection_tu2.hpp @@ -51,5 +51,6 @@ using websocketpp::lib::bind; } };*/ -void on_message(server* s, websocketpp::connection_hdl hdl, message_ptr msg); -std::string run_server_test(std::string input); \ No newline at end of file +void echo_func(server* s, websocketpp::connection_hdl hdl, message_ptr msg); +std::string run_server_test(std::string input); +std::string run_server_test(server & s,std::string input); \ No newline at end of file diff --git a/test/http/parser.cpp b/test/http/parser.cpp index c61fb3d62..49604268f 100644 --- a/test/http/parser.cpp +++ b/test/http/parser.cpp @@ -236,123 +236,123 @@ BOOST_AUTO_TEST_CASE( extract_parameters ) { p.clear(); it = extract_parameters(s2.begin(),s2.end(),p); BOOST_CHECK( it == s2.end() ); - BOOST_CHECK( p.size() == 1 ); - BOOST_CHECK( p.find("foo") != p.end() ); - BOOST_CHECK( p.find("foo")->second.size() == 0 ); + BOOST_CHECK_EQUAL( p.size(), 1 ); + BOOST_CHECK( p[0].first == "foo" ); + BOOST_CHECK_EQUAL( p[0].second.size(), 0 ); p.clear(); it = extract_parameters(s3.begin(),s3.end(),p); BOOST_CHECK( it == s3.begin()+5 ); - BOOST_CHECK( p.size() == 1 ); - BOOST_CHECK( p.find("foo") != p.end() ); - BOOST_CHECK( p.find("foo")->second.size() == 0 ); + BOOST_CHECK_EQUAL( p.size(), 1 ); + BOOST_CHECK( p[0].first == "foo" ); + BOOST_CHECK_EQUAL( p[0].second.size(), 0 ); p.clear(); it = extract_parameters(s4.begin(),s4.end(),p); BOOST_CHECK( it == s4.end() ); - BOOST_CHECK( p.size() == 1 ); - BOOST_CHECK( p.find("foo") != p.end() ); - BOOST_CHECK( p.find("foo")->second.size() == 0 ); + BOOST_CHECK_EQUAL( p.size(), 1 ); + BOOST_CHECK( p[0].first == "foo" ); + BOOST_CHECK_EQUAL( p[0].second.size(), 0 ); p.clear(); it = extract_parameters(s5.begin(),s5.end(),p); BOOST_CHECK( it == s5.end() ); - BOOST_CHECK( p.size() == 2 ); - BOOST_CHECK( p.find("foo") != p.end() ); - BOOST_CHECK( p.find("foo")->second.size() == 0 ); - BOOST_CHECK( p.find("bar") != p.end() ); - BOOST_CHECK( p.find("bar")->second.size() == 0 ); + BOOST_CHECK_EQUAL( p.size(), 2 ); + BOOST_CHECK( p[0].first == "foo" ); + BOOST_CHECK_EQUAL( p[0].second.size(), 0 ); + BOOST_CHECK( p[1].first == "bar" ); + BOOST_CHECK_EQUAL( p[1].second.size(), 0 ); p.clear(); it = extract_parameters(s6.begin(),s6.end(),p); BOOST_CHECK( it == s6.end() ); - BOOST_CHECK( p.size() == 1 ); - BOOST_CHECK( p.find("foo") != p.end() ); - a = p.find("foo")->second; - BOOST_CHECK( a.size() == 1 ); + BOOST_CHECK_EQUAL( p.size(), 1 ); + BOOST_CHECK( p[0].first == "foo" ); + a = p[0].second; + BOOST_CHECK_EQUAL( a.size(), 1 ); BOOST_CHECK( a.find("bar") != a.end() ); - BOOST_CHECK( a.find("bar")->second == "" ); + BOOST_CHECK_EQUAL( a.find("bar")->second, "" ); p.clear(); it = extract_parameters(s7.begin(),s7.end(),p); BOOST_CHECK( it == s7.end() ); - BOOST_CHECK( p.size() == 2 ); - BOOST_CHECK( p.find("foo") != p.end() ); - a = p.find("foo")->second; - BOOST_CHECK( a.size() == 1 ); + BOOST_CHECK_EQUAL( p.size(), 2 ); + BOOST_CHECK( p[0].first == "foo" ); + a = p[0].second; + BOOST_CHECK_EQUAL( a.size(), 1 ); BOOST_CHECK( a.find("baz") != a.end() ); - BOOST_CHECK( a.find("baz")->second == "" ); - BOOST_CHECK( p.find("bar") != p.end() ); - a = p.find("bar")->second; - BOOST_CHECK( a.size() == 0 ); + BOOST_CHECK_EQUAL( a.find("baz")->second, "" ); + BOOST_CHECK( p[1].first == "bar" ); + a = p[1].second; + BOOST_CHECK_EQUAL( a.size(), 0 ); p.clear(); it = extract_parameters(s8.begin(),s8.end(),p); BOOST_CHECK( it == s8.end() ); - BOOST_CHECK( p.size() == 1 ); - BOOST_CHECK( p.find("foo") != p.end() ); - a = p.find("foo")->second; - BOOST_CHECK( a.size() == 2 ); + BOOST_CHECK_EQUAL( p.size(), 1 ); + BOOST_CHECK( p[0].first == "foo" ); + a = p[0].second; + BOOST_CHECK_EQUAL( a.size(), 2 ); BOOST_CHECK( a.find("bar") != a.end() ); - BOOST_CHECK( a.find("bar")->second == "" ); + BOOST_CHECK_EQUAL( a.find("bar")->second, "" ); BOOST_CHECK( a.find("baz") != a.end() ); - BOOST_CHECK( a.find("baz")->second == "" ); + BOOST_CHECK_EQUAL( a.find("baz")->second, "" ); p.clear(); it = extract_parameters(s9.begin(),s9.end(),p); BOOST_CHECK( it == s9.end() ); - BOOST_CHECK( p.size() == 1 ); - BOOST_CHECK( p.find("foo") != p.end() ); - a = p.find("foo")->second; - BOOST_CHECK( a.size() == 1 ); + BOOST_CHECK_EQUAL( p.size(), 1 ); + BOOST_CHECK( p[0].first == "foo" ); + a = p[0].second; + BOOST_CHECK_EQUAL( a.size(), 1 ); BOOST_CHECK( a.find("bar") != a.end() ); - BOOST_CHECK( a.find("bar")->second == "baz" ); + BOOST_CHECK_EQUAL( a.find("bar")->second, "baz" ); p.clear(); it = extract_parameters(s10.begin(),s10.end(),p); BOOST_CHECK( it == s10.end() ); - BOOST_CHECK( p.size() == 1 ); - BOOST_CHECK( p.find("foo") != p.end() ); - a = p.find("foo")->second; - BOOST_CHECK( a.size() == 2 ); + BOOST_CHECK_EQUAL( p.size(), 1 ); + BOOST_CHECK( p[0].first == "foo" ); + a = p[0].second; + BOOST_CHECK_EQUAL( a.size(), 2 ); BOOST_CHECK( a.find("bar") != a.end() ); - BOOST_CHECK( a.find("bar")->second == "baz" ); + BOOST_CHECK_EQUAL( a.find("bar")->second, "baz" ); BOOST_CHECK( a.find("boo") != a.end() ); - BOOST_CHECK( a.find("boo")->second == "" ); + BOOST_CHECK_EQUAL( a.find("boo")->second, "" ); p.clear(); it = extract_parameters(s11.begin(),s11.end(),p); BOOST_CHECK( it == s11.end() ); - BOOST_CHECK( p.size() == 2 ); - BOOST_CHECK( p.find("foo") != p.end() ); - a = p.find("foo")->second; - BOOST_CHECK( a.size() == 2 ); + BOOST_CHECK_EQUAL( p.size(), 2 ); + BOOST_CHECK( p[0].first == "foo" ); + a = p[0].second; + BOOST_CHECK_EQUAL( a.size(), 2 ); BOOST_CHECK( a.find("bar") != a.end() ); - BOOST_CHECK( a.find("bar")->second == "baz" ); + BOOST_CHECK_EQUAL( a.find("bar")->second, "baz" ); BOOST_CHECK( a.find("boo") != a.end() ); - BOOST_CHECK( a.find("boo")->second == "" ); - a = p.find("bob")->second; - BOOST_CHECK( a.size() == 0 ); + BOOST_CHECK_EQUAL( a.find("boo")->second, "" ); + a = p[1].second; + BOOST_CHECK_EQUAL( a.size(), 0 ); p.clear(); it = extract_parameters(s12.begin(),s12.end(),p); BOOST_CHECK( it == s12.end() ); - BOOST_CHECK( p.size() == 1 ); - BOOST_CHECK( p.find("foo") != p.end() ); - a = p.find("foo")->second; - BOOST_CHECK( a.size() == 1 ); + BOOST_CHECK_EQUAL( p.size(), 1 ); + BOOST_CHECK( p[0].first == "foo" ); + a = p[0].second; + BOOST_CHECK_EQUAL( a.size(), 1 ); BOOST_CHECK( a.find("bar") != a.end() ); - BOOST_CHECK( a.find("bar")->second == "a b c" ); + BOOST_CHECK_EQUAL( a.find("bar")->second, "a b c" ); p.clear(); it = extract_parameters(s13.begin(),s13.end(),p); BOOST_CHECK( it == s13.end() ); - BOOST_CHECK( p.size() == 1 ); - BOOST_CHECK( p.find("foo") != p.end() ); - a = p.find("foo")->second; - BOOST_CHECK( a.size() == 1 ); + BOOST_CHECK_EQUAL( p.size(), 1 ); + BOOST_CHECK( p[0].first == "foo" ); + a = p[0].second; + BOOST_CHECK_EQUAL( a.size(), 1 ); BOOST_CHECK( a.find("bar") != a.end() ); - BOOST_CHECK( a.find("bar")->second == "a \"b\" c" ); + BOOST_CHECK_EQUAL( a.find("bar")->second, "a \"b\" c" ); } @@ -829,6 +829,29 @@ BOOST_AUTO_TEST_CASE( plain_http_response ) { BOOST_CHECK( r.get_body() == "\n\n\nThor\n\n \n

Thor

\n" ); } +BOOST_AUTO_TEST_CASE( parse_istream ) { + websocketpp::http::parser::response r; + + std::stringstream s; + + s << "HTTP/1.1 200 OK\r\nDate: Thu, 10 May 2012 11:59:25 GMT\r\nServer: Apache/2.2.21 (Unix) mod_ssl/2.2.21 OpenSSL/0.9.8r DAV/2 PHP/5.3.8 with Suhosin-Patch\r\nLast-Modified: Tue, 30 Mar 2010 17:41:28 GMT\r\nETag: \"16799d-55-4830823a78200\"\r\nAccept-Ranges: bytes\r\nContent-Length: 85\r\nVary: Accept-Encoding\r\nContent-Type: text/html\r\n\r\n\n\n\nThor\n\n \n

Thor

\n"; + + bool exception = false; + size_t pos = 0; + + try { + pos += r.consume(s); + } catch (std::exception &e) { + exception = true; + std::cout << e.what() << std::endl; + } + + BOOST_CHECK_EQUAL( exception, false ); + BOOST_CHECK_EQUAL( pos, 405 ); + BOOST_CHECK_EQUAL( r.headers_ready(), true ); + BOOST_CHECK_EQUAL( r.ready(), true ); +} + BOOST_AUTO_TEST_CASE( write_request_basic ) { websocketpp::http::parser::request r; @@ -866,6 +889,5 @@ BOOST_AUTO_TEST_CASE( write_request_with_body ) { r.replace_header("Content-Type","application/x-www-form-urlencoded"); r.set_body("licenseID=string&content=string¶msXML=string"); - std::cout << r.raw() << std::endl; BOOST_CHECK( r.raw() == raw ); -} \ No newline at end of file +} diff --git a/test/processors/extension_permessage_compress.cpp b/test/processors/extension_permessage_compress.cpp index 832187182..d8e23f62a 100644 --- a/test/processors/extension_permessage_compress.cpp +++ b/test/processors/extension_permessage_compress.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: @@ -25,7 +25,7 @@ * */ //#define BOOST_TEST_DYN_LINK -#define BOOST_TEST_MODULE extension_permessage_compress +#define BOOST_TEST_MODULE extension_permessage_deflate #include #include @@ -33,25 +33,25 @@ #include #include -#include +#include struct config { typedef websocketpp::http::parser::request request_type; }; -typedef websocketpp::extensions::permessage_compress::enabled +typedef websocketpp::extensions::permessage_deflate::enabled compressor_type; using namespace websocketpp; BOOST_AUTO_TEST_CASE( deflate_init ) { - compressor_type compressor; + /*compressor_type compressor; websocketpp::http::parser::attribute_list attributes; std::pair neg_ret; neg_ret = compressor.negotiate(attributes); BOOST_CHECK_EQUAL( neg_ret.first, - extensions::permessage_compress::error::invalid_parameters ); + extensions::permessage_deflate::error::invalid_parameters );*/ /** * Window size is primarily controlled by the writer. A stream can only be diff --git a/test/processors/hybi00.cpp b/test/processors/hybi00.cpp index 6de746e8f..ed830ed24 100644 --- a/test/processors/hybi00.cpp +++ b/test/processors/hybi00.cpp @@ -47,139 +47,226 @@ struct stub_config { con_msg_manager_type; }; -BOOST_AUTO_TEST_CASE( exact_match ) { - stub_config::request_type r; - stub_config::response_type response; - stub_config::con_msg_manager_type::ptr msg_manager; - websocketpp::processor::hybi00 p(false,true,msg_manager); +struct processor_setup { + processor_setup(bool server) + : msg_manager(new stub_config::con_msg_manager_type()) + , p(false,server,msg_manager) {} + websocketpp::lib::error_code ec; + stub_config::con_msg_manager_type::ptr msg_manager; + stub_config::request_type req; + stub_config::response_type res; + websocketpp::processor::hybi00 p; +}; + +typedef stub_config::message_type::ptr message_ptr; + +BOOST_AUTO_TEST_CASE( exact_match ) { + processor_setup env(true); std::string handshake = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nOrigin: http://example.com\r\nSec-WebSocket-Key1: 3e6b263 4 17 80\r\nSec-WebSocket-Key2: 17 9 G`ZD9 2 2b 7X 3 /r90\r\n\r\n"; - r.consume(handshake.c_str(),handshake.size()); - r.replace_header("Sec-WebSocket-Key3","WjN}|M(6"); + env.req.consume(handshake.c_str(),handshake.size()); + env.req.replace_header("Sec-WebSocket-Key3","WjN}|M(6"); - BOOST_CHECK(websocketpp::processor::is_websocket_handshake(r)); - BOOST_CHECK(websocketpp::processor::get_websocket_version(r) == p.get_version()); - ec = p.validate_handshake(r); - BOOST_CHECK(!ec); + BOOST_CHECK(websocketpp::processor::is_websocket_handshake(env.req)); + BOOST_CHECK_EQUAL(websocketpp::processor::get_websocket_version(env.req), env.p.get_version()); + env.ec = env.p.validate_handshake(env.req); + BOOST_CHECK(!env.ec); websocketpp::uri_ptr u; - bool exception = false; - - try { - u = p.get_uri(r); - } catch (const websocketpp::uri_exception& e) { - exception = true; - } + + BOOST_CHECK_NO_THROW( u = env.p.get_uri(env.req) ); - BOOST_CHECK(exception == false); - BOOST_CHECK(u->get_secure() == false); - BOOST_CHECK(u->get_host() == "www.example.com"); - BOOST_CHECK(u->get_resource() == "/"); - BOOST_CHECK(u->get_port() == websocketpp::URI_DEFAULT_PORT); + BOOST_CHECK_EQUAL(u->get_secure(), false); + BOOST_CHECK_EQUAL(u->get_host(), "www.example.com"); + BOOST_CHECK_EQUAL(u->get_resource(), "/"); + BOOST_CHECK_EQUAL(u->get_port(), websocketpp::URI_DEFAULT_PORT); - p.process_handshake(r,response); + env.p.process_handshake(env.req,"",env.res); - BOOST_CHECK(response.get_header("Connection") == "Upgrade"); - BOOST_CHECK(response.get_header("Upgrade") == "websocket"); - BOOST_CHECK(response.get_header("Sec-WebSocket-Origin") == "http://example.com"); + BOOST_CHECK_EQUAL(env.res.get_header("Connection"), "Upgrade"); + BOOST_CHECK_EQUAL(env.res.get_header("Upgrade"), "WebSocket"); + BOOST_CHECK_EQUAL(env.res.get_header("Sec-WebSocket-Origin"), "http://example.com"); - BOOST_CHECK(response.get_header("Sec-WebSocket-Location") == "ws://www.example.com/"); - BOOST_CHECK(response.get_header("Sec-WebSocket-Key3") == "n`9eBk9z$R8pOtVb"); + BOOST_CHECK_EQUAL(env.res.get_header("Sec-WebSocket-Location"), "ws://www.example.com/"); + BOOST_CHECK_EQUAL(env.res.get_header("Sec-WebSocket-Key3"), "n`9eBk9z$R8pOtVb"); } BOOST_AUTO_TEST_CASE( non_get_method ) { - stub_config::request_type r; - stub_config::con_msg_manager_type::ptr msg_manager; - websocketpp::processor::hybi00 p(false,true,msg_manager); - websocketpp::lib::error_code ec; + processor_setup env(true); std::string handshake = "POST / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Key1: 3e6b263 4 17 80\r\nSec-WebSocket-Key2: 17 9 G`ZD9 2 2b 7X 3 /r90\r\n\r\n"; - r.consume(handshake.c_str(),handshake.size()); - r.replace_header("Sec-WebSocket-Key3","janelle!"); + env.req.consume(handshake.c_str(),handshake.size()); + env.req.replace_header("Sec-WebSocket-Key3","janelle!"); - BOOST_CHECK(websocketpp::processor::is_websocket_handshake(r)); - BOOST_CHECK(websocketpp::processor::get_websocket_version(r) == p.get_version()); - ec = p.validate_handshake(r); - BOOST_CHECK( ec == websocketpp::processor::error::invalid_http_method ); + BOOST_CHECK(websocketpp::processor::is_websocket_handshake(env.req)); + BOOST_CHECK_EQUAL(websocketpp::processor::get_websocket_version(env.req), env.p.get_version()); + BOOST_CHECK_EQUAL( env.p.validate_handshake(env.req), websocketpp::processor::error::invalid_http_method ); } BOOST_AUTO_TEST_CASE( old_http_version ) { - stub_config::request_type r; - stub_config::con_msg_manager_type::ptr msg_manager; - websocketpp::processor::hybi00 p(false,true,msg_manager); - websocketpp::lib::error_code ec; + processor_setup env(true); std::string handshake = "GET / HTTP/1.0\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Key1: 3e6b263 4 17 80\r\nSec-WebSocket-Key2: 17 9 G`ZD9 2 2b 7X 3 /r90\r\n\r\n"; - r.consume(handshake.c_str(),handshake.size()); - r.replace_header("Sec-WebSocket-Key3","janelle!"); + env.req.consume(handshake.c_str(),handshake.size()); + env.req.replace_header("Sec-WebSocket-Key3","janelle!"); - BOOST_CHECK(websocketpp::processor::is_websocket_handshake(r)); - BOOST_CHECK(websocketpp::processor::get_websocket_version(r) == p.get_version()); - ec = p.validate_handshake(r); - BOOST_CHECK( ec == websocketpp::processor::error::invalid_http_version ); + BOOST_CHECK(websocketpp::processor::is_websocket_handshake(env.req)); + BOOST_CHECK_EQUAL(websocketpp::processor::get_websocket_version(env.req), env.p.get_version()); + BOOST_CHECK_EQUAL( env.p.validate_handshake(env.req), websocketpp::processor::error::invalid_http_version ); } BOOST_AUTO_TEST_CASE( missing_handshake_key1 ) { - stub_config::request_type r; - stub_config::con_msg_manager_type::ptr msg_manager; - websocketpp::processor::hybi00 p(false,true,msg_manager); - websocketpp::lib::error_code ec; + processor_setup env(true); std::string handshake = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Key1: 3e6b263 4 17 80\r\n\r\n"; - r.consume(handshake.c_str(),handshake.size()); - r.replace_header("Sec-WebSocket-Key3","janelle!"); + env.req.consume(handshake.c_str(),handshake.size()); + env.req.replace_header("Sec-WebSocket-Key3","janelle!"); - BOOST_CHECK(websocketpp::processor::is_websocket_handshake(r)); - BOOST_CHECK(websocketpp::processor::get_websocket_version(r) == p.get_version()); - ec = p.validate_handshake(r); - BOOST_CHECK( ec == websocketpp::processor::error::missing_required_header ); + BOOST_CHECK(websocketpp::processor::is_websocket_handshake(env.req)); + BOOST_CHECK_EQUAL(websocketpp::processor::get_websocket_version(env.req), env.p.get_version()); + BOOST_CHECK_EQUAL( env.p.validate_handshake(env.req), websocketpp::processor::error::missing_required_header ); } BOOST_AUTO_TEST_CASE( missing_handshake_key2 ) { - stub_config::request_type r; - stub_config::con_msg_manager_type::ptr msg_manager; - websocketpp::processor::hybi00 p(false,true,msg_manager); - websocketpp::lib::error_code ec; + processor_setup env(true); std::string handshake = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Key2: 17 9 G`ZD9 2 2b 7X 3 /r90\r\n\r\n"; - r.consume(handshake.c_str(),handshake.size()); - r.replace_header("Sec-WebSocket-Key3","janelle!"); + env.req.consume(handshake.c_str(),handshake.size()); + env.req.replace_header("Sec-WebSocket-Key3","janelle!"); - BOOST_CHECK(websocketpp::processor::is_websocket_handshake(r)); - BOOST_CHECK(websocketpp::processor::get_websocket_version(r) == p.get_version()); - ec = p.validate_handshake(r); - BOOST_CHECK( ec == websocketpp::processor::error::missing_required_header ); + BOOST_CHECK(websocketpp::processor::is_websocket_handshake(env.req)); + BOOST_CHECK_EQUAL(websocketpp::processor::get_websocket_version(env.req), env.p.get_version()); + BOOST_CHECK_EQUAL( env.p.validate_handshake(env.req), websocketpp::processor::error::missing_required_header ); } BOOST_AUTO_TEST_CASE( bad_host ) { - stub_config::request_type r; - stub_config::con_msg_manager_type::ptr msg_manager; - websocketpp::processor::hybi00 p(false,true,msg_manager); + processor_setup env(true); websocketpp::uri_ptr u; - bool exception = false; - websocketpp::lib::error_code ec; std::string handshake = "GET / HTTP/1.1\r\nHost: www.example.com:70000\r\nConnection: upgrade\r\nUpgrade: websocket\r\nOrigin: http://example.com\r\nSec-WebSocket-Key1: 3e6b263 4 17 80\r\nSec-WebSocket-Key2: 17 9 G`ZD9 2 2b 7X 3 /r90\r\n\r\n"; - r.consume(handshake.c_str(),handshake.size()); - r.replace_header("Sec-WebSocket-Key3","janelle!"); + env.req.consume(handshake.c_str(),handshake.size()); + env.req.replace_header("Sec-WebSocket-Key3","janelle!"); - BOOST_CHECK(websocketpp::processor::is_websocket_handshake(r)); - BOOST_CHECK(websocketpp::processor::get_websocket_version(r) == p.get_version()); - ec = p.validate_handshake(r); - BOOST_CHECK( !ec ); + BOOST_CHECK(websocketpp::processor::is_websocket_handshake(env.req)); + BOOST_CHECK_EQUAL(websocketpp::processor::get_websocket_version(env.req), env.p.get_version()); + BOOST_CHECK( !env.p.validate_handshake(env.req) ); + + BOOST_CHECK_THROW( u = env.p.get_uri(env.req), websocketpp::uri_exception ); +} + +BOOST_AUTO_TEST_CASE( extract_subprotocols ) { + processor_setup env(true); + + std::vector subps; + + BOOST_CHECK( !env.p.extract_subprotocols(env.req,subps) ); + BOOST_CHECK_EQUAL( subps.size(), 0 ); +} + +BOOST_AUTO_TEST_CASE( prepare_data_frame_null ) { + processor_setup env(true); + + message_ptr in = env.msg_manager->get_message(); + message_ptr out = env.msg_manager->get_message(); + message_ptr invalid; + + + // empty pointers arguements should return sane error + BOOST_CHECK_EQUAL( env.p.prepare_data_frame(invalid,invalid), websocketpp::processor::error::invalid_arguments ); + + BOOST_CHECK_EQUAL( env.p.prepare_data_frame(in,invalid), websocketpp::processor::error::invalid_arguments ); + + BOOST_CHECK_EQUAL( env.p.prepare_data_frame(invalid,out), websocketpp::processor::error::invalid_arguments ); - try { - u = p.get_uri(r); - } catch (const websocketpp::uri_exception& e) { - exception = true; + // test valid opcodes + // text (1) should be the only valid opcode + for (int i = 0; i < 0xF; i++) { + in->set_opcode(websocketpp::frame::opcode::value(i)); + + env.ec = env.p.prepare_data_frame(in,out); + + if (i != 1) { + BOOST_CHECK_EQUAL( env.ec, websocketpp::processor::error::invalid_opcode ); + } else { + BOOST_CHECK_NE( env.ec, websocketpp::processor::error::invalid_opcode ); + } } - BOOST_CHECK(exception == true); + /* + * TODO: tests for invalid UTF8 + char buf[2] = {0x00, 0x00}; + + in->set_opcode(websocketpp::frame::opcode::text); + in->set_payload("foo"); + + env.ec = env.p.prepare_data_frame(in,out); + BOOST_CHECK_EQUAL( env.ec, websocketpp::processor::error::invalid_payload ); + */ +} + +BOOST_AUTO_TEST_CASE( prepare_data_frame ) { + processor_setup env(true); + + message_ptr in = env.msg_manager->get_message(); + message_ptr out = env.msg_manager->get_message(); + + in->set_opcode(websocketpp::frame::opcode::text); + in->set_payload("foo"); + + env.ec = env.p.prepare_data_frame(in,out); + + unsigned char raw_header[1] = {0x00}; + unsigned char raw_payload[4] = {0x66,0x6f,0x6f,0xff}; + + BOOST_CHECK( !env.ec ); + BOOST_CHECK_EQUAL( out->get_header(), std::string(reinterpret_cast(raw_header),1) ); + BOOST_CHECK_EQUAL( out->get_payload(), std::string(reinterpret_cast(raw_payload),4) ); +} + + +BOOST_AUTO_TEST_CASE( empty_consume ) { + uint8_t frame[2] = {0x00,0x00}; + + processor_setup env(true); + + size_t ret = env.p.consume(frame,0,env.ec); + + BOOST_CHECK_EQUAL( ret, 0); + BOOST_CHECK( !env.ec ); + BOOST_CHECK_EQUAL( env.p.ready(), false ); +} + +BOOST_AUTO_TEST_CASE( empty_frame ) { + uint8_t frame[2] = {0x00, 0xff}; + + processor_setup env(true); + + size_t ret = env.p.consume(frame,2,env.ec); + + BOOST_CHECK_EQUAL( ret, 2); + BOOST_CHECK( !env.ec ); + BOOST_CHECK_EQUAL( env.p.ready(), true ); + BOOST_CHECK_EQUAL( env.p.get_message()->get_payload(), "" ); + BOOST_CHECK_EQUAL( env.p.ready(), false ); +} + +BOOST_AUTO_TEST_CASE( short_frame ) { + uint8_t frame[5] = {0x00, 0x66, 0x6f, 0x6f, 0xff}; + + processor_setup env(true); + + size_t ret = env.p.consume(frame,5,env.ec); + + BOOST_CHECK_EQUAL( ret, 5); + BOOST_CHECK( !env.ec ); + BOOST_CHECK_EQUAL( env.p.ready(), true ); + BOOST_CHECK_EQUAL( env.p.get_message()->get_payload(), "foo" ); + BOOST_CHECK_EQUAL( env.p.ready(), false ); } diff --git a/test/processors/hybi07.cpp b/test/processors/hybi07.cpp index f07042d3a..6231a58e4 100644 --- a/test/processors/hybi07.cpp +++ b/test/processors/hybi07.cpp @@ -36,7 +36,7 @@ #include #include #include -#include +#include #include struct stub_config { @@ -56,12 +56,12 @@ struct stub_config { /// Extension specific config /// permessage_compress_config - struct permessage_compress_config { + struct permessage_deflate_config { typedef stub_config::request_type request_type; }; - typedef websocketpp::extensions::permessage_compress::disabled - permessage_compress_type; + typedef websocketpp::extensions::permessage_deflate::disabled + permessage_deflate_type; }; BOOST_AUTO_TEST_CASE( exact_match ) { @@ -96,7 +96,7 @@ BOOST_AUTO_TEST_CASE( exact_match ) { BOOST_CHECK(u->get_resource() == "/"); BOOST_CHECK(u->get_port() == websocketpp::uri_default_port); - p.process_handshake(r,response); + p.process_handshake(r,"",response); BOOST_CHECK(response.get_header("Connection") == "upgrade"); BOOST_CHECK(response.get_header("Upgrade") == "websocket"); diff --git a/test/processors/hybi08.cpp b/test/processors/hybi08.cpp index 60336b903..b704604cc 100644 --- a/test/processors/hybi08.cpp +++ b/test/processors/hybi08.cpp @@ -36,7 +36,7 @@ #include #include #include -#include +#include #include struct stub_config { @@ -55,13 +55,13 @@ struct stub_config { /// Extension specific config - /// permessage_compress_config - struct permessage_compress_config { + /// permessage_deflate_config + struct permessage_deflate_config { typedef stub_config::request_type request_type; }; - typedef websocketpp::extensions::permessage_compress::disabled - permessage_compress_type; + typedef websocketpp::extensions::permessage_deflate::disabled + permessage_deflate_type; }; BOOST_AUTO_TEST_CASE( exact_match ) { @@ -96,7 +96,7 @@ BOOST_AUTO_TEST_CASE( exact_match ) { BOOST_CHECK(u->get_resource() == "/"); BOOST_CHECK(u->get_port() == websocketpp::uri_default_port); - p.process_handshake(r,response); + p.process_handshake(r,"",response); BOOST_CHECK(response.get_header("Connection") == "upgrade"); BOOST_CHECK(response.get_header("Upgrade") == "websocket"); diff --git a/test/processors/hybi13.cpp b/test/processors/hybi13.cpp index 85104f2f1..20470f930 100644 --- a/test/processors/hybi13.cpp +++ b/test/processors/hybi13.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: @@ -39,8 +39,8 @@ #include #include -#include -#include +#include +#include struct stub_config { typedef websocketpp::http::parser::request request_type; @@ -53,12 +53,12 @@ struct stub_config { typedef websocketpp::random::none::int_generator rng_type; - struct permessage_compress_config { + struct permessage_deflate_config { typedef stub_config::request_type request_type; }; - typedef websocketpp::extensions::permessage_compress::disabled - permessage_compress_type; + typedef websocketpp::extensions::permessage_deflate::disabled + permessage_deflate_type; static const bool enable_extensions = false; }; @@ -74,14 +74,14 @@ struct stub_config_ext { typedef websocketpp::random::none::int_generator rng_type; - struct permessage_compress_config { + struct permessage_deflate_config { typedef stub_config_ext::request_type request_type; }; - typedef websocketpp::extensions::permessage_compress::enabled - permessage_compress_type; + typedef websocketpp::extensions::permessage_deflate::enabled + permessage_deflate_type; - static const bool enable_extensions = false; + static const bool enable_extensions = true; }; typedef stub_config::con_msg_manager_type con_msg_manager_type; @@ -102,6 +102,19 @@ struct processor_setup { websocketpp::processor::hybi13 p; }; +struct processor_setup_ext { + processor_setup_ext(bool server) + : msg_manager(new con_msg_manager_type()) + , p(false,server,msg_manager,rng) {} + + websocketpp::lib::error_code ec; + con_msg_manager_type::ptr msg_manager; + stub_config::rng_type rng; + stub_config::request_type req; + stub_config::response_type res; + websocketpp::processor::hybi13 p; +}; + BOOST_AUTO_TEST_CASE( exact_match ) { processor_setup env(true); @@ -122,7 +135,7 @@ BOOST_AUTO_TEST_CASE( exact_match ) { BOOST_CHECK_EQUAL(u->get_resource(), "/"); BOOST_CHECK_EQUAL(u->get_port(), websocketpp::uri_default_port); - env.p.process_handshake(env.req,env.res); + env.p.process_handshake(env.req,"",env.res); BOOST_CHECK_EQUAL(env.res.get_header("Connection"), "upgrade"); BOOST_CHECK_EQUAL(env.res.get_header("Upgrade"), "websocket"); @@ -482,7 +495,7 @@ BOOST_AUTO_TEST_CASE( client_handshake_request ) { websocketpp::uri_ptr u(new websocketpp::uri("ws://localhost/")); - env.p.client_handshake_request(env.req,u); + env.p.client_handshake_request(env.req,u, std::vector()); BOOST_CHECK_EQUAL( env.req.get_method(), "GET" ); BOOST_CHECK_EQUAL( env.req.get_version(), "HTTP/1.1"); @@ -554,4 +567,96 @@ BOOST_AUTO_TEST_CASE( client_handshake_response ) { env.res.consume(res.data(),res.size()); BOOST_CHECK( !env.p.validate_server_handshake_response(env.req,env.res) ); -} \ No newline at end of file +} + +BOOST_AUTO_TEST_CASE( extensions_disabled ) { + processor_setup env(true); + + env.req.replace_header("Sec-WebSocket-Extensions",""); + + std::pair neg_results; + neg_results = env.p.negotiate_extensions(env.req); + + BOOST_CHECK_EQUAL( neg_results.first, websocketpp::processor::error::extensions_disabled ); + BOOST_CHECK_EQUAL( neg_results.second, "" ); +} + +BOOST_AUTO_TEST_CASE( extension_negotiation_blank ) { + processor_setup_ext env(true); + + env.req.replace_header("Sec-WebSocket-Extensions",""); + + std::pair neg_results; + neg_results = env.p.negotiate_extensions(env.req); + + BOOST_CHECK( !neg_results.first ); + BOOST_CHECK_EQUAL( neg_results.second, "" ); +} + +BOOST_AUTO_TEST_CASE( extension_negotiation_unknown ) { + processor_setup_ext env(true); + + env.req.replace_header("Sec-WebSocket-Extensions","foo"); + + std::pair neg_results; + neg_results = env.p.negotiate_extensions(env.req); + + BOOST_CHECK( !neg_results.first ); + BOOST_CHECK_EQUAL( neg_results.second, "" ); +} + +BOOST_AUTO_TEST_CASE( extract_subprotocols_empty ) { + processor_setup env(true); + std::vector subps; + + BOOST_CHECK( !env.p.extract_subprotocols(env.req,subps) ); + BOOST_CHECK_EQUAL( subps.size(), 0 ); +} + +BOOST_AUTO_TEST_CASE( extract_subprotocols_one ) { + processor_setup env(true); + std::vector subps; + + env.req.replace_header("Sec-WebSocket-Protocol","foo"); + + BOOST_CHECK( !env.p.extract_subprotocols(env.req,subps) ); + BOOST_REQUIRE_EQUAL( subps.size(), 1 ); + BOOST_CHECK_EQUAL( subps[0], "foo" ); +} + +BOOST_AUTO_TEST_CASE( extract_subprotocols_multiple ) { + processor_setup env(true); + std::vector subps; + + env.req.replace_header("Sec-WebSocket-Protocol","foo,bar"); + + BOOST_CHECK( !env.p.extract_subprotocols(env.req,subps) ); + BOOST_REQUIRE_EQUAL( subps.size(), 2 ); + BOOST_CHECK_EQUAL( subps[0], "foo" ); + BOOST_CHECK_EQUAL( subps[1], "bar" ); +} + +BOOST_AUTO_TEST_CASE( extract_subprotocols_invalid) { + processor_setup env(true); + std::vector subps; + + env.req.replace_header("Sec-WebSocket-Protocol","foo,bar,,,,"); + + BOOST_CHECK_EQUAL( env.p.extract_subprotocols(env.req,subps), websocketpp::processor::error::make_error_code(websocketpp::processor::error::subprotocol_parse_error) ); + BOOST_CHECK_EQUAL( subps.size(), 0 ); +} + +/*BOOST_AUTO_TEST_CASE( extension_negotiation_permessage_deflate ) { + processor_setup_ext env(true); + + env.req.replace_header("Sec-WebSocket-Extensions","permessage-deflate; foo; bar=\"x x\""); + + std::pair neg_results; + neg_results = env.p.negotiate_extensions(env.req); + + std::cout << neg_results.first.message() << neg_results.second << std::endl; + + BOOST_CHECK( !neg_results.first ); + BOOST_CHECK_EQUAL( neg_results.second, "permessage-deflate" ); +}*/ + diff --git a/test/roles/client.cpp b/test/roles/client.cpp index 045d1aa11..2dcbc1806 100644 --- a/test/roles/client.cpp +++ b/test/roles/client.cpp @@ -133,7 +133,63 @@ BOOST_AUTO_TEST_CASE( connect_con ) { channel2 >> *con; } +BOOST_AUTO_TEST_CASE( select_subprotocol ) { + client c; + websocketpp::lib::error_code ec; + using websocketpp::error::make_error_code; + + connection_ptr con = c.get_connection("ws://localhost/", ec); + + BOOST_CHECK( con ); + + con->select_subprotocol("foo",ec); + BOOST_CHECK_EQUAL( ec , make_error_code(websocketpp::error::server_only) ); + BOOST_CHECK_THROW( con->select_subprotocol("foo") , websocketpp::lib::error_code ); +} +BOOST_AUTO_TEST_CASE( add_subprotocols_invalid ) { + client c; + websocketpp::lib::error_code ec; + using websocketpp::error::make_error_code; + + connection_ptr con = c.get_connection("ws://localhost/", ec); + BOOST_CHECK( con ); + + con->add_subprotocol("",ec); + BOOST_CHECK_EQUAL( ec , make_error_code(websocketpp::error::invalid_subprotocol) ); + BOOST_CHECK_THROW( con->add_subprotocol("") , websocketpp::lib::error_code ); + + con->add_subprotocol("foo,bar",ec); + BOOST_CHECK_EQUAL( ec , make_error_code(websocketpp::error::invalid_subprotocol) ); + BOOST_CHECK_THROW( con->add_subprotocol("foo,bar") , websocketpp::lib::error_code ); +} +BOOST_AUTO_TEST_CASE( add_subprotocols ) { + client c; + websocketpp::lib::error_code ec; + std::stringstream out; + std::string o; + + c.register_ostream(&out); + + connection_ptr con = c.get_connection("ws://localhost/", ec); + BOOST_CHECK( con ); + + con->add_subprotocol("foo",ec); + BOOST_CHECK( !ec ); + + BOOST_CHECK_NO_THROW( con->add_subprotocol("bar") ); + + c.connect(con); + + o = out.str(); + websocketpp::http::parser::request r; + r.consume(o.data(),o.size()); + + std::cout << o << std::endl; + + BOOST_CHECK( r.ready() ); + BOOST_CHECK_EQUAL( r.get_header("Sec-WebSocket-Protocol"), "foo, bar"); +} diff --git a/test/roles/server.cpp b/test/roles/server.cpp index 36715ac6c..bf47bfba5 100644 --- a/test/roles/server.cpp +++ b/test/roles/server.cpp @@ -30,10 +30,19 @@ #include +// Test Environment: +// server, no TLS, no locks, iostream based transport #include #include -struct stub_config : public websocketpp::config::core { +typedef websocketpp::server server; +typedef websocketpp::config::core::message_type::ptr message_ptr; + +using websocketpp::lib::placeholders::_1; +using websocketpp::lib::placeholders::_2; +using websocketpp::lib::bind; + +/*struct stub_config : public websocketpp::config::core { typedef core::concurrency_type concurrency_type; typedef core::request_type request_type; @@ -51,9 +60,186 @@ struct stub_config : public websocketpp::config::core { typedef core::transport_type transport_type; typedef core::endpoint_base endpoint_base; -}; +};*/ + +/* Run server and return output test rig */ +std::string run_server_test(server& s, std::string input) { + server::connection_ptr con; + std::stringstream output; + + s.register_ostream(&output); + + con = s.get_connection(); + con->start(); + + std::stringstream channel; + + channel << input; + channel >> *con; + + return output.str(); +} + +/* handler library*/ +void echo_func(server* s, websocketpp::connection_hdl hdl, message_ptr msg) { + s->send(hdl, msg->get_payload(), msg->get_opcode()); +} + +bool validate_func_subprotocol(server* s, std::string* out, std::string accept, + websocketpp::connection_hdl hdl) +{ + server::connection_ptr con = s->get_con_from_hdl(hdl); + + std::stringstream o; + + const std::vector & protocols = con->get_requested_subprotocols(); + std::vector::const_iterator it; + + for (it = protocols.begin(); it != protocols.end(); ++it) { + o << *it << ","; + } + *out = o.str(); + + if (accept != "") { + con->select_subprotocol(accept); + } + + return true; +} -BOOST_AUTO_TEST_CASE( foo ) { +void open_func_subprotocol(server* s, std::string* out, websocketpp::connection_hdl hdl) { + server::connection_ptr con = s->get_con_from_hdl(hdl); + + *out = con->get_subprotocol(); +} +/* Tests */ +BOOST_AUTO_TEST_CASE( basic_websocket_request ) { + std::string input = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example.com\r\n\r\n"; + std::string output = "HTTP/1.1 101 Switching Protocols\r\nConnection: upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\nServer: test\r\nUpgrade: websocket\r\n\r\n"; + + server s; + s.set_user_agent("test"); + + BOOST_CHECK_EQUAL(run_server_test(s,input), output); } + +BOOST_AUTO_TEST_CASE( invalid_websocket_version ) { + std::string input = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: a\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example.com\r\n\r\n"; + std::string output = "HTTP/1.1 400 Bad Request\r\nServer: test\r\n\r\n"; + + server s; + s.set_user_agent("test"); + //s.set_message_handler(bind(&echo_func,&s,::_1,::_2)); + + BOOST_CHECK_EQUAL(run_server_test(s,input), output); +} + +BOOST_AUTO_TEST_CASE( unimplimented_websocket_version ) { + std::string input = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 14\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example.com\r\n\r\n"; + + std::string output = "HTTP/1.1 400 Bad Request\r\nSec-WebSocket-Version: 0,7,8,13\r\nServer: test\r\n\r\n"; + + server s; + s.set_user_agent("test"); + + BOOST_CHECK_EQUAL(run_server_test(s,input), output); +} + +BOOST_AUTO_TEST_CASE( list_subprotocol_empty ) { + std::string input = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example.com\r\nSec-WebSocket-Protocol: foo\r\n\r\n"; + + std::string output = "HTTP/1.1 101 Switching Protocols\r\nConnection: upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\nServer: test\r\nUpgrade: websocket\r\n\r\n"; + + std::string subprotocol; + + server s; + s.set_user_agent("test"); + s.set_open_handler(bind(&open_func_subprotocol,&s,&subprotocol,::_1)); + + BOOST_CHECK_EQUAL(run_server_test(s,input), output); + BOOST_CHECK_EQUAL(subprotocol, ""); +} + +BOOST_AUTO_TEST_CASE( list_subprotocol_one ) { + std::string input = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example.com\r\nSec-WebSocket-Protocol: foo\r\n\r\n"; + + std::string output = "HTTP/1.1 101 Switching Protocols\r\nConnection: upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\nServer: test\r\nUpgrade: websocket\r\n\r\n"; + + std::string validate; + std::string open; + + server s; + s.set_user_agent("test"); + s.set_validate_handler(bind(&validate_func_subprotocol,&s,&validate,"",::_1)); + s.set_open_handler(bind(&open_func_subprotocol,&s,&open,::_1)); + + BOOST_CHECK_EQUAL(run_server_test(s,input), output); + BOOST_CHECK_EQUAL(validate, "foo,"); + BOOST_CHECK_EQUAL(open, ""); +} + +BOOST_AUTO_TEST_CASE( accept_subprotocol_one ) { + std::string input = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example.com\r\nSec-WebSocket-Protocol: foo\r\n\r\n"; + + std::string output = "HTTP/1.1 101 Switching Protocols\r\nConnection: upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\nSec-WebSocket-Protocol: foo\r\nServer: test\r\nUpgrade: websocket\r\n\r\n"; + + std::string validate; + std::string open; + + server s; + s.set_user_agent("test"); + s.set_validate_handler(bind(&validate_func_subprotocol,&s,&validate,"foo",::_1)); + s.set_open_handler(bind(&open_func_subprotocol,&s,&open,::_1)); + + BOOST_CHECK_EQUAL(run_server_test(s,input), output); + BOOST_CHECK_EQUAL(validate, "foo,"); + BOOST_CHECK_EQUAL(open, "foo"); +} + +BOOST_AUTO_TEST_CASE( accept_subprotocol_invalid ) { + std::string input = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example.com\r\nSec-WebSocket-Protocol: foo\r\n\r\n"; + + std::string output = "HTTP/1.1 101 Switching Protocols\r\nConnection: upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\nSec-WebSocket-Protocol: foo\r\nServer: test\r\nUpgrade: websocket\r\n\r\n"; + + std::string validate; + std::string open; + + server s; + s.set_user_agent("test"); + s.set_validate_handler(bind(&validate_func_subprotocol,&s,&validate,"foo2",::_1)); + s.set_open_handler(bind(&open_func_subprotocol,&s,&open,::_1)); + + std::string o; + + BOOST_CHECK_THROW(o = run_server_test(s,input), websocketpp::lib::error_code); +} + +BOOST_AUTO_TEST_CASE( accept_subprotocol_two ) { + std::string input = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example.com\r\nSec-WebSocket-Protocol: foo, bar\r\n\r\n"; + + std::string output = "HTTP/1.1 101 Switching Protocols\r\nConnection: upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\nSec-WebSocket-Protocol: bar\r\nServer: test\r\nUpgrade: websocket\r\n\r\n"; + + std::string validate; + std::string open; + + server s; + s.set_user_agent("test"); + s.set_validate_handler(bind(&validate_func_subprotocol,&s,&validate,"bar",::_1)); + s.set_open_handler(bind(&open_func_subprotocol,&s,&open,::_1)); + + BOOST_CHECK_EQUAL(run_server_test(s,input), output); + BOOST_CHECK_EQUAL(validate, "foo,bar,"); + BOOST_CHECK_EQUAL(open, "bar"); +} + +/*BOOST_AUTO_TEST_CASE( user_reject_origin ) { + std::string input = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example2.com\r\n\r\n"; + std::string output = "HTTP/1.1 403 Forbidden\r\nServer: test\r\n\r\n"; + + server s; + s.set_user_agent("test"); + + BOOST_CHECK(run_server_test(s,input) == output); +}*/ diff --git a/test/utility/close.cpp b/test/utility/close.cpp index 44d89e577..6d4881534 100644 --- a/test/utility/close.cpp +++ b/test/utility/close.cpp @@ -65,25 +65,25 @@ BOOST_AUTO_TEST_CASE( value_extraction ) { // Value = 1000 payload[0] = 0x03; - payload[1] = 0xe8; + payload[1] = char(0xe8); BOOST_CHECK( close::extract_code(payload,ec) == close::status::normal ); BOOST_CHECK( !ec ); // Value = 1004 payload[0] = 0x03; - payload[1] = 0xec; + payload[1] = char(0xec); BOOST_CHECK( close::extract_code(payload,ec) == 1004 ); BOOST_CHECK( ec == error::reserved_close_code ); // Value = 1005 payload[0] = 0x03; - payload[1] = 0xed; + payload[1] = char(0xed); BOOST_CHECK( close::extract_code(payload,ec) == close::status::no_status ); BOOST_CHECK( ec == error::invalid_close_code ); // Value = 3000 payload[0] = 0x0b; - payload[1] = 0xb8; + payload[1] = char(0xb8); BOOST_CHECK( close::extract_code(payload,ec) == 3000 ); BOOST_CHECK( !ec ); } @@ -120,7 +120,7 @@ BOOST_AUTO_TEST_CASE( extract_reason ) { BOOST_CHECK( !ec ); payload = "000"; - payload[2] = 0xFF; + payload[2] = char(0xFF); close::extract_reason(payload,ec); BOOST_CHECK( ec == error::invalid_utf8 ); diff --git a/test/utility/frame.cpp b/test/utility/frame.cpp index b57023068..f6adc5df1 100644 --- a/test/utility/frame.cpp +++ b/test/utility/frame.cpp @@ -264,7 +264,7 @@ BOOST_AUTO_TEST_CASE( prepare_masking_key2 ) { // TODO: figure out a way to run/test both 4 and 8 byte versions. BOOST_AUTO_TEST_CASE( circshift ) { - if (sizeof(size_t) == 8) { + /*if (sizeof(size_t) == 8) { size_t test = 0x0123456789abcdef; BOOST_CHECK( frame::circshift_prepared_key(test,0) == 0x0123456789abcdef); @@ -278,7 +278,7 @@ BOOST_AUTO_TEST_CASE( circshift ) { BOOST_CHECK( frame::circshift_prepared_key(test,1) == 0x67012345); BOOST_CHECK( frame::circshift_prepared_key(test,2) == 0x45670123); BOOST_CHECK( frame::circshift_prepared_key(test,3) == 0x23456701); - } + }*/ } BOOST_AUTO_TEST_CASE( block_byte_mask ) { @@ -392,12 +392,14 @@ BOOST_AUTO_TEST_CASE( continuous_word_mask ) { size_t pkey,pkey_temp; pkey = frame::prepare_masking_key(key); std::fill_n(input,16,0x00); + std::fill_n(output,16,0x00); frame::word_mask_circ(input,output,15,pkey); BOOST_CHECK( std::equal(output,output+16,masked) ); // calls not split on word boundaries pkey = frame::prepare_masking_key(key); std::fill_n(input,16,0x00); + std::fill_n(output,16,0x00); pkey_temp = frame::word_mask_circ(input,output,7,pkey); BOOST_CHECK( std::equal(output,output+7,masked) ); @@ -462,4 +464,4 @@ BOOST_AUTO_TEST_CASE( continuous_word_mask2 ) { pkey = frame::prepare_masking_key(key); frame::word_mask_circ(buffer,12,pkey); BOOST_CHECK( std::equal(buffer,buffer+12,unmasked) ); -} \ No newline at end of file +} diff --git a/test/utility/uri.cpp b/test/utility/uri.cpp index e112fc38d..001e44db1 100644 --- a/test/utility/uri.cpp +++ b/test/utility/uri.cpp @@ -132,7 +132,7 @@ BOOST_AUTO_TEST_CASE( uri_valid_2 ) { BOOST_CHECK( uri.get_secure() == true ); BOOST_CHECK( uri.get_host() == "thor-websocket.zaphoyd.net"); BOOST_CHECK( uri.get_port() == 88 ); - BOOST_CHECK( uri.get_resource() == "/" ); + BOOST_CHECK( uri.get_resource() == "/" ); } catch (websocketpp::uri_exception&) { exception = true; } @@ -153,11 +153,11 @@ BOOST_AUTO_TEST_CASE( uri_invalid_long_port ) { BOOST_CHECK( exception == true); } -// Invalid URI (http method) -BOOST_AUTO_TEST_CASE( uri_invalid_http ) { +// Invalid URI (bogus scheme method) +BOOST_AUTO_TEST_CASE( uri_invalid_scheme ) { bool exception = false; try { - websocketpp::uri uri("http://localhost:9000/chat"); + websocketpp::uri uri("foo://localhost:9000/chat"); } catch (websocketpp::uri_exception&) { exception = true; } @@ -165,6 +165,23 @@ BOOST_AUTO_TEST_CASE( uri_invalid_http ) { BOOST_CHECK( exception == true); } +// Valid URI (http method) +BOOST_AUTO_TEST_CASE( uri_http_scheme ) { + bool exception = false; + try { + websocketpp::uri uri("http://localhost:9000/chat"); + + BOOST_CHECK( uri.get_secure() == false ); + BOOST_CHECK( uri.get_host() == "localhost"); + BOOST_CHECK( uri.get_port() == 9000 ); + BOOST_CHECK( uri.get_resource() == "/chat" ); + } catch (websocketpp::uri_exception&) { + exception = true; + } + + BOOST_CHECK( exception == false); +} + // Valid URI IPv4 literal BOOST_AUTO_TEST_CASE( uri_valid_ipv4_literal ) { bool exception = false; diff --git a/test/utility/utilities.cpp b/test/utility/utilities.cpp index 5dc394e65..3dd78fba2 100644 --- a/test/utility/utilities.cpp +++ b/test/utility/utilities.cpp @@ -56,4 +56,12 @@ BOOST_AUTO_TEST_CASE( substr_not_found ) { BOOST_CHECK(websocketpp::utility::ci_find_substr(haystack,needle) == haystack.end()); } +BOOST_AUTO_TEST_CASE( string_replace_all ) { + std::string source = "foo \"bar\" baz"; + std::string dest = "foo \\\"bar\\\" baz"; + + using websocketpp::utility::string_replace_all; + BOOST_CHECK_EQUAL(string_replace_all(source,"\"","\\\""),dest); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/websocketpp/base64/base64.hpp b/websocketpp/base64/base64.hpp index 4c09d69bb..6c12ec19c 100644 --- a/websocketpp/base64/base64.hpp +++ b/websocketpp/base64/base64.hpp @@ -1,10 +1,11 @@ /* - ****** - base64.hpp is a repackaging of the base64.cpp and base64.h files into a single header - suitable for use as a header only library. This conversion was done by Peter Thorson - (webmaster@zaphoyd.com) in 2012. All modifications to the code are redistributed under - the same license as the original, which is listed below. - ****** + ****** + base64.hpp is a repackaging of the base64.cpp and base64.h files into a + single headersuitable for use as a header only library. This conversion was + done by Peter Thorson (webmaster@zaphoyd.com) in 2012. All modifications to + the code are redistributed under the same license as the original, which is + listed below. + ****** base64.cpp and base64.h @@ -36,7 +37,6 @@ #define _BASE64_HPP_ #include -#include static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -44,7 +44,10 @@ static const std::string base64_chars = "0123456789+/"; static inline bool is_base64(unsigned char c) { - return (isalnum(c) || (c == '+') || (c == '/')); + return (c == 43 || // + + (c >= 47 && c <= 57) || // /-9 + (c >= 65 && c <= 90) || // A-Z + (c >= 97 && c <= 122)); // a-z } inline std::string base64_encode(unsigned char const* bytes_to_encode, unsigned @@ -97,6 +100,10 @@ inline std::string base64_encode(unsigned char const* bytes_to_encode, unsigned return ret; } +inline std::string base64_encode(const std::string & data) { + return base64_encode(reinterpret_cast(data.data()),data.size()); +} + inline std::string base64_decode(std::string const& encoded_string) { size_t in_len = encoded_string.size(); int i = 0; @@ -134,10 +141,12 @@ inline std::string base64_decode(std::string const& encoded_string) { char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; + for (j = 0; (j < i - 1); j++) { + ret += static_cast(char_array_3[j]); + } } return ret; } -#endif // _BASE64_HPP_ \ No newline at end of file +#endif // _BASE64_HPP_ diff --git a/websocketpp/common/network.hpp b/websocketpp/common/network.hpp index dbe455151..fcbb6a214 100644 --- a/websocketpp/common/network.hpp +++ b/websocketpp/common/network.hpp @@ -40,6 +40,12 @@ namespace websocketpp { namespace lib { namespace net { +inline bool is_little_endian() { + short int val = 0x1; + char *ptr = (char*)&val; + return (ptr[0] == 1); +} + #define TYP_INIT 0 #define TYP_SMLE 1 #define TYP_BIGE 2 @@ -56,7 +62,7 @@ inline uint64_t htonll(uint64_t src) { typ = (x.c[7] == 0x01ULL) ? TYP_BIGE : TYP_SMLE; } if (typ == TYP_BIGE) - return src; + return src; x.ull = src; c = x.c[0]; x.c[0] = x.c[7]; x.c[7] = c; c = x.c[1]; x.c[1] = x.c[6]; x.c[6] = c; diff --git a/websocketpp/common/thread.hpp b/websocketpp/common/thread.hpp index a82e7bacd..411cd7daf 100644 --- a/websocketpp/common/thread.hpp +++ b/websocketpp/common/thread.hpp @@ -49,9 +49,11 @@ namespace lib { #ifdef _WEBSOCKETPP_CPP11_THREAD_ using std::mutex; using std::lock_guard; + using std::thread; #else using boost::mutex; using boost::lock_guard; + using boost::thread; #endif } // namespace lib diff --git a/websocketpp/config/asio.hpp b/websocketpp/config/asio.hpp index e8410bc1c..adb6b1efc 100644 --- a/websocketpp/config/asio.hpp +++ b/websocketpp/config/asio.hpp @@ -40,6 +40,8 @@ namespace websocketpp { namespace config { struct asio_tls : public core { + typedef asio_tls type; + typedef core::concurrency_type concurrency_type; typedef core::request_type request_type; @@ -55,9 +57,11 @@ struct asio_tls : public core { typedef core::rng_type rng_type; struct transport_config { - typedef asio_tls::concurrency_type concurrency_type; - typedef asio_tls::alog_type alog_type; - typedef asio_tls::elog_type elog_type; + typedef type::concurrency_type concurrency_type; + typedef type::alog_type alog_type; + typedef type::elog_type elog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; typedef websocketpp::transport::asio::tls_socket::endpoint socket_type; }; diff --git a/websocketpp/config/asio_client.hpp b/websocketpp/config/asio_client.hpp index e48782bb0..bf25e7c81 100644 --- a/websocketpp/config/asio_client.hpp +++ b/websocketpp/config/asio_client.hpp @@ -40,6 +40,8 @@ namespace websocketpp { namespace config { struct asio_tls : public core_client { + typedef asio_tls type; + typedef core_client::concurrency_type concurrency_type; typedef core_client::request_type request_type; @@ -55,9 +57,11 @@ struct asio_tls : public core_client { typedef core_client::rng_type rng_type; struct transport_config { - typedef asio_tls::concurrency_type concurrency_type; - typedef asio_tls::alog_type alog_type; - typedef asio_tls::elog_type elog_type; + typedef type::concurrency_type concurrency_type; + typedef type::alog_type alog_type; + typedef type::elog_type elog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; typedef websocketpp::transport::asio::tls_socket::endpoint socket_type; }; diff --git a/websocketpp/config/asio_no_tls.hpp b/websocketpp/config/asio_no_tls.hpp index c8c2b3e15..86a2cf2a4 100644 --- a/websocketpp/config/asio_no_tls.hpp +++ b/websocketpp/config/asio_no_tls.hpp @@ -35,6 +35,8 @@ namespace websocketpp { namespace config { struct asio : public core { + typedef asio type; + typedef core::concurrency_type concurrency_type; typedef core::request_type request_type; @@ -50,9 +52,11 @@ struct asio : public core { typedef core::rng_type rng_type; struct transport_config { - typedef asio::concurrency_type concurrency_type; - typedef asio::alog_type alog_type; - typedef asio::elog_type elog_type; + typedef type::concurrency_type concurrency_type; + typedef type::alog_type alog_type; + typedef type::elog_type elog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; typedef websocketpp::transport::asio::basic_socket::endpoint socket_type; }; diff --git a/websocketpp/config/asio_no_tls_client.hpp b/websocketpp/config/asio_no_tls_client.hpp index 7b19100e3..5a18d0c76 100644 --- a/websocketpp/config/asio_no_tls_client.hpp +++ b/websocketpp/config/asio_no_tls_client.hpp @@ -35,6 +35,8 @@ namespace websocketpp { namespace config { struct asio_client : public core_client { + typedef asio_client type; + typedef core_client::concurrency_type concurrency_type; typedef core_client::request_type request_type; @@ -50,9 +52,11 @@ struct asio_client : public core_client { typedef core_client::rng_type rng_type; struct transport_config { - typedef asio_client::concurrency_type concurrency_type; - typedef asio_client::alog_type alog_type; - typedef asio_client::elog_type elog_type; + typedef type::concurrency_type concurrency_type; + typedef type::alog_type alog_type; + typedef type::elog_type elog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; typedef websocketpp::transport::asio::basic_socket::endpoint socket_type; }; diff --git a/websocketpp/config/core.hpp b/websocketpp/config/core.hpp index 9fea55fbb..dc5d68467 100644 --- a/websocketpp/config/core.hpp +++ b/websocketpp/config/core.hpp @@ -59,12 +59,14 @@ #include // Extensions -#include +#include namespace websocketpp { namespace config { struct core { + typedef core type; + // Concurrency policy typedef websocketpp::concurrency::basic concurrency_type; @@ -90,9 +92,11 @@ struct core { typedef websocketpp::random::none::int_generator rng_type; struct transport_config { - typedef core::concurrency_type concurrency_type; - typedef core::elog_type elog_type; - typedef core::alog_type alog_type; + typedef type::concurrency_type concurrency_type; + typedef type::elog_type elog_type; + typedef type::alog_type alog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; }; /// Transport Endpoint Component @@ -111,7 +115,33 @@ struct core { * recommended. */ static const int client_version = 13; // RFC6455 + + /// Default static error logging channels + /** + * Which error logging channels to enable at compile time. Channels not + * enabled here will be unable to be selected by programs using the library. + * This option gives an optimizing compiler the ability to remove entirely + * code to test whether or not to print out log messages on a certain + * channel + * + * Default is all except for development/debug level errors + */ + static const websocketpp::log::level elog_level = + websocketpp::log::elevel::all ^ websocketpp::log::elevel::devel; + /// Default static access logging channels + /** + * Which access logging channels to enable at compile time. Channels not + * enabled here will be unable to be selected by programs using the library. + * This option gives an optimizing compiler the ability to remove entirely + * code to test whether or not to print out log messages on a certain + * channel + * + * Default is all except for development/debug level access messages + */ + static const websocketpp::log::level alog_level = + websocketpp::log::alevel::all ^ websocketpp::log::alevel::devel; + /// static const size_t connection_read_buffer_size = 512; @@ -145,7 +175,7 @@ struct core { /// Extension specific settings: /// permessage_compress extension - struct permessage_compress_config { + struct permessage_deflate_config { typedef core::request_type request_type; /// If the remote endpoint requests that we reset the compression @@ -160,14 +190,14 @@ struct core { static const uint8_t minimum_outgoing_window_bits = 8; }; - typedef websocketpp::extensions::permessage_compress::disabled - permessage_compress_type; + typedef websocketpp::extensions::permessage_deflate::disabled + permessage_deflate_type; - /// Autonegotiate permessage-compress + /// Autonegotiate permessage-deflate /** - * Automatically enables the permessage-compress extension. + * Automatically enables the permessage-deflate extension. * - * For clients this results in a permessage-compress extension request being + * For clients this results in a permessage-deflate extension request being * sent with every request rather than requiring it to be requested manually * * For servers this results in accepting the first set of extension settings diff --git a/websocketpp/config/core_client.hpp b/websocketpp/config/core_client.hpp index 222eb3595..a7896af39 100644 --- a/websocketpp/config/core_client.hpp +++ b/websocketpp/config/core_client.hpp @@ -57,12 +57,14 @@ #include // Extensions -#include +#include namespace websocketpp { namespace config { struct core_client { + typedef core_client type; + // Concurrency policy typedef websocketpp::concurrency::basic concurrency_type; @@ -89,9 +91,11 @@ struct core_client { concurrency_type> rng_type; struct transport_config { - typedef core_client::concurrency_type concurrency_type; - typedef core_client::elog_type elog_type; - typedef core_client::alog_type alog_type; + typedef type::concurrency_type concurrency_type; + typedef type::elog_type elog_type; + typedef type::alog_type alog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; }; /// Transport Endpoint Component @@ -110,7 +114,33 @@ struct core_client { * recommended. */ static const int client_version = 13; // RFC6455 + + /// Default static error logging channels + /** + * Which error logging channels to enable at compile time. Channels not + * enabled here will be unable to be selected by programs using the library. + * This option gives an optimizing compiler the ability to remove entirely + * code to test whether or not to print out log messages on a certain + * channel + * + * Default is all except for development/debug level errors + */ + static const websocketpp::log::level elog_level = + websocketpp::log::elevel::all ^ websocketpp::log::elevel::devel; + /// Default static access logging channels + /** + * Which access logging channels to enable at compile time. Channels not + * enabled here will be unable to be selected by programs using the library. + * This option gives an optimizing compiler the ability to remove entirely + * code to test whether or not to print out log messages on a certain + * channel + * + * Default is all except for development/debug level access messages + */ + static const websocketpp::log::level alog_level = + websocketpp::log::alevel::all ^ websocketpp::log::alevel::devel; + /// static const size_t connection_read_buffer_size = 512; @@ -143,8 +173,8 @@ struct core_client { /// Extension specific settings: - /// permessage_compress extension - struct permessage_compress_config { + /// permessage_deflate extension + struct permessage_deflate_config { typedef core_client::request_type request_type; /// If the remote endpoint requests that we reset the compression @@ -159,8 +189,8 @@ struct core_client { static const uint8_t minimum_outgoing_window_bits = 8; }; - typedef websocketpp::extensions::permessage_compress::disabled - permessage_compress_type; + typedef websocketpp::extensions::permessage_deflate::disabled + permessage_deflate_type; /// Autonegotiate permessage-compress /** diff --git a/websocketpp/config/debug.hpp b/websocketpp/config/debug.hpp new file mode 100644 index 000000000..3a8ad772e --- /dev/null +++ b/websocketpp/config/debug.hpp @@ -0,0 +1,215 @@ +/* + * 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: + * * 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_CONFIG_DEBUG_HPP +#define WEBSOCKETPP_CONFIG_DEBUG_HPP + + + +// Non-Policy common stuff +#include +#include + +// Concurrency +#include + +// Transport +#include + +// HTTP +#include +#include + +// Messages +#include +#include + +// Loggers +#include + +// RNG +#include + +// User stub base classes +#include +#include + +// Extensions +#include + +namespace websocketpp { +namespace config { + +struct debug_core { + typedef debug_core type; + + // Concurrency policy + typedef websocketpp::concurrency::basic concurrency_type; + + // HTTP Parser Policies + typedef http::parser::request request_type; + typedef http::parser::response response_type; + + // Message Policies + typedef message_buffer::message + message_type; + typedef message_buffer::alloc::con_msg_manager + con_msg_manager_type; + typedef message_buffer::alloc::endpoint_msg_manager + endpoint_msg_manager_type; + + /// Logging policies + typedef websocketpp::log::basic elog_type; + typedef websocketpp::log::basic alog_type; + + /// RNG policies + typedef websocketpp::random::none::int_generator rng_type; + + struct transport_config { + typedef type::concurrency_type concurrency_type; + typedef type::elog_type elog_type; + typedef type::alog_type alog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; + }; + + /// Transport Endpoint Component + typedef websocketpp::transport::iostream::endpoint + transport_type; + + /// User overridable Endpoint base class + typedef websocketpp::endpoint_base endpoint_base; + /// User overridable Connection base class + typedef websocketpp::connection_base connection_base; + + /// WebSocket Protocol version to use as a client + /** + * What version of the WebSocket Protocol to use for outgoing client + * connections. Setting this to a value other than 13 (RFC6455) is not + * recommended. + */ + static const int client_version = 13; // RFC6455 + + /// Default static error logging channels + /** + * Which error logging channels to enable at compile time. Channels not + * enabled here will be unable to be selected by programs using the library. + * This option gives an optimizing compiler the ability to remove entirely + * code to test whether or not to print out log messages on a certain + * channel + * + * Default is all except for development/debug level errors + */ + static const websocketpp::log::level elog_level = + websocketpp::log::elevel::all; + + /// Default static access logging channels + /** + * Which access logging channels to enable at compile time. Channels not + * enabled here will be unable to be selected by programs using the library. + * This option gives an optimizing compiler the ability to remove entirely + * code to test whether or not to print out log messages on a certain + * channel + * + * Default is all except for development/debug level access messages + */ + static const websocketpp::log::level alog_level = + websocketpp::log::alevel::all; + + /// + static const size_t connection_read_buffer_size = 512; + + /// Drop connections immediately on protocol error. + /** + * Drop connections on protocol error rather than sending a close frame. + * Off by default. This may result in legit messages near the error being + * dropped as well. It may free up resources otherwise spent dealing with + * misbehaving clients. + */ + static const bool drop_on_protocol_error = false; + + /// Suppresses the return of detailed connection close information + /** + * Silence close suppresses the return of detailed connection close + * information during the closing handshake. This information is useful + * for debugging and presenting useful errors to end users but may be + * undesirable for security reasons in some production environments. + * Close reasons could be used by an attacker to confirm that the endpoint + * is out of resources or be used to identify the WebSocket implimentation + * in use. + * + * Note: this will suppress *all* close codes, including those explicitly + * sent by local applications. + */ + static const bool silent_close = false; + + /// Global flag for enabling/disabling extensions + static const bool enable_extensions = true; + + /// Extension specific settings: + + /// permessage_compress extension + struct permessage_deflate_config { + typedef type::request_type request_type; + + /// If the remote endpoint requests that we reset the compression + /// context after each message should we honor the request? + static const bool allow_disabling_context_takeover = true; + + /// If the remote endpoint requests that we reduce the size of the + /// LZ77 sliding window size this is the lowest value that will be + /// allowed. Values range from 8 to 15. A value of 8 means we will + /// allow any possible window size. A value of 15 means do not allow + /// negotiation of the window size (ie require the default). + static const uint8_t minimum_outgoing_window_bits = 8; + }; + + typedef websocketpp::extensions::permessage_deflate::disabled + permessage_deflate_type; + + /// Autonegotiate permessage-deflate + /** + * Automatically enables the permessage-deflate extension. + * + * For clients this results in a permessage-deflate extension request being + * sent with every request rather than requiring it to be requested manually + * + * For servers this results in accepting the first set of extension settings + * requested by the client that we understand being used. The alternative is + * requiring the extension to be manually negotiated in `validate`. With + * auto-negotiate on, you may still override the auto-negotiate manually if + * needed. + */ + //static const bool autonegotiate_compression = false; +}; + +} // namespace config +} // namespace websocketpp + +#endif // WEBSOCKETPP_CONFIG_CORE_HPP diff --git a/websocketpp/config/debug_asio.hpp b/websocketpp/config/debug_asio.hpp new file mode 100644 index 000000000..010fb5d14 --- /dev/null +++ b/websocketpp/config/debug_asio.hpp @@ -0,0 +1,76 @@ +/* + * 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: + * * 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_CONFIG_ASIO_TLS_DEBUG_HPP +#define WEBSOCKETPP_CONFIG_ASIO_TLS_DEBUG_HPP + +#include +#include +#include + +// Pull in non-tls config +#include + +// Define TLS config +namespace websocketpp { +namespace config { + +struct debug_asio_tls : public debug_core { + typedef debug_asio_tls type; + typedef debug_core base; + + typedef base::concurrency_type concurrency_type; + + typedef base::request_type request_type; + typedef base::response_type response_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 base::alog_type alog_type; + typedef base::elog_type elog_type; + + typedef base::rng_type rng_type; + + struct transport_config { + typedef type::concurrency_type concurrency_type; + typedef type::alog_type alog_type; + typedef type::elog_type elog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; + typedef websocketpp::transport::asio::tls_socket::endpoint socket_type; + }; + + typedef websocketpp::transport::asio::endpoint + transport_type; +}; + +} // namespace config +} // namespace websocketpp + +#endif // WEBSOCKETPP_CONFIG_ASIO_TLS_DEBUG_HPP diff --git a/websocketpp/config/debug_asio_no_tls.hpp b/websocketpp/config/debug_asio_no_tls.hpp new file mode 100644 index 000000000..7645a0533 --- /dev/null +++ b/websocketpp/config/debug_asio_no_tls.hpp @@ -0,0 +1,72 @@ +/* + * 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: + * * 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_CONFIG_ASIO_DEBUG_HPP +#define WEBSOCKETPP_CONFIG_ASIO_DEBUG_HPP + +#include +#include + +namespace websocketpp { +namespace config { + +struct debug_asio : public debug_core { + typedef debug_asio type; + typedef debug_core base; + + typedef base::concurrency_type concurrency_type; + + typedef base::request_type request_type; + typedef base::response_type response_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 base::alog_type alog_type; + typedef base::elog_type elog_type; + + typedef base::rng_type rng_type; + + struct transport_config { + typedef type::concurrency_type concurrency_type; + typedef type::alog_type alog_type; + typedef type::elog_type elog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; + typedef websocketpp::transport::asio::basic_socket::endpoint + socket_type; + }; + + typedef websocketpp::transport::asio::endpoint + transport_type; +}; + +} // namespace config +} // namespace websocketpp + +#endif // WEBSOCKETPP_CONFIG_ASIO_DEBUG_HPP diff --git a/websocketpp/connection.hpp b/websocketpp/connection.hpp index 1085c04f8..11d05588d 100644 --- a/websocketpp/connection.hpp +++ b/websocketpp/connection.hpp @@ -39,11 +39,11 @@ #include #include - +#include #include -#include #include #include +#include namespace websocketpp { @@ -73,12 +73,11 @@ typedef lib::function http_handler; namespace session { namespace state { // externally visible session state (states based on the RFC) - enum value { - CONNECTING = 0, - OPEN = 1, - CLOSING = 2, - CLOSED = 3 + connecting = 0, + open = 1, + closing = 2, + closed = 3 }; } // namespace state @@ -172,7 +171,7 @@ class connection elog_type& elog, rng_type & rng) : transport_con_type(is_server,alog,elog) , m_user_agent(ua) - , m_state(session::state::CONNECTING) + , m_state(session::state::connecting) , m_internal_state(session::internal_state::USER_INIT) , m_msg_manager(new con_msg_manager_type()) , m_send_buffer_size(0) @@ -181,6 +180,8 @@ class connection , m_alog(alog) , m_elog(elog) , m_rng(rng) + , m_local_close_code(close::status::abnormal_close) + , m_remote_close_code(close::status::abnormal_close) { m_alog.write(log::alevel::devel,"connection constructor"); } @@ -519,11 +520,107 @@ class connection * @param uri The new URI to set */ void set_uri(uri_ptr uri); - + + ///////////////////////////// + // Subprotocol negotiation // + ///////////////////////////// + + /// Gets the negotated subprotocol + /** + * Retrieves the subprotocol that was negotiated during the handshake. This + * method is valid in the open handler and later. + * + * @return The negotiated subprotocol + */ + const std::string& get_subprotocol() const; + + /// Gets all of the subprotocols requested by the client + /** + * Retrieves the subprotocols that were requested during the handshake. This + * method is valid in the validate handler and later. + * + * @return A vector of the requested subprotocol + */ + const std::vector & get_requested_subprotocols() const; + + /// Adds the given subprotocol string to the request list (exception free) + /** + * Adds a subprotocol to the list to send with the opening handshake. This + * may be called multiple times to request more than one. If the server + * supports one of these, it may choose one. If so, it will return it + * in it's handshake reponse and the value will be available via + * get_subprotocol(). Subprotocol requests should be added in order of + * preference. + * + * @param request The subprotocol to request + * + * @param ec A reference to an error code that will be filled in the case of + * errors + */ + void add_subprotocol(const std::string &request, lib::error_code & ec); + + /// Adds the given subprotocol string to the request list + /** + * Adds a subprotocol to the list to send with the opening handshake. This + * may be called multiple times to request more than one. If the server + * supports one of these, it may choose one. If so, it will return it + * in it's handshake reponse and the value will be available via + * get_subprotocol(). Subprotocol requests should be added in order of + * preference. + * + * @param request The subprotocol to request + */ + void add_subprotocol(const std::string &request); + + /// Select a subprotocol to use (exception free) + /** + * Indicates which subprotocol should be used for this connection. Valid + * only during the validate handler callback. Subprotocol selected must have + * been requested by the client. Consult get_requested_subprotocols() for a + * list of valid subprotocols. + * + * This member function is valid on server endpoints/connections only + * + * @param value The subprotocol to select + * + * @param ec A reference to an error code that will be filled in the case of + * errors + */ + void select_subprotocol(const std::string & value, lib::error_code & ec); + + /// Select a subprotocol to use + /** + * Indicates which subprotocol should be used for this connection. Valid + * only during the validate handler callback. Subprotocol selected must have + * been requested by the client. Consult get_requested_subprotocols() for a + * list of valid subprotocols. + * + * This member function is valid on server endpoints/connections only + * + * @param value The subprotocol to select + */ + void select_subprotocol(const std::string & value); + ///////////////////////////////////////////////////////////// // Pass-through access to the request and response objects // ///////////////////////////////////////////////////////////// + /// Retrieve a request header + /** + * Retrieve the value of a header from the handshake HTTP request. + * + * @param key Name of the header to get + */ + const std::string & get_request_header(const std::string &key); + + /// Retrieve a response header + /** + * Retrieve the value of a header from the handshake HTTP request. + * + * @param key Name of the header to get + */ + const std::string & get_response_header(const std::string &key); + /// Set response status code and message /** * Sets the response status code to `code` and looks up the corresponding @@ -646,6 +743,14 @@ class connection */ const std::string& get_origin() const; + /// Return the connection state. + /** + * Values can be connecting, open, closing, and closed + * + * @return The connection's current state. + */ + session::state::value get_state() const; + //////////////////////////////////////////////////////////////////////// // The remaining public member functions are for internal/policy use // // only. Do not call from application code unless you understand what // @@ -864,6 +969,13 @@ class connection */ void log_open_result(); + /// Prints information about a connection being closed to the access log + /** + * Prints information about a connection being closed to the access log. + * Includes: local and remote close codes and reasons + */ + void log_close_result(); + // static settings const std::string m_user_agent; @@ -942,6 +1054,10 @@ class connection */ std::vector m_send_buffer; + /// a pointer to hold on to the current message being written to keep it + /// from going out of scope before the write is complete. + message_ptr m_current_msg; + /// True if there is currently an outstanding transport write /** * Lock m_write_lock @@ -952,6 +1068,11 @@ class connection request_type m_request; response_type m_response; uri_ptr m_uri; + std::string m_subprotocol; + + // connection data that might not be necessary to keep around for the life + // of the whole connection. + std::vector m_requested_subprotocols; const bool m_is_server; alog_type& m_alog; diff --git a/websocketpp/endpoint.hpp b/websocketpp/endpoint.hpp index a70175a6a..902ae4409 100644 --- a/websocketpp/endpoint.hpp +++ b/websocketpp/endpoint.hpp @@ -89,12 +89,13 @@ class endpoint : public config::transport_type, public config::endpoint_base { typedef lib::shared_ptr hdl_type; explicit endpoint(bool is_server) - : m_elog(&std::cerr) + : m_alog(config::alog_level,&std::cout) + , m_elog(config::elog_level,&std::cerr) , m_user_agent(::websocketpp::user_agent) , m_is_server(is_server) { - m_alog.set_channels(0xffffffff); - m_elog.set_channels(0xffffffff); + m_alog.set_channels(config::alog_level); + m_elog.set_channels(config::elog_level); m_alog.write(log::alevel::devel,"endpoint constructor"); @@ -304,13 +305,7 @@ class endpoint : public config::transport_type, public config::endpoint_base { */ connection_ptr get_con_from_hdl(connection_hdl hdl, lib::error_code & ec) { scoped_lock_type lock(m_mutex); -/** @todo This wont build on windows with null as the second template argument. - I tried to look at the boost docs for this and cound not find why the second param is here. - Cleanup ifdefs when verified this builds on other systems. */ -#ifdef WIN32 connection_ptr con = lib::static_pointer_cast( -#else - connection_ptr con = lib::static_pointer_cast( #endif hdl.lock()); if (!con) { diff --git a/websocketpp/error.hpp b/websocketpp/error.hpp index 27b020fc4..9c34a6218 100644 --- a/websocketpp/error.hpp +++ b/websocketpp/error.hpp @@ -61,6 +61,9 @@ enum value { /// The endpoint is out of outgoing message buffers no_outgoing_buffers, + + /// The endpoint is out of incoming message buffers + no_incoming_buffers, /// The connection was in the wrong state for this operation invalid_state, @@ -77,6 +80,9 @@ enum value { /// Invalid UTF-8 invalid_utf8, + /// Invalid subprotocol + invalid_subprotocol, + /// Bad or unknown connection bad_connection, @@ -84,7 +90,16 @@ enum value { test, /// Connection creation attempted failed - con_creation_failed + con_creation_failed, + + /// Selected subprotocol was not requested by the client + unrequested_subprotocol, + + /// Attempted to use a client specific feature on a server endpoint + client_only, + + /// Attempted to use a server specific feature on a client endpoint + server_only, }; // enum value @@ -112,6 +127,8 @@ class category : public lib::error_category { return "invalid uri"; case error::no_outgoing_buffers: return "no outgoing message buffers"; + case error::no_incoming_buffers: + return "no incoming message buffers"; case error::invalid_state: return "invalid state"; case error::bad_close_code: @@ -122,12 +139,20 @@ class category : public lib::error_category { return "Extracted close code is in a reserved range"; case error::invalid_utf8: return "Invalid UTF-8"; + case error::invalid_subprotocol: + return "Invalid subprotocol"; case error::bad_connection: return "Bad Connection"; case error::test: return "Test Error"; case error::con_creation_failed: return "Connection creation attempt failed"; + case error::unrequested_subprotocol: + return "Selected subprotocol was not requested by the client"; + case error::client_only: + return "Feature not available on server endpoints"; + case error::server_only: + return "Feature not available on client endpoints"; default: return "Unknown"; } diff --git a/websocketpp/extensions/extension.hpp b/websocketpp/extensions/extension.hpp index 7e2c020bd..4b89a9ed8 100644 --- a/websocketpp/extensions/extension.hpp +++ b/websocketpp/extensions/extension.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 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: diff --git a/websocketpp/extensions/permessage_compress/disabled.hpp b/websocketpp/extensions/permessage_deflate/disabled.hpp similarity index 81% rename from websocketpp/extensions/permessage_compress/disabled.hpp rename to websocketpp/extensions/permessage_deflate/disabled.hpp index fb59aa4ba..b9ad025f3 100644 --- a/websocketpp/extensions/permessage_compress/disabled.hpp +++ b/websocketpp/extensions/permessage_deflate/disabled.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 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: @@ -25,8 +25,8 @@ * */ -#ifndef WEBSOCKETPP_EXTENSION_PERMESSAGE_COMPRESS_DISABLED_HPP -#define WEBSOCKETPP_EXTENSION_PERMESSAGE_COMPRESS_DISABLED_HPP +#ifndef WEBSOCKETPP_EXTENSION_PERMESSAGE_DEFLATE_DISABLED_HPP +#define WEBSOCKETPP_EXTENSION_PERMESSAGE_DEFLATE_DISABLED_HPP #include #include @@ -39,12 +39,12 @@ namespace websocketpp { namespace extensions { -namespace permessage_compress { +namespace permessage_deflate { -/// Stub class for use when disabling permessage_compress extension +/// Stub class for use when disabling permessage_deflate extension /** - * This class is a stub that impliments the permessage_compress interface - * with minimal dependencies. It is used to disable permessage_compress + * This class is a stub that impliments the permessage_deflate interface + * with minimal dependencies. It is used to disable permessage_deflate * functionality at compile time without loading any unnecessary code. */ template @@ -58,12 +58,12 @@ class disabled { } /// Returns true if the extension is capable of providing - /// permessage_compress functionality + /// permessage_deflate functionality bool is_implimented() const { return false; } - /// Returns true if permessage_compress functionality is active for this + /// Returns true if permessage_deflate functionality is active for this /// connection bool is_enabled() const { return false; @@ -84,8 +84,8 @@ class disabled { } }; -} // namespace permessage_compress +} // namespace permessage_deflate } // namespace extensions } // namespace websocketpp -#endif // WEBSOCKETPP_EXTENSION_PERMESSAGE_COMPRESS_DISABLED_HPP +#endif // WEBSOCKETPP_EXTENSION_PERMESSAGE_DEFLATE_DISABLED_HPP diff --git a/websocketpp/extensions/permessage_compress/enabled.hpp b/websocketpp/extensions/permessage_deflate/enabled.hpp similarity index 90% rename from websocketpp/extensions/permessage_compress/enabled.hpp rename to websocketpp/extensions/permessage_deflate/enabled.hpp index 386085b6f..937a81963 100644 --- a/websocketpp/extensions/permessage_compress/enabled.hpp +++ b/websocketpp/extensions/permessage_deflate/enabled.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 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: @@ -25,8 +25,8 @@ * */ -#ifndef WEBSOCKETPP_PROCESSOR_EXTENSION_PERMESSAGECOMPRESS_HPP -#define WEBSOCKETPP_PROCESSOR_EXTENSION_PERMESSAGECOMPRESS_HPP +#ifndef WEBSOCKETPP_PROCESSOR_EXTENSION_PERMESSAGEDEFLATE_HPP +#define WEBSOCKETPP_PROCESSOR_EXTENSION_PERMESSAGEDEFLATE_HPP #include #include @@ -41,7 +41,7 @@ namespace websocketpp { namespace extensions { -namespace permessage_compress { +namespace permessage_deflate { namespace error { enum value { @@ -72,7 +72,7 @@ class category : public lib::error_category { category() {} const char *name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { - return "websocketpp.extension.permessage-compress"; + return "websocketpp.extension.permessage-deflate"; } std::string message(int value) const { @@ -107,20 +107,20 @@ lib::error_code make_error_code(error::value e) { } } // namespace error -} // namespace permessage_compress +} // namespace permessage_deflate } // namespace extensions } // namespace websocketpp _WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_ template<> struct is_error_code_enum - + { static const bool value = true; }; _WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_ namespace websocketpp { namespace extensions { -namespace permessage_compress { +namespace permessage_deflate { template class method { @@ -404,7 +404,7 @@ class deflate_engine { z_stream m_istate; // inflate state }; -/// Impliments the permessage_compress extension interface +/// Impliments the permessage_deflate extension interface /** * * Template parameter econfig defines compile time types, constants, and @@ -422,7 +422,7 @@ class deflate_engine { * * * Methods: - * permessage_compress::enabled does not define or impliment any methods + * permessage_deflate::enabled does not define or impliment any methods * itself. It uses the attribute list to determine * * @@ -431,6 +431,8 @@ class deflate_engine { */ template class enabled { + typedef std::map string_map; + typedef typename econfig::request_type::attribute_list attribute_list; typedef typename attribute_list::const_iterator attribute_iterator; typedef lib::shared_ptr< method > method_ptr; @@ -442,7 +444,7 @@ class enabled { public: enabled() : m_enabled(false) {} - /// Attempt to negotiate the permessage_compress extension + /// Attempt to negotiate the permessage_deflate extension /** * Parses the attribute list for this extension and attempts to negotiate * the extension. Returns a pair. On success @@ -450,14 +452,41 @@ class enabled { * to return in the handshake response. On error * * @param attributes A list of attributes extracted from the - * 'permessage_compress' extension parameter from the original handshake. + * 'permessage_deflate' extension parameter from the original handshake. * * @return A pair containing a status code * and a value whose interpretation is dependent on the status code. */ - err_str_pair negotiate(const attribute_list& attributes) { + err_str_pair negotiate(const string_map& attributes) { err_str_pair ret; + std::cout << "foo: " << attributes.size() << std::endl; + + string_map::const_iterator it; + + for (it = attributes.begin(); it != attributes.end(); ++it) { + std::cout << it->first << ": " << it->second << std::endl; + } + + // start by not accepting any parameters + if (attributes.size() == 0) { + ret.second = "permessage-deflate"; + } else { + ret.first = make_error_code(error::invalid_parameters); + } + + /* + * + * Sec-WebSocket-Extensions: foo; bar; baz=5, foo2; bar2; baz2=5 + * + * map> + * + * vector>>> + * + */ + + + /* // Exactly one parameter is required if (attributes.size() != 1) { ret.first = make_error_code(error::invalid_parameters); @@ -500,17 +529,17 @@ class enabled { } } - m_enabled = true; + m_enabled = true;*/ return ret; } - /// Returns true if this object impliments permessage_compress functionality + /// Returns true if this object impliments permessage_deflate functionality bool is_implimented() const { return true; } /// returns true if this object is initialized and ready to provide - /// permessage_compress functionality. + /// permessage_deflate functionality. bool is_enabled() const { return m_enabled; } @@ -542,8 +571,8 @@ class enabled { method_ptr m_method; }; -} // namespace permessage_compress +} // namespace permessage_deflate } // namespace extensions } // namespace websocketpp -#endif // WEBSOCKETPP_PROCESSOR_EXTENSION_PERMESSAGECOMPRESS_HPP +#endif // WEBSOCKETPP_PROCESSOR_EXTENSION_PERMESSAGEDEFLATE_HPP diff --git a/websocketpp/frame.hpp b/websocketpp/frame.hpp index fe430db0a..8bad56f5c 100644 --- a/websocketpp/frame.hpp +++ b/websocketpp/frame.hpp @@ -198,7 +198,7 @@ struct extended_header { copy_payload(payload_size); } - extended_header(uint64_t payload_size, int32_t masking_key) { + extended_header(uint64_t payload_size, uint32_t masking_key) { std::fill_n(this->bytes,MAX_EXTENDED_HEADER_LENGTH,0x00); // Copy payload size @@ -590,8 +590,13 @@ inline size_t prepare_masking_key(const masking_key_type& key) { * to zero and less than sizeof(size_t). */ inline size_t circshift_prepared_key(size_t prepared_key, size_t offset) { - size_t temp = prepared_key << (sizeof(size_t)-offset)*8; - return (prepared_key >> offset*8) | temp; + if (lib::net::is_little_endian()) { + size_t temp = prepared_key << (sizeof(size_t)-offset)*8; + return (prepared_key >> offset*8) | temp; + } else { + size_t temp = prepared_key >> (sizeof(size_t)-offset)*8; + return (prepared_key << offset*8) | temp; + } } /// Byte by byte mask/unmask @@ -735,13 +740,13 @@ inline void word_mask_exact(uint8_t* data, size_t length, const * * @return the prepared_key shifted to account for the input length */ -inline size_t word_mask_circ(uint8_t* input, uint8_t* output, size_t length, +inline size_t word_mask_circ(uint8_t * input, uint8_t * output, size_t length, size_t prepared_key) { size_t n = length / sizeof(size_t); // whole words size_t l = length - (n * sizeof(size_t)); // remaining bytes - size_t* input_word = reinterpret_cast(input); - size_t* output_word = reinterpret_cast(output); + size_t * input_word = reinterpret_cast(input); + size_t * output_word = reinterpret_cast(output); // mask word by word for (size_t i = 0; i < n; i++) { @@ -749,10 +754,11 @@ inline size_t word_mask_circ(uint8_t* input, uint8_t* output, size_t length, } // mask partial word at the end - if (l > 0) { - size_t r = 8*(sizeof(size_t) - l); // convert from bytes to bits - output_word[n] = input_word[n] ^ ((prepared_key << r) >> r); - } + size_t start = length - l; + uint8_t * byte_key = reinterpret_cast(&prepared_key); + for (size_t i = 0; i < l; ++i) { + output[start+i] = input[start+i] ^ byte_key[i]; + } return circshift_prepared_key(prepared_key,l); } diff --git a/websocketpp/http/constants.hpp b/websocketpp/http/constants.hpp index 9463c568b..dfdf1dabe 100644 --- a/websocketpp/http/constants.hpp +++ b/websocketpp/http/constants.hpp @@ -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: @@ -39,6 +39,9 @@ namespace http { // Maximum size in bytes before rejecting an HTTP header as too big. const size_t max_header_size = 16000; + // Number of bytes to use for temporary istream read buffers + const size_t istream_buffer = 512; + // invalid HTTP token characters // 0x00 - 0x32, 0x7f-0xff // ( ) < > @ , ; : \ " / [ ] ? = { } diff --git a/websocketpp/http/impl/response.hpp b/websocketpp/http/impl/response.hpp index 0882fd8bf..2eaf047b7 100644 --- a/websocketpp/http/impl/response.hpp +++ b/websocketpp/http/impl/response.hpp @@ -131,6 +131,45 @@ inline size_t response::consume(const char *buf, size_t len) { } } +inline size_t response::consume(std::istream & s) { + char buf[istream_buffer]; + size_t bytes_read; + size_t bytes_processed; + size_t total = 0; + + while (s.good()) { + s.getline(buf,istream_buffer); + bytes_read = static_cast(s.gcount()); + + if (s.fail() || s.eof()) { + bytes_processed = this->consume(buf,bytes_read); + total += bytes_processed; + + if (bytes_processed != bytes_read) { + // problem + break; + } + } else if (s.bad()) { + // problem + break; + } else { + // the delimiting newline was found. Replace the trailing null with + // the newline that was discarded, since our raw consume function + // expects the newline to be be there. + buf[bytes_read-1] = '\n'; + bytes_processed = this->consume(buf,bytes_read); + total += bytes_processed; + + if (bytes_processed != bytes_read) { + // problem + break; + } + } + } + + return total; +} + inline bool response::parse_complete(std::istream& s) { // parse a complete header (ie \r\n\r\n MUST be in the input stream) std::string response; diff --git a/websocketpp/http/parser.hpp b/websocketpp/http/parser.hpp index 8b601c9c7..0f2ae321d 100644 --- a/websocketpp/http/parser.hpp +++ b/websocketpp/http/parser.hpp @@ -104,7 +104,9 @@ InputIterator extract_lws(InputIterator begin, InputIterator end) { InputIterator it = begin; // strip leading CRLF - if (end-begin > 2 && *begin == '\r' && *(begin+1) == '\n' && is_whitespace_char(*(begin+2))) { + if (end-begin > 2 && *begin == '\r' && *(begin+1) == '\n' && + is_whitespace_char(static_cast(*(begin+2)))) + { it+=3; } @@ -156,8 +158,12 @@ struct parameter { typedef std::vector parameter_list; */ +//typedef std::map string_map; +//typedef std::vector< std::pair< std::string, attribute_list > > parameter_list; + typedef std::map attribute_list; -typedef std::map parameter_list; +//typedef std::map parameter_list; +typedef std::vector< std::pair< std::string, attribute_list > > parameter_list; template InputIterator extract_attributes(InputIterator begin, InputIterator end, @@ -286,7 +292,8 @@ InputIterator extract_parameters(InputIterator begin, InputIterator end, // Safe break point, insert parameter with blank attributes and exit cursor = http::parser::extract_all_lws(cursor,end); if (cursor == end) { - parameters[parameter_name] = attributes; + //parameters[parameter_name] = attributes; + parameters.push_back(std::make_pair(parameter_name,attributes)); break; } @@ -306,7 +313,8 @@ InputIterator extract_parameters(InputIterator begin, InputIterator end, } // insert parameter into output list - parameters[parameter_name] = attributes; + //parameters[parameter_name] = attributes; + parameters.push_back(std::make_pair(parameter_name,attributes)); cursor = http::parser::extract_all_lws(cursor,end); if (cursor == end) {break;} diff --git a/websocketpp/http/request.hpp b/websocketpp/http/request.hpp index 6d6f08d30..47c05034c 100644 --- a/websocketpp/http/request.hpp +++ b/websocketpp/http/request.hpp @@ -47,6 +47,9 @@ namespace parser { */ class request : public parser { public: + typedef request type; + typedef lib::shared_ptr ptr; + typedef parser::attribute_list attribute_list; typedef parser::parameter_list parameter_list; diff --git a/websocketpp/http/response.hpp b/websocketpp/http/response.hpp index da29e4f69..eea7f7d63 100644 --- a/websocketpp/http/response.hpp +++ b/websocketpp/http/response.hpp @@ -53,6 +53,9 @@ namespace parser { */ class response : public parser { public: + typedef response type; + typedef lib::shared_ptr ptr; + response() : m_read(0) , m_buf(new std::string()) @@ -81,6 +84,8 @@ class response : public parser { */ size_t consume(const char *buf, size_t len); + size_t consume(std::istream & s); + /// Returns true if the response is ready. /** * @note will never return true if the content length header is not present diff --git a/websocketpp/impl/connection_impl.hpp b/websocketpp/impl/connection_impl.hpp index 117740f66..2076aec1c 100644 --- a/websocketpp/impl/connection_impl.hpp +++ b/websocketpp/impl/connection_impl.hpp @@ -65,6 +65,11 @@ size_t connection::get_buffered_amount() const { return m_send_buffer_size; } +template +session::state::value connection::get_state() const { + //scoped_lock_type lock(m_connection_state_lock); + return m_state; +} template lib::error_code connection::send(const std::string& payload, @@ -92,7 +97,7 @@ lib::error_code connection::send(typename config::message_type::ptr msg) m_alog.write(log::alevel::devel,"connection send"); // TODO: - if (m_state != session::state::OPEN) { + if (m_state != session::state::open) { return error::make_error_code(error::invalid_state); } @@ -137,7 +142,7 @@ template void connection::ping(const std::string& payload) { m_alog.write(log::alevel::devel,"connection ping"); - if (m_state != session::state::OPEN) { + if (m_state != session::state::open) { throw error::make_error_code(error::invalid_state); } @@ -170,7 +175,7 @@ template void connection::pong(const std::string& payload, lib::error_code& ec) { m_alog.write(log::alevel::devel,"connection pong"); - if (m_state != session::state::OPEN) { + if (m_state != session::state::open) { ec = error::make_error_code(error::invalid_state); return; } @@ -218,7 +223,7 @@ void connection::close(const close::status::value code, { m_alog.write(log::alevel::devel,"connection close"); - if (m_state != session::state::OPEN) { + if (m_state != session::state::open) { ec = error::make_error_code(error::invalid_state); return; } @@ -316,11 +321,96 @@ void connection::set_uri(uri_ptr uri) { +template +const std::string & connection::get_subprotocol() const { + return m_subprotocol; +} + +template +const std::vector & +connection::get_requested_subprotocols() const { + return m_requested_subprotocols; +} + +template +void connection::add_subprotocol(const std::string & value, + lib::error_code & ec) +{ + if (m_is_server) { + ec = error::make_error_code(error::client_only); + return; + } + + // If the value is empty or has a non-RFC2616 token character it is invalid. + if (value.empty() || std::find_if(value.begin(),value.end(), + http::is_not_token_char) != value.end()) + { + ec = error::make_error_code(error::invalid_subprotocol); + return; + } + + m_requested_subprotocols.push_back(value); +} + +template +void connection::add_subprotocol(const std::string & value) { + lib::error_code ec; + this->add_subprotocol(value,ec); + if (ec) { + throw ec; + } +} +template +void connection::select_subprotocol(const std::string & value, + lib::error_code & ec) +{ + if (!m_is_server) { + ec = error::make_error_code(error::server_only); + return; + } + + if (value.empty()) { + ec = lib::error_code(); + return; + } + + std::vector::iterator it; + + it = std::find(m_requested_subprotocols.begin(), + m_requested_subprotocols.end(), + value); + + if (it == m_requested_subprotocols.end()) { + ec = error::make_error_code(error::unrequested_subprotocol); + return; + } + + m_subprotocol = value; +} +template +void connection::select_subprotocol(const std::string & value) { + lib::error_code ec; + this->select_subprotocol(value,ec); + if (ec) { + throw ec; + } +} +template +const std::string & +connection::get_request_header(const std::string &key) { + return m_request.get_header(key); +} + +template +const std::string & +connection::get_response_header(const std::string &key) { + return m_response.get_header(key); +} template void connection::set_status( @@ -459,7 +549,7 @@ void connection::handle_transport_init(const lib::error_code& ec) { if (ec) { std::stringstream s; - s << "handle_transport_init recieved error: "<< ec; + s << "handle_transport_init recieved error: "<< ec.message(); m_elog.write(log::elevel::fatal,s.str()); this->terminate(); @@ -516,7 +606,7 @@ void connection::handle_handshake_read(const lib::error_code& ec, if (ec) { std::stringstream s; - s << "error in handle_read_handshake: "<< ec; + s << "error in handle_read_handshake: "<< ec.message(); m_elog.write(log::elevel::fatal,s.str()); this->terminate(); return; @@ -579,6 +669,14 @@ void connection::handle_handshake_read(const lib::error_code& ec, } } + if (m_alog.static_test(log::alevel::devel)) { + m_alog.write(log::alevel::devel,m_request.raw()); + if (m_request.get_header("Sec-WebSocket-Key3") != "") { + m_alog.write(log::alevel::devel, + utility::to_hex(m_request.get_header("Sec-WebSocket-Key3"))); + } + } + // The remaining bytes in m_buf are frame data. Copy them to the // beginning of the buffer and note the length. They will be read after // the handshake completes and before more bytes are read. @@ -631,7 +729,7 @@ template void connection::handle_read_frame(const lib::error_code& ec, size_t bytes_transferred) { - m_alog.write(log::alevel::devel,"connection handle_read_frame"); + //m_alog.write(log::alevel::devel,"connection handle_read_frame"); this->atomic_state_check( istate::PROCESS_CONNECTION, @@ -639,8 +737,16 @@ void connection::handle_read_frame(const lib::error_code& ec, ); if (ec) { + if (ec == transport::error::eof) { + // we expect to get eof if the connection is closed already + if (m_state == session::state::closed) { + m_alog.write(log::alevel::devel,"got eof from closed con"); + return; + } + } + std::stringstream s; - s << "error in handle_read_frame: " << ec; + s << "error in handle_read_frame: " << ec.message() << " (" << ec << ")"; m_elog.write(log::elevel::fatal,s.str()); this->terminate(); return; @@ -655,14 +761,18 @@ void connection::handle_read_frame(const lib::error_code& ec, size_t p = 0; - std::stringstream s; - s << "p = " << p << " bytes transferred = " << bytes_transferred; - m_alog.write(log::alevel::devel,s.str()); + if (m_alog.static_test(log::alevel::devel)) { + std::stringstream s; + s << "p = " << p << " bytes transferred = " << bytes_transferred; + m_alog.write(log::alevel::devel,s.str()); + } while (p < bytes_transferred) { - s.str(""); - s << "calling consume with " << bytes_transferred-p << " bytes"; - m_alog.write(log::alevel::devel,s.str()); + if (m_alog.static_test(log::alevel::devel)) { + std::stringstream s; + s << "calling consume with " << bytes_transferred-p << " bytes"; + m_alog.write(log::alevel::devel,s.str()); + } lib::error_code ec; @@ -671,11 +781,12 @@ void connection::handle_read_frame(const lib::error_code& ec, bytes_transferred-p, ec ); - - s.str(""); - s << "bytes left after consume: " << bytes_transferred-p; - m_alog.write(log::alevel::devel,s.str()); - + + if (m_alog.static_test(log::alevel::devel)) { + std::stringstream s; + s << "bytes left after consume: " << bytes_transferred-p; + m_alog.write(log::alevel::devel,s.str()); + } if (ec) { m_elog.write(log::elevel::rerror,"consume error: "+ec.message()); @@ -698,7 +809,7 @@ void connection::handle_read_frame(const lib::error_code& ec, } if (m_processor->ready()) { - m_alog.write(log::alevel::devel,"consume ended in ready"); + //m_alog.write(log::alevel::devel,"consume ended in ready"); message_ptr msg = m_processor->get_message(); @@ -838,13 +949,21 @@ bool connection::process_handshake_request() { return false; } + // extract subprotocols + lib::error_code subp_ec = m_processor->extract_subprotocols(m_request, + m_requested_subprotocols); + + if (subp_ec) { + // should we do anything? + } + // Ask application to validate the connection if (!m_validate_handler || m_validate_handler(m_connection_hdl)) { m_response.set_status(http::status_code::switching_protocols); // Write the appropriate response headers based on request and // processor version - ec = m_processor->process_handshake(m_request,m_response); + ec = m_processor->process_handshake(m_request,m_subprotocol,m_response); if (ec) { std::stringstream s; @@ -938,7 +1057,9 @@ void connection::send_http_response() { // Set some common headers m_response.replace_header("Server",m_user_agent); - + + + // have the processor generate the raw bytes for the wire (if it exists) if (m_processor) { m_handshake_buffer = m_processor->get_raw(m_response); @@ -949,6 +1070,10 @@ void connection::send_http_response() { if (m_alog.static_test(log::alevel::devel)) { m_alog.write(log::alevel::devel,"Raw Handshake response:\n"+m_handshake_buffer); + if (m_response.get_header("Sec-WebSocket-Key3") != "") { + m_alog.write(log::alevel::devel, + utility::to_hex(m_response.get_header("Sec-WebSocket-Key3"))); + } } // write raw bytes @@ -1004,8 +1129,8 @@ void connection::handle_send_http_response( this->atomic_state_change( istate::PROCESS_HTTP_REQUEST, istate::PROCESS_CONNECTION, - session::state::CONNECTING, - session::state::OPEN, + session::state::connecting, + session::state::open, "handle_send_http_response must be called from PROCESS_HTTP_REQUEST state" ); @@ -1020,14 +1145,14 @@ template void connection::send_http_request() { m_alog.write(log::alevel::devel,"connection send_http_request"); - // TODO: origin header - // TODO: subprotocol requests + // TODO: origin header? // Have the protocol processor fill in the appropriate fields based on the // selected client version if (m_processor) { lib::error_code ec; - ec = m_processor->client_handshake_request(m_request,m_uri); + ec = m_processor->client_handshake_request(m_request,m_uri, + m_requested_subprotocols); if (ec) { m_elog.write(log::elevel::fatal, @@ -1044,7 +1169,7 @@ void connection::send_http_request() { if (m_request.get_header("User-Agent") == "") { m_request.replace_header("User-Agent",m_user_agent); } - + m_handshake_buffer = m_request.raw(); if (m_alog.static_test(log::alevel::devel)) { @@ -1127,7 +1252,7 @@ void connection::handle_read_http_response(const lib::error_code& ec, return; } - m_elog.write(log::elevel::rerror,std::string("Raw response: ")+m_response.raw()); + m_alog.write(log::alevel::devel,std::string("Raw response: ")+m_response.raw()); if (m_response.headers_ready()) { lib::error_code ec = m_processor->validate_server_handshake_response( @@ -1147,8 +1272,8 @@ void connection::handle_read_http_response(const lib::error_code& ec, this->atomic_state_change( istate::READ_HTTP_RESPONSE, istate::PROCESS_CONNECTION, - session::state::CONNECTING, - session::state::OPEN, + session::state::connecting, + session::state::open, "handle_read_http_response must be called from READ_HTTP_RESPONSE state" ); @@ -1189,19 +1314,22 @@ void connection::terminate() { transport_con_type::shutdown(); - if (m_state == session::state::CONNECTING) { - m_state = session::state::CLOSED; + 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; + } 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()); @@ -1222,9 +1350,8 @@ void connection::terminate() { template void connection::write_frame() { - m_alog.write(log::alevel::devel,"connection write_frame"); + //m_alog.write(log::alevel::devel,"connection write_frame"); - message_ptr msg; { scoped_lock_type lock(m_write_lock); @@ -1238,9 +1365,9 @@ void connection::write_frame() { // Get the next message in the queue. This will return an empty // message if the queue was empty. - msg = write_pop(); + m_current_msg = write_pop(); - if (!msg) { + if (!m_current_msg) { return; } @@ -1250,13 +1377,15 @@ void connection::write_frame() { m_write_flag = true; } - const std::string& header = msg->get_header(); - const std::string& payload = msg->get_payload(); + const std::string& header = m_current_msg->get_header(); + const std::string& payload = m_current_msg->get_payload(); m_send_buffer.push_back(transport::buffer(header.c_str(),header.size())); m_send_buffer.push_back(transport::buffer(payload.c_str(),payload.size())); - + + if (m_alog.static_test(log::alevel::frame_header)) { + if (m_alog.dynamic_test(log::alevel::frame_header)) { std::stringstream s; s << "Dispatching write with " << header.size() << " header bytes and " << payload.size() @@ -1264,16 +1393,19 @@ void connection::write_frame() { m_alog.write(log::alevel::frame_header,s.str()); m_alog.write(log::alevel::frame_header,"Header: "+utility::to_hex(header)); } + } if (m_alog.static_test(log::alevel::frame_payload)) { + if (m_alog.dynamic_test(log::alevel::frame_payload)) { m_alog.write(log::alevel::frame_payload,"Payload: "+utility::to_hex(payload)); } + } transport_con_type::async_write( m_send_buffer, lib::bind( &type::handle_write_frame, type::shared_from_this(), - msg->get_terminal(), + m_current_msg->get_terminal(), lib::placeholders::_1 ) ); @@ -1284,6 +1416,7 @@ void connection::handle_write_frame(bool terminate, const lib::error_code& ec) { m_send_buffer.clear(); + m_current_msg.reset(); if (ec) { m_elog.write(log::elevel::fatal,"error in handle_write_frame: "+ec.message()); @@ -1441,7 +1574,7 @@ void connection::process_control_frame(typename return; } - if (m_state == session::state::OPEN) { + if (m_state == session::state::open) { std::stringstream s; s << "Received close frame with code " << m_remote_close_code << " and reason " << m_remote_close_reason; @@ -1452,7 +1585,7 @@ void connection::process_control_frame(typename m_elog.write(log::elevel::devel, "send_close_ack error: "+ec.message()); } - } else if (m_state == session::state::CLOSING) { + } else if (m_state == session::state::closing) { // ack of our close m_alog.write(log::alevel::devel,"Got acknowledgement of close"); this->terminate(); @@ -1650,7 +1783,7 @@ void connection::log_open_result() s << (version == -1 ? "HTTP" : "WebSocket") << " Connection "; // Remote endpoint address - s << "Unknown" << " "; + s << transport_con_type::get_remote_endpoint() << " "; // Version string if WebSocket if (version != -1) { @@ -1659,8 +1792,13 @@ void connection::log_open_result() // User Agent std::string ua = m_request.get_header("User-Agent"); - s << (ua == "" ? "NULL" : ua) << " "; - + if (ua == "") { + s << "\"\" "; + } else { + // check if there are any quotes in the user agent + s << "\"" << utility::string_replace_all(ua,"\"","\\\"") << "\" "; + } + // URI s << (m_uri ? m_uri->get_resource() : "NULL") << " "; @@ -1670,6 +1808,20 @@ void connection::log_open_result() m_alog.write(log::alevel::connect,s.str()); } +template +void connection::log_close_result() +{ + std::stringstream s; + + s << "Disconnect " + << "close local:[" << m_local_close_code + << (m_local_close_reason == "" ? "" : ","+m_local_close_reason) + << "] remote:[" << m_remote_close_code + << (m_remote_close_reason == "" ? "" : ","+m_remote_close_reason) << "]"; + + m_alog.write(log::alevel::disconnect,s.str()); +} + } // namespace websocketpp #endif // WEBSOCKETPP_CONNECTION_IMPL_HPP diff --git a/websocketpp/impl/endpoint_impl.hpp b/websocketpp/impl/endpoint_impl.hpp index 7f8b0b52b..9b3c4a68c 100644 --- a/websocketpp/impl/endpoint_impl.hpp +++ b/websocketpp/impl/endpoint_impl.hpp @@ -174,7 +174,7 @@ void endpoint::close(connection_hdl hdl, const close::status::value code, const std::string & reason) { lib::error_code ec; - send(hdl,code,reason,ec); + close(hdl,code,reason,ec); if (ec) { throw ec; } } diff --git a/websocketpp/impl/utilities_impl.hpp b/websocketpp/impl/utilities_impl.hpp index 0762d26e0..b3ff1a085 100644 --- a/websocketpp/impl/utilities_impl.hpp +++ b/websocketpp/impl/utilities_impl.hpp @@ -61,6 +61,17 @@ inline std::string to_hex(const char* input,size_t length) { return to_hex(reinterpret_cast(input),length); } +inline std::string string_replace_all(std::string subject, const std::string& + search, const std::string& replace) +{ + size_t pos = 0; + while((pos = subject.find(search, pos)) != std::string::npos) { + subject.replace(pos, search.length(), replace); + pos += replace.length(); + } + return subject; +} + } // namespace utility } // namespace websocketpp diff --git a/websocketpp/logger/basic.hpp b/websocketpp/logger/basic.hpp index ea9f44737..adf329d87 100644 --- a/websocketpp/logger/basic.hpp +++ b/websocketpp/logger/basic.hpp @@ -62,7 +62,16 @@ class basic { , m_dynamic_channels(0) , m_out(out) {} + void set_ostream(std::ostream* out) { + m_out = out; + } + void set_channels(level channels) { + if (channels == names::none) { + clear_channels(names::all); + return; + } + scoped_lock_type lock(m_lock); m_dynamic_channels |= (channels & m_static_channels); } @@ -78,6 +87,7 @@ class basic { *m_out << "[" << get_timestamp() << "] " << "[" << names::channel_name(channel) << "] " << msg << "\n"; + m_out->flush(); } void write(level channel, const char* msg) { @@ -86,29 +96,29 @@ class basic { *m_out << "[" << get_timestamp() << "] " << "[" << names::channel_name(channel) << "] " << msg << "\n"; + m_out->flush(); } bool static_test(level channel) const { return ((channel & m_static_channels) != 0); } + bool dynamic_test(level channel) { + return ((channel & m_dynamic_channels) != 0); + } private: typedef typename concurrency::scoped_lock_type scoped_lock_type; typedef typename concurrency::mutex_type mutex_type; - bool dynamic_test(level channel) { - return ((channel & m_dynamic_channels) != 0); - } - const char* get_timestamp() { std::time_t t = std::time(NULL); - std::strftime(buffer,30,"%Y-%m-%d %H:%M:%S%z",std::localtime(&t)); + std::strftime(buffer,39,"%Y-%m-%d %H:%M:%S%z",std::localtime(&t)); return buffer; } mutex_type m_lock; - - char buffer[30]; + + char buffer[40]; const level m_static_channels; level m_dynamic_channels; std::ostream* m_out; diff --git a/websocketpp/md5/md5.hpp b/websocketpp/md5/md5.hpp index 4eaffb737..b06a2be82 100644 --- a/websocketpp/md5/md5.hpp +++ b/websocketpp/md5/md5.hpp @@ -202,7 +202,7 @@ md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) X = (const md5_word_t *)data; } else { /* not aligned */ - memcpy(xbuf, data, 64); + std::memcpy(xbuf, data, 64); X = xbuf; } } @@ -374,9 +374,9 @@ md5_append(md5_state_t *pms, const md5_byte_t *data, size_t nbytes) /* Process an initial partial block. */ if (offset) { - int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + int copy = (offset + nbytes > 64 ? 64 - offset : static_cast(nbytes)); - memcpy(pms->buf + offset, p, copy); + std::memcpy(pms->buf + offset, p, copy); if (offset + copy < 64) return; p += copy; @@ -390,7 +390,7 @@ md5_append(md5_state_t *pms, const md5_byte_t *data, size_t nbytes) /* Process a final partial block. */ if (left) - memcpy(pms->buf, p, left); + std::memcpy(pms->buf, p, left); } void diff --git a/websocketpp/processors/base.hpp b/websocketpp/processors/base.hpp index 459ae5263..61cdffcd9 100644 --- a/websocketpp/processors/base.hpp +++ b/websocketpp/processors/base.hpp @@ -154,6 +154,9 @@ enum processor_errors { /// Using a reason requires a close code reason_requires_code, + /// Error parsing subprotocols + subprotocol_parse_error, + /// Error parsing extensions extension_parse_error, @@ -223,6 +226,8 @@ class processor_category : public lib::error_category { return "Invalid close code used"; case error::reason_requires_code: return "Using a close reason requires a valid close code"; + case error::subprotocol_parse_error: + return "Error parsing subprotocol header"; case error::extension_parse_error: return "Error parsing extension header"; case error::extensions_disabled: diff --git a/websocketpp/processors/hybi00.hpp b/websocketpp/processors/hybi00.hpp index 28d5e7fbf..a037d859b 100644 --- a/websocketpp/processors/hybi00.hpp +++ b/websocketpp/processors/hybi00.hpp @@ -39,6 +39,9 @@ #include +#include +#include + #include namespace websocketpp { @@ -62,7 +65,11 @@ class hybi00 : public processor { typedef typename config::con_msg_manager_type::ptr msg_manager_ptr; explicit hybi00(bool secure, bool server, msg_manager_ptr manager) - : processor(secure, server) {} + : processor(secure, server) + , msg_hdr(0x00) + , msg_ftr(0xff) + , m_state(HEADER) + , m_msg_manager(manager) {} int get_version() const { return 0; @@ -91,8 +98,8 @@ class hybi00 : public processor { return lib::error_code(); } - lib::error_code process_handshake(const request_type& req, - response_type& res) const + lib::error_code process_handshake(const request_type& req, const + std::string & subprotocol, response_type& res) const { char key_final[16]; @@ -117,7 +124,7 @@ class hybi00 : public processor { md5::md5_hash_string(std::string(key_final,16)) ); - res.append_header("Upgrade","websocket"); + res.append_header("Upgrade","WebSocket"); res.append_header("Connection","Upgrade"); // Echo back client's origin unless our local application set a @@ -133,12 +140,16 @@ class hybi00 : public processor { res.append_header("Sec-WebSocket-Location",uri->str()); } + if (subprotocol != "") { + res.replace_header("Sec-WebSocket-Protocol",subprotocol); + } + return lib::error_code(); } // outgoing client connection processing is not supported for this version - lib::error_code client_handshake_request(request_type& req, uri_ptr uri) - const + lib::error_code client_handshake_request(request_type& req, uri_ptr uri, + const std::vector & subprotocols) const { return error::make_error_code(error::no_protocol_support); } @@ -150,13 +161,22 @@ class hybi00 : public processor { } std::string get_raw(const response_type& res) const { - return res.raw() + res.get_header("Sec-WebSocket-Key3"); + response_type temp = res; + temp.remove_header("Sec-WebSocket-Key3"); + return temp.raw() + res.get_header("Sec-WebSocket-Key3"); } const std::string& get_origin(const request_type& r) const { return r.get_header("Origin"); } + // hybi00 doesn't support subprotocols so there never will be any requested + lib::error_code extract_subprotocols(const request_type & req, + std::vector & subprotocol_list) + { + return lib::error_code(); + } + uri_ptr get_uri(const request_type& request) const { std::string h = request.get_header("Host"); @@ -188,12 +208,64 @@ class hybi00 : public processor { /// Process new websocket connection bytes size_t consume(uint8_t * buf, size_t len, lib::error_code & ec) { - ec = make_error_code(error::not_implimented); - return 0; + // if in state header we are expecting a 0x00 byte, if we don't get one + // it is a fatal error + size_t p = 0; // bytes processed + size_t l = 0; + + ec = lib::error_code(); + + while (p < len) { + if (m_state == HEADER) { + if (buf[p] == msg_hdr) { + p++; + m_msg_ptr = m_msg_manager->get_message(frame::opcode::text,1); + + if (!m_msg_ptr) { + ec = make_error_code(websocketpp::error::no_incoming_buffers); + m_state = FATAL_ERROR; + } else { + m_state = PAYLOAD; + } + } else { + ec = make_error_code(error::protocol_violation); + m_state = FATAL_ERROR; + } + } else if (m_state == PAYLOAD) { + uint8_t *it = std::find(buf+p,buf+len,msg_ftr); + + // 0 1 2 3 4 5 + // 0x00 0x23 0x23 0x23 0xff 0xXX + + // Copy payload bytes into message + l = static_cast(it-(buf+p)); + m_msg_ptr->append_payload(buf+p,l); + p += l; + + if (it != buf+len) { + // message is done, copy it and the trailing + p++; + // TODO: validation + m_state = READY; + } + } else { + // TODO + break; + } + } + // If we get one, we create a new message and move to application state + + // if in state application we are copying bytes into the output message + // and validating them for UTF8 until we hit a 0xff byte. Once we hit + // 0x00, the message is complete and is dispatched. Then we go back to + // header state. + + //ec = make_error_code(error::not_implimented); + return p; } bool ready() const { - return false; + return (m_state == READY); } bool get_error() const { @@ -201,7 +273,10 @@ class hybi00 : public processor { } message_ptr get_message() { - return message_ptr(); + message_ptr ret = m_msg_ptr; + m_msg_ptr = message_ptr(); + m_state = HEADER; + return ret; } /// Prepare a message for writing @@ -211,19 +286,37 @@ class hybi00 : public processor { */ virtual lib::error_code prepare_data_frame(message_ptr in, message_ptr out) { - // assert msg + if (!in || !out) { + return make_error_code(error::invalid_arguments); + } - // check if the message is prepared already + // TODO: check if the message is prepared already // validate opcode + if (in->get_opcode() != frame::opcode::text) { + return make_error_code(error::invalid_opcode); + } + + std::string& i = in->get_raw_payload(); + //std::string& o = out->get_raw_payload(); + // validate payload utf8 - - // if we are a client generate a masking key - + if (!utf8_validator::validate(i)) { + return make_error_code(error::invalid_payload); + } + // generate header - // perform compression - // perform masking + out->set_header(std::string(reinterpret_cast(&msg_hdr),1)); + + // process payload + out->set_payload(i); + out->append_payload(std::string(reinterpret_cast(&msg_ftr),1)); + + // hybi00 doesn't support compression + // hybi00 doesn't have masking + out->set_prepared(true); + return lib::error_code(); } @@ -240,11 +333,21 @@ class hybi00 : public processor { lib::error_code prepare_close(close::status::value code, const std::string & reason, message_ptr out) const { - return lib::error_code(error::no_protocol_support); + if (!out) { + return lib::error_code(error::invalid_arguments); + } + + std::string val; + val.append(1,0xff); + val.append(1,0x00); + out->set_payload(val); + out->set_prepared(true); + + return lib::error_code(); } private: void decode_client_key(const std::string& key, char* result) const { - int spaces = 0; + unsigned int spaces = 0; std::string digits = ""; uint32_t num; @@ -257,7 +360,7 @@ class hybi00 : public processor { } } - num = strtoul(digits.c_str(), NULL, 10); + num = static_cast(strtoul(digits.c_str(), NULL, 10)); if (spaces > 0 && num > 0) { num = htonl(num/spaces); std::copy(reinterpret_cast(&num), @@ -267,6 +370,22 @@ class hybi00 : public processor { std::fill(result,result+4,0); } } + + enum state { + HEADER = 0, + PAYLOAD = 1, + READY = 2, + FATAL_ERROR = 3 + }; + + const uint8_t msg_hdr; + const uint8_t msg_ftr; + + state m_state; + + msg_manager_ptr m_msg_manager; + message_ptr m_msg_ptr; + utf8_validator::validator m_validator; }; diff --git a/websocketpp/processors/hybi07.hpp b/websocketpp/processors/hybi07.hpp index 04712a1fe..fe8c928f1 100644 --- a/websocketpp/processors/hybi07.hpp +++ b/websocketpp/processors/hybi07.hpp @@ -40,6 +40,8 @@ namespace processor { template class hybi07 : public hybi08 { public: + typedef typename config::request_type request_type; + typedef typename config::con_msg_manager_type::ptr msg_manager_ptr; typedef typename config::rng_type rng_type; @@ -47,6 +49,13 @@ class hybi07 : public hybi08 { rng_type& rng) : hybi08(secure, server, manager, rng) {} + // outgoing client connection processing is not supported for this version + lib::error_code client_handshake_request(request_type& req, uri_ptr uri, + const std::vector & subprotocols) const + { + return error::make_error_code(error::no_protocol_support); + } + int get_version() const { return 7; } diff --git a/websocketpp/processors/hybi08.hpp b/websocketpp/processors/hybi08.hpp index 4658ea4a3..719b9d270 100644 --- a/websocketpp/processors/hybi08.hpp +++ b/websocketpp/processors/hybi08.hpp @@ -50,6 +50,13 @@ class hybi08 : public hybi13 { rng_type& rng) : hybi13(secure, server, manager, rng) {} + // outgoing client connection processing is not supported for this version + lib::error_code client_handshake_request(request_type& req, uri_ptr uri, + const std::vector & subprotocols) const + { + return error::make_error_code(error::no_protocol_support); + } + int get_version() const { return 8; } diff --git a/websocketpp/processors/hybi13.hpp b/websocketpp/processors/hybi13.hpp index 470e5d9fb..af8baa9de 100644 --- a/websocketpp/processors/hybi13.hpp +++ b/websocketpp/processors/hybi13.hpp @@ -69,7 +69,7 @@ class hybi13 : public processor { typedef typename msg_manager_type::ptr msg_manager_ptr; typedef typename config::rng_type rng_type; - typedef typename config::permessage_compress_type permessage_compress_type; + typedef typename config::permessage_deflate_type permessage_deflate_type; typedef std::pair err_str_pair; @@ -86,8 +86,8 @@ class hybi13 : public processor { return 13; } - bool has_permessage_compress() const { - return m_permessage_compress.is_implimented(); + bool has_permessage_deflate() const { + return m_permessage_deflate.is_implimented(); } err_str_pair negotiate_extensions(const request_type& req) { @@ -116,26 +116,28 @@ class hybi13 : public processor { typename request_type::parameter_list::const_iterator it; - // if permessage_compress is implimented, check if it was requested - if (m_permessage_compress.is_implimented()) { - it = p.find("permessage-compress"); - + if (m_permessage_deflate.is_implimented()) { err_str_pair neg_ret; - - if (it != p.end()) { - neg_ret = m_permessage_compress.negotiate(it->second); - - if (neg_ret.first) { - // Figure out if this is an error that should halt all - // extension negotiations or simply cause negotiation of - // this specific extension to fail. - std::cout << "permessage-compress negotiation failed: " - << neg_ret.first << " and message " - << neg_ret.second << std::endl; - } else { - // Note: this list will need commas if WebSocket++ ever - // supports more than one extension - ret.second += neg_ret.second; + for (it = p.begin(); it != p.end(); ++it) { + // look through each extension, if the key is permessage-deflate + if (it->first == "permessage-deflate") { + std::cout << "mark3: " << std::endl; + neg_ret = m_permessage_deflate.negotiate(it->second); + + std::cout << neg_ret.first.message() << " - " << neg_ret.second << std::endl; + + if (neg_ret.first) { + // Figure out if this is an error that should halt all + // extension negotiations or simply cause negotiation of + // this specific extension to fail. + std::cout << "permessage-compress negotiation failed: " + << neg_ret.first.message() << std::endl; + } else { + // Note: this list will need commas if WebSocket++ ever + // supports more than one extension + ret.second += neg_ret.second; + continue; + } } } } @@ -163,8 +165,12 @@ class hybi13 : public processor { return lib::error_code(); } - lib::error_code process_handshake(const request_type& request, - response_type& response) const + /* TODO: the 'subprotocol' parameter may need to be expanded into a more + * generic struct if other user input parameters to the processed handshake + * are found. + */ + lib::error_code process_handshake(const request_type& request, const + std::string & subprotocol, response_type& response) const { std::string server_key = request.get_header("Sec-WebSocket-Key"); @@ -178,11 +184,15 @@ class hybi13 : public processor { response.append_header("Upgrade",constants::upgrade_token); response.append_header("Connection",constants::connection_token); + if (!subprotocol.empty()) { + response.replace_header("Sec-WebSocket-Protocol",subprotocol); + } + return lib::error_code(); } lib::error_code client_handshake_request(request_type& req, uri_ptr - uri) const + uri, const std::vector & subprotocols) const { req.set_method("GET"); req.set_uri(uri->get_resource()); @@ -193,6 +203,17 @@ class hybi13 : public processor { req.replace_header("Sec-WebSocket-Version","13"); req.replace_header("Host",uri->get_host_port()); + if (!subprotocols.empty()) { + std::ostringstream result; + std::vector::const_iterator it = subprotocols.begin(); + result << *it++; + while (it != subprotocols.end()) { + result << ", " << *it++; + } + + req.replace_header("Sec-WebSocket-Protocol",result.str()); + } + // Generate handshake key frame::uint32_converter conv; unsigned char raw_key[16]; @@ -250,6 +271,25 @@ class hybi13 : public processor { return r.get_header("Origin"); } + lib::error_code extract_subprotocols(const request_type & req, + std::vector & subprotocol_list) + { + if (!req.get_header("Sec-WebSocket-Protocol").empty()) { + typename request_type::parameter_list p; + + if (!req.get_header_as_plist("Sec-WebSocket-Protocol",p)) { + typename request_type::parameter_list::const_iterator it; + + for (it = p.begin(); it != p.end(); ++it) { + subprotocol_list.push_back(it->first); + } + } else { + return error::make_error_code(error::subprotocol_parse_error); + } + } + return lib::error_code(); + } + uri_ptr get_uri(const request_type& request) const { std::string h = request.get_header("Host"); @@ -346,7 +386,9 @@ class hybi13 : public processor { // check if this frame is the start of a new message and set up // the appropriate message metadata. frame::opcode::value op = frame::get_opcode(m_basic_header); - + + // TODO: get_message failure conditions + if (frame::opcode::is_control(op)) { m_control_msg = msg_metadata( m_msg_manager->get_message(op,m_bytes_needed), @@ -508,7 +550,7 @@ class hybi13 : public processor { frame::masking_key_type key; bool masked = !base::m_server; - bool compressed = m_permessage_compress.is_enabled() + bool compressed = m_permessage_deflate.is_enabled() && in->get_compressed(); bool fin = in->get_fin(); @@ -529,7 +571,7 @@ class hybi13 : public processor { // prepare payload if (compressed) { // compress and store in o after header. - m_permessage_compress.compress(i,o); + m_permessage_deflate.compress(i,o); // mask in place if necessary if (masked) { @@ -699,11 +741,11 @@ class hybi13 : public processor { size_t offset = out.size(); // decompress message if needed. - if (m_permessage_compress.is_enabled() + if (m_permessage_deflate.is_enabled() && frame::get_rsv1(m_basic_header)) { // Decompress current buffer into the message buffer - m_permessage_compress.decompress(buf,len,out); + m_permessage_deflate.decompress(buf,len,out); // get the length of the newly uncompressed output offset = out.size() - offset; @@ -761,7 +803,7 @@ class hybi13 : public processor { // a control message. // // TODO: unit tests for this - if (frame::get_rsv1(h) && (!m_permessage_compress.is_enabled() + if (frame::get_rsv1(h) && (!m_permessage_deflate.is_enabled() || frame::opcode::is_control(op))) { return make_error_code(error::invalid_rsv_bit); @@ -978,7 +1020,7 @@ class hybi13 : public processor { state m_state; // Extensions - permessage_compress_type m_permessage_compress; + permessage_deflate_type m_permessage_deflate; }; } // namespace processor diff --git a/websocketpp/processors/processor.hpp b/websocketpp/processors/processor.hpp index fa8837d67..d06d88f9a 100644 --- a/websocketpp/processors/processor.hpp +++ b/websocketpp/processors/processor.hpp @@ -128,9 +128,7 @@ int get_websocket_version(request_type& r) { // // // handle msg; // } -// } -// -// +// } template class processor { @@ -186,12 +184,14 @@ class processor { /** * @param req The request to process * + * @param subprotocol The subprotocol in use + * * @param res The response to store the processed response in * * @return An error code, 0 on success, non-zero for other errors */ - virtual lib::error_code process_handshake(const request_type& req, - response_type& res) const = 0; + virtual lib::error_code process_handshake(const request_type& req, const + std::string & subprotocol, response_type& res) const = 0; /// Fill in an HTTP request for an outgoing connection handshake /** @@ -200,7 +200,7 @@ class processor { * @return An error code, 0 on success, non-zero for other errors */ virtual lib::error_code client_handshake_request(request_type& req, - uri_ptr uri) const = 0; + uri_ptr uri, const std::vector & subprotocols) const = 0; /// Validate the server's response to an outgoing handshake request /** @@ -220,6 +220,19 @@ class processor { virtual const std::string& get_origin(const request_type& request) const = 0; + /// Extracts requested subprotocols from a handshake request + /** + * Extracts a list of all subprotocols that the client has requested in the + * given opening handshake request. + * + * @param req The request to extract from + * + * @param subprotocol_list A reference to a vector of strings to store the + * results in. + */ + virtual lib::error_code extract_subprotocols(const request_type & req, + std::vector & subprotocol_list) = 0; + /// Extracts client uri from a handshake request virtual uri_ptr get_uri(const request_type& request) const = 0; diff --git a/websocketpp/transport/asio/base.hpp b/websocketpp/transport/asio/base.hpp index d1802a366..1c618d5c6 100644 --- a/websocketpp/transport/asio/base.hpp +++ b/websocketpp/transport/asio/base.hpp @@ -56,7 +56,13 @@ enum value { invalid_num_bytes, /// there was an error in the underlying transport library - pass_through + pass_through, + + /// The connection to the requested proxy server failed + proxy_failed, + + /// Invalid Proxy URI + proxy_invalid }; class category : public lib::error_category { @@ -73,6 +79,10 @@ class category : public lib::error_category { return "async_read_at_least call requested more bytes than buffer can store"; case error::pass_through: return "Underlying Transport Error"; + case error::proxy_failed: + return "Proxy connection failed"; + case error::proxy_invalid: + return "Invalid proxy URI"; default: return "Unknown"; } diff --git a/websocketpp/transport/asio/connection.hpp b/websocketpp/transport/asio/connection.hpp index 6773e34f2..b21bec765 100644 --- a/websocketpp/transport/asio/connection.hpp +++ b/websocketpp/transport/asio/connection.hpp @@ -35,6 +35,9 @@ #include #include +#include +#include + #include #include @@ -57,7 +60,7 @@ template class connection : public config::socket_type::socket_con_type { public: /// Type of this connection transport component - typedef connection type; + typedef connection type; /// Type of a shared pointer to this connection transport component typedef lib::shared_ptr ptr; @@ -69,139 +72,348 @@ class connection : public config::socket_type::socket_con_type { 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 boost::asio::io_service* io_service_ptr; + typedef boost::asio::io_service* io_service_ptr; - // 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) - { + // 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"); - } - - bool is_secure() const { - return socket_con_type::is_secure(); - } - - /// Finish constructing the transport - /** - * init_asio is called once immediately after construction to initialize - * boost::asio components to the io_service - * - * TODO: this method is not protected because the endpoint needs to call it. - * need to figure out if there is a way to friend the endpoint safely across - * different compilers. - */ + } + + bool is_secure() const { + return socket_con_type::is_secure(); + } + + /// Finish constructing the transport + /** + * init_asio is called once immediately after construction to initialize + * boost::asio components to the io_service + * + * TODO: this method is not protected because the endpoint needs to call it. + * need to figure out if there is a way to friend the endpoint safely across + * different compilers. + */ void init_asio (io_service_ptr io_service) { - // do we need to store or use the io_service at this level? - m_io_service = io_service; + // do we need to store or use the io_service at this level? + m_io_service = io_service; //m_strand.reset(new boost::asio::strand(*io_service)); - - socket_con_type::init_asio(io_service, m_is_server); + + socket_con_type::init_asio(io_service, m_is_server); } void set_tcp_init_handler(tcp_init_handler h) { m_tcp_init_handler = h; } + + void set_proxy(const std::string & proxy) { + m_proxy = proxy; + m_proxy_data.reset(new proxy_data()); + } + 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? + } + } + + const std::string & 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; } + + /// 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) + */ + lib::error_code proxy_init(const std::string & 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(); + } + protected: /// Initialize transport for reading - /** - * init_asio is called once immediately after construction to initialize - * boost::asio components to the io_service - */ + /** + * init_asio is called once immediately after construction to initialize + * boost::asio components to the io_service + */ void init(init_handler callback) { m_alog.write(log::alevel::devel,"asio connection init"); - - socket_con_type::init( - lib::bind( - &type::handle_init, - this, - callback, - lib::placeholders::_1 - ) - ); - } - - void handle_init(init_handler callback, const lib::error_code& ec) { - if (m_tcp_init_handler) { - m_tcp_init_handler(m_connection_hdl); - } - - callback(ec); - } + + socket_con_type::init( + lib::bind( + &type::handle_init, + this, + callback, + lib::placeholders::_1 + ) + ); + } + + void handle_init(init_handler callback, const lib::error_code& ec) { + if (m_tcp_init_handler) { + m_tcp_init_handler(m_connection_hdl); + } + + if (ec) { + callback(ec); + } + + // If no error, and we are an insecure connection with a proxy set + // issue a proxy connect. + if (!m_proxy.empty()) { + proxy_write(callback); + } else { + callback(ec); + } + } + + void proxy_write(init_handler callback) { + 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(boost::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); + + boost::asio::async_write( + socket_con_type::get_next_layer(), + m_bufs, + lib::bind( + &type::handle_proxy_write, + this, + callback, + lib::placeholders::_1 + ) + ); + } + + void handle_proxy_write(init_handler callback, const + boost::system::error_code& ec) + { + m_bufs.clear(); + + if (ec) { + m_elog.write(log::elevel::info, + "asio handle_proxy_write error: "+ec.message()); + callback(make_error_code(error::pass_through)); + } else { + proxy_read(callback); + } + } + + void proxy_read(init_handler callback) { + if (!m_proxy_data) { + m_elog.write(log::elevel::library, + "assertion failed: !m_proxy_data in asio::connection::proxy_read"); + callback(make_error_code(error::general)); + return; + } + + boost::asio::async_read_until( + socket_con_type::get_socket(), + m_proxy_data->read_buf, + "\r\n\r\n", + lib::bind( + &type::handle_proxy_read, + this, + callback, + lib::placeholders::_1, + lib::placeholders::_2 + ) + ); + } + + void handle_proxy_read(init_handler callback, const + boost::system::error_code& ec, size_t bytes_transferred) + { + 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 programatically 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(); + + // call the original init handler back. + callback(lib::error_code()); + } + } /// 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) - { - std::stringstream s; - s << "asio async_read_at_least: " << num_bytes; - m_alog.write(log::alevel::devel,s.str()); - - if (num_bytes > len) { + /** + * + * + */ + 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()); + } + + 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; - } - - boost::asio::async_read( - socket_con_type::get_socket(), - boost::asio::buffer(buf,len), - boost::asio::transfer_at_least(num_bytes), - lib::bind( - &type::handle_async_read, - this, - handler, - lib::placeholders::_1, - lib::placeholders::_2 - ) - ); - } + handler(make_error_code(transport::error::invalid_num_bytes), + size_t(0)); + return; + } + + boost::asio::async_read( + socket_con_type::get_socket(), + boost::asio::buffer(buf,len), + boost::asio::transfer_at_least(num_bytes), + lib::bind( + &type::handle_async_read, + this, + handler, + lib::placeholders::_1, + lib::placeholders::_2 + ) + ); + } void handle_async_read(read_handler handler, const boost::system::error_code& ec, size_t bytes_transferred) { - // TODO: translate this better - if (ec) { + if (!ec) { + handler(lib::error_code(), bytes_transferred); + return; + } + + // translate boost error codes into more lib::error_codes + if (ec == boost::asio::error::eof) { + handler(make_error_code(transport::error::eof), + 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::pass_through" - << "Original Error: " << ec << " (" << ec.message() << ")"; - m_elog.write(log::elevel::devel,s.str()); - handler(make_error_code(transport::error::pass_through), - bytes_transferred); - } else { - handler(lib::error_code(), bytes_transferred); - } + << ", Original Error: " << ec << " (" << ec.message() << ")"; + m_elog.write(log::elevel::info,s.str()); + handler(make_error_code(transport::error::pass_through), + bytes_transferred); + } } void async_write(const char* buf, size_t len, write_handler handler) { m_bufs.push_back(boost::asio::buffer(buf,len)); - + boost::asio::async_write( - socket_con_type::get_socket(), + socket_con_type::get_socket(), m_bufs, - lib::bind( - &type::handle_async_write, - this, - handler, - lib::placeholders::_1 - ) - ); + lib::bind( + &type::handle_async_write, + this, + handler, + lib::placeholders::_1 + ) + ); } void async_write(const std::vector& bufs, write_handler handler) { @@ -211,28 +423,28 @@ class connection : public config::socket_type::socket_con_type { m_bufs.push_back(boost::asio::buffer((*it).buf,(*it).len)); } - boost::asio::async_write( - socket_con_type::get_socket(), - m_bufs, - lib::bind( - &type::handle_async_write, - this, - handler, - lib::placeholders::_1 - ) - ); + boost::asio::async_write( + socket_con_type::get_socket(), + m_bufs, + lib::bind( + &type::handle_async_write, + this, + handler, + lib::placeholders::_1 + ) + ); } void handle_async_write(write_handler handler, const - boost::system::error_code& ec) + boost::system::error_code& ec) { m_bufs.clear(); - // TODO: translate this better - if (ec) { - handler(make_error_code(error::pass_through)); - } else { - handler(lib::error_code()); - } + // TODO: translate this better + if (ec) { + handler(make_error_code(error::pass_through)); + } else { + handler(lib::error_code()); + } } /// Set Connection Handle @@ -295,16 +507,22 @@ class connection : public config::socket_type::socket_con_type { } } private: - // static settings - const bool m_is_server; + // static settings + const bool m_is_server; alog_type& m_alog; elog_type& m_elog; - - // dynamic settings - - // transport state - - // transport resources + + struct proxy_data { + request_type req; + response_type res; + std::string write_buf; + boost::asio::streambuf read_buf; + }; + + std::string m_proxy; + lib::shared_ptr m_proxy_data; + + // transport resources io_service_ptr m_io_service; connection_hdl m_connection_hdl; std::vector m_bufs; diff --git a/websocketpp/transport/asio/endpoint.hpp b/websocketpp/transport/asio/endpoint.hpp index 21f09d957..1425187e0 100644 --- a/websocketpp/transport/asio/endpoint.hpp +++ b/websocketpp/transport/asio/endpoint.hpp @@ -92,6 +92,7 @@ class endpoint : public config::socket_type { ~endpoint() { // clean up our io_service if we were initialized with an internal one. + m_acceptor.reset(); if (m_state != UNINITIALIZED && !m_external_io_service) { delete m_io_service; } @@ -241,8 +242,8 @@ class endpoint : public config::socket_type { } /// wraps the run method of the internal io_service object - void run() { - m_io_service->run(); + std::size_t run() { + return m_io_service->run(); } /// wraps the stop method of the internal io_service object @@ -250,6 +251,16 @@ class endpoint : public config::socket_type { m_io_service->stop(); } + /// wraps the poll method of the internal io_service object + std::size_t poll() { + return m_io_service->poll(); + } + + /// wraps the poll_one method of the internal io_service object + std::size_t poll_one() { + return m_io_service->poll_one(); + } + /// wraps the reset method of the internal io_service object void reset() { m_io_service->reset(); @@ -353,20 +364,33 @@ class endpoint : public config::socket_type { m_resolver.reset(new boost::asio::ip::tcp::resolver(*m_io_service)); } - tcp::resolver::query query(u->get_host(),u->get_port_str()); - /*tcp::resolver::iterator iterator = resolver.resolve(query); + std::string proxy = tcon->get_proxy(); + std::string host; + std::string port; - boost::asio::async_connect( - tcon->get_raw_socket(), - iterator, - lib::bind( - &type::handle_connect, - this, // shared from this? - tcon, - cb, - lib::placeholders::_1 - ) - );*/ + if (proxy.empty()) { + host = u->get_host(); + port = u->get_port_str(); + } else { + try { + lib::error_code ec; + + uri_ptr pu(new uri(proxy)); + ec = tcon->proxy_init(u->get_host_port()); + if (ec) { + cb(tcon->get_handle(),ec); + return; + } + + host = pu->get_host(); + port = pu->get_port_str(); + } catch (uri_exception) { + cb(tcon->get_handle(),make_error_code(error::proxy_invalid)); + return; + } + } + + tcp::resolver::query query(host,port); m_resolver->async_resolve( query, @@ -391,7 +415,7 @@ class endpoint : public config::socket_type { std::stringstream s; s << "asio async_resolve error::pass_through: " << "Original Error: " << ec << " (" << ec.message() << ")"; - m_elog->write(log::elevel::devel,s.str()); + m_elog->write(log::elevel::info,s.str()); callback(tcon->get_handle(),make_error_code(error::pass_through)); return; } @@ -418,7 +442,7 @@ class endpoint : public config::socket_type { std::stringstream s; s << "asio async_connect error::pass_through: " << "Original Error: " << ec << " (" << ec.message() << ")"; - m_elog->write(log::elevel::devel,s.str()); + m_elog->write(log::elevel::info,s.str()); callback(tcon->get_handle(),make_error_code(error::pass_through)); return; } diff --git a/websocketpp/transport/asio/security/base.hpp b/websocketpp/transport/asio/security/base.hpp index 631633661..158d09ead 100644 --- a/websocketpp/transport/asio/security/base.hpp +++ b/websocketpp/transport/asio/security/base.hpp @@ -50,59 +50,59 @@ namespace socket { */ namespace error { - enum value { - /// Catch-all error for security policy errors that don't fit in other - /// categories - security = 1, - + enum value { + /// Catch-all error for security policy errors that don't fit in other + /// categories + security = 1, + /// Catch-all error for socket component errors that don't fit in other /// categories socket, - /// A function was called in a state that it was illegal to do so. - invalid_state, - - /// The application was prompted to provide a TLS context and it was - /// empty or otherwise invalid - invalid_tls_context, - - /// TLS Handshake Timeout - tls_handshake_timeout, - - /// pass_through from underlying library - pass_through, + /// A function was called in a state that it was illegal to do so. + invalid_state, + + /// The application was prompted to provide a TLS context and it was + /// empty or otherwise invalid + invalid_tls_context, + + /// TLS Handshake Timeout + tls_handshake_timeout, + + /// pass_through from underlying library + pass_through, /// Required tls_init handler not present missing_tls_init_handler - }; + }; } // namespace error class socket_category : public lib::error_category { public: - const char *name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { - return "websocketpp.transport.asio.socket"; - } - - std::string message(int value) const { - switch(value) { - case error::security: - return "Security policy error"; - case error::socket: - return "Socket component error"; - case error::invalid_state: - return "Invalid state"; - case error::invalid_tls_context: - return "Invalid or empty TLS context supplied"; - case error::tls_handshake_timeout: - return "TLS handshake timed out"; - case error::pass_through: - return "Pass through from underlying library"; + const char *name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { + return "websocketpp.transport.asio.socket"; + } + + std::string message(int value) const { + switch(value) { + case error::security: + return "Security policy error"; + case error::socket: + return "Socket component error"; + case error::invalid_state: + return "Invalid state"; + case error::invalid_tls_context: + return "Invalid or empty TLS context supplied"; + case error::tls_handshake_timeout: + return "TLS handshake timed out"; + case error::pass_through: + return "Pass through from underlying library"; case error::missing_tls_init_handler: - return "Required tls_init handler not present."; - default: - return "Unknown"; - } - } + return "Required tls_init handler not present."; + default: + return "Unknown"; + } + } }; inline const lib::error_category& get_socket_category() { @@ -111,7 +111,7 @@ inline const lib::error_category& get_socket_category() { } inline lib::error_code make_error(error::value e) { - return lib::error_code(static_cast(e), get_socket_category()); + return lib::error_code(static_cast(e), get_socket_category()); } typedef lib::function init_handler; diff --git a/websocketpp/transport/asio/security/none.hpp b/websocketpp/transport/asio/security/none.hpp index 0e04a5ab0..5e160c594 100644 --- a/websocketpp/transport/asio/security/none.hpp +++ b/websocketpp/transport/asio/security/none.hpp @@ -51,28 +51,28 @@ typedef lib::function class connection { public: /// Type of this connection socket component - typedef connection type; + typedef connection type; /// Type of a shared pointer to this connection socket component typedef lib::shared_ptr ptr; /// Type of a pointer to the ASIO io_service being used - typedef boost::asio::io_service* io_service_ptr; + typedef boost::asio::io_service* io_service_ptr; /// Type of a shared pointer to the socket being used. typedef lib::shared_ptr socket_ptr; - - explicit connection() : m_state(UNINITIALIZED) { - //std::cout << "transport::asio::basic_socket::connection constructor" + + explicit connection() : m_state(UNINITIALIZED) { + //std::cout << "transport::asio::basic_socket::connection constructor" // << std::endl; - } - + } + /// Check whether or not this connection is secure /** * @return Wether or not this connection is secure */ - bool is_secure() const { - return false; - } - + bool is_secure() const { + return false; + } + /// Set the socket initialization handler /** * The socket initialization handler is called after the socket object is @@ -85,59 +85,95 @@ class connection { m_socket_init_handler = h; } - /// Retrieve a pointer to the underlying socket - /** - * This is used internally. It can also be used to set socket options, etc - */ + /// Retrieve a pointer to the underlying socket + /** + * This is used internally. It can also be used to set socket options, etc + */ boost::asio::ip::tcp::socket& get_socket() { - return *m_socket; + return *m_socket; + } + + /// Retrieve a pointer to the underlying socket + /** + * This is used internally. + */ + boost::asio::ip::tcp::socket& get_next_layer() { + return *m_socket; } /// Retrieve a pointer to the underlying socket - /** - * This is used internally. It can also be used to set socket options, etc - */ + /** + * This is used internally. It can also be used to set socket options, etc + */ boost::asio::ip::tcp::socket& get_raw_socket() { - return *m_socket; + return *m_socket; + } + + /// 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(lib::error_code &ec) const { + std::stringstream s; + + boost::system::error_code bec; + boost::asio::ip::tcp::endpoint ep = m_socket->remote_endpoint(bec); + + if (bec) { + ec = error::make_error_code(error::pass_through); + s << "Error getting remote endpoint: " << bec + << " (" << bec.message() << ")"; + return s.str(); + } else { + ec = lib::error_code(); + s << ep; + return s.str(); + } } protected: - /// Perform one time initializations - /** - * init_asio is called once immediately after construction to initialize - * boost::asio components to the io_service - * - * @param service A pointer to the endpoint's io_service - * @param strand A shared pointer to the connection's asio strand - * @param is_server Whether or not the endpoint is a server or not. - */ + /// Perform one time initializations + /** + * init_asio is called once immediately after construction to initialize + * boost::asio components to the io_service + * + * @param service A pointer to the endpoint's io_service + * @param strand A shared pointer to the connection's asio strand + * @param is_server Whether or not the endpoint is a server or not. + */ lib::error_code init_asio (io_service_ptr service, bool is_server) { - if (m_state != UNINITIALIZED) { - return socket::make_error(socket::error::invalid_state); - } - - m_socket.reset(new boost::asio::ip::tcp::socket(*service)); - - m_state = READY; - - return lib::error_code(); + if (m_state != UNINITIALIZED) { + return socket::make_error(socket::error::invalid_state); + } + + m_socket.reset(new boost::asio::ip::tcp::socket(*service)); + + m_state = READY; + + return lib::error_code(); } - - /// Initialize security policy for reading - void init(init_handler callback) { - if (m_state != READY) { - callback(socket::make_error(socket::error::invalid_state)); - return; - } - + + /// Initialize security policy for reading + void init(init_handler callback) { + if (m_state != READY) { + callback(socket::make_error(socket::error::invalid_state)); + return; + } + if (m_socket_init_handler) { m_socket_init_handler(m_hdl,*m_socket); } - - m_state = READING; - - callback(lib::error_code()); - } - + + m_state = READING; + + callback(lib::error_code()); + } + /// Sets the connection handle /** * The connection handle is passed to any handlers to identify the @@ -156,14 +192,14 @@ class connection { // TODO: handle errors } private: - enum state { - UNINITIALIZED = 0, - READY = 1, - READING = 2 - }; - - socket_ptr m_socket; - state m_state; + enum state { + UNINITIALIZED = 0, + READY = 1, + READING = 2 + }; + + socket_ptr m_socket; + state m_state; connection_hdl m_hdl; socket_init_handler m_socket_init_handler; @@ -185,10 +221,7 @@ class endpoint { /// component. typedef socket_con_type::ptr socket_con_ptr; - explicit endpoint() { - std::cout << "transport::asio::basic_socket::endpoint constructor" - << std::endl; - } + explicit endpoint() {} /// Checks whether the endpoint creates secure connections /** diff --git a/websocketpp/transport/asio/security/tls.hpp b/websocketpp/transport/asio/security/tls.hpp index c6b05b47f..e4951e42e 100644 --- a/websocketpp/transport/asio/security/tls.hpp +++ b/websocketpp/transport/asio/security/tls.hpp @@ -58,51 +58,59 @@ typedef lib::function(connection_hdl) class connection { public: /// Type of this connection socket component - typedef connection type; - /// Type of a shared pointer to this connection socket component + typedef connection type; + /// Type of a shared pointer to this connection socket component typedef lib::shared_ptr ptr; /// Type of the ASIO socket being used - typedef boost::asio::ssl::stream socket_type; + typedef boost::asio::ssl::stream socket_type; /// Type of a shared pointer to the ASIO socket being used typedef lib::shared_ptr socket_ptr; /// Type of a pointer to the ASIO io_service being used - typedef boost::asio::io_service* io_service_ptr; + typedef boost::asio::io_service* io_service_ptr; /// Type of a shared pointer to the ASIO TLS context being used - typedef lib::shared_ptr context_ptr; + typedef lib::shared_ptr context_ptr; /// Type of a shared pointer to the ASIO timer being used - typedef lib::shared_ptr timer_ptr; - - typedef boost::system::error_code boost_error; - - explicit connection() { - //std::cout << "transport::asio::tls_socket::connection constructor" + typedef lib::shared_ptr timer_ptr; + + typedef boost::system::error_code boost_error; + + explicit connection() { + //std::cout << "transport::asio::tls_socket::connection constructor" // << std::endl; - } - + } + /// Check whether or not this connection is secure /** * @return Wether or not this connection is secure */ - bool is_secure() const { - return true; - } - - /// Retrieve a pointer to the underlying socket - /** - * This is used internally. It can also be used to set socket options, etc - */ - socket_type::lowest_layer_type& get_raw_socket() { - return m_socket->lowest_layer(); - } - - /// Retrieve a pointer to the wrapped socket - /** - * This is used internally. - */ - socket_type& get_socket() { - return *m_socket; - } + bool is_secure() const { + return true; + } + + /// Retrieve a pointer to the underlying socket + /** + * This is used internally. It can also be used to set socket options, etc + */ + socket_type::lowest_layer_type& get_raw_socket() { + return m_socket->lowest_layer(); + } + + /// Retrieve a pointer to the layer below the ssl stream + /** + * This is used internally. + */ + socket_type::next_layer_type& get_next_layer() { + return m_socket->next_layer(); + } + + /// Retrieve a pointer to the wrapped socket + /** + * This is used internally. + */ + socket_type& get_socket() { + return *m_socket; + } /// Set the socket initialization handler /** @@ -128,51 +136,77 @@ class connection { void set_tls_init_handler(tls_init_handler h) { m_tls_init_handler = h; } + + /// 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(lib::error_code &ec) const { + std::stringstream s; + + boost::system::error_code bec; + boost::asio::ip::tcp::endpoint ep = m_socket->lowest_layer().remote_endpoint(bec); + + if (bec) { + ec = error::make_error_code(error::pass_through); + s << "Error getting remote endpoint: " << bec + << " (" << bec.message() << ")"; + return s.str(); + } else { + ec = lib::error_code(); + s << ep; + return s.str(); + } + } protected: - /// Perform one time initializations - /** - * init_asio is called once immediately after construction to initialize - * boost::asio components to the io_service - * - * @param service A pointer to the endpoint's io_service - * @param strand A shared pointer to the connection's asio strand - * @param is_server Whether or not the endpoint is a server or not. - */ + /// Perform one time initializations + /** + * init_asio is called once immediately after construction to initialize + * boost::asio components to the io_service + * + * @param service A pointer to the endpoint's io_service + * @param strand A shared pointer to the connection's asio strand + * @param is_server Whether or not the endpoint is a server or not. + */ lib::error_code init_asio (io_service_ptr service, bool is_server) { - std::cout << "transport::security::tls_socket::init_asio" << std::endl; if (!m_tls_init_handler) { - std::cout << "missing_tls_init_handler" << std::endl; - return socket::make_error(socket::error::missing_tls_init_handler); + return socket::make_error(socket::error::missing_tls_init_handler); } m_context = m_tls_init_handler(m_hdl); - - if (!m_context) { - return socket::make_error(socket::error::invalid_tls_context); - } - - m_socket.reset(new socket_type(*service,*m_context)); - - m_timer.reset(new boost::asio::deadline_timer( - *service, - boost::posix_time::seconds(0)) - ); - - m_io_service = service; - m_is_server = is_server; - - return lib::error_code(); + + if (!m_context) { + return socket::make_error(socket::error::invalid_tls_context); + } + + m_socket.reset(new socket_type(*service,*m_context)); + + m_timer.reset(new boost::asio::deadline_timer( + *service, + boost::posix_time::seconds(0)) + ); + + m_io_service = service; + m_is_server = is_server; + + return lib::error_code(); } - - /// Initialize security policy for reading - void init(init_handler callback) { - if (m_socket_init_handler) { + + /// Initialize security policy for reading + void init(init_handler callback) { + if (m_socket_init_handler) { m_socket_init_handler(m_hdl,get_raw_socket()); } - // register timeout - m_timer->expires_from_now(boost::posix_time::milliseconds(5000)); - // TEMP - m_timer->async_wait( + // register timeout + m_timer->expires_from_now(boost::posix_time::milliseconds(5000)); + // TEMP + m_timer->async_wait( lib::bind( &type::handle_timeout, this, @@ -180,19 +214,19 @@ class connection { lib::placeholders::_1 ) ); - - // TLS handshake - m_socket->async_handshake( - get_handshake_type(), - lib::bind( - &type::handle_init, - this, - callback, - lib::placeholders::_1 - ) - ); - } - + + // TLS handshake + m_socket->async_handshake( + get_handshake_type(), + lib::bind( + &type::handle_init, + this, + callback, + lib::placeholders::_1 + ) + ); + } + /// Sets the connection handle /** * The connection handle is passed to any handlers to identify the @@ -204,39 +238,39 @@ class connection { m_hdl = hdl; } - void handle_timeout(init_handler callback, const - boost::system::error_code& error) - { - if (error) { - if (error.value() == boost::asio::error::operation_aborted) { - // The timer was cancelled because the handshake succeeded. - return; - } - - // Some other ASIO error, pass it through - // TODO: make this error pass through better - callback(socket::make_error(socket::error::pass_through)); - return; - } - - callback(socket::make_error(socket::error::tls_handshake_timeout)); - } - - void handle_init(init_handler callback, const - boost::system::error_code& error) - { - /// stop waiting for our handshake timer. - m_timer->cancel(); - - if (error) { - // TODO: make this error pass through better - callback(socket::make_error(socket::error::pass_through)); - return; - } - - callback(lib::error_code()); - } - + void handle_timeout(init_handler callback, const + boost::system::error_code& error) + { + if (error) { + if (error.value() == boost::asio::error::operation_aborted) { + // The timer was cancelled because the handshake succeeded. + return; + } + + // Some other ASIO error, pass it through + // TODO: make this error pass through better + callback(socket::make_error(socket::error::pass_through)); + return; + } + + callback(socket::make_error(socket::error::tls_handshake_timeout)); + } + + void handle_init(init_handler callback, const + boost::system::error_code& error) + { + /// stop waiting for our handshake timer. + m_timer->cancel(); + + if (error) { + // TODO: make this error pass through better + callback(socket::make_error(socket::error::pass_through)); + return; + } + + callback(lib::error_code()); + } + void shutdown() { boost::system::error_code ec; m_socket->shutdown(ec); @@ -244,19 +278,19 @@ class connection { // TODO: error handling } private: - socket_type::handshake_type get_handshake_type() { + socket_type::handshake_type get_handshake_type() { if (m_is_server) { return boost::asio::ssl::stream_base::server; } else { return boost::asio::ssl::stream_base::client; } } - - io_service_ptr m_io_service; - context_ptr m_context; - socket_ptr m_socket; - timer_ptr m_timer; - bool m_is_server; + + io_service_ptr m_io_service; + context_ptr m_context; + socket_ptr m_socket; + timer_ptr m_timer; + bool m_is_server; connection_hdl m_hdl; socket_init_handler m_socket_init_handler; @@ -279,10 +313,7 @@ class endpoint { /// component. typedef socket_con_type::ptr socket_con_ptr; - explicit endpoint() { - std::cout << "transport::asio::tls_socket::endpoint constructor" - << std::endl; - } + explicit endpoint() {} /// Checks whether the endpoint creates secure connections /** @@ -323,7 +354,6 @@ class endpoint { * the socket component of the connection. */ void init(socket_con_ptr scon) { - std::cout << "transport::asio::tls_socket::init" << std::endl; scon->set_socket_init_handler(m_socket_init_handler); scon->set_tls_init_handler(m_tls_init_handler); } diff --git a/websocketpp/transport/base/connection.hpp b/websocketpp/transport/base/connection.hpp index 1255c2573..a84064d5a 100644 --- a/websocketpp/transport/base/connection.hpp +++ b/websocketpp/transport/base/connection.hpp @@ -101,7 +101,10 @@ enum value { operation_aborted, /// Operation not supported - operation_not_supported + operation_not_supported, + + /// End of file + eof }; class category : public lib::error_category { @@ -124,6 +127,8 @@ class category : public lib::error_category { return "The operation was aborted"; case operation_not_supported: return "The operation is not supported by this transport"; + case eof: + return "End of File"; default: return "Unknown"; } diff --git a/websocketpp/transport/iostream/connection.hpp b/websocketpp/transport/iostream/connection.hpp index dedc32c36..6bb94426d 100644 --- a/websocketpp/transport/iostream/connection.hpp +++ b/websocketpp/transport/iostream/connection.hpp @@ -112,6 +112,22 @@ class connection { return in; } + /// Manual input supply + /** + * Copies bytes from buf into WebSocket++'s input buffers. Bytes will be + * copied from the supplied buffer to fullfull any pending library reads. It + * will return the number of bytes successfully processed. If there are no + * pending reads readsome will return immediately. Not all of the bytes may + * be able to be read in one call + */ + size_t readsome(const char *buf, size_t len) { + // this serializes calls to external read. + scoped_lock_type lock(m_read_mutex); + + return this->readsome_impl(buf,len); + } + + /// Tests whether or not the underlying transport is secure /** * iostream transport will return false always because it has no information @@ -304,20 +320,20 @@ class connection { void read(std::istream &in) { m_alog.write(log::alevel::devel,"iostream_con read"); - while (in.good()) { - if (!m_reading) { + while (in.good()) { + if (!m_reading) { m_elog.write(log::elevel::devel,"write while not reading"); break; - } - - in.read(m_buf+m_cursor,m_len-m_cursor); + } + + in.read(m_buf+m_cursor,static_cast(m_len-m_cursor)); if (in.gcount() == 0) { m_elog.write(log::elevel::devel,"read zero bytes"); break; } - m_cursor += in.gcount(); + m_cursor += static_cast(in.gcount()); // TODO: error handling if (in.bad()) { @@ -332,6 +348,28 @@ class connection { } } + size_t readsome_impl(const char * buf, size_t len) { + m_alog.write(log::alevel::devel,"iostream_con readsome"); + + if (!m_reading) { + m_elog.write(log::elevel::devel,"write while not reading"); + return 0; + } + + size_t bytes_to_copy = std::min(len,m_len-m_cursor); + + std::copy(buf,buf+bytes_to_copy,m_buf); + + m_cursor += bytes_to_copy; + + if (m_cursor >= m_bytes_needed) { + m_reading = false; + m_read_handler(lib::error_code(), m_cursor); + } + + return bytes_to_copy; + } + // Read space (Protected by m_read_mutex) char* m_buf; size_t m_len; diff --git a/websocketpp/transport/iostream/endpoint.hpp b/websocketpp/transport/iostream/endpoint.hpp index 29ce54af3..6eab5c9f5 100644 --- a/websocketpp/transport/iostream/endpoint.hpp +++ b/websocketpp/transport/iostream/endpoint.hpp @@ -65,7 +65,7 @@ class endpoint { // generate and manage our own io_service explicit endpoint() : output_stream(NULL) { - std::cout << "transport::iostream::endpoint constructor" << std::endl; + //std::cout << "transport::iostream::endpoint constructor" << std::endl; } void register_ostream(std::ostream* o) { diff --git a/websocketpp/uri.hpp b/websocketpp/uri.hpp index 7e3a04160..c14dbcebd 100644 --- a/websocketpp/uri.hpp +++ b/websocketpp/uri.hpp @@ -61,6 +61,77 @@ static const uint16_t uri_default_secure_port = 443; class uri { public: explicit uri(const std::string& uri) { + // TODO: should this split resource into path/query? + lib::cmatch matches; + const lib::regex expression("(http|ws|wss)://([^/:\\[]+|\\[[0-9a-fA-F:.]+\\])(:\\d{1,5})?(/[^#]*)?"); + + if (lib::regex_match(uri.c_str(), matches, expression)) { + m_secure = (matches[1] == "wss"); + m_host = matches[2]; + + // strip brackets from IPv6 literal URIs + if (m_host[0] == '[') { + m_host = m_host.substr(1,m_host.size()-2); + } + + std::string port(matches[3]); + + if (port != "") { + // strip off the : + // this could probably be done with a better regex. + port = port.substr(1); + } + + m_port = get_port_from_string(port); + + m_resource = matches[4]; + + if (m_resource == "") { + m_resource = "/"; + } + + return; + } + + throw websocketpp::uri_exception("Error parsing WebSocket URI"); + } + + /*explicit uri(const std::string& uri) { + // test for ws or wss + std::string::const_iterator it; + std::string::const_iterator temp; + + it = uri.begin(); + + if (std::equal(it,it+6,"wss://")) { + m_secure = true; + it += 6; + } else if (std::equal(it,it+5,"ws://")) { + m_secure = false; + it += 5; + } else { + // error + } + + // extract host. + // either a host string + // an IPv4 address + // or an IPv6 address + if (*it == '[') { + ++it; + // IPv6 literal + // extract IPv6 digits until ] + temp = std::find(it,uri.end(),']'); + if (temp == uri.end()) { + // error + } else { + // validate IPv6 literal parts + // can contain numbers, a-f and A-F + } + } else { + // IPv4 or hostname + } + // TODO: should this split resource into path/query? lib::cmatch matches; const lib::regex expression("(ws|wss)://([^/:\\[]+|\\[[0-9a-fA-F:.]+\\])(:\\d{1,5})?(/[^#]*)?"); @@ -95,7 +166,7 @@ class uri { throw websocketpp::uri_exception("Error parsing WebSocket URI"); - } + }*/ uri(bool secure, const std::string& host, uint16_t port, const std::string& resource) : m_host(host) @@ -184,7 +255,7 @@ class uri { return (m_secure ? uri_default_secure_port : uri_default_port); } - unsigned int t_port = atoi(port.c_str()); + unsigned int t_port = static_cast(atoi(port.c_str())); if (t_port > 65535) { throw websocketpp::uri_exception("Port must be less than 65535"); diff --git a/websocketpp/utf8_validator.hpp b/websocketpp/utf8_validator.hpp index 43cbea0de..58b339162 100644 --- a/websocketpp/utf8_validator.hpp +++ b/websocketpp/utf8_validator.hpp @@ -55,7 +55,7 @@ class validator { template bool decode (iterator_type b, iterator_type e) { for (iterator_type i = b; i != e; i++) { - if (utf8_validator::decode(&m_state,&m_codepoint,*i) == UTF8_REJECT) { + if (utf8_validator::decode(&m_state,&m_codepoint,static_cast(*i)) == UTF8_REJECT) { return false; } } diff --git a/websocketpp/utilities.hpp b/websocketpp/utilities.hpp index 15687954f..991ab3dcc 100644 --- a/websocketpp/utilities.hpp +++ b/websocketpp/utilities.hpp @@ -68,10 +68,8 @@ typename T::const_iterator ci_find_substr(const T& str1, -/// Host to network long long -//uint64_t htonll(uint64_t src); -/// Network to host long long -//uint64_t ntohll(uint64_t src); +std::string string_replace_all(std::string subject, const std::string& search, + const std::string& replace); /// Convert std::string to ascii printed string of hex digits std::string to_hex(const std::string& input); diff --git a/websocketpp/websocketpp.hpp b/websocketpp/websocketpp.hpp deleted file mode 100644 index ff964d708..000000000 --- a/websocketpp/websocketpp.hpp +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2012, 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_HPP -#define WEBSOCKETPP_HPP - -/* - -Endpoint -- container for connections -- Store and forward default connection settings - -Connection -- Represents the state and functionality of a single WebSocket session starting - with the opening handshake and completing with the closing one. -- After a connection is created settings may be applied that will be used for - this connection. -- Once setup is complete a start method is run and the connection enters its - event loop. The connection requests bytes from its transport, then runs those - bytes through the appropriate websocket frame processor, and calls handler - methods appropriate for the types of frames recieved. - - - - - -Policies: - -Concurrency - - - - - - -Concurrency Models -Single Thread Async (lock free) -- WS++ runs lock free (Access to endpoint and connection from non-WS++ threads is unsafe) -- All handlers and networking operations run in a single thread. -- Handlers can block each other and network operations -- Good for low traffic workflows where connections are independent and requests are short. - -Single Thread Async -- Same as lock free version except access to endpoint and connection from non-WS++ threads is safe -- Good for workflows where any long running handler job is deferred to a non-WS++ thread for processing. - -Thread Pool (lock free) -- WS++ runs lock free (access to endpoint and connection from non-WS++ threads is unsafe) -- Handlers and networking operations invoked by multiple threads. Individual connections are serialized. -- n handlers will block network operations (n=num_threads) -- Allows much better multi-core utilization, does not require end user syncronization as long as all work is performed inside handlers and handlers only reference their own connection. Handler local data must be syncronized. - -Thread pool -- Same as lock free version except access to endpoint and connection from non-WS++ threads is safe. - -Thread per connection -- - -io_service policies -- external vs internal -- per endpoint or per connection - - -message policies? -- Control Messages: --- Each connection should have a single control message permanently allocated -- Data Messages --- Dynamically allocate a new data message as needed. --- Re-usable pool of data messages per endpoint --- Re-usable pool of data messages per connection - - -*/ - - -#include -#include - -#include -#include - -#endif // WEBSOCKETPP_ENDPOINT_HPP \ No newline at end of file