diff --git a/COPYING b/COPYING index 0ea3913..7eeeb8f 100644 --- a/COPYING +++ b/COPYING @@ -1,3 +1,5 @@ +Main Library: + Copyright (c) 2013, Peter Thorson. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -21,3 +23,123 @@ 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. + +Bundled Libraries: + +****** Base 64 Library (base64/base64.hpp) ****** +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.cpp and base64.h + +Copyright (C) 2004-2008 René Nyffenegger + +This source code is provided 'as-is', without any express or implied +warranty. In no event will the author be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + +3. This notice may not be removed or altered from any source distribution. + +René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +****** SHA1 Library (sha1/sha1.hpp) ****** +sha1.hpp is a repackaging of the sha1.cpp and sha1.h files from the shallsha1 +library (http://code.google.com/p/smallsha1/) into a single header suitable for +use as a header only library. This conversion was done by Peter Thorson +(webmaster@zaphoyd.com) in 2013. All modifications to the code are redistributed +under the same license as the original, which is listed below. + + Copyright (c) 2011, Micael Hildenborg + 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 Micael Hildenborg 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 Micael Hildenborg ''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 Micael Hildenborg 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. + +****** MD5 Library (common/md5.hpp) ****** +md5.hpp is a reformulation of the md5.h and md5.c code from +http://www.opensource.apple.com/source/cups/cups-59/cups/md5.c to allow it to +function as a component of a header only library. This conversion was done by +Peter Thorson (webmaster@zaphoyd.com) in 2012 for the WebSocket++ project. The +changes are released under the same license as the original (listed below) + +Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +L. Peter Deutsch +ghost@aladdin.com + +****** UTF8 Validation logic (utf8_validation.hpp) ****** +utf8_validation.hpp is adapted from code originally written by Bjoern Hoehrmann +. See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for +details. + +The original license: + +Copyright (c) 2008-2009 Bjoern Hoehrmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Doxyfile b/Doxyfile index 98462d3..daf1cfe 100644 --- a/Doxyfile +++ b/Doxyfile @@ -33,7 +33,7 @@ PROJECT_NAME = "websocketpp" # if some version control system is used. -PROJECT_NUMBER = "0.3.0-alpha3" +PROJECT_NUMBER = "0.3.0-alpha4" # Using the PROJECT_BRIEF tag one can provide an optional one line description diff --git a/SConstruct b/SConstruct index 91cb89a..ae65f45 100644 --- a/SConstruct +++ b/SConstruct @@ -79,6 +79,8 @@ elif env['PLATFORM'] == 'posix': env.Append(CCFLAGS = ['-Wall']) #env['LINKFLAGS'] = '' elif env['PLATFORM'] == 'darwin': + if not os.environ.has_key('CXX'): + env['CXX'] = "clang++" if env.has_key('DEBUG'): env.Append(CCFLAGS = ['-g', '-O0']) else: @@ -98,11 +100,13 @@ else: if env['CXX'].startswith('g++'): #env.Append(CCFLAGS = ['-Wconversion']) env.Append(CCFLAGS = ['-Wcast-align']) + env.Append(CCFLAGS = ['-Wshadow']) elif env['CXX'].startswith('clang++'): #env.Append(CCFLAGS = ['-Wcast-align']) #env.Append(CCFLAGS = ['-Wglobal-constructors']) #env.Append(CCFLAGS = ['-Wconversion']) env.Append(CCFLAGS = ['-Wno-padded']) + env.Append(CCFLAGS = ['-Wshadow']) # Wpadded # Wsign-conversion @@ -142,6 +146,7 @@ if env_cpp11['CXX'].startswith('g++'): print "C++11 build environment is not supported on this version of G++" elif env_cpp11['CXX'].startswith('clang++'): print "C++11 build environment enabled" + env.Append(CXXFLANGS = ['-stdlib=libc++'],LINKFLAGS=['-stdlib=libc++']) 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 @@ -226,6 +231,12 @@ scratch_client = SConscript('#/examples/scratch_client/SConscript',variant_dir = scratch_server = SConscript('#/examples/scratch_server/SConscript',variant_dir = builddir + 'scratch_server',duplicate = 0) +# debug_client +debug_client = SConscript('#/examples/debug_client/SConscript',variant_dir = builddir + 'debug_client',duplicate = 0) + +# debug_server +debug_server = SConscript('#/examples/debug_server/SConscript',variant_dir = builddir + 'debug_server',duplicate = 0) + # subprotocol_server subprotocol_server = SConscript('#/examples/subprotocol_server/SConscript',variant_dir = builddir + 'subprotocol_server',duplicate = 0) diff --git a/changelog.md b/changelog.md index 24b9471..25d1014 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,89 @@ HEAD +- Feature: Adds `start_perpetual` and `stop_perpetual` methods to asio transport + These may be used to replace manually managed `asio::io_service::work` objects +- Feature: Allow setting pong and handshake timeouts at runtime. +- Feature: Allows changing the listen backlog queue length. +- Feature: Split tcp init into pre and post init. +- Feature: Adds URI method to extract query string from URI. Thank you Banaan + for code. #298 +- Feature: Adds a compile time switch to asio transport config to disable + certain multithreading features (some locks, asio strands) +- Feature: Adds the ability to pause reading on a connection. Paused connections + will not read more data from their socket, allowing TCP flow control to work + without blocking the main thread. +- Feature: Adds the ability to specify whether or not to use the `SO_REUSEADDR` + TCP socket option. The default for this value has been changed from `true` to + `false`. +- Feature: Adds the ability to specify a maximum message size. +- Feature: Adds `close::status::get_string(...)` method to look up a human + readable string given a close code value. +- Improvement: Open, close, and pong timeouts can be disabled entirely by + setting their duration to 0. +- Improvement: Numerous performance improvements. Including: tuned default + buffer sizes based on profiling, caching of handler binding for async + reads/writes, non-malloc allocators for read/write handlers, disabling of a + number of questionably useful range sanity checks in tight inner loops. +- Improvement: Cleaned up the handling of TLS related errors. TLS errors will + now be reported with more detail on the info channel rather than all being + `tls_short_read` or `pass_through`. In addition, many cases where a TLS short + read was in fact expected are no longer classified as errors. Expected TLS + short reads and quasi-expected socket shutdown related errors will no longer + be reported as unclean WebSocket shutdowns to the application. Information + about them will remain in the info error channel for debugging purposes. +- Improvement: `start_accept` and `listen` errors are now reported to the caller + either via an exception or an ec parameter. +- Improvement: Outgoing writes are now batched for improved message throughput + and reduced system call and TCP frame overhead. +- Bug: Fix some cases of calls to empty lib::function objects. +- Bug: Fix memory leak of connection objects due to cached handlers holding on to + reference counted pointers. #310 Thank you otaras for reporting. +- Bug: Fix issue with const endpoint accessors (such as `get_user_agent`) not + compiling due to non-const mutex use. #292 Thank you logofive for reporting. +- Bug: Fix handler allocation crash with multithreaded `io_service`. +- Bug: Fixes incorrect whitespace handling in header parsing. #301 Thank you + Wolfram Schroers for reporting +- Bug: Fix a crash when parsing empty HTTP headers. Thank you Thingol for + reporting. +- Bug: Fix a crash following use of the `stop_listening` function. Thank you + Thingol for reporting. +- Bug: Fix use of variable names that shadow function parameters. The library + should compile cleanly with -Wshadow now. Thank you giszo for reporting. #318 +- Bug: Fix an issue where `set_open_handshake_timeout` was ignored by server + code. Thank you Robin Rowe for reporting. +- Bug: Fix an issue where custom timeout values weren't being propagated from + endpoints to new connections. +- Bug: Fix a number of memory leaks related to server connection failures. #323 + #333 #334 #335 Thank you droppy and aydany for reporting and patches. + reporting. +- Compatibility: Fix compile time conflict with Visual Studio's MIN/MAX macros. + Thank you Robin Rowe for reporting. +- Documentation: Examples and test suite build system now defaults to clang on + OS X + +0.3.0-alpha4 - 2013-10-11 +- HTTP requests ending normally are no longer logged as errors. Thank you Banaan + for reporting. #294 +- Eliminates spurious expired timers in certain error conditions. Thank you + Banaan for reporting. #295 +- Consolidates all bundled library licenses into the COPYING file. #294 +- Updates bundled sha1 library to one with a cleaner interface and more + straight-forward license. Thank you lotodore for reporting and Evgeni Golov + for reviewing. #294 +- Re-introduces strands to asio transport, allowing `io_service` thread pools to + be used (with some limitations). +- Removes endpoint code that kept track of a connection list that was never used + anywhere. Removes a lock and reduces connection creation/deletion complexity + from O(log n) to O(1) in the number of connections. +- A number of internal changes to transport APIs +- Deprecates iostream transport `readsome` in favor of `read_some` which is more + consistent with the naming of the rest of the library. +- Adds preliminary signaling to iostream transport of eof and fatal transport + errors +- Updates transport code to use shared pointers rather than raw pointers to + prevent asio from retaining pointers to connection methods after the + connection goes out of scope. #293 Thank you otaras for reporting. +- Fixes an issue where custom headers couldn't be set for client connections + Thank you Jerry Win and Wolfram Schroers for reporting. - Fixes a compile error on visual studio when using interrupts. Thank you Javier Rey Neira for reporting this. - Adds new 1012 and 1013 close codes per IANA registry diff --git a/examples/broadcast_server/broadcast_server.cpp b/examples/broadcast_server/broadcast_server.cpp index 4066e07..bdb01fe 100644 --- a/examples/broadcast_server/broadcast_server.cpp +++ b/examples/broadcast_server/broadcast_server.cpp @@ -34,7 +34,8 @@ enum action_type { struct action { action(action_type t, connection_hdl h) : type(t), hdl(h) {} - action(action_type t, server::message_ptr m) : type(t), msg(m) {} + action(action_type t, connection_hdl h, server::message_ptr m) + : type(t), hdl(h), msg(m) {} action_type type; websocketpp::connection_hdl hdl; @@ -92,7 +93,7 @@ public: // queue message up for sending by processing thread unique_lock lock(m_action_lock); //std::cout << "on_message" << std::endl; - m_actions.push(action(MESSAGE,msg)); + m_actions.push(action(MESSAGE,hdl,msg)); lock.unlock(); m_action_cond.notify_one(); } @@ -111,13 +112,13 @@ public: lock.unlock(); if (a.type == SUBSCRIBE) { - unique_lock lock(m_connection_lock); + unique_lock con_lock(m_connection_lock); m_connections.insert(a.hdl); } else if (a.type == UNSUBSCRIBE) { - unique_lock lock(m_connection_lock); + unique_lock con_lock(m_connection_lock); m_connections.erase(a.hdl); } else if (a.type == MESSAGE) { - unique_lock lock(m_connection_lock); + unique_lock con_lock(m_connection_lock); con_list::iterator it; for (it = m_connections.begin(); it != m_connections.end(); ++it) { @@ -142,13 +143,13 @@ private: int main() { try { - broadcast_server server; + broadcast_server server_instance; // Start a thread to run the processing loop - thread t(bind(&broadcast_server::process_messages,&server)); + thread t(bind(&broadcast_server::process_messages,&server_instance)); // Run the asio loop with the main thread - server.run(9002); + server_instance.run(9002); t.join(); diff --git a/examples/debug_client/SConscript b/examples/debug_client/SConscript new file mode 100644 index 0000000..781db83 --- /dev/null +++ b/examples/debug_client/SConscript @@ -0,0 +1,24 @@ +## Debug client example +## + +Import('env') +Import('env_cpp11') +Import('boostlibs') +Import('platform_libs') +Import('polyfill_libs') +Import('tls_libs') + +env = env.Clone () +env_cpp11 = env_cpp11.Clone () + +prgs = [] + +# if a C++11 environment is available build using that, otherwise use boost +if env_cpp11.has_key('WSPP_CPP11_ENABLED'): + ALL_LIBS = boostlibs(['system'],env_cpp11) + [platform_libs] + [polyfill_libs] + [tls_libs] + prgs += env_cpp11.Program('debug_client', ["debug_client.cpp"], LIBS = ALL_LIBS) +else: + ALL_LIBS = boostlibs(['system','random'],env) + [platform_libs] + [polyfill_libs] + [tls_libs] + prgs += env.Program('debug_client', ["debug_client.cpp"], LIBS = ALL_LIBS) + +Return('prgs') diff --git a/examples/debug_client/debug_client.cpp b/examples/debug_client/debug_client.cpp new file mode 100644 index 0000000..f685596 --- /dev/null +++ b/examples/debug_client/debug_client.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2014, 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. + */ + +/** ====== WARNING ======== + * This example is presently used as a scratch space. It may or may not be broken + * at any given time. + */ + +#include + +#include + +#include +#include + +typedef websocketpp::client client; + +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 websocketpp::config::asio_tls_client::message_type::ptr message_ptr; +typedef websocketpp::lib::shared_ptr context_ptr; +typedef client::connection_ptr connection_ptr; + + + +class perftest { +public: + typedef perftest type; + typedef std::chrono::duration dur_type; + + perftest () { + m_endpoint.set_access_channels(websocketpp::log::alevel::all); + m_endpoint.set_error_channels(websocketpp::log::elevel::all); + + // Initialize ASIO + m_endpoint.init_asio(); + + // Register our handlers + m_endpoint.set_socket_init_handler(bind(&type::on_socket_init,this,::_1)); + m_endpoint.set_tls_init_handler(bind(&type::on_tls_init,this,::_1)); + m_endpoint.set_message_handler(bind(&type::on_message,this,::_1,::_2)); + m_endpoint.set_open_handler(bind(&type::on_open,this,::_1)); + m_endpoint.set_close_handler(bind(&type::on_close,this,::_1)); + } + + void start(std::string uri) { + websocketpp::lib::error_code ec; + client::connection_ptr con = m_endpoint.get_connection(uri, ec); + + if (ec) { + m_endpoint.get_alog().write(websocketpp::log::alevel::app,ec.message()); + } + + //con->set_proxy("http://humupdates.uchicago.edu:8443"); + + m_endpoint.connect(con); + + // Start the ASIO io_service run loop + m_start = std::chrono::high_resolution_clock::now(); + m_endpoint.run(); + } + + void on_socket_init(websocketpp::connection_hdl hdl) { + m_socket_init = std::chrono::high_resolution_clock::now(); + } + + context_ptr on_tls_init(websocketpp::connection_hdl hdl) { + m_tls_init = std::chrono::high_resolution_clock::now(); + context_ptr ctx(new boost::asio::ssl::context(boost::asio::ssl::context::tlsv1)); + + try { + ctx->set_options(boost::asio::ssl::context::default_workarounds | + boost::asio::ssl::context::no_sslv2 | + boost::asio::ssl::context::single_dh_use); + } catch (std::exception& e) { + std::cout << e.what() << std::endl; + } + return ctx; + } + + void on_open(websocketpp::connection_hdl hdl) { + m_open = std::chrono::high_resolution_clock::now(); + m_endpoint.send(hdl, "", websocketpp::frame::opcode::text); + } + void on_message(websocketpp::connection_hdl hdl, message_ptr msg) { + m_message = std::chrono::high_resolution_clock::now(); + m_endpoint.close(hdl,websocketpp::close::status::going_away,""); + } + void on_close(websocketpp::connection_hdl hdl) { + m_close = std::chrono::high_resolution_clock::now(); + + std::cout << "Socket Init: " << std::chrono::duration_cast(m_socket_init-m_start).count() << std::endl; + std::cout << "TLS Init: " << std::chrono::duration_cast(m_tls_init-m_start).count() << std::endl; + std::cout << "Open: " << std::chrono::duration_cast(m_open-m_start).count() << std::endl; + std::cout << "Message: " << std::chrono::duration_cast(m_message-m_start).count() << std::endl; + std::cout << "Close: " << std::chrono::duration_cast(m_close-m_start).count() << std::endl; + } +private: + client m_endpoint; + + std::chrono::high_resolution_clock::time_point m_start; + std::chrono::high_resolution_clock::time_point m_socket_init; + std::chrono::high_resolution_clock::time_point m_tls_init; + std::chrono::high_resolution_clock::time_point m_open; + std::chrono::high_resolution_clock::time_point m_message; + std::chrono::high_resolution_clock::time_point m_close; +}; + +int main(int argc, char* argv[]) { + std::string uri = "wss://echo.websocket.org"; + + if (argc == 2) { + uri = argv[1]; + } + + try { + perftest endpoint; + endpoint.start(uri); + } 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/examples/debug_server/CMakeLists.txt b/examples/debug_server/CMakeLists.txt new file mode 100644 index 0000000..02f6dcf --- /dev/null +++ b/examples/debug_server/CMakeLists.txt @@ -0,0 +1,10 @@ + +file (GLOB SOURCE_FILES *.cpp) +file (GLOB HEADER_FILES *.hpp) + +init_target (debug_server) + +build_executable (${TARGET_NAME} ${SOURCE_FILES} ${HEADER_FILES}) + +link_boost () +final_target () diff --git a/examples/debug_server/SConscript b/examples/debug_server/SConscript new file mode 100644 index 0000000..4d02261 --- /dev/null +++ b/examples/debug_server/SConscript @@ -0,0 +1,23 @@ +## Debug 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 available 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('debug_server', ["debug_server.cpp"], LIBS = ALL_LIBS) +else: + ALL_LIBS = boostlibs(['system'],env) + [platform_libs] + [polyfill_libs] + prgs += env.Program('debug_server', ["debug_server.cpp"], LIBS = ALL_LIBS) + +Return('prgs') diff --git a/examples/debug_server/debug_server.cpp b/examples/debug_server/debug_server.cpp new file mode 100644 index 0000000..724ec3c --- /dev/null +++ b/examples/debug_server/debug_server.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2014, 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. + */ + +/** ====== WARNING ======== + * This example is presently used as a scratch space. It may or may not be broken + * at any given time. + */ + +#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) { + std::cout << "on_message called with hdl: " << hdl.lock().get() + << " and message: " << msg->get_payload() + << std::endl; + + try { + s->send(hdl, msg->get_payload(), msg->get_opcode()); + } catch (const websocketpp::lib::error_code& e) { + std::cout << "Echo failed because: " << e + << "(" << e.message() << ")" << std::endl; + } +} + +int main() { + // Create a server endpoint + server echo_server; + + try { + // Set logging settings + echo_server.set_access_channels(websocketpp::log::alevel::all); + echo_server.clear_access_channels(websocketpp::log::alevel::frame_payload); + + // Initialize ASIO + echo_server.init_asio(); + echo_server.set_reuse_addr(true); + + // Register our message handler + echo_server.set_message_handler(bind(&on_message,&echo_server,::_1,::_2)); + + // Listen on port 9012 + echo_server.listen(9012); + + // Start the server accept loop + echo_server.start_accept(); + + // Start the ASIO io_service run loop + echo_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/examples/iostream_server/iostream_server.cpp b/examples/iostream_server/iostream_server.cpp index 6176313..5ade71a 100644 --- a/examples/iostream_server/iostream_server.cpp +++ b/examples/iostream_server/iostream_server.cpp @@ -74,11 +74,13 @@ int main() { if (buffered_io) { std::cin >> *con; + con->eof(); } else { char a; while(std::cin.get(a)) { - con->readsome(&a,1); + con->read_some(&a,1); } + con->eof(); } } catch (const std::exception & e) { std::cout << e.what() << std::endl; diff --git a/examples/scratch_client/SConscript b/examples/scratch_client/SConscript index 7215bc1..4c6c6a8 100644 --- a/examples/scratch_client/SConscript +++ b/examples/scratch_client/SConscript @@ -6,7 +6,6 @@ Import('env_cpp11') Import('boostlibs') Import('platform_libs') Import('polyfill_libs') -Import('tls_libs') env = env.Clone () env_cpp11 = env_cpp11.Clone () @@ -18,7 +17,7 @@ if env_cpp11.has_key('WSPP_CPP11_ENABLED'): ALL_LIBS = boostlibs(['system'],env_cpp11) + [platform_libs] + [polyfill_libs] + [tls_libs] prgs += env_cpp11.Program('scratch_client', ["scratch_client.cpp"], LIBS = ALL_LIBS) else: - ALL_LIBS = boostlibs(['system','random'],env) + [platform_libs] + [polyfill_libs] + [tls_libs] + ALL_LIBS = boostlibs(['system','random'],env) + [platform_libs] + [polyfill_libs] prgs += env.Program('utility_client', ["utility_client.cpp"], LIBS = ALL_LIBS) Return('prgs') diff --git a/examples/scratch_client/scratch_client.cpp b/examples/scratch_client/scratch_client.cpp index 64282fe..9b5a25c 100644 --- a/examples/scratch_client/scratch_client.cpp +++ b/examples/scratch_client/scratch_client.cpp @@ -1,126 +1,270 @@ -/** - * This example is presently used as a scratch space. It may or may not be broken - * at any given time. +/* + * Copyright (c) 2014, 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 +// **NOTE:** This file is a snapshot of the WebSocket++ utility client tutorial. +// Additional related material can be found in the tutorials/utility_client +// directory of the WebSocket++ repository. +#include #include +#include +#include + +#include #include -#include +#include +#include +#include -typedef websocketpp::client client; +typedef websocketpp::client client; -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 websocketpp::config::asio_tls_client::message_type::ptr message_ptr; -typedef websocketpp::lib::shared_ptr context_ptr; -typedef client::connection_ptr connection_ptr; - - - -class perftest { +class connection_metadata { public: - typedef perftest type; - typedef std::chrono::duration dur_type; + typedef websocketpp::lib::shared_ptr ptr; - perftest () { - m_endpoint.set_access_channels(websocketpp::log::alevel::all); - m_endpoint.set_error_channels(websocketpp::log::elevel::all); + connection_metadata(int id, websocketpp::connection_hdl hdl, std::string uri) + : m_id(id) + , m_hdl(hdl) + , m_status("Connecting") + , m_uri(uri) + , m_server("N/A") + {} - // Initialize ASIO - m_endpoint.init_asio(); + void on_open(client * c, websocketpp::connection_hdl hdl) { + m_status = "Open"; - // Register our handlers - m_endpoint.set_socket_init_handler(bind(&type::on_socket_init,this,::_1)); - m_endpoint.set_tls_init_handler(bind(&type::on_tls_init,this,::_1)); - m_endpoint.set_message_handler(bind(&type::on_message,this,::_1,::_2)); - m_endpoint.set_open_handler(bind(&type::on_open,this,::_1)); - m_endpoint.set_close_handler(bind(&type::on_close,this,::_1)); + client::connection_ptr con = c->get_con_from_hdl(hdl); + m_server = con->get_response_header("Server"); } - void start(std::string uri) { + void on_fail(client * c, websocketpp::connection_hdl hdl) { + m_status = "Failed"; + + client::connection_ptr con = c->get_con_from_hdl(hdl); + m_server = con->get_response_header("Server"); + m_error_reason = con->get_ec().message(); + } + + void on_close(client * c, websocketpp::connection_hdl hdl) { + m_status = "Closed"; + client::connection_ptr con = c->get_con_from_hdl(hdl); + std::stringstream s; + s << "close code: " << con->get_remote_close_code() << " (" + << websocketpp::close::status::get_string(con->get_remote_close_code()) + << "), close reason: " << con->get_remote_close_reason(); + m_error_reason = s.str(); + } + + websocketpp::connection_hdl get_hdl() const { + return m_hdl; + } + + int get_id() const { + return m_id; + } + + std::string get_status() const { + return m_status; + } + + friend std::ostream & operator<< (std::ostream & out, connection_metadata const & data); +private: + int m_id; + websocketpp::connection_hdl m_hdl; + std::string m_status; + std::string m_uri; + std::string m_server; + std::string m_error_reason; +}; + +std::ostream & operator<< (std::ostream & out, connection_metadata const & data) { + out << "> URI: " << data.m_uri << "\n" + << "> Status: " << data.m_status << "\n" + << "> Remote Server: " << (data.m_server.empty() ? "None Specified" : data.m_server) << "\n" + << "> Error/close reason: " << (data.m_error_reason.empty() ? "N/A" : data.m_error_reason); + + return out; +} + +class websocket_endpoint { +public: + websocket_endpoint () : m_next_id(0) { + m_endpoint.clear_access_channels(websocketpp::log::alevel::all); + m_endpoint.clear_error_channels(websocketpp::log::elevel::all); + + m_endpoint.init_asio(); + m_endpoint.start_perpetual(); + + m_thread.reset(new websocketpp::lib::thread(&client::run, &m_endpoint)); + } + + ~websocket_endpoint() { + m_endpoint.stop_perpetual(); + + for (con_list::const_iterator it = m_connection_list.begin(); it != m_connection_list.end(); ++it) { + if (it->second->get_status() != "Open") { + // Only close open connections + continue; + } + + std::cout << "> Closing connection " << it->second->get_id() << std::endl; + + websocketpp::lib::error_code ec; + m_endpoint.close(it->second->get_hdl(), websocketpp::close::status::going_away, "", ec); + if (ec) { + std::cout << "> Error closing connection " << it->second->get_id() << ": " + << ec.message() << std::endl; + } + } + + m_thread->join(); + } + + int connect(std::string const & uri) { websocketpp::lib::error_code ec; + client::connection_ptr con = m_endpoint.get_connection(uri, ec); if (ec) { - m_endpoint.get_alog().write(websocketpp::log::alevel::app,ec.message()); + std::cout << "> Connect initialization error: " << ec.message() << std::endl; + return -1; } - //con->set_proxy("http://humupdates.uchicago.edu:8443"); + int new_id = m_next_id++; + connection_metadata::ptr metadata_ptr(new connection_metadata(new_id, con->get_handle(), uri)); + m_connection_list[new_id] = metadata_ptr; + + con->set_open_handler(websocketpp::lib::bind( + &connection_metadata::on_open, + metadata_ptr, + &m_endpoint, + websocketpp::lib::placeholders::_1 + )); + con->set_fail_handler(websocketpp::lib::bind( + &connection_metadata::on_fail, + metadata_ptr, + &m_endpoint, + websocketpp::lib::placeholders::_1 + )); + con->set_close_handler(websocketpp::lib::bind( + &connection_metadata::on_close, + metadata_ptr, + &m_endpoint, + websocketpp::lib::placeholders::_1 + )); m_endpoint.connect(con); - // Start the ASIO io_service run loop - m_start = std::chrono::high_resolution_clock::now(); - m_endpoint.run(); + return new_id; } - void on_socket_init(websocketpp::connection_hdl hdl) { - m_socket_init = std::chrono::high_resolution_clock::now(); - } - - context_ptr on_tls_init(websocketpp::connection_hdl hdl) { - m_tls_init = std::chrono::high_resolution_clock::now(); - context_ptr ctx(new boost::asio::ssl::context(boost::asio::ssl::context::tlsv1)); - - try { - ctx->set_options(boost::asio::ssl::context::default_workarounds | - boost::asio::ssl::context::no_sslv2 | - boost::asio::ssl::context::single_dh_use); - } catch (std::exception& e) { - std::cout << e.what() << std::endl; + void close(int id, websocketpp::close::status::value code, std::string reason) { + websocketpp::lib::error_code ec; + + con_list::iterator metadata_it = m_connection_list.find(id); + if (metadata_it == m_connection_list.end()) { + std::cout << "> No connection found with id " << id << std::endl; + return; + } + + m_endpoint.close(metadata_it->second->get_hdl(), code, reason, ec); + if (ec) { + std::cout << "> Error initiating close: " << ec.message() << std::endl; } - return ctx; } - void on_open(websocketpp::connection_hdl hdl) { - m_open = std::chrono::high_resolution_clock::now(); - m_endpoint.send(hdl, "", websocketpp::frame::opcode::text); - } - void on_message(websocketpp::connection_hdl hdl, message_ptr msg) { - m_message = std::chrono::high_resolution_clock::now(); - m_endpoint.close(hdl,websocketpp::close::status::going_away,""); - } - void on_close(websocketpp::connection_hdl hdl) { - m_close = std::chrono::high_resolution_clock::now(); - - std::cout << "Socket Init: " << std::chrono::duration_cast(m_socket_init-m_start).count() << std::endl; - std::cout << "TLS Init: " << std::chrono::duration_cast(m_tls_init-m_start).count() << std::endl; - std::cout << "Open: " << std::chrono::duration_cast(m_open-m_start).count() << std::endl; - std::cout << "Message: " << std::chrono::duration_cast(m_message-m_start).count() << std::endl; - std::cout << "Close: " << std::chrono::duration_cast(m_close-m_start).count() << std::endl; + connection_metadata::ptr get_metadata(int id) const { + con_list::const_iterator metadata_it = m_connection_list.find(id); + if (metadata_it == m_connection_list.end()) { + return connection_metadata::ptr(); + } else { + return metadata_it->second; + } } private: - client m_endpoint; + typedef std::map con_list; - std::chrono::high_resolution_clock::time_point m_start; - std::chrono::high_resolution_clock::time_point m_socket_init; - std::chrono::high_resolution_clock::time_point m_tls_init; - std::chrono::high_resolution_clock::time_point m_open; - std::chrono::high_resolution_clock::time_point m_message; - std::chrono::high_resolution_clock::time_point m_close; + client m_endpoint; + websocketpp::lib::shared_ptr m_thread; + + con_list m_connection_list; + int m_next_id; }; -int main(int argc, char* argv[]) { - std::string uri = "wss://echo.websocket.org"; +int main() { + bool done = false; + std::string input; + websocket_endpoint endpoint; - if (argc == 2) { - uri = argv[1]; - } + while (!done) { + std::cout << "Enter Command: "; + std::getline(std::cin, input); - try { - perftest endpoint; - endpoint.start(uri); - } 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; + if (input == "quit") { + done = true; + } else if (input == "help") { + std::cout + << "\nCommand List:\n" + << "connect \n" + << "close [] []\n" + << "show \n" + << "help: Display this help text\n" + << "quit: Exit the program\n" + << std::endl; + } else if (input.substr(0,7) == "connect") { + int id = endpoint.connect(input.substr(8)); + if (id != -1) { + std::cout << "> Created connection with id " << id << std::endl; + } + } else if (input.substr(0,5) == "close") { + std::stringstream ss(input); + + std::string cmd; + int id; + int close_code = websocketpp::close::status::normal; + std::string reason = ""; + + ss >> cmd >> id >> close_code; + std::getline(ss,reason); + + endpoint.close(id, close_code, reason); + } else if (input.substr(0,4) == "show") { + int id = atoi(input.substr(5).c_str()); + + connection_metadata::ptr metadata = endpoint.get_metadata(id); + if (metadata) { + std::cout << *metadata << std::endl; + } else { + std::cout << "> Unknown connection id " << id << std::endl; + } + } else { + std::cout << "> Unrecognized Command" << std::endl; + } } + + return 0; } diff --git a/examples/telemetry_client/telemetry_client.cpp b/examples/telemetry_client/telemetry_client.cpp index e1bd1df..f3db649 100644 --- a/examples/telemetry_client/telemetry_client.cpp +++ b/examples/telemetry_client/telemetry_client.cpp @@ -97,6 +97,8 @@ public: websocketpp::lib::error_code ec; while(1) { + bool wait = false; + { scoped_lock guard(m_lock); // If the connection has been closed, stop generating telemetry @@ -104,11 +106,15 @@ public: // If the connection hasn't been opened yet wait a bit and retry if (!m_open) { - sleep(1); - continue; + wait = true; } } + if (wait) { + sleep(1); + continue; + } + val.str(""); val << "count is " << count++; diff --git a/examples/testee_server/testee_server.cpp b/examples/testee_server/testee_server.cpp index 09e8091..94bfa10 100644 --- a/examples/testee_server/testee_server.cpp +++ b/examples/testee_server/testee_server.cpp @@ -29,7 +29,44 @@ #include #include -typedef websocketpp::server server; +struct testee_config : public websocketpp::config::asio { + // pull default settings from our core config + typedef websocketpp::config::asio core; + + typedef core::concurrency_type concurrency_type; + typedef core::request_type request_type; + typedef core::response_type response_type; + typedef core::message_type message_type; + typedef core::con_msg_manager_type con_msg_manager_type; + typedef core::endpoint_msg_manager_type endpoint_msg_manager_type; + + typedef core::alog_type alog_type; + typedef core::elog_type elog_type; + typedef core::rng_type rng_type; + typedef core::endpoint_base endpoint_base; + + static bool const enable_multithreading = false; + + struct transport_config : public core::transport_config { + typedef core::concurrency_type concurrency_type; + typedef core::elog_type elog_type; + typedef core::alog_type alog_type; + typedef core::request_type request_type; + typedef core::response_type response_type; + + static bool const enable_multithreading = false; + }; + + typedef websocketpp::transport::asio::endpoint + transport_type; + + static const websocketpp::log::level elog_level = + websocketpp::log::elevel::none; + static const websocketpp::log::level alog_level = + websocketpp::log::alevel::none; +}; + +typedef websocketpp::server server; using websocketpp::lib::placeholders::_1; using websocketpp::lib::placeholders::_2; @@ -43,11 +80,24 @@ 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 +void on_socket_init(websocketpp::connection_hdl hdl, boost::asio::ip::tcp::socket & s) { + boost::asio::ip::tcp::no_delay option(true); + s.set_option(option); +} + +int main(int argc, char * argv[]) { + // Create a server endpoint server testee_server; - try { + short port = 9002; + size_t num_threads = 1; + + if (argc == 3) { + port = atoi(argv[1]); + num_threads = atoi(argv[2]); + } + + try { // Total silence testee_server.clear_access_channels(websocketpp::log::alevel::all); testee_server.clear_error_channels(websocketpp::log::alevel::all); @@ -57,19 +107,34 @@ int main() { // Register our message handler testee_server.set_message_handler(bind(&on_message,&testee_server,::_1,::_2)); + testee_server.set_socket_init_handler(bind(&on_socket_init,::_1,::_2)); - // Listen on port 9002 - testee_server.listen(9002); + // Listen on specified port with extended listen backlog + testee_server.set_listen_backlog(8192); + testee_server.listen(port); // Start the server accept loop testee_server.start_accept(); - // Start the ASIO io_service run loop - testee_server.run(); + // Start the ASIO io_service run loop + if (num_threads == 1) { + testee_server.run(); + } else { + typedef websocketpp::lib::shared_ptr thread_ptr; + std::vector ts; + for (size_t i = 0; i < num_threads; i++) { + ts.push_back(thread_ptr(new websocketpp::lib::thread(&server::run, &testee_server))); + } + + for (size_t i = 0; i < num_threads; i++) { + ts[i]->join(); + } + } + } catch (const std::exception & e) { - std::cout << e.what() << std::endl; + std::cout << "exception: " << e.what() << std::endl; } catch (websocketpp::lib::error_code e) { - std::cout << e.message() << std::endl; + std::cout << "error code: " << e.message() << std::endl; } catch (...) { std::cout << "other exception" << std::endl; } diff --git a/examples/utility_client/CMakeLists.txt b/examples/utility_client/CMakeLists.txt new file mode 100644 index 0000000..6e6cd8c --- /dev/null +++ b/examples/utility_client/CMakeLists.txt @@ -0,0 +1,23 @@ +## Utility 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 available 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('utility_client', ["utility_client.cpp"], LIBS = ALL_LIBS) +else: + ALL_LIBS = boostlibs(['system','random'],env) + [platform_libs] + [polyfill_libs] + prgs += env.Program('utility_client', ["utility_client.cpp"], LIBS = ALL_LIBS) + +Return('prgs') \ No newline at end of file diff --git a/examples/utility_client/SConscript b/examples/utility_client/SConscript new file mode 100644 index 0000000..865a4c7 --- /dev/null +++ b/examples/utility_client/SConscript @@ -0,0 +1,11 @@ + +file (GLOB SOURCE_FILES *.cpp) +file (GLOB HEADER_FILES *.hpp) + +init_target (utility_client) + +build_executable (${TARGET_NAME} ${SOURCE_FILES} ${HEADER_FILES}) + +link_boost () +final_target () + diff --git a/examples/utility_client/utility_client.cpp b/examples/utility_client/utility_client.cpp new file mode 100644 index 0000000..3464dd0 --- /dev/null +++ b/examples/utility_client/utility_client.cpp @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2014, 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. + */ + +// **NOTE:** This file is a snapshot of the WebSocket++ utility client tutorial. +// Additional related material can be found in the tutorials/utility_client +// directory of the WebSocket++ repository. + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +typedef websocketpp::client client; + +class connection_metadata { +public: + typedef websocketpp::lib::shared_ptr ptr; + + connection_metadata(int id, websocketpp::connection_hdl hdl, std::string uri) + : m_id(id) + , m_hdl(hdl) + , m_status("Connecting") + , m_uri(uri) + , m_server("N/A") + {} + + void on_open(client * c, websocketpp::connection_hdl hdl) { + m_status = "Open"; + + client::connection_ptr con = c->get_con_from_hdl(hdl); + m_server = con->get_response_header("Server"); + } + + void on_fail(client * c, websocketpp::connection_hdl hdl) { + m_status = "Failed"; + + client::connection_ptr con = c->get_con_from_hdl(hdl); + m_server = con->get_response_header("Server"); + m_error_reason = con->get_ec().message(); + } + + void on_close(client * c, websocketpp::connection_hdl hdl) { + m_status = "Closed"; + client::connection_ptr con = c->get_con_from_hdl(hdl); + std::stringstream s; + s << "close code: " << con->get_remote_close_code() << " (" + << websocketpp::close::status::get_string(con->get_remote_close_code()) + << "), close reason: " << con->get_remote_close_reason(); + m_error_reason = s.str(); + } + + websocketpp::connection_hdl get_hdl() const { + return m_hdl; + } + + int get_id() const { + return m_id; + } + + std::string get_status() const { + return m_status; + } + + friend std::ostream & operator<< (std::ostream & out, connection_metadata const & data); +private: + int m_id; + websocketpp::connection_hdl m_hdl; + std::string m_status; + std::string m_uri; + std::string m_server; + std::string m_error_reason; +}; + +std::ostream & operator<< (std::ostream & out, connection_metadata const & data) { + out << "> URI: " << data.m_uri << "\n" + << "> Status: " << data.m_status << "\n" + << "> Remote Server: " << (data.m_server.empty() ? "None Specified" : data.m_server) << "\n" + << "> Error/close reason: " << (data.m_error_reason.empty() ? "N/A" : data.m_error_reason); + + return out; +} + +class websocket_endpoint { +public: + websocket_endpoint () : m_next_id(0) { + m_endpoint.clear_access_channels(websocketpp::log::alevel::all); + m_endpoint.clear_error_channels(websocketpp::log::elevel::all); + + m_endpoint.init_asio(); + m_endpoint.start_perpetual(); + + m_thread.reset(new websocketpp::lib::thread(&client::run, &m_endpoint)); + } + + ~websocket_endpoint() { + m_endpoint.stop_perpetual(); + + for (con_list::const_iterator it = m_connection_list.begin(); it != m_connection_list.end(); ++it) { + if (it->second->get_status() != "Open") { + // Only close open connections + continue; + } + + std::cout << "> Closing connection " << it->second->get_id() << std::endl; + + websocketpp::lib::error_code ec; + m_endpoint.close(it->second->get_hdl(), websocketpp::close::status::going_away, "", ec); + if (ec) { + std::cout << "> Error closing connection " << it->second->get_id() << ": " + << ec.message() << std::endl; + } + } + + m_thread->join(); + } + + int connect(std::string const & uri) { + websocketpp::lib::error_code ec; + + client::connection_ptr con = m_endpoint.get_connection(uri, ec); + + if (ec) { + std::cout << "> Connect initialization error: " << ec.message() << std::endl; + return -1; + } + + int new_id = m_next_id++; + connection_metadata::ptr metadata_ptr(new connection_metadata(new_id, con->get_handle(), uri)); + m_connection_list[new_id] = metadata_ptr; + + con->set_open_handler(websocketpp::lib::bind( + &connection_metadata::on_open, + metadata_ptr, + &m_endpoint, + websocketpp::lib::placeholders::_1 + )); + con->set_fail_handler(websocketpp::lib::bind( + &connection_metadata::on_fail, + metadata_ptr, + &m_endpoint, + websocketpp::lib::placeholders::_1 + )); + con->set_close_handler(websocketpp::lib::bind( + &connection_metadata::on_close, + metadata_ptr, + &m_endpoint, + websocketpp::lib::placeholders::_1 + )); + + m_endpoint.connect(con); + + return new_id; + } + + void close(int id, websocketpp::close::status::value code, std::string reason) { + websocketpp::lib::error_code ec; + + con_list::iterator metadata_it = m_connection_list.find(id); + if (metadata_it == m_connection_list.end()) { + std::cout << "> No connection found with id " << id << std::endl; + return; + } + + m_endpoint.close(metadata_it->second->get_hdl(), code, reason, ec); + if (ec) { + std::cout << "> Error initiating close: " << ec.message() << std::endl; + } + } + + connection_metadata::ptr get_metadata(int id) const { + con_list::const_iterator metadata_it = m_connection_list.find(id); + if (metadata_it == m_connection_list.end()) { + return connection_metadata::ptr(); + } else { + return metadata_it->second; + } + } +private: + typedef std::map con_list; + + client m_endpoint; + websocketpp::lib::shared_ptr m_thread; + + con_list m_connection_list; + int m_next_id; +}; + +int main() { + bool done = false; + std::string input; + websocket_endpoint endpoint; + + while (!done) { + std::cout << "Enter Command: "; + std::getline(std::cin, input); + + if (input == "quit") { + done = true; + } else if (input == "help") { + std::cout + << "\nCommand List:\n" + << "connect \n" + << "close [] []\n" + << "show \n" + << "help: Display this help text\n" + << "quit: Exit the program\n" + << std::endl; + } else if (input.substr(0,7) == "connect") { + int id = endpoint.connect(input.substr(8)); + if (id != -1) { + std::cout << "> Created connection with id " << id << std::endl; + } + } else if (input.substr(0,5) == "close") { + std::stringstream ss(input); + + std::string cmd; + int id; + int close_code = websocketpp::close::status::normal; + std::string reason = ""; + + ss >> cmd >> id >> close_code; + std::getline(ss,reason); + + endpoint.close(id, close_code, reason); + } else if (input.substr(0,4) == "show") { + int id = atoi(input.substr(5).c_str()); + + connection_metadata::ptr metadata = endpoint.get_metadata(id); + if (metadata) { + std::cout << *metadata << std::endl; + } else { + std::cout << "> Unknown connection id " << id << std::endl; + } + } else { + std::cout << "> Unrecognized Command" << std::endl; + } + } + + return 0; +} \ No newline at end of file diff --git a/readme.md b/readme.md index 0c36a8a..4e2600f 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -WebSocket++ (0.3.0-alpha3) +WebSocket++ (0.3.0-alpha4) ========================== WebSocket++ is a header only C++ library that implements RFC6455 The WebSocket diff --git a/roadmap.md b/roadmap.md index 35af1ed..ee74655 100644 --- a/roadmap.md +++ b/roadmap.md @@ -10,16 +10,18 @@ Complete & Tested: - 32 bit support - Logging - Client role - -Implimented, needs more testing -- TLS support -- echo_server & echo_server_tls -- External io_service support -- socket_init_handler -- tls_init_handler - message_handler - ping_handler - pong_handler +- open_handler +- close_handler +- echo_server & echo_server_tls + +Implemented, needs more testing +- TLS support +- External io_service support +- socket_init_handler +- tls_init_handler - tcp_init_handler - exception/error handling - Subprotocol negotiation @@ -30,8 +32,7 @@ Implimented, needs more testing - Visual Studio / Windows support - Timeouts - CMake build/install support -- open_handler -- close_handler + - validate_handler - http_handler @@ -39,3 +40,5 @@ Future feature roadmap - Extension support - permessage_compress extension - Message buffer pool +- flow control +- tutorials & documentation diff --git a/test/connection/connection.cpp b/test/connection/connection.cpp index c8f7db0..401ea0f 100644 --- a/test/connection/connection.cpp +++ b/test/connection/connection.cpp @@ -80,8 +80,7 @@ struct stub_config : public websocketpp::config::core { }; struct connection_setup { - connection_setup(bool server) - : c(server,"",alog,elog,rng) {} + connection_setup(bool p_is_server) : c(p_is_server, "", alog, elog, rng) {} websocketpp::lib::error_code ec; stub_config::alog_type alog; @@ -174,7 +173,7 @@ BOOST_AUTO_TEST_CASE( basic_client_websocket ) { //std::string output = "HTTP/1.1 101 Switching Protocols\r\nConnection: upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\nServer: foo\r\nUpgrade: websocket\r\n\r\n"; - std::string ref = "GET / HTTP/1.1\r\nConnection: Upgrade\r\nHost: localhost\r\nSec-WebSocket-Key: AAAAAAAAAAAAAAAAAAAAAA==\r\nSec-WebSocket-Version: 13\r\nUpgrade: websocket\r\nUser-Agent: foo\r\n\r\n"; + std::string ref = "GET / HTTP/1.1\r\nConnection: Upgrade\r\nFoo: Bar\r\nHost: localhost\r\nSec-WebSocket-Key: AAAAAAAAAAAAAAAAAAAAAA==\r\nSec-WebSocket-Version: 13\r\nUpgrade: websocket\r\nUser-Agent: foo\r\n\r\n"; std::stringstream output; @@ -187,11 +186,39 @@ BOOST_AUTO_TEST_CASE( basic_client_websocket ) { client::connection_ptr con; websocketpp::lib::error_code ec; con = e.get_connection(uri, ec); + con->append_header("Foo","Bar"); e.connect(con); BOOST_CHECK_EQUAL(ref, output.str()); } +BOOST_AUTO_TEST_CASE( set_max_message_size ) { + 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\n\r\n"; + + // After the handshake, add a single frame with a message that is too long. + char frame0[10] = {char(0x82), char(0x83), 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01}; + input.append(frame0, 10); + + std::string output = "HTTP/1.1 101 Switching Protocols\r\nConnection: upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\nServer: foo\r\nUpgrade: websocket\r\n\r\n"; + + // After the handshake, add a single frame with a close message with message too big + // error code. + char frame1[4] = {char(0x88), 0x19, 0x03, char(0xf1)}; + output.append(frame1, 4); + output.append("A message was too large"); + + server s; + s.set_user_agent(""); + s.set_validate_handler(bind(&validate_set_ua,&s,::_1)); + s.set_max_message_size(2); + + BOOST_CHECK_EQUAL(run_server_test(s,input), output); +} + +// TODO: set max message size in client endpoint test case +// TODO: set max message size mid connection test case +// TODO: [maybe] set max message size in open handler + /* BOOST_AUTO_TEST_CASE( user_reject_origin ) { diff --git a/test/http/parser.cpp b/test/http/parser.cpp index 19a4909..beaa8fb 100644 --- a/test/http/parser.cpp +++ b/test/http/parser.cpp @@ -355,6 +355,26 @@ BOOST_AUTO_TEST_CASE( extract_parameters ) { BOOST_CHECK_EQUAL( a.find("bar")->second, "a \"b\" c" ); } +BOOST_AUTO_TEST_CASE( strip_lws ) { + std::string test1 = "foo"; + std::string test2 = " foo "; + std::string test3 = "foo "; + std::string test4 = " foo"; + std::string test5 = " foo "; + std::string test6 = " \r\n foo "; + std::string test7 = " \t foo "; + std::string test8 = " \t "; + + BOOST_CHECK_EQUAL( websocketpp::http::parser::strip_lws(test1), "foo" ); + BOOST_CHECK_EQUAL( websocketpp::http::parser::strip_lws(test2), "foo" ); + BOOST_CHECK_EQUAL( websocketpp::http::parser::strip_lws(test3), "foo" ); + BOOST_CHECK_EQUAL( websocketpp::http::parser::strip_lws(test4), "foo" ); + BOOST_CHECK_EQUAL( websocketpp::http::parser::strip_lws(test5), "foo" ); + BOOST_CHECK_EQUAL( websocketpp::http::parser::strip_lws(test6), "foo" ); + BOOST_CHECK_EQUAL( websocketpp::http::parser::strip_lws(test7), "foo" ); + BOOST_CHECK_EQUAL( websocketpp::http::parser::strip_lws(test8), "" ); +} + BOOST_AUTO_TEST_CASE( case_insensitive_headers ) { websocketpp::http::parser::parser r; @@ -655,12 +675,12 @@ BOOST_AUTO_TEST_CASE( old_http_version ) { } BOOST_CHECK( exception == false ); - BOOST_CHECK( pos == 41 ); + BOOST_CHECK_EQUAL( pos, 41 ); BOOST_CHECK( r.ready() == true ); - BOOST_CHECK( r.get_version() == "HTTP/1.0" ); - BOOST_CHECK( r.get_method() == "GET" ); - BOOST_CHECK( r.get_uri() == "/" ); - BOOST_CHECK( r.get_header("Host") == "www.example.com" ); + BOOST_CHECK_EQUAL( r.get_version(), "HTTP/1.0" ); + BOOST_CHECK_EQUAL( r.get_method(), "GET" ); + BOOST_CHECK_EQUAL( r.get_uri(), "/" ); + BOOST_CHECK_EQUAL( r.get_header("Host"), "www.example.com" ); } BOOST_AUTO_TEST_CASE( new_http_version1 ) { @@ -678,12 +698,12 @@ BOOST_AUTO_TEST_CASE( new_http_version1 ) { } BOOST_CHECK( exception == false ); - BOOST_CHECK( pos == 42 ); + BOOST_CHECK_EQUAL( pos, 42 ); BOOST_CHECK( r.ready() == true ); - BOOST_CHECK( r.get_version() == "HTTP/1.12" ); - BOOST_CHECK( r.get_method() == "GET" ); - BOOST_CHECK( r.get_uri() == "/" ); - BOOST_CHECK( r.get_header("Host") == "www.example.com" ); + BOOST_CHECK_EQUAL( r.get_version(), "HTTP/1.12" ); + BOOST_CHECK_EQUAL( r.get_method(), "GET" ); + BOOST_CHECK_EQUAL( r.get_uri(), "/" ); + BOOST_CHECK_EQUAL( r.get_header("Host"), "www.example.com" ); } BOOST_AUTO_TEST_CASE( new_http_version2 ) { @@ -701,12 +721,12 @@ BOOST_AUTO_TEST_CASE( new_http_version2 ) { } BOOST_CHECK( exception == false ); - BOOST_CHECK( pos == 43 ); + BOOST_CHECK_EQUAL( pos, 43 ); BOOST_CHECK( r.ready() == true ); - BOOST_CHECK( r.get_version() == "HTTP/12.12" ); - BOOST_CHECK( r.get_method() == "GET" ); - BOOST_CHECK( r.get_uri() == "/" ); - BOOST_CHECK( r.get_header("Host") == "www.example.com" ); + BOOST_CHECK_EQUAL( r.get_version(), "HTTP/12.12" ); + BOOST_CHECK_EQUAL( r.get_method(), "GET" ); + BOOST_CHECK_EQUAL( r.get_uri(), "/" ); + BOOST_CHECK_EQUAL( r.get_header("Host"), "www.example.com" ); } /* commented out due to not being implemented yet @@ -726,7 +746,7 @@ BOOST_AUTO_TEST_CASE( new_http_version3 ) { } BOOST_CHECK( exception == true ); -} +}*/ BOOST_AUTO_TEST_CASE( header_whitespace1 ) { websocketpp::http::parser::request r; @@ -743,12 +763,12 @@ BOOST_AUTO_TEST_CASE( header_whitespace1 ) { } BOOST_CHECK( exception == false ); - BOOST_CHECK( pos == 43 ); + BOOST_CHECK_EQUAL( pos, 43 ); BOOST_CHECK( r.ready() == true ); - BOOST_CHECK( r.get_version() == "HTTP/1.1" ); - BOOST_CHECK( r.get_method() == "GET" ); - BOOST_CHECK( r.get_uri() == "/" ); - BOOST_CHECK( r.get_header("Host") == "www.example.com" ); + BOOST_CHECK_EQUAL( r.get_version(), "HTTP/1.1" ); + BOOST_CHECK_EQUAL( r.get_method(), "GET" ); + BOOST_CHECK_EQUAL( r.get_uri(), "/" ); + BOOST_CHECK_EQUAL( r.get_header("Host"), "www.example.com" ); } BOOST_AUTO_TEST_CASE( header_whitespace2 ) { @@ -766,13 +786,13 @@ BOOST_AUTO_TEST_CASE( header_whitespace2 ) { } BOOST_CHECK( exception == false ); - BOOST_CHECK( pos == 40 ); + BOOST_CHECK_EQUAL( pos, 40 ); BOOST_CHECK( r.ready() == true ); - BOOST_CHECK( r.get_version() == "HTTP/1.1" ); - BOOST_CHECK( r.get_method() == "GET" ); - BOOST_CHECK( r.get_uri() == "/" ); - BOOST_CHECK( r.get_header("Host") == "www.example.com" ); -}*/ + BOOST_CHECK_EQUAL( r.get_version(), "HTTP/1.1" ); + BOOST_CHECK_EQUAL( r.get_method(), "GET" ); + BOOST_CHECK_EQUAL( r.get_uri(), "/" ); + BOOST_CHECK_EQUAL( r.get_header("Host"), "www.example.com" ); +} BOOST_AUTO_TEST_CASE( header_aggregation ) { websocketpp::http::parser::request r; @@ -789,12 +809,12 @@ BOOST_AUTO_TEST_CASE( header_aggregation ) { } BOOST_CHECK( exception == false ); - BOOST_CHECK( pos == 61 ); + BOOST_CHECK_EQUAL( pos, 61 ); BOOST_CHECK( r.ready() == true ); - BOOST_CHECK( r.get_version() == "HTTP/1.1" ); - BOOST_CHECK( r.get_method() == "GET" ); - BOOST_CHECK( r.get_uri() == "/" ); - BOOST_CHECK( r.get_header("Foo") == "bar, bat" ); + BOOST_CHECK_EQUAL( r.get_version(), "HTTP/1.1" ); + BOOST_CHECK_EQUAL( r.get_method(), "GET" ); + BOOST_CHECK_EQUAL( r.get_uri(), "/" ); + BOOST_CHECK_EQUAL( r.get_header("Foo"), "bar, bat" ); } BOOST_AUTO_TEST_CASE( wikipedia_example_response ) { @@ -813,15 +833,42 @@ BOOST_AUTO_TEST_CASE( wikipedia_example_response ) { } BOOST_CHECK( exception == false ); - BOOST_CHECK( pos == 159 ); + BOOST_CHECK_EQUAL( pos, 159 ); BOOST_CHECK( r.headers_ready() == true ); - BOOST_CHECK( r.get_version() == "HTTP/1.1" ); - BOOST_CHECK( r.get_status_code() == websocketpp::http::status_code::switching_protocols ); - BOOST_CHECK( r.get_status_msg() == "Switching Protocols" ); - BOOST_CHECK( r.get_header("Upgrade") == "websocket" ); - BOOST_CHECK( r.get_header("Connection") == "Upgrade" ); - BOOST_CHECK( r.get_header("Sec-WebSocket-Accept") == "HSmrc0sMlYUkAGmm5OPpG2HaGWk=" ); - BOOST_CHECK( r.get_header("Sec-WebSocket-Protocol") == "chat" ); + BOOST_CHECK_EQUAL( r.get_version(), "HTTP/1.1" ); + BOOST_CHECK_EQUAL( r.get_status_code(), websocketpp::http::status_code::switching_protocols ); + BOOST_CHECK_EQUAL( r.get_status_msg(), "Switching Protocols" ); + BOOST_CHECK_EQUAL( r.get_header("Upgrade"), "websocket" ); + BOOST_CHECK_EQUAL( r.get_header("Connection"), "Upgrade" ); + BOOST_CHECK_EQUAL( r.get_header("Sec-WebSocket-Accept"), "HSmrc0sMlYUkAGmm5OPpG2HaGWk=" ); + BOOST_CHECK_EQUAL( r.get_header("Sec-WebSocket-Protocol"), "chat" ); +} + +BOOST_AUTO_TEST_CASE( response_with_non_standard_lws ) { + websocketpp::http::parser::response r; + + std::string raw = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept:HSmrc0sMlYUkAGmm5OPpG2HaGWk=\r\nSec-WebSocket-Protocol: chat\r\n\r\n"; + + bool exception = false; + size_t pos = 0; + + try { + pos += r.consume(raw.c_str(),raw.size()); + } catch (std::exception &e) { + exception = true; + std::cout << e.what() << std::endl; + } + + BOOST_CHECK( exception == false ); + BOOST_CHECK_EQUAL( pos, 158 ); + BOOST_CHECK( r.headers_ready() ); + BOOST_CHECK_EQUAL( r.get_version(), "HTTP/1.1" ); + BOOST_CHECK_EQUAL( r.get_status_code(), websocketpp::http::status_code::switching_protocols ); + BOOST_CHECK_EQUAL( r.get_status_msg(), "Switching Protocols" ); + BOOST_CHECK_EQUAL( r.get_header("Upgrade"), "websocket" ); + BOOST_CHECK_EQUAL( r.get_header("Connection"), "Upgrade" ); + BOOST_CHECK_EQUAL( r.get_header("Sec-WebSocket-Accept"), "HSmrc0sMlYUkAGmm5OPpG2HaGWk=" ); + BOOST_CHECK_EQUAL( r.get_header("Sec-WebSocket-Protocol"), "chat" ); } BOOST_AUTO_TEST_CASE( plain_http_response ) { @@ -840,21 +887,21 @@ BOOST_AUTO_TEST_CASE( plain_http_response ) { } BOOST_CHECK( exception == false ); - BOOST_CHECK( pos == 405 ); + BOOST_CHECK_EQUAL( pos, 405 ); BOOST_CHECK( r.headers_ready() == true ); BOOST_CHECK( r.ready() == true ); - BOOST_CHECK( r.get_version() == "HTTP/1.1" ); - BOOST_CHECK( r.get_status_code() == websocketpp::http::status_code::ok ); - BOOST_CHECK( r.get_status_msg() == "OK" ); - BOOST_CHECK( r.get_header("Date") == "Thu, 10 May 2012 11:59:25 GMT" ); - BOOST_CHECK( r.get_header("Server") == "Apache/2.2.21 (Unix) mod_ssl/2.2.21 OpenSSL/0.9.8r DAV/2 PHP/5.3.8 with Suhosin-Patch" ); - BOOST_CHECK( r.get_header("Last-Modified") == "Tue, 30 Mar 2010 17:41:28 GMT" ); - BOOST_CHECK( r.get_header("ETag") == "\"16799d-55-4830823a78200\"" ); - BOOST_CHECK( r.get_header("Accept-Ranges") == "bytes" ); - BOOST_CHECK( r.get_header("Content-Length") == "85" ); - BOOST_CHECK( r.get_header("Vary") == "Accept-Encoding" ); - BOOST_CHECK( r.get_header("Content-Type") == "text/html" ); - BOOST_CHECK( r.get_body() == "\n\n\nThor\n\n \n

Thor

\n" ); + BOOST_CHECK_EQUAL( r.get_version(), "HTTP/1.1" ); + BOOST_CHECK_EQUAL( r.get_status_code(), websocketpp::http::status_code::ok ); + BOOST_CHECK_EQUAL( r.get_status_msg(), "OK" ); + BOOST_CHECK_EQUAL( r.get_header("Date"), "Thu, 10 May 2012 11:59:25 GMT" ); + BOOST_CHECK_EQUAL( r.get_header("Server"), "Apache/2.2.21 (Unix) mod_ssl/2.2.21 OpenSSL/0.9.8r DAV/2 PHP/5.3.8 with Suhosin-Patch" ); + BOOST_CHECK_EQUAL( r.get_header("Last-Modified"), "Tue, 30 Mar 2010 17:41:28 GMT" ); + BOOST_CHECK_EQUAL( r.get_header("ETag"), "\"16799d-55-4830823a78200\"" ); + BOOST_CHECK_EQUAL( r.get_header("Accept-Ranges"), "bytes" ); + BOOST_CHECK_EQUAL( r.get_header("Content-Length"), "85" ); + BOOST_CHECK_EQUAL( r.get_header("Vary"), "Accept-Encoding" ); + BOOST_CHECK_EQUAL( r.get_header("Content-Type"), "text/html" ); + BOOST_CHECK_EQUAL( r.get_body(), "\n\n\nThor\n\n \n

Thor

\n" ); } BOOST_AUTO_TEST_CASE( parse_istream ) { @@ -889,7 +936,7 @@ BOOST_AUTO_TEST_CASE( write_request_basic ) { r.set_method("GET"); r.set_uri("/"); - BOOST_CHECK( r.raw() == raw ); + BOOST_CHECK_EQUAL( r.raw(), raw ); } BOOST_AUTO_TEST_CASE( write_request_with_header ) { @@ -902,7 +949,7 @@ BOOST_AUTO_TEST_CASE( write_request_with_header ) { r.set_uri("/"); r.replace_header("Host","http://example.com"); - BOOST_CHECK( r.raw() == raw ); + BOOST_CHECK_EQUAL( r.raw(), raw ); } BOOST_AUTO_TEST_CASE( write_request_with_body ) { @@ -917,5 +964,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"); - BOOST_CHECK( r.raw() == raw ); + BOOST_CHECK_EQUAL( r.raw(), raw ); } diff --git a/test/processors/hybi00.cpp b/test/processors/hybi00.cpp index 65b5f55..de5a4e6 100644 --- a/test/processors/hybi00.cpp +++ b/test/processors/hybi00.cpp @@ -45,6 +45,8 @@ struct stub_config { message_type; typedef websocketpp::message_buffer::alloc::con_msg_manager con_msg_manager_type; + + static const size_t max_message_size = 16000000; }; struct processor_setup { diff --git a/test/processors/hybi07.cpp b/test/processors/hybi07.cpp index 2cddc8a..5966808 100644 --- a/test/processors/hybi07.cpp +++ b/test/processors/hybi07.cpp @@ -50,6 +50,8 @@ struct stub_config { typedef websocketpp::random::none::int_generator rng_type; + static const size_t max_message_size = 16000000; + /// Extension related config static const bool enable_extensions = false; diff --git a/test/processors/hybi08.cpp b/test/processors/hybi08.cpp index 60da59e..369de77 100644 --- a/test/processors/hybi08.cpp +++ b/test/processors/hybi08.cpp @@ -50,6 +50,8 @@ struct stub_config { typedef websocketpp::random::none::int_generator rng_type; + static const size_t max_message_size = 16000000; + /// Extension related config static const bool enable_extensions = false; diff --git a/test/processors/hybi13.cpp b/test/processors/hybi13.cpp index 0cacb56..e4c1bff 100644 --- a/test/processors/hybi13.cpp +++ b/test/processors/hybi13.cpp @@ -60,6 +60,7 @@ struct stub_config { typedef websocketpp::extensions::permessage_deflate::disabled permessage_deflate_type; + static const size_t max_message_size = 16000000; static const bool enable_extensions = false; }; @@ -81,6 +82,7 @@ struct stub_config_ext { typedef websocketpp::extensions::permessage_deflate::enabled permessage_deflate_type; + static const size_t max_message_size = 16000000; static const bool enable_extensions = true; }; @@ -489,6 +491,36 @@ BOOST_AUTO_TEST_CASE( prepare_data_frame ) { } +BOOST_AUTO_TEST_CASE( single_frame_message_too_large ) { + processor_setup env(true); + + env.p.set_max_message_size(3); + + uint8_t frame0[10] = {0x82, 0x84, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01}; + + // read message that is one byte too large + BOOST_CHECK_EQUAL( env.p.consume(frame0,10,env.ec), 6 ); + BOOST_CHECK_EQUAL( env.ec, websocketpp::processor::error::message_too_big ); +} + +BOOST_AUTO_TEST_CASE( multiple_frame_message_too_large ) { + processor_setup env(true); + + env.p.set_max_message_size(4); + + uint8_t frame0[8] = {0x02, 0x82, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}; + uint8_t frame1[9] = {0x80, 0x83, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01}; + + // read first message frame with size under the limit + BOOST_CHECK_EQUAL( env.p.consume(frame0,8,env.ec), 8 ); + BOOST_CHECK( !env.ec ); + + // read second message frame that puts the size over the limit + BOOST_CHECK_EQUAL( env.p.consume(frame1,9,env.ec), 6 ); + BOOST_CHECK_EQUAL( env.ec, websocketpp::processor::error::message_too_big ); +} + + BOOST_AUTO_TEST_CASE( client_handshake_request ) { processor_setup env(false); diff --git a/test/processors/processor.cpp b/test/processors/processor.cpp index aa3c038..0505646 100644 --- a/test/processors/processor.cpp +++ b/test/processors/processor.cpp @@ -132,4 +132,4 @@ BOOST_AUTO_TEST_CASE( version_non_numeric ) { r.consume(handshake.c_str(),handshake.size()); BOOST_CHECK(websocketpp::processor::get_websocket_version(r) == -1); -} +} \ No newline at end of file diff --git a/test/transport/asio/timers.cpp b/test/transport/asio/timers.cpp index f37d2e9..bec963f 100644 --- a/test/transport/asio/timers.cpp +++ b/test/transport/asio/timers.cpp @@ -95,6 +95,8 @@ struct config { typedef websocketpp::http::parser::response response_type; typedef websocketpp::transport::asio::tls_socket::endpoint socket_type; + static const bool enable_multithreading = true; + static const long timeout_socket_pre_init = 1000; static const long timeout_proxy = 1000; static const long timeout_socket_post_init = 1000; @@ -156,14 +158,12 @@ struct mock_endpoint : public websocketpp::transport::asio::endpoint { &mock_endpoint::handle_connect, this, m_con, - websocketpp::lib::placeholders::_1, - websocketpp::lib::placeholders::_2 + websocketpp::lib::placeholders::_1 ) ); } - void handle_connect(connection_ptr con, websocketpp::connection_hdl, - const websocketpp::lib::error_code & ec) + void handle_connect(connection_ptr con, websocketpp::lib::error_code const & ec) { BOOST_CHECK( !ec ); con->start(); diff --git a/test/transport/integration.cpp b/test/transport/integration.cpp index 1c12c87..6c18eb0 100644 --- a/test/transport/integration.cpp +++ b/test/transport/integration.cpp @@ -30,8 +30,11 @@ #include -#include -#include +#include +#include +#include +#include +#include #include #include @@ -77,9 +80,57 @@ struct config : public websocketpp::config::asio_client { static const long timeout_pong = 500; }; +struct config_tls : public websocketpp::config::asio_tls_client { + typedef config type; + typedef websocketpp::config::asio 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 : public base::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; + + //static const websocketpp::log::level elog_level = websocketpp::log::elevel::all; + //static const websocketpp::log::level alog_level = websocketpp::log::alevel::all; + + /// Length of time before an opening handshake is aborted + static const long timeout_open_handshake = 500; + /// Length of time before a closing handshake is aborted + static const long timeout_close_handshake = 500; + /// Length of time to wait for a pong after a ping + static const long timeout_pong = 500; +}; + typedef websocketpp::server server; typedef websocketpp::client client; +typedef websocketpp::server server_tls; +typedef websocketpp::client client_tls; + +typedef websocketpp::server iostream_server; +typedef websocketpp::client iostream_client; + using websocketpp::lib::placeholders::_1; using websocketpp::lib::placeholders::_2; using websocketpp::lib::bind; @@ -87,7 +138,10 @@ using websocketpp::lib::bind; template void close_after_timeout(T & e, websocketpp::connection_hdl hdl, long timeout) { sleep(timeout); - e.close(hdl,websocketpp::close::status::normal,""); + + websocketpp::lib::error_code ec; + e.close(hdl,websocketpp::close::status::normal,"",ec); + BOOST_CHECK(!ec); } void run_server(server * s, int port, bool log = false) { @@ -100,6 +154,7 @@ void run_server(server * s, int port, bool log = false) { } s->init_asio(); + s->set_reuse_addr(true); s->listen(port); s->start_accept(); @@ -114,9 +169,11 @@ void run_client(client & c, std::string uri, bool log = false) { c.clear_access_channels(websocketpp::log::alevel::all); c.clear_error_channels(websocketpp::log::elevel::all); } - c.init_asio(); - websocketpp::lib::error_code ec; + c.init_asio(ec); + c.set_reuse_addr(true); + BOOST_CHECK(!ec); + client::connection_ptr con = c.get_connection(uri,ec); BOOST_CHECK( !ec ); c.connect(con); @@ -124,6 +181,14 @@ void run_client(client & c, std::string uri, bool log = false) { c.run(); } +void run_client_and_mark(client * c, bool * flag, websocketpp::lib::mutex * mutex) { + c->run(); + BOOST_CHECK( true ); + websocketpp::lib::lock_guard lock(*mutex); + *flag = true; + BOOST_CHECK( true ); +} + void run_time_limited_client(client & c, std::string uri, long timeout, bool log) { @@ -391,7 +456,7 @@ BOOST_AUTO_TEST_CASE( client_self_initiated_close_handshake_timeout ) { websocketpp::lib::thread tthread(websocketpp::lib::bind(&run_test_timer,6)); tthread.detach(); - run_client(c, "http://localhost:9005",false); + run_client(c, "http://localhost:9005", false); sthread.join(); } @@ -427,3 +492,115 @@ BOOST_AUTO_TEST_CASE( server_self_initiated_close_handshake_timeout ) { sthread.join(); } + +BOOST_AUTO_TEST_CASE( client_runs_out_of_work ) { + client c; + + websocketpp::lib::thread tthread(websocketpp::lib::bind(&run_test_timer,3)); + tthread.detach(); + + websocketpp::lib::error_code ec; + c.init_asio(ec); + BOOST_CHECK(!ec); + + c.run(); + + // This test checks that an io_service with no work ends immediately. + BOOST_CHECK(true); +} + + + + +BOOST_AUTO_TEST_CASE( client_is_perpetual ) { + client c; + bool flag = false; + websocketpp::lib::mutex mutex; + + websocketpp::lib::error_code ec; + c.init_asio(ec); + BOOST_CHECK(!ec); + + c.start_perpetual(); + + websocketpp::lib::thread cthread(websocketpp::lib::bind(&run_client_and_mark,&c,&flag,&mutex)); + + sleep(1); + + { + // Checks that the thread hasn't exited yet + websocketpp::lib::lock_guard lock(mutex); + BOOST_CHECK( !flag ); + } + + c.stop_perpetual(); + + sleep(1); + + { + // Checks that the thread has exited + websocketpp::lib::lock_guard lock(mutex); + BOOST_CHECK( flag ); + } + + cthread.join(); +} + +BOOST_AUTO_TEST_CASE( client_failed_connection ) { + client c; + + run_time_limited_client(c,"http://localhost:9005", 5, false); +} + +BOOST_AUTO_TEST_CASE( stop_listening ) { + server s; + client c; + + // the first connection stops the server from listening + s.set_open_handler(bind(&cancel_on_open,&s,::_1)); + + // client immediately closes after opening a connection + c.set_open_handler(bind(&close,&c,::_1)); + + websocketpp::lib::thread sthread(websocketpp::lib::bind(&run_server,&s,9005,false)); + websocketpp::lib::thread tthread(websocketpp::lib::bind(&run_test_timer,2)); + tthread.detach(); + + run_client(c, "http://localhost:9005",false); + + sthread.join(); +} + +BOOST_AUTO_TEST_CASE( pause_reading ) { + iostream_server s; + std::string handshake = "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\n\r\n"; + char buffer[2] = { char(0x81), char(0x80) }; + + // suppress output (it needs a place to go to avoid error but we don't care what it is) + std::stringstream null_output; + s.register_ostream(&null_output); + + iostream_server::connection_ptr con = s.get_connection(); + con->start(); + + // read handshake, should work + BOOST_CHECK_EQUAL( con->read_some(handshake.data(), handshake.length()), handshake.length()); + + // pause reading and try again. The first read should work, the second should return 0 + // the first read was queued already after the handshake so it will go through because + // reading wasn't paused when it was queued. The byte it reads wont be enough to + // complete the frame so another read will be requested. This one wont actually happen + // because the connection is paused now. + con->pause_reading(); + BOOST_CHECK_EQUAL( con->read_some(buffer, 1), 1); + BOOST_CHECK_EQUAL( con->read_some(buffer+1, 1), 0); + // resume reading and try again. Should work this time because the resume should have + // re-queued a read. + con->resume_reading(); + BOOST_CHECK_EQUAL( con->read_some(buffer+1, 1), 1); +} + + +BOOST_AUTO_TEST_CASE( server_connection_cleanup ) { + server_tls s; +} \ No newline at end of file diff --git a/test/transport/iostream/connection.cpp b/test/transport/iostream/connection.cpp index 200f3e5..234d1b1 100644 --- a/test/transport/iostream/connection.cpp +++ b/test/transport/iostream/connection.cpp @@ -32,6 +32,8 @@ #include #include +#include + #include #include @@ -52,22 +54,29 @@ typedef websocketpp::transport::iostream::connection iostream_con; using websocketpp::transport::iostream::error::make_error_code; struct stub_con : public iostream_con { + typedef stub_con type; + typedef websocketpp::lib::shared_ptr ptr; typedef iostream_con::timer_ptr timer_ptr; - stub_con(bool is_server, config::alog_type &a, config::elog_type & e) + stub_con(bool is_server, config::alog_type & a, config::elog_type & e) : iostream_con(is_server,a,e) // Set the error to a known code that is unused by the library // This way we can easily confirm that the handler was run at all. , ec(websocketpp::error::make_error_code(websocketpp::error::test)) {} + /// Get a shared pointer to this component + ptr get_shared() { + return websocketpp::lib::static_pointer_cast(iostream_con::get_shared()); + } + void write(std::string msg) { iostream_con::async_write( msg.data(), msg.size(), websocketpp::lib::bind( &stub_con::handle_op, - this, + type::get_shared(), websocketpp::lib::placeholders::_1 ) ); @@ -78,7 +87,7 @@ struct stub_con : public iostream_con { bufs, websocketpp::lib::bind( &stub_con::handle_op, - this, + type::get_shared(), websocketpp::lib::placeholders::_1 ) ); @@ -92,13 +101,13 @@ struct stub_con : public iostream_con { len, websocketpp::lib::bind( &stub_con::handle_op, - this, + type::get_shared(), websocketpp::lib::placeholders::_1 ) ); } - void handle_op(const websocketpp::lib::error_code& e) { + void handle_op(websocketpp::lib::error_code const & e) { ec = e; } @@ -106,59 +115,59 @@ struct stub_con : public iostream_con { }; // Stubs -config::alog_type a; -config::elog_type e; +config::alog_type alogger; +config::elog_type elogger; BOOST_AUTO_TEST_CASE( const_methods ) { - iostream_con con(true,a,e); + iostream_con::ptr con(new iostream_con(true,alogger,elogger)); - BOOST_CHECK( con.is_secure() == false ); - BOOST_CHECK( con.get_remote_endpoint() == "iostream transport" ); + BOOST_CHECK( con->is_secure() == false ); + BOOST_CHECK( con->get_remote_endpoint() == "iostream transport" ); } BOOST_AUTO_TEST_CASE( write_before_ostream_set ) { - stub_con con(true,a,e); + stub_con::ptr con(new stub_con(true,alogger,elogger)); - con.write("foo"); - BOOST_CHECK( con.ec == make_error_code(websocketpp::transport::iostream::error::output_stream_required) ); + con->write("foo"); + BOOST_CHECK( con->ec == make_error_code(websocketpp::transport::iostream::error::output_stream_required) ); std::vector bufs; - con.write(bufs); - BOOST_CHECK( con.ec == make_error_code(websocketpp::transport::iostream::error::output_stream_required) ); + con->write(bufs); + BOOST_CHECK( con->ec == make_error_code(websocketpp::transport::iostream::error::output_stream_required) ); } BOOST_AUTO_TEST_CASE( async_write ) { - stub_con con(true,a,e); + stub_con::ptr con(new stub_con(true,alogger,elogger)); std::stringstream output; - con.register_ostream(&output); + con->register_ostream(&output); - con.write("foo"); + con->write("foo"); - BOOST_CHECK( !con.ec ); + BOOST_CHECK( !con->ec ); BOOST_CHECK( output.str() == "foo" ); } BOOST_AUTO_TEST_CASE( async_write_vector_0 ) { std::stringstream output; - stub_con con(true,a,e); - con.register_ostream(&output); + stub_con::ptr con(new stub_con(true,alogger,elogger)); + con->register_ostream(&output); std::vector bufs; - con.write(bufs); + con->write(bufs); - BOOST_CHECK( !con.ec ); + BOOST_CHECK( !con->ec ); BOOST_CHECK( output.str() == "" ); } BOOST_AUTO_TEST_CASE( async_write_vector_1 ) { std::stringstream output; - stub_con con(true,a,e); - con.register_ostream(&output); + stub_con::ptr con(new stub_con(true,alogger,elogger)); + con->register_ostream(&output); std::vector bufs; @@ -166,17 +175,17 @@ BOOST_AUTO_TEST_CASE( async_write_vector_1 ) { bufs.push_back(websocketpp::transport::buffer(foo.data(),foo.size())); - con.write(bufs); + con->write(bufs); - BOOST_CHECK( !con.ec ); + BOOST_CHECK( !con->ec ); BOOST_CHECK( output.str() == "foo" ); } BOOST_AUTO_TEST_CASE( async_write_vector_2 ) { std::stringstream output; - stub_con con(true,a,e); - con.register_ostream(&output); + stub_con::ptr con(new stub_con(true,alogger,elogger)); + con->register_ostream(&output); std::vector bufs; @@ -186,97 +195,167 @@ BOOST_AUTO_TEST_CASE( async_write_vector_2 ) { bufs.push_back(websocketpp::transport::buffer(foo.data(),foo.size())); bufs.push_back(websocketpp::transport::buffer(bar.data(),bar.size())); - con.write(bufs); + con->write(bufs); - BOOST_CHECK( !con.ec ); + BOOST_CHECK( !con->ec ); BOOST_CHECK( output.str() == "foobar" ); } BOOST_AUTO_TEST_CASE( async_read_at_least_too_much ) { - stub_con con(true,a,e); + stub_con::ptr con(new stub_con(true,alogger,elogger)); char buf[10]; - con.async_read_at_least(11,buf,10); - BOOST_CHECK( con.ec == make_error_code(websocketpp::transport::iostream::error::invalid_num_bytes) ); + con->async_read_at_least(11,buf,10); + BOOST_CHECK( con->ec == make_error_code(websocketpp::transport::iostream::error::invalid_num_bytes) ); } BOOST_AUTO_TEST_CASE( async_read_at_least_double_read ) { - stub_con con(true,a,e); + stub_con::ptr con(new stub_con(true,alogger,elogger)); char buf[10]; - con.async_read_at_least(5,buf,10); - con.async_read_at_least(5,buf,10); - BOOST_CHECK( con.ec == make_error_code(websocketpp::transport::iostream::error::double_read) ); + con->async_read_at_least(5,buf,10); + con->async_read_at_least(5,buf,10); + BOOST_CHECK( con->ec == make_error_code(websocketpp::transport::iostream::error::double_read) ); } BOOST_AUTO_TEST_CASE( async_read_at_least ) { - stub_con con(true,a,e); + stub_con::ptr con(new stub_con(true,alogger,elogger)); char buf[10]; memset(buf,'x',10); - con.async_read_at_least(5,buf,10); - BOOST_CHECK( con.ec == make_error_code(websocketpp::error::test) ); + con->async_read_at_least(5,buf,10); + BOOST_CHECK( con->ec == make_error_code(websocketpp::error::test) ); std::stringstream channel; channel << "abcd"; - channel >> con; + channel >> *con; BOOST_CHECK( channel.tellg() == -1 ); - BOOST_CHECK( con.ec == make_error_code(websocketpp::error::test) ); + BOOST_CHECK( con->ec == make_error_code(websocketpp::error::test) ); std::stringstream channel2; channel2 << "e"; - channel2 >> con; + channel2 >> *con; BOOST_CHECK( channel2.tellg() == -1 ); - BOOST_CHECK( !con.ec ); + BOOST_CHECK( !con->ec ); BOOST_CHECK( std::string(buf,10) == "abcdexxxxx" ); std::stringstream channel3; channel3 << "f"; - channel3 >> con; + channel3 >> *con; BOOST_CHECK( channel3.tellg() == 0 ); - BOOST_CHECK( !con.ec ); + BOOST_CHECK( !con->ec ); BOOST_CHECK( std::string(buf,10) == "abcdexxxxx" ); - con.async_read_at_least(1,buf+5,5); - channel3 >> con; + con->async_read_at_least(1,buf+5,5); + channel3 >> *con; BOOST_CHECK( channel3.tellg() == -1 ); - BOOST_CHECK( !con.ec ); + BOOST_CHECK( !con->ec ); BOOST_CHECK( std::string(buf,10) == "abcdefxxxx" ); } BOOST_AUTO_TEST_CASE( async_read_at_least2 ) { - stub_con con(true,a,e); + stub_con::ptr con(new stub_con(true,alogger,elogger)); char buf[10]; memset(buf,'x',10); - con.async_read_at_least(5,buf,5); - BOOST_CHECK( con.ec == make_error_code(websocketpp::error::test) ); + con->async_read_at_least(5,buf,5); + BOOST_CHECK( con->ec == make_error_code(websocketpp::error::test) ); std::stringstream channel; channel << "abcdefg"; - channel >> con; + channel >> *con; BOOST_CHECK( channel.tellg() == 5 ); - BOOST_CHECK( !con.ec ); + BOOST_CHECK( !con->ec ); BOOST_CHECK( std::string(buf,10) == "abcdexxxxx" ); - con.async_read_at_least(1,buf+5,5); - channel >> con; + con->async_read_at_least(1,buf+5,5); + channel >> *con; BOOST_CHECK( channel.tellg() == -1 ); - BOOST_CHECK( !con.ec ); + BOOST_CHECK( !con->ec ); BOOST_CHECK( std::string(buf,10) == "abcdefgxxx" ); } void timer_callback_stub(const websocketpp::lib::error_code & ec) {} BOOST_AUTO_TEST_CASE( set_timer ) { - stub_con con(true,a,e); + stub_con::ptr con(new stub_con(true,alogger,elogger)); - stub_con::timer_ptr tp = con.set_timer(1000,timer_callback_stub); + stub_con::timer_ptr tp = con->set_timer(1000,timer_callback_stub); BOOST_CHECK( !tp ); } + +BOOST_AUTO_TEST_CASE( async_read_at_least_read_some ) { + stub_con::ptr con(new stub_con(true,alogger,elogger)); + + char buf[10]; + memset(buf,'x',10); + + con->async_read_at_least(5,buf,5); + BOOST_CHECK( con->ec == make_error_code(websocketpp::error::test) ); + + char input[10] = "abcdefg"; + BOOST_CHECK_EQUAL(con->read_some(input,5), 5); + BOOST_CHECK( !con->ec ); + BOOST_CHECK_EQUAL( std::string(buf,10), "abcdexxxxx" ); + + BOOST_CHECK_EQUAL(con->read_some(input+5,2), 0); + BOOST_CHECK( !con->ec ); + BOOST_CHECK_EQUAL( std::string(buf,10), "abcdexxxxx" ); + + con->async_read_at_least(1,buf+5,5); + BOOST_CHECK_EQUAL(con->read_some(input+5,2), 2); + BOOST_CHECK( !con->ec ); + BOOST_CHECK_EQUAL( std::string(buf,10), "abcdefgxxx" ); +} + +BOOST_AUTO_TEST_CASE( eof_flag ) { + stub_con::ptr con(new stub_con(true,alogger,elogger)); + char buf[10]; + con->async_read_at_least(5,buf,5); + BOOST_CHECK( con->ec == make_error_code(websocketpp::error::test) ); + con->eof(); + BOOST_CHECK_EQUAL( con->ec, make_error_code(websocketpp::transport::error::eof) ); +} + +BOOST_AUTO_TEST_CASE( fatal_error_flag ) { + stub_con::ptr con(new stub_con(true,alogger,elogger)); + char buf[10]; + con->async_read_at_least(5,buf,5); + BOOST_CHECK( con->ec == make_error_code(websocketpp::error::test) ); + con->fatal_error(); + BOOST_CHECK_EQUAL( con->ec, make_error_code(websocketpp::transport::error::pass_through) ); +} + +BOOST_AUTO_TEST_CASE( shared_pointer_memory_cleanup ) { + stub_con::ptr con(new stub_con(true,alogger,elogger)); + + BOOST_CHECK_EQUAL(con.use_count(), 1); + + char buf[10]; + memset(buf,'x',10); + con->async_read_at_least(5,buf,5); + BOOST_CHECK( con->ec == make_error_code(websocketpp::error::test) ); + BOOST_CHECK_EQUAL(con.use_count(), 2); + + char input[10] = "foo"; + con->read_some(input,3); + BOOST_CHECK_EQUAL(con.use_count(), 2); + + con->read_some(input,2); + BOOST_CHECK_EQUAL( std::string(buf,10), "foofoxxxxx" ); + BOOST_CHECK_EQUAL(con.use_count(), 1); + + con->async_read_at_least(5,buf,5); + BOOST_CHECK_EQUAL(con.use_count(), 2); + + con->eof(); + BOOST_CHECK_EQUAL( con->ec, make_error_code(websocketpp::transport::error::eof) ); + BOOST_CHECK_EQUAL(con.use_count(), 1); +} + diff --git a/test/utility/sha1.cpp b/test/utility/sha1.cpp index 500e297..ab25f97 100644 --- a/test/utility/sha1.cpp +++ b/test/utility/sha1.cpp @@ -37,48 +37,45 @@ BOOST_AUTO_TEST_SUITE ( sha1 ) BOOST_AUTO_TEST_CASE( sha1_test_a ) { - websocketpp::sha1 sha; - uint32_t digest[5]; + unsigned char hash[20]; + unsigned char reference[20] = {0xa9, 0x99, 0x3e, 0x36, 0x47, + 0x06, 0x81, 0x6a, 0xba, 0x3e, + 0x25, 0x71, 0x78, 0x50, 0xc2, + 0x6c, 0x9c, 0xd0, 0xd8, 0x9d}; - sha << "abc"; - BOOST_CHECK(sha.get_raw_digest(digest)); + websocketpp::sha1::calc("abc",3,hash); - BOOST_CHECK_EQUAL( digest[0], 0xa9993e36 ); - BOOST_CHECK_EQUAL( digest[1], 0x4706816a ); - BOOST_CHECK_EQUAL( digest[2], 0xba3e2571 ); - BOOST_CHECK_EQUAL( digest[3], 0x7850c26c ); - BOOST_CHECK_EQUAL( digest[4], 0x9cd0d89d ); + BOOST_CHECK_EQUAL_COLLECTIONS(hash, hash+20, reference, reference+20); } BOOST_AUTO_TEST_CASE( sha1_test_b ) { - websocketpp::sha1 sha; - uint32_t digest[5]; + unsigned char hash[20]; + unsigned char reference[20] = {0x84, 0x98, 0x3e, 0x44, 0x1c, + 0x3b, 0xd2, 0x6e, 0xba, 0xae, + 0x4a, 0xa1, 0xf9, 0x51, 0x29, + 0xe5, 0xe5, 0x46, 0x70, 0xf1}; - sha << "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; - BOOST_CHECK(sha.get_raw_digest(digest)); + websocketpp::sha1::calc( + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",56,hash); - BOOST_CHECK_EQUAL( digest[0], 0x84983e44 ); - BOOST_CHECK_EQUAL( digest[1], 0x1c3bd26e ); - BOOST_CHECK_EQUAL( digest[2], 0xbaae4aa1 ); - BOOST_CHECK_EQUAL( digest[3], 0xf95129e5 ); - BOOST_CHECK_EQUAL( digest[4], 0xe54670f1 ); + BOOST_CHECK_EQUAL_COLLECTIONS(hash, hash+20, reference, reference+20); } BOOST_AUTO_TEST_CASE( sha1_test_c ) { - websocketpp::sha1 sha; - uint32_t digest[5]; + std::string input; + unsigned char hash[20]; + unsigned char reference[20] = {0x34, 0xaa, 0x97, 0x3c, 0xd4, + 0xc4, 0xda, 0xa4, 0xf6, 0x1e, + 0xeb, 0x2b, 0xdb, 0xad, 0x27, + 0x31, 0x65, 0x34, 0x01, 0x6f}; - for (int i = 1; i <= 1000000; i++) { - sha.input('a'); + for (int i = 0; i < 1000000; i++) { + input += 'a'; } - BOOST_CHECK(sha.get_raw_digest(digest)); + websocketpp::sha1::calc(input.c_str(),input.size(),hash); - BOOST_CHECK_EQUAL( digest[0], 0x34aa973c ); - BOOST_CHECK_EQUAL( digest[1], 0xd4c4daa4 ); - BOOST_CHECK_EQUAL( digest[2], 0xf61eeb2b ); - BOOST_CHECK_EQUAL( digest[3], 0xdbad2731 ); - BOOST_CHECK_EQUAL( digest[4], 0x6534016f ); + BOOST_CHECK_EQUAL_COLLECTIONS(hash, hash+20, reference, reference+20); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/utility/uri.cpp b/test/utility/uri.cpp index e88a8d9..922a91b 100644 --- a/test/utility/uri.cpp +++ b/test/utility/uri.cpp @@ -43,6 +43,7 @@ BOOST_AUTO_TEST_CASE( uri_valid ) { BOOST_CHECK_EQUAL( uri.get_host(), "localhost"); BOOST_CHECK_EQUAL( uri.get_port(), 9000 ); BOOST_CHECK_EQUAL( uri.get_resource(), "/chat" ); + BOOST_CHECK_EQUAL( uri.get_query(), "" ); } // Test a regular valid ws URI @@ -201,6 +202,7 @@ BOOST_AUTO_TEST_CASE( uri_valid_4 ) { BOOST_CHECK_EQUAL( uri.get_host(), "localhost"); BOOST_CHECK_EQUAL( uri.get_port(), 9000 ); BOOST_CHECK_EQUAL( uri.get_resource(), "/chat/foo/bar?foo=bar" ); + BOOST_CHECK_EQUAL( uri.get_query(), "foo=bar" ); } // Valid URI with a mapped v4 ipv6 literal @@ -227,6 +229,13 @@ BOOST_AUTO_TEST_CASE( uri_valid_v6_mixed_case ) { BOOST_CHECK_EQUAL( uri.get_resource(), "/" ); } +// Valid URI with a v6 address with mixed case +BOOST_AUTO_TEST_CASE( uri_invalid_no_scheme ) { + websocketpp::uri uri("myserver.com"); + + BOOST_CHECK( !uri.get_valid() ); +} + // Invalid IPv6 literal /*BOOST_AUTO_TEST_CASE( uri_invalid_v6_nonhex ) { websocketpp::uri uri("wss://[g::1]:9000/"); diff --git a/tutorials/broadcast_tutorial/broadcast_tutorial.md b/tutorials/broadcast_tutorial/broadcast_tutorial.md new file mode 100644 index 0000000..02e5001 --- /dev/null +++ b/tutorials/broadcast_tutorial/broadcast_tutorial.md @@ -0,0 +1,17 @@ +Broadcast Tutorial +================== + +This tutorial will dig into some more nitty gritty details on how to build high +scalability, high performance websocket servers for broadcast like workflows. + +Will go into features like: +- minimizing work done in handlers +- using asio thread pool mode +- teaming multiple endpoints +- setting accept queue depth +- tuning compile time buffer sizes +- prepared messages +- flow control +- basic operating system level tuning, particularly increasing file descriptor limits. +- measuring performance with wsperf / autobahn +- tuning permessage-deflate compression settings \ No newline at end of file diff --git a/tutorials/chat_tutorial/chat_tutorial.md b/tutorials/chat_tutorial/chat_tutorial.md new file mode 100644 index 0000000..e50fca5 --- /dev/null +++ b/tutorials/chat_tutorial/chat_tutorial.md @@ -0,0 +1,13 @@ +Chat Tutorial +============= + +Goals of this tutorial: +- Impliment a realtime websocket chat server + + +Server +- Nicknames +- Channels +- Subprotocol +- Origin restrictions +- HTTP statistics page diff --git a/tutorials/utility_client/step1.cpp b/tutorials/utility_client/step1.cpp new file mode 100644 index 0000000..9c8fad9 --- /dev/null +++ b/tutorials/utility_client/step1.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2014, 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. + */ + +// **NOTE:** This file is a snapshot of the WebSocket++ utility client tutorial. +// Additional related material can be found in the tutorials/utility_client +// directory of the WebSocket++ repository. + +#include +#include + +int main() { + bool done = false; + std::string input; + + while (!done) { + std::cout << "Enter Command: "; + std::getline(std::cin, input); + + if (input == "quit") { + done = true; + } else if (input == "help") { + std::cout + << "\nCommand List:\n" + << "help: Display this help text\n" + << "quit: Exit the program\n" + << std::endl; + } else { + std::cout << "Unrecognized Command" << std::endl; + } + } + + return 0; +} diff --git a/tutorials/utility_client/step2.cpp b/tutorials/utility_client/step2.cpp new file mode 100644 index 0000000..a1e5796 --- /dev/null +++ b/tutorials/utility_client/step2.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2014, 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. + */ + +// **NOTE:** This file is a snapshot of the WebSocket++ utility client tutorial. +// Additional related material can be found in the tutorials/utility_client +// directory of the WebSocket++ repository. + +#include +#include + +#include +#include + +typedef websocketpp::client client; + +int main() { + bool done = false; + std::string input; + + while (!done) { + std::cout << "Enter Command: "; + std::getline(std::cin, input); + + if (input == "quit") { + done = true; + } else if (input == "help") { + std::cout + << "\nCommand List:\n" + << "help: Display this help text\n" + << "quit: Exit the program\n" + << std::endl; + } else { + std::cout << "Unrecognized Command" << std::endl; + } + } + + return 0; +} diff --git a/tutorials/utility_client/step3.cpp b/tutorials/utility_client/step3.cpp new file mode 100644 index 0000000..42441f2 --- /dev/null +++ b/tutorials/utility_client/step3.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2014, 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. + */ + +// **NOTE:** This file is a snapshot of the WebSocket++ utility client tutorial. +// Additional related material can be found in the tutorials/utility_client +// directory of the WebSocket++ repository. + +#include +#include + +#include +#include + +#include +#include + +typedef websocketpp::client client; + +class websocket_endpoint { +public: + websocket_endpoint () { + m_endpoint.clear_access_channels(websocketpp::log::alevel::all); + m_endpoint.clear_error_channels(websocketpp::log::elevel::all); + + m_endpoint.init_asio(); + m_endpoint.start_perpetual(); + + m_thread.reset(new websocketpp::lib::thread(&client::run, &m_endpoint)); + } +private: + client m_endpoint; + websocketpp::lib::shared_ptr m_thread; +}; + +int main() { + bool done = false; + std::string input; + websocket_endpoint endpoint; + + while (!done) { + std::cout << "Enter Command: "; + std::getline(std::cin, input); + + if (input == "quit") { + done = true; + } else if (input == "help") { + std::cout + << "\nCommand List:\n" + << "help: Display this help text\n" + << "quit: Exit the program\n" + << std::endl; + } else { + std::cout << "Unrecognized Command" << std::endl; + } + } + + return 0; +} diff --git a/tutorials/utility_client/step4.cpp b/tutorials/utility_client/step4.cpp new file mode 100644 index 0000000..9c2a00b --- /dev/null +++ b/tutorials/utility_client/step4.cpp @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2014, 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. + */ + +// **NOTE:** This file is a snapshot of the WebSocket++ utility client tutorial. +// Additional related material can be found in the tutorials/utility_client +// directory of the WebSocket++ repository. + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +typedef websocketpp::client client; + +class connection_metadata { +public: + typedef websocketpp::lib::shared_ptr ptr; + + connection_metadata(int id, websocketpp::connection_hdl hdl, std::string uri) + : m_id(id) + , m_hdl(hdl) + , m_status("Connecting") + , m_uri(uri) + , m_server("N/A") + {} + + void on_open(client * c, websocketpp::connection_hdl hdl) { + m_status = "Open"; + + client::connection_ptr con = c->get_con_from_hdl(hdl); + m_server = con->get_response_header("Server"); + } + + void on_fail(client * c, websocketpp::connection_hdl hdl) { + m_status = "Failed"; + + client::connection_ptr con = c->get_con_from_hdl(hdl); + m_server = con->get_response_header("Server"); + m_error_reason = con->get_ec().message(); + } + + friend std::ostream & operator<< (std::ostream & out, connection_metadata const & data); +private: + int m_id; + websocketpp::connection_hdl m_hdl; + std::string m_status; + std::string m_uri; + std::string m_server; + std::string m_error_reason; +}; + +std::ostream & operator<< (std::ostream & out, connection_metadata const & data) { + out << "> URI: " << data.m_uri << "\n" + << "> Status: " << data.m_status << "\n" + << "> Remote Server: " << (data.m_server.empty() ? "None Specified" : data.m_server) << "\n" + << "> Error/close reason: " << (data.m_error_reason.empty() ? "N/A" : data.m_error_reason); + + return out; +} + +class websocket_endpoint { +public: + websocket_endpoint () : m_next_id(0) { + m_endpoint.clear_access_channels(websocketpp::log::alevel::all); + m_endpoint.clear_error_channels(websocketpp::log::elevel::all); + + m_endpoint.init_asio(); + m_endpoint.start_perpetual(); + + m_thread.reset(new websocketpp::lib::thread(&client::run, &m_endpoint)); + } + + int connect(std::string const & uri) { + websocketpp::lib::error_code ec; + + client::connection_ptr con = m_endpoint.get_connection(uri, ec); + + if (ec) { + std::cout << "> Connect initialization error: " << ec.message() << std::endl; + return -1; + } + + int new_id = m_next_id++; + connection_metadata::ptr metadata_ptr(new connection_metadata(new_id, con->get_handle(), uri)); + m_connection_list[new_id] = metadata_ptr; + + con->set_open_handler(websocketpp::lib::bind( + &connection_metadata::on_open, + metadata_ptr, + &m_endpoint, + websocketpp::lib::placeholders::_1 + )); + con->set_fail_handler(websocketpp::lib::bind( + &connection_metadata::on_fail, + metadata_ptr, + &m_endpoint, + websocketpp::lib::placeholders::_1 + )); + + m_endpoint.connect(con); + + return new_id; + } + + connection_metadata::ptr get_metadata(int id) const { + con_list::const_iterator metadata_it = m_connection_list.find(id); + if (metadata_it == m_connection_list.end()) { + return connection_metadata::ptr(); + } else { + return metadata_it->second; + } + } +private: + typedef std::map con_list; + + client m_endpoint; + websocketpp::lib::shared_ptr m_thread; + + con_list m_connection_list; + int m_next_id; +}; + +int main() { + bool done = false; + std::string input; + websocket_endpoint endpoint; + + while (!done) { + std::cout << "Enter Command: "; + std::getline(std::cin, input); + + if (input == "quit") { + done = true; + } else if (input == "help") { + std::cout + << "\nCommand List:\n" + << "connect \n" + << "show \n" + << "help: Display this help text\n" + << "quit: Exit the program\n" + << std::endl; + } else if (input.substr(0,7) == "connect") { + int id = endpoint.connect(input.substr(8)); + if (id != -1) { + std::cout << "> Created connection with id " << id << std::endl; + } + } else if (input.substr(0,4) == "show") { + int id = atoi(input.substr(5).c_str()); + + connection_metadata::ptr metadata = endpoint.get_metadata(id); + if (metadata) { + std::cout << *metadata << std::endl; + } else { + std::cout << "> Unknown connection id " << id << std::endl; + } + } else { + std::cout << "> Unrecognized Command" << std::endl; + } + } + + return 0; +} + +/* + +clang++ -std=c++11 -stdlib=libc++ -I/Users/zaphoyd/software/websocketpp/ -I/Users/zaphoyd/software/boost_1_55_0/ -D_WEBSOCKETPP_CPP11_STL_ step4.cpp /Users/zaphoyd/software/boost_1_55_0/stage/lib/libboost_system.a + +clang++ -I/Users/zaphoyd/software/websocketpp/ -I/Users/zaphoyd/software/boost_1_55_0/ step4.cpp /Users/zaphoyd/software/boost_1_55_0/stage/lib/libboost_system.a /Users/zaphoyd/software/boost_1_55_0/stage/lib/libboost_thread.a /Users/zaphoyd/software/boost_1_55_0/stage/lib/libboost_random.a + +clang++ -std=c++11 -stdlib=libc++ -I/Users/zaphoyd/Documents/websocketpp/ -I/Users/zaphoyd/Documents/boost_1_53_0_libcpp/ -D_WEBSOCKETPP_CPP11_STL_ step4.cpp /Users/zaphoyd/Documents/boost_1_53_0_libcpp/stage/lib/libboost_system.a + +*/ diff --git a/tutorials/utility_client/step5.cpp b/tutorials/utility_client/step5.cpp new file mode 100644 index 0000000..130c608 --- /dev/null +++ b/tutorials/utility_client/step5.cpp @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2014, 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. + */ + +// **NOTE:** This file is a snapshot of the WebSocket++ utility client tutorial. +// Additional related material can be found in the tutorials/utility_client +// directory of the WebSocket++ repository. + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +typedef websocketpp::client client; + +class connection_metadata { +public: + typedef websocketpp::lib::shared_ptr ptr; + + connection_metadata(int id, websocketpp::connection_hdl hdl, std::string uri) + : m_id(id) + , m_hdl(hdl) + , m_status("Connecting") + , m_uri(uri) + , m_server("N/A") + {} + + void on_open(client * c, websocketpp::connection_hdl hdl) { + m_status = "Open"; + + client::connection_ptr con = c->get_con_from_hdl(hdl); + m_server = con->get_response_header("Server"); + } + + void on_fail(client * c, websocketpp::connection_hdl hdl) { + m_status = "Failed"; + + client::connection_ptr con = c->get_con_from_hdl(hdl); + m_server = con->get_response_header("Server"); + m_error_reason = con->get_ec().message(); + } + + void on_close(client * c, websocketpp::connection_hdl hdl) { + m_status = "Closed"; + client::connection_ptr con = c->get_con_from_hdl(hdl); + std::stringstream s; + s << "close code: " << con->get_remote_close_code() << " (" + << websocketpp::close::status::get_string(con->get_remote_close_code()) + << "), close reason: " << con->get_remote_close_reason(); + m_error_reason = s.str(); + } + + websocketpp::connection_hdl get_hdl() const { + return m_hdl; + } + + int get_id() const { + return m_id; + } + + std::string get_status() const { + return m_status; + } + + friend std::ostream & operator<< (std::ostream & out, connection_metadata const & data); +private: + int m_id; + websocketpp::connection_hdl m_hdl; + std::string m_status; + std::string m_uri; + std::string m_server; + std::string m_error_reason; +}; + +std::ostream & operator<< (std::ostream & out, connection_metadata const & data) { + out << "> URI: " << data.m_uri << "\n" + << "> Status: " << data.m_status << "\n" + << "> Remote Server: " << (data.m_server.empty() ? "None Specified" : data.m_server) << "\n" + << "> Error/close reason: " << (data.m_error_reason.empty() ? "N/A" : data.m_error_reason); + + return out; +} + +class websocket_endpoint { +public: + websocket_endpoint () : m_next_id(0) { + m_endpoint.clear_access_channels(websocketpp::log::alevel::all); + m_endpoint.clear_error_channels(websocketpp::log::elevel::all); + + m_endpoint.init_asio(); + m_endpoint.start_perpetual(); + + m_thread.reset(new websocketpp::lib::thread(&client::run, &m_endpoint)); + } + + ~websocket_endpoint() { + m_endpoint.stop_perpetual(); + + for (con_list::const_iterator it = m_connection_list.begin(); it != m_connection_list.end(); ++it) { + if (it->second->get_status() != "Open") { + // Only close open connections + continue; + } + + std::cout << "> Closing connection " << it->second->get_id() << std::endl; + + websocketpp::lib::error_code ec; + m_endpoint.close(it->second->get_hdl(), websocketpp::close::status::going_away, "", ec); + if (ec) { + std::cout << "> Error closing connection " << it->second->get_id() << ": " + << ec.message() << std::endl; + } + } + + m_thread->join(); + } + + int connect(std::string const & uri) { + websocketpp::lib::error_code ec; + + client::connection_ptr con = m_endpoint.get_connection(uri, ec); + + if (ec) { + std::cout << "> Connect initialization error: " << ec.message() << std::endl; + return -1; + } + + int new_id = m_next_id++; + connection_metadata::ptr metadata_ptr(new connection_metadata(new_id, con->get_handle(), uri)); + m_connection_list[new_id] = metadata_ptr; + + con->set_open_handler(websocketpp::lib::bind( + &connection_metadata::on_open, + metadata_ptr, + &m_endpoint, + websocketpp::lib::placeholders::_1 + )); + con->set_fail_handler(websocketpp::lib::bind( + &connection_metadata::on_fail, + metadata_ptr, + &m_endpoint, + websocketpp::lib::placeholders::_1 + )); + con->set_close_handler(websocketpp::lib::bind( + &connection_metadata::on_close, + metadata_ptr, + &m_endpoint, + websocketpp::lib::placeholders::_1 + )); + + m_endpoint.connect(con); + + return new_id; + } + + void close(int id, websocketpp::close::status::value code, std::string reason) { + websocketpp::lib::error_code ec; + + con_list::iterator metadata_it = m_connection_list.find(id); + if (metadata_it == m_connection_list.end()) { + std::cout << "> No connection found with id " << id << std::endl; + return; + } + + m_endpoint.close(metadata_it->second->get_hdl(), code, reason, ec); + if (ec) { + std::cout << "> Error initiating close: " << ec.message() << std::endl; + } + } + + connection_metadata::ptr get_metadata(int id) const { + con_list::const_iterator metadata_it = m_connection_list.find(id); + if (metadata_it == m_connection_list.end()) { + return connection_metadata::ptr(); + } else { + return metadata_it->second; + } + } +private: + typedef std::map con_list; + + client m_endpoint; + websocketpp::lib::shared_ptr m_thread; + + con_list m_connection_list; + int m_next_id; +}; + +int main() { + bool done = false; + std::string input; + websocket_endpoint endpoint; + + while (!done) { + std::cout << "Enter Command: "; + std::getline(std::cin, input); + + if (input == "quit") { + done = true; + } else if (input == "help") { + std::cout + << "\nCommand List:\n" + << "connect \n" + << "close [] []\n" + << "show \n" + << "help: Display this help text\n" + << "quit: Exit the program\n" + << std::endl; + } else if (input.substr(0,7) == "connect") { + int id = endpoint.connect(input.substr(8)); + if (id != -1) { + std::cout << "> Created connection with id " << id << std::endl; + } + } else if (input.substr(0,5) == "close") { + std::stringstream ss(input); + + std::string cmd; + int id; + int close_code = websocketpp::close::status::normal; + std::string reason = ""; + + ss >> cmd >> id >> close_code; + std::getline(ss,reason); + + endpoint.close(id, close_code, reason); + } else if (input.substr(0,4) == "show") { + int id = atoi(input.substr(5).c_str()); + + connection_metadata::ptr metadata = endpoint.get_metadata(id); + if (metadata) { + std::cout << *metadata << std::endl; + } else { + std::cout << "> Unknown connection id " << id << std::endl; + } + } else { + std::cout << "> Unrecognized Command" << std::endl; + } + } + + return 0; +} + +/* + +clang++ -std=c++11 -stdlib=libc++ -I/Users/zaphoyd/software/websocketpp/ -I/Users/zaphoyd/software/boost_1_55_0/ -D_WEBSOCKETPP_CPP11_STL_ step4.cpp /Users/zaphoyd/software/boost_1_55_0/stage/lib/libboost_system.a + +clang++ -I/Users/zaphoyd/software/websocketpp/ -I/Users/zaphoyd/software/boost_1_55_0/ step4.cpp /Users/zaphoyd/software/boost_1_55_0/stage/lib/libboost_system.a /Users/zaphoyd/software/boost_1_55_0/stage/lib/libboost_thread.a /Users/zaphoyd/software/boost_1_55_0/stage/lib/libboost_random.a + +clang++ -std=c++11 -stdlib=libc++ -I/Users/zaphoyd/Documents/websocketpp/ -I/Users/zaphoyd/Documents/boost_1_53_0_libcpp/ -D_WEBSOCKETPP_CPP11_STL_ step4.cpp /Users/zaphoyd/Documents/boost_1_53_0_libcpp/stage/lib/libboost_system.a + +*/ diff --git a/tutorials/utility_client/utility_client.md b/tutorials/utility_client/utility_client.md new file mode 100644 index 0000000..f90516a --- /dev/null +++ b/tutorials/utility_client/utility_client.md @@ -0,0 +1,719 @@ +Utility Client Example Application +================================== + +Chapter 1: Initial Setup & Basics +--------------------------------- + +Setting up the basic types, opening and closing connections, sending and receiving messages. + +### Step 1 + +A basic program loop that prompts the user for a command and then processes it. In this tutorial we will modify this program to perform tasks and retrieve data from a remote server over a WebSocket connection. + +#### Build +`clang++ step1.cpp` + +#### Code so far + +*note* A code snapshot for each step is present next to this tutorial file in the git repository. + +```cpp +#include +#include + +int main() { + bool done = false; + std::string input; + + while (!done) { + std::cout << "Enter Command: "; + std::getline(std::cin, input); + + if (input == "quit") { + done = true; + } else if (input == "help") { + std::cout + << "\nCommand List:\n" + << "help: Display this help text\n" + << "quit: Exit the program\n" + << std::endl; + } else { + std::cout << "Unrecognized Command" << std::endl; + } + } + + return 0; +} +``` + +### Step 2 + +_Add WebSocket++ includes and set up an endpoint type._ + +WebSocket++ includes two major object types. The endpoint and the connection. The +endpoint creates and launches new connections and maintains default settings for +those connections. Endpoints also manage any shared network resources. + +The connection stores information specific to each WebSocket session. + +> **Note:** Once a connection is launched, there is no link between the endpoint and the connection. All default settings are copied into the new connection by the endpoint. Changing default settings on an endpoint will only affect future connections. +Connections do not maintain a link back to their associated endpoint. Endpoints do not maintain a list of outstanding connections. If your application needs to iterate over all connections it will need to maintain a list of them itself. + +WebSocket++ endpoints are built by combining an endpoint role with an endpoint config. There are two different types of endpoint roles, one each for the client and server roles in a WebSocket session. This is a client tutorial so we will use the client role `websocketpp::client` which is provided by the `` header. + +> ###### Terminology: Endpoint Config +> WebSocket++ endpoints have a group of settings that may be configured at compile time via the `config` template parameter. A config is a struct that contains types and static constants that are used to produce an endpoint with specific properties. Depending on which config is being used the endpoint will have different methods available and may have additional third party dependencies. + +The endpoint role takes a template parameter called `config` that is used to configure the behavior of endpoint at compile time. For this example we are going to use a default config provided by the library called `asio_client`, provided by ``. This is a client config that uses boost::asio to provide network transport and does not support TLS based security. Later on we will discuss how to introduce TLS based security into a WebSocket++ application, more about the other stock configs, and how to build your own custom configs. + +Combine a config with an endpoint role to produce a fully configured endpoint. This type will be used frequently so I would recommend a typedef here. + +`typedef websocketpp::client client` + +#### Build +Adding WebSocket++ has added a few dependencies to our program that must be addressed in the build system. Firstly, the WebSocket++ and Boost library headers must be in the include search path of your build system. How exactly this is done depends on where you have the WebSocket++ headers installed and what build system you are using. + +In addition to the new headers, boost::asio depends on the `boost_system` shared library. This will need to be added (either as a static or dynamic) to the linker. Refer to your build environment documentation for instructions on linking to shared libraries. + +`clang++ step2.cpp -lboost_system` + +#### Code so far +```cpp +#include +#include + +#include +#include + +typedef websocketpp::client client; + +int main() { + bool done = false; + std::string input; + + while (!done) { + std::cout << "Enter Command: "; + std::getline(std::cin, input); + + if (input == "quit") { + done = true; + } else if (input == "help") { + std::cout + << "\nCommand List:\n" + << "help: Display this help text\n" + << "quit: Exit the program\n" + << std::endl; + } else { + std::cout << "Unrecognized Command" << std::endl; + } + } + + return 0; +} + +``` + +### Step 3 + +_Create endpoint wrapper object that handles initialization and setting up the background thread._ + +In order to process user input while network processing occurs in the background we are going to use a separate thread for the WebSocket++ processing loop. This leaves the main thread free to process foreground user input. In order to enable simple RAII style resource management for our thread and endpoint we will use a wrapper object that configures them both in its constructor. + +> ###### Terminology: websocketpp::lib namespace +> WebSocket++ is designed to be used with a C++11 standard library. As this is not universally available in popular build systems the Boost libraries may be used as polyfills for the C++11 standard library in C++98 build environments. The `websocketpp::lib` namespace is used by the library and its associated examples to abstract away the distinctions between the two. `websocketpp::lib::shared_ptr` will evaluate to `std::shared_ptr` in a C++11 environment and `boost::shared_ptr` otherwise. +> +> This tutorial uses the `websocketpp::lib` wrappers because it doesn't know what the build environment of the reader is. For your applications, unless you are interested in similar portability, are free to use the boost or std versions of these types directly. +> +>[TODO: link to more information about websocketpp::lib namespace and C++11 setup] + +Within the `websocket_endpoint` constructor several things happen: + +First, we set the endpoint logging behavior to silent by clearing all of the access and error logging channels. [TODO: link to more information about logging] +```cpp +m_endpoint.clear_access_channels(websocketpp::log::alevel::all); +m_endpoint.clear_error_channels(websocketpp::log::elevel::all); +``` + +Next, we initialize the transport system underlying the endpoint and set it to perpetual mode. In perpetual mode the endpoint's processing loop will not exit automatically when it has no connections. This is important because we want this endpoint to remain active while our application is running and process requests for new WebSocket connections on demand as we need them. Both of these methods are specific to the asio transport. They will not be necessary or present in endpoints that use a non-asio config. +```cpp +m_endpoint.init_asio(); +m_endpoint.start_perpetual(); +``` + +Finally, we launch a thread to run the `run` method of our client endpoint. While the endpoint is running it will process connection tasks (read and deliver incoming messages, frame and send outgoing messages, etc). Because it is running in perpetual mode, when there are no connections active it will wait for a new connection. +```cpp +m_thread.reset(new websocketpp::lib::thread(&client::run, &m_endpoint)); +``` + +#### Build + +Now that our client endpoint template is actually instantiated a few more linker dependencies will show up. In particular, WebSocket clients require a cryptographically secure random number generator. WebSocket++ is able to use either `boost_random` or the C++11 standard library for this purpose. Because this example also uses threads, if we do not have C++11 std::thread available we will need to include `boost_thread`. + +##### Clang (C++98 & boost) +`clang++ step3.cpp -lboost_system -lboost_random -lboost_thread` + +##### Clang (C++11) +`clang++ -std=c++0x -stdlib=libc++ step3.cpp -lboost_system -D_WEBSOCKETPP_CPP11_STL_` + +##### G++ (C++98 & Boost) +`g++ step3.cpp -lboost_system -lboost_random -lboost_thread` + +##### G++ v4.6+ (C++11) +`g++ -std=c++0x step3.cpp -lboost_system -D_WEBSOCKETPP_CPP11_STL_` + +#### Code so far + +```cpp +#include +#include + +#include +#include + +#include +#include + +typedef websocketpp::client client; + +class websocket_endpoint { +public: + websocket_endpoint () { + m_endpoint.clear_access_channels(websocketpp::log::alevel::all); + m_endpoint.clear_error_channels(websocketpp::log::elevel::all); + + m_endpoint.init_asio(); + m_endpoint.start_perpetual(); + + m_thread.reset(new websocketpp::lib::thread(&client::run, &m_endpoint)); + } +private: + client m_endpoint; + websocketpp::lib::shared_ptr m_thread; +}; + +int main() { + bool done = false; + std::string input; + websocket_endpoint endpoint; + + while (!done) { + std::cout << "Enter Command: "; + std::getline(std::cin, input); + + if (input == "quit") { + done = true; + } else if (input == "help") { + std::cout + << "\nCommand List:\n" + << "help: Display this help text\n" + << "quit: Exit the program\n" + << std::endl; + } else { + std::cout << "Unrecognized Command" << std::endl; + } + } + + return 0; +} +``` + +### Step 4 + +_Opening WebSocket connections_ + +This step adds two new commands to app_client. The ability to open a new connection and the ability to view information about a previously opened connection. Every connection that gets opened will be assigned an integer connection id that the user of the program can use to interact with that connection. + +#### New Connection Metadata Object + +In order to track information about each connection a `connection_metadata` object is defined. This object stores the numeric connection id and a number of fields that will be filled in as the connection is processed. Initially this includes the state of the connection (opening, open, failed, closed, etc), the original URI connected to, an identifying value from the server, and a description of the reason for connection failure/closure. Future steps will add more information to this metadata object. + +#### Update `websocket_endpoint` + +The `websocket_endpoint` object has gained some new data members and methods. It now tracks a mapping between connection IDs and their associated metadata as well as the next sequential ID number to hand out. The `connect()` method initiates a new connection. The `get_metadata` method retrieves metadata given an ID. + +#### The connect method +A new WebSocket connection is initiated via a three step process. First, a connection request is created by `endpoint::get_connection(uri)`. Next, the connection request is configured. Lastly, the connection request is submitted back to the endpoint via `endpoint::connect()` which adds it to the queue of new connections to make. + +> ###### Terminology `connection_ptr` +> WebSocket++ keeps track of connection related resources using a reference counted shared pointer. The type of this pointer is `endpoint::connection_ptr`. A `connection_ptr` allows direct access to information about the connection and allows changing connection settings. Because of this direct access and their internal resource management role within the library it is not safe to for end applications to use `connection_ptr` except in the specific circumstances detailed below. +> +> **When is it safe to use `connection_ptr`?** +> - After `endpoint::get_connection(...)` and before `endpoint::connect()`: `get_connection` returns a `connection_ptr`. It is safe to use this pointer to configure your new connection. Once you submit the connection to `connect` you may no longer use the `connection_ptr` and should discard it immediately for optimal memory management. +> - During a handler: WebSocket++ allows you to register hooks / callbacks / event handlers for specific events that happen during a connection's lifetime. During the invocation of one of these handlers the library guarantees that it is safe to use a `connection_ptr` for the connection associated with the currently running handler. + +> ###### Terminology `connection_hdl` +> Because of the limited thread safety of the `connection_ptr` the library also provides a more flexible connection identifier, the `connection_hdl`. The `connection_hdl` has type `websocketpp::connection_hdl` and it is defined in ``. Note that unlike `connection_ptr` this is not dependent on the type or config of the endpoint. Code that simply stores or transmits `connection_hdl` but does not use them can include only the header above and can treat its hdls like values. +> +> Connection handles are not used directly. They are used by endpoint methods to identify the target of the desired action. For example, the endpoint method that sends a new message will take as a parameter the hdl of the connection to send the message to. +> +> **When is it safe to use `connection_hdl`?** +> `connection_hdl`s may be used at any time from any thread. They may be copied and stored in containers. Deleting a hdl will not affect the connection in any way. Handles may be upgraded to a `connection_ptr` during a handler call by using `endpoint::get_con_from_hdl()`. The resulting `connection_ptr` is safe to use for the duration of that handler invocation. +> +> **`connection_hdl` FAQs** +> - `connection_hdl`s are guaranteed to be unique within a program. Multiple endpoints in a single program will always create connections with unique handles. +> - Using a `connection_hdl` with a different endpoint than the one that created its associated connection will result in undefined behavior. +> - Using a `connection_hdl` whose associated connection has been closed or deleted is safe. The endpoint will return a specific error saying the operation couldn't be completed because the associated connection doesn't exist. +> [TODO: more here? link to a connection_hdl FAQ elsewhere?] + +`websocket_endpoint::connect()` begins by calling `endpoint::get_connection()` using a uri passed as a parameter. Additionally, an error output value is passed to capture any errors that might occur during. If an error does occur an error notice is printed along with a descriptive message and the -1 / 'invalid' value is returned as the new ID. + +> ###### Terminology: `error handling: exceptions vs error_code` +> WebSocket++ uses the error code system defined by the C++11 `` library. It can optionally fall back to a similar system provided by the Boost libraries. All user facing endpoint methods that can fail take an `error_code` in an output parameter and store the error that occured there before returning. An empty/default constructed value is returned in the case of success. +> +> **Exception throwing varients** +> All user facing endpoint methods that take and use an `error_code` parameter have a version that throws an exception instead. These methods are identical in function and signature except for the lack of the final ec parameter. The type of the exception thrown is `websocketpp::exception`. This type derives from `std::exception` so it can be caught by catch blocks grabbing generic `std::exception`s. The `websocketpp::exception::code()` method may be used to extract the machine readable `error_code` value from an exception. +> +> For clarity about error handling the app_client example uses exclusively the exception free varients of these methods. Your application may choose to use either. + +If connection creation succeeds, the next sequential connection ID is generated and a `connection_metadata` object is inserted into the connection list under that ID. Initially the metadata object stores the connection ID, the `connection_hdl`, and the URI the connection was opened to. + +```cpp +int new_id = m_next_id++; +metadata_ptr metadata(new connection_metadata(new_id, con->get_handle(), uri)); +m_connection_list[new_id] = metadata; +``` + +Next, the connection request is configured. For this step the only configuration we will do is setting up a few default handlers. Later on we will return and demonstrate some more detailed configuration that can happen here (setting user agents, origin, proxies, custom headers, subprotocols, etc). + +> ###### Terminology: Registering handlers +> WebSocket++ provides a number of execution points where you can register to have a handler run. Which of these points are available to your endpoint will depend on its config. TLS handlers will not exist on non-TLS endpoints for example. A complete list of handlers can be found at http://www.zaphoyd.com/websocketpp/manual/reference/handler-list. +> +> Handlers can be registered at the endpoint level and at the connection level. Endpoint handlers are copied into new connections as they are created. Changing an endpoint handler will affect only future connections. Handlers registered at the connection level will be bound to that specific connection only. +> +> The signature of handler binding methods is the same for endpoints and connections. The format is: `set_*_handler(...)`. Where * is the name of the handler. For example, `set_open_handler(...)` will set the handler to be called when a new connection is open. `set_fail_handler(...)` will set the handler to be called when a connection fails to connect. +> +> All handlers take one argument, a callable type that can be converted to a `std::function` with the correct count and type of arguments. You can pass free functions, functors, and Lambdas with matching argument lists as handlers. In addition, you can use `std::bind` (or `boost::bind`) to register functions with non-matching argument lists. This is useful for passing additional parameters not present in the handler signature or member functions that need to carry a 'this' pointer. +> +> The function signature of each handler can be looked up in the list above in the manual. In general, all handlers include the `connection_hdl` identifying which connection this even is associated with as the first parameter. Some handlers (such as the message handler) include additional parameters. Most handlers have a void return value but some (`validate`, `ping`, `tls_init`) do not. The specific meanings of the return values are documented in the handler list linked above. + +`app_client` registers an open and a fail handler. We will use these to track whether each connection was successfully opened or failed. If it successfully opens, we will gather some information from the opening handshake and store it with our connection metadata. + +In this example we are going to set connection specific handlers that are bound directly to the metadata object associated with our connection. This allows us to avoid performing a lookup in each handler to find the metadata object we plan to update which is a bit more efficient. + +Lets look at the parameters being sent to bind in detail: + +```cpp +con->set_open_handler(websocketpp::lib::bind( + &connection_metadata::on_open, + metadata, + &m_endpoint, + websocketpp::lib::placeholders::_1 +)); +``` + +`&connection_metadata::on_open` is the address of the `on_open` member function of the `connection_metadata` class. `metadata_ptr` is a pointer to the `connection_metadata` object associated with this class. It will be used as the object on which the `on_open` member function will be called. `&m_endpoint` is the address of the endpoint in use. This parameter will be passed as-is to the `on_open` method. Lastly, `websocketpp::lib::placeholders::_1` is a placeholder indicating that the bound function should take one additional argument to be filled in at a later time. WebSocket++ will fill in this placeholder with the `connection_hdl` when it invokes the handler. + +Finally, we call `endpoint::connect()` on our configured connection request and return the new connection ID. + +#### Handler Member Functions + +The open handler we registered, `connection_metadata::on_open`, sets the status metadata field to "Open" and retrieves the value of the "Server" header from the remote endpoint's HTTP response and stores it in the metadata object. Servers often set an identifying string in this header. + +The fail handler we registered, `connection_metadata::on_fail`, sets the status metadata field to "Failed", the server field similarly to `on_open`, and retrieves the error code describing why the connection failed. The human readable message associated with that error code is saved to the metadata object. + +#### New Commands + +Two new commands have been set up. "connect [uri]" will pass the URI to the `websocket_endpoint` connect method and report an error or the connection ID of the new connection. "show [connection id]" will retrieve and print out the metadata associated with that connection. The help text has been updated accordingly. + +```cpp +} else if (input.substr(0,7) == "connect") { + int id = endpoint.connect(input.substr(8)); + if (id != -1) { + std::cout << "> Created connection with id " << id << std::endl; + } +} else if (input.substr(0,4) == "show") { + int id = atoi(input.substr(5).c_str()); + + connection_metadata::ptr metadata = endpoint.get_metadata(id); + if (metadata) { + std::cout << *metadata << std::endl; + } else { + std::cout << "> Unknown connection id " << id << std::endl; + } +} +``` + +#### Build + +There are no changes to the build instructions from step 3 + +#### Run + +``` +Enter Command: connect not a websocket uri +> Connect initialization error: invalid uri +Enter Command: show 0 +> Unknown connection id 0 +Enter Command: connect ws://echo.websocket.org +> Created connection with id 0 +Enter Command: show 0 +> URI: ws://echo.websocket.org +> Status: Open +> Remote Server: Kaazing Gateway +> Error/close reason: N/A +Enter Command: connect ws://wikipedia.org +> Created connection with id 1 +Enter Command: show 1 +> URI: ws://wikipedia.org +> Status: Failed +> Remote Server: Apache +> Error/close reason: Invalid HTTP status. +``` + +#### Code so far + +```cpp +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +typedef websocketpp::client client; + +class connection_metadata { +public: + typedef websocketpp::lib::shared_ptr ptr; + + connection_metadata(int id, websocketpp::connection_hdl hdl, std::string uri) + : m_id(id) + , m_hdl(hdl) + , m_status("Connecting") + , m_uri(uri) + , m_server("N/A") + {} + + void on_open(client * c, websocketpp::connection_hdl hdl) { + m_status = "Open"; + + client::connection_ptr con = c->get_con_from_hdl(hdl); + m_server = con->get_response_header("Server"); + } + + void on_fail(client * c, websocketpp::connection_hdl hdl) { + m_status = "Failed"; + + client::connection_ptr con = c->get_con_from_hdl(hdl); + m_server = con->get_response_header("Server"); + m_error_reason = con->get_ec().message(); + } + + friend std::ostream & operator<< (std::ostream & out, connection_metadata const & data); +private: + int m_id; + websocketpp::connection_hdl m_hdl; + std::string m_status; + std::string m_uri; + std::string m_server; + std::string m_error_reason; +}; + +std::ostream & operator<< (std::ostream & out, connection_metadata const & data) { + out << "> URI: " << data.m_uri << "\n" + << "> Status: " << data.m_status << "\n" + << "> Remote Server: " << (data.m_server.empty() ? "None Specified" : data.m_server) << "\n" + << "> Error/close reason: " << (data.m_error_reason.empty() ? "N/A" : data.m_error_reason); + + return out; +} + +class websocket_endpoint { +public: + websocket_endpoint () : m_next_id(0) { + m_endpoint.clear_access_channels(websocketpp::log::alevel::all); + m_endpoint.clear_error_channels(websocketpp::log::elevel::all); + + m_endpoint.init_asio(); + m_endpoint.start_perpetual(); + + m_thread.reset(new websocketpp::lib::thread(&client::run, &m_endpoint)); + } + + int connect(std::string const & uri) { + websocketpp::lib::error_code ec; + + client::connection_ptr con = m_endpoint.get_connection(uri, ec); + + if (ec) { + std::cout << "> Connect initialization error: " << ec.message() << std::endl; + return -1; + } + + int new_id = m_next_id++; + connection_metadata::ptr metadata_ptr(new connection_metadata(new_id, con->get_handle(), uri)); + m_connection_list[new_id] = metadata_ptr; + + con->set_open_handler(websocketpp::lib::bind( + &connection_metadata::on_open, + metadata_ptr, + &m_endpoint, + websocketpp::lib::placeholders::_1 + )); + con->set_fail_handler(websocketpp::lib::bind( + &connection_metadata::on_fail, + metadata_ptr, + &m_endpoint, + websocketpp::lib::placeholders::_1 + )); + + m_endpoint.connect(con); + + return new_id; + } + + connection_metadata::ptr get_metadata(int id) const { + con_list::const_iterator metadata_it = m_connection_list.find(id); + if (metadata_it == m_connection_list.end()) { + return connection_metadata::ptr(); + } else { + return metadata_it->second; + } + } +private: + typedef std::map con_list; + + client m_endpoint; + websocketpp::lib::shared_ptr m_thread; + + con_list m_connection_list; + int m_next_id; +}; + +int main() { + bool done = false; + std::string input; + websocket_endpoint endpoint; + + while (!done) { + std::cout << "Enter Command: "; + std::getline(std::cin, input); + + if (input == "quit") { + done = true; + } else if (input == "help") { + std::cout + << "\nCommand List:\n" + << "connect \n" + << "show \n" + << "help: Display this help text\n" + << "quit: Exit the program\n" + << std::endl; + } else if (input.substr(0,7) == "connect") { + int id = endpoint.connect(input.substr(8)); + if (id != -1) { + std::cout << "> Created connection with id " << id << std::endl; + } + } else if (input.substr(0,4) == "show") { + int id = atoi(input.substr(5).c_str()); + + connection_metadata::ptr metadata = endpoint.get_metadata(id); + if (metadata) { + std::cout << *metadata << std::endl; + } else { + std::cout << "> Unknown connection id " << id << std::endl; + } + } else { + std::cout << "> Unrecognized Command" << std::endl; + } + } + + return 0; +} +``` + +### Step 5 + +_Closing connections_ + +This step adds a command that allows you to close a WebSocket connection and adjusts the quit command so that it cleanly closes all outstanding connections before quitting. + +#### Getting connection close information out of WebSocket++ + +> ###### Terminology: WebSocket close codes & reasons +> The WebSocket close handshake involves an exchange of optional machine readable close codes and human readable reason strings. Each endpoint sends independent close details. The codes are short integers. The reasons are UTF8 text strings of at most 125 characters. More details about valid close code ranges and the meaning of each code can be found at https://tools.ietf.org/html/rfc6455#section-7.4 + +The `websocketpp::close::status` namespace contains named constants for all of the IANA defined close codes. It also includes free functions to determine whether a value is reserved or invalid and to convert a code to a human readable text representation. + +During the close handler call WebSocket++ connections offer the following methods for accessing close handshake information: + +- `connection::get_remote_close_code()`: Get the close code as reported by the remote endpoint +- `connection::get_remote_close_reason()`: Get the close reason as reported by the remote endpoint +- `connection::get_local_close_code()`: Get the close code that this endpoint sent. +- `connection::get_local_close_reason()`: Get the close reason that this endpoint sent. +- `connection::get_ec()`: Get a more detailed/specific WebSocket++ `error_code` indicating what library error (if any) ultimately resulted in the connection closure. + +*Note:* there are some special close codes that will report a code that was not actually sent on the wire. For example 1005/"no close code" indicates that the endpoint omitted a close code entirely and 1006/"abnormal close" indicates that there was a problem that resulted in the connection closing without having performed a close handshake. + +#### Add close handler + +The `connection_metadata::on_close` method is added. This method retrieves the close code and reason from the closing handshake and stores it in the local error reason field. + +```cpp +void on_close(client * c, websocketpp::connection_hdl hdl) { + m_status = "Closed"; + client::connection_ptr con = c->get_con_from_hdl(hdl); + std::stringstream s; + s << "close code: " << con->get_remote_close_code() << " (" + << websocketpp::close::status::get_string(con->get_remote_close_code()) + << "), close reason: " << con->get_remote_close_reason(); + m_error_reason = s.str(); +} +``` + +Similarly to `on_open` and `on_fail`, `websocket_endpoint::connect` registers this close handler when a new connection is made. + +#### Add close method to `websocket_endpoint` + +This method starts by looking up the given connection ID in the connection list. Next a close request is sent to the connection's handle with the specified WebSocket close code. This is done by calling `endpoint::close`. This is a thread safe method that is used to asynchronously dispatch a close signal to the connection with the given handle. When the operation is complete the connection's close handler will be triggered. + +```cpp +void close(int id, websocketpp::close::status::value code) { + websocketpp::lib::error_code ec; + + con_list::iterator metadata_it = m_connection_list.find(id); + if (metadata_it == m_connection_list.end()) { + std::cout << "> No connection found with id " << id << std::endl; + return; + } + + m_endpoint.close(metadata_it->second->get_hdl(), code, "", ec); + if (ec) { + std::cout << "> Error initiating close: " << ec.message() << std::endl; + } +} +``` + +#### Add close option to the command loop and help message + +A close option is added to the command loop. It takes a connection ID and optionally a close code and a close reason. If no code is specified the default of 1000/Normal is used. If no reason is specified, none is sent. The `endpoint::send` method will do some error checking and abort the close request if you try and send an invalid code or a reason with invalid UTF8 formatting. Reason strings longer than 125 characters will be truncated. + +An entry is also added to the help system to describe how the new command may be used. + +```cpp +else if (input.substr(0,5) == "close") { + std::stringstream ss(input); + + std::string cmd; + int id; + int close_code = websocketpp::close::status::normal; + std::string reason = ""; + + ss >> cmd >> id >> close_code; + std::getline(ss,reason); + + endpoint.close(id, close_code, reason); +} +``` + +#### Close all outstanding connections in `websocket_endpoint` destructor + +Until now quitting the program left outstanding connections and the WebSocket++ network thread in a lurch. Now that we have a method of closing connections we can clean this up properly. + +The destructor for `websocket_endpoint` now stops perpetual mode (so the run thread exits after the last connection is closed) and iterates through the list of open connections and requests a clean close for each. Finally, the run thread is joined which causes the program to wait until those connection closes complete. + +```cpp +~websocket_endpoint() { + m_endpoint.stop_perpetual(); + + for (con_list::const_iterator it = m_connection_list.begin(); it != m_connection_list.end(); ++it) { + if (it->second->get_status() != "Open") { + // Only close open connections + continue; + } + + std::cout << "> Closing connection " << it->second->get_id() << std::endl; + + websocketpp::lib::error_code ec; + m_endpoint.close(it->second->get_hdl(), websocketpp::close::status::going_away, "", ec); + if (ec) { + std::cout << "> Error closing connection " << it->second->get_id() << ": " + << ec.message() << std::endl; + } + } + + m_thread->join(); +} +``` + +#### Build + +There are no changes to the build instructions from step 4 + +#### Run + +``` +Enter Command: connect ws://localhost:9002 +> Created connection with id 0 +Enter Command: close 0 1001 example message +Enter Command: show 0 +> URI: ws://localhost:9002 +> Status: Closed +> Remote Server: WebSocket++/0.3.0-alpha4 +> Error/close reason: close code: 1001 (Going away), close reason: example message +Enter Command: connect ws://localhost:9002 +> Created connection with id 1 +Enter Command: close 1 1006 +> Error initiating close: Invalid close code used +Enter Command: quit +> Closing connection 1 +``` + +### Step 6 + +_Sending and receiving messages_ + +- Sending a messages +- terminology: WebSocket opcodes, text vs binary messages +- Receiving a message + +### Step 7 + +_Using TLS / Secure WebSockets_ + +Chapter 2: Intermediate Features +-------------------------------- + +### Step 8 + +_Intermediate level features_ + +- Subprotocol negotiation +- Setting and reading custom headers +- Ping and Pong +- Proxies? +- Setting user agent +- Setting Origin +- Timers and security +- Close behavior +- Send one message to all connections + + +### Misc stuff not sure if it should be included here or elsewhere? + +core websocket++ control flow. +A handshake, followed by a split into 2 independent control strands +- Handshake +-- use information specified before the call to endpoint::connect to construct a WebSocket handshake request. +-- Pass the WebSocket handshake request to the transport policy. The transport policy determines how to get these bytes to the endpoint playing the server role. Depending on which transport policy your endpoint uses this method will be different. +-- Receive a handshake response from the underlying transport. This is parsed and checked for conformance to RFC6455. If the validation fails, the fail handler is called. Otherwise the open handler is called. +- At this point control splits into two separate strands. One that reads new bytes from the transport policy on the incoming channle, the other that accepts new messages from the local application for framing and writing to the outgoing transport channel. +- Read strand +-- Read and process new bytes from transport +-- If the bytes contain at least one complete message dispatch each message by calling the appropriate handler. This is either the message handler for data messages, or ping/pong/close handlers for each respective control message. If no handler is registered for a particular message it is ignored. +-- Ask the transport layer for more bytes +- Write strand +-- Wait for messages from the application +-- Perform error checking on message input, +-- Frame message per RFC6455 +-- Queue message for sending +-- Pass all outstanding messages to the transport policy for output +-- When there are no messages left to send, return to waiting + +Important observations +Handlers run in line with library processing which has several implications applications should be aware of: diff --git a/websocketpp/base64/base64.cpp b/websocketpp/base64/base64.cpp deleted file mode 100644 index 8738802..0000000 --- a/websocketpp/base64/base64.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* - base64.cpp and base64.h - - Copyright (C) 2004-2008 RenĂ© Nyffenegger - - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. - - 3. This notice may not be removed or altered from any source distribution. - - RenĂ© Nyffenegger rene.nyffenegger@adp-gmbh.ch - -*/ - -#include "base64.h" -#include - -static const std::string base64_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - - -static inline bool is_base64(unsigned char c) { - return (isalnum(c) || (c == '+') || (c == '/')); -} - -std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { - std::string ret; - int i = 0; - int j = 0; - unsigned char char_array_3[3]; - unsigned char char_array_4[4]; - - while (in_len--) { - char_array_3[i++] = *(bytes_to_encode++); - if (i == 3) { - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - - for(i = 0; (i <4) ; i++) - ret += base64_chars[char_array_4[i]]; - i = 0; - } - } - - if (i) - { - for(j = i; j < 3; j++) - char_array_3[j] = '\0'; - - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - - for (j = 0; (j < i + 1); j++) - ret += base64_chars[char_array_4[j]]; - - while((i++ < 3)) - ret += '='; - - } - - return ret; - -} - -std::string base64_decode(std::string const& encoded_string) { - size_t in_len = encoded_string.size(); - int i = 0; - int j = 0; - int in_ = 0; - unsigned char char_array_4[4], char_array_3[3]; - std::string ret; - - while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { - char_array_4[i++] = encoded_string[in_]; in_++; - if (i ==4) { - for (i = 0; i <4; i++) - char_array_4[i] = static_cast(base64_chars.find(char_array_4[i])); - - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - 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 (i = 0; (i < 3); i++) - ret += char_array_3[i]; - i = 0; - } - } - - if (i) { - for (j = i; j <4; j++) - char_array_4[j] = 0; - - for (j = 0; j <4; j++) - char_array_4[j] = static_cast(base64_chars.find(char_array_4[j])); - - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - 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]; - } - - return ret; -} diff --git a/websocketpp/base64/base64.h b/websocketpp/base64/base64.h deleted file mode 100644 index 65d5db8..0000000 --- a/websocketpp/base64/base64.h +++ /dev/null @@ -1,4 +0,0 @@ -#include - -std::string base64_encode(unsigned char const* , unsigned int len); -std::string base64_decode(std::string const& s); diff --git a/websocketpp/base64/base64.hpp b/websocketpp/base64/base64.hpp index aa2fc8f..6572c49 100644 --- a/websocketpp/base64/base64.hpp +++ b/websocketpp/base64/base64.hpp @@ -1,7 +1,7 @@ /* ****** 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 + 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. @@ -38,7 +38,9 @@ #include -static const std::string base64_chars = +namespace websocketpp { + +static std::string const base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; @@ -50,7 +52,7 @@ static inline bool is_base64(unsigned char c) { (c >= 97 && c <= 122)); // a-z } -inline std::string base64_encode(unsigned char const* bytes_to_encode, unsigned +inline std::string base64_encode(unsigned char const * bytes_to_encode, unsigned int in_len) { std::string ret; @@ -100,11 +102,11 @@ inline std::string base64_encode(unsigned char const* bytes_to_encode, unsigned return ret; } -inline std::string base64_encode(const std::string & data) { +inline std::string base64_encode(std::string const & data) { return base64_encode(reinterpret_cast(data.data()),data.size()); } -inline std::string base64_decode(std::string const& encoded_string) { +inline std::string base64_decode(std::string const & encoded_string) { size_t in_len = encoded_string.size(); int i = 0; int j = 0; @@ -149,4 +151,6 @@ inline std::string base64_decode(std::string const& encoded_string) { return ret; } +} // namespace websocketpp + #endif // _BASE64_HPP_ diff --git a/websocketpp/close.hpp b/websocketpp/close.hpp index 107b345..9b83e62 100644 --- a/websocketpp/close.hpp +++ b/websocketpp/close.hpp @@ -51,6 +51,26 @@ namespace status { /// A blank value for internal use. static value const blank = 0; + /// Close the connection without a WebSocket close handshake. + /** + * This special value requests that the WebSocket connection be closed + * without performing the WebSocket closing handshake. This does not comply + * with RFC6455, but should be safe to do if necessary. This could be useful + * for clients that need to disconnect quickly and cannot afford the + * complete handshake. + */ + static value const omit_handshake = 1; + + /// Close the connection with a forced TCP drop. + /** + * This special value requests that the WebSocket connection be closed by + * forcibly dropping the TCP connection. This will leave the other side of + * the connection with a broken connection and some expensive timeouts. this + * should not be done except in extreme cases or in cases of malicious + * remote endpoints. + */ + static value const force_tcp_drop = 2; + /// Normal closure, meaning that the purpose for which the connection was /// established has been fulfilled. static value const normal = 1000; @@ -179,6 +199,46 @@ namespace status { code == policy_violation || code == message_too_big || code == internal_endpoint_error); } + + /// Return a human readable interpretation of a WebSocket close code + /** + * See https://tools.ietf.org/html/rfc6455#section-7.4 for more details. + * + * @since 0.4.0-beta1 + * + * @param [in] code The code to look up. + * @return A human readable interpretation of the code. + */ + inline std::string get_string(value code) { + switch (code) { + case normal: + return "Normal close"; + case going_away: + return "Going away"; + case protocol_error: + return "Protocol error"; + case unsupported_data: + return "Unsupported data"; + case no_status: + return "No status set"; + case abnormal_close: + return "Abnormal close"; + case invalid_payload: + return "Invalid payload"; + case policy_violation: + return "Policy violoation"; + case message_too_big: + return "Message too big"; + case extension_required: + return "Extension required"; + case internal_endpoint_error: + return "Internal endpoint error"; + case tls_handshake: + return "TLS handshake failure"; + default: + return "Unknown"; + } + } } // namespace status /// Type used to convert close statuses between integer and wire representations @@ -197,7 +257,7 @@ union code_converter { * * If the value is in an invalid or reserved range ec is set accordingly. * - * @param [in] payload Close frame payload value recieved over the wire. + * @param [in] payload Close frame payload value received over the wire. * @param [out] ec Set to indicate what error occurred, if any. * @return The extracted value */ diff --git a/websocketpp/common/connection_hdl.hpp b/websocketpp/common/connection_hdl.hpp index 71f490c..fa46068 100644 --- a/websocketpp/common/connection_hdl.hpp +++ b/websocketpp/common/connection_hdl.hpp @@ -34,7 +34,7 @@ namespace websocketpp { /// A handle to uniquely identify a connection. /** - * This type uniquely identifies a connection. It is implimented as a weak + * This type uniquely identifies a connection. It is implemented as a weak * pointer to the connection in question. This provides uniqueness across * multiple endpoints and ensures that IDs never conflict or run out. * diff --git a/websocketpp/common/cpp11.hpp b/websocketpp/common/cpp11.hpp index 2a638dc..4b980ef 100644 --- a/websocketpp/common/cpp11.hpp +++ b/websocketpp/common/cpp11.hpp @@ -41,9 +41,11 @@ #endif -#ifdef _WEBSOCKETPP_CPP11_STL_ - // This flag indicates that all of the C++11 language features are available - // to us. +#if defined(_WEBSOCKETPP_CPP11_STL_) || __cplusplus >= 201103L + // _WEBSOCKETPP_CPP11_STL_ is a flag from the build system that forces + // WebSocket++ into C++11 mode. __cplusplus is a define set by the compiler + // if it has full support for C++11 language features. If either are set use + // C++11 language features #ifndef _WEBSOCKETPP_NOEXCEPT_TOKEN_ #define _WEBSOCKETPP_NOEXCEPT_TOKEN_ noexcept #endif @@ -53,6 +55,9 @@ #ifndef _WEBSOCKETPP_INITIALIZER_LISTS_ #define _WEBSOCKETPP_INITIALIZER_LISTS_ #endif + #ifndef _WEBSOCKETPP_NULLPTR_TOKEN_ + #define _WEBSOCKETPP_NULLPTR_TOKEN_ nullptr + #endif #else // Test for noexcept #ifndef _WEBSOCKETPP_NOEXCEPT_TOKEN_ @@ -90,6 +95,25 @@ #if __has_feature(cxx_generalized_initializers) && !defined(_WEBSOCKETPP_INITIALIZER_LISTS_) #define _WEBSOCKETPP_INITIALIZER_LISTS_ #endif + + // Test for nullptr + #ifndef _WEBSOCKETPP_NULLPTR_TOKEN_ + #ifdef _WEBSOCKETPP_NULLPTR_ + // build system says we have nullptr + #define _WEBSOCKETPP_NULLPTR_TOKEN_ nullptr + #else + #if __has_feature(cxx_nullptr) + // clang feature detect says we have nullptr + #define _WEBSOCKETPP_NULLPTR_TOKEN_ nullptr + #elif _MSC_VER >= 1600 + // Visual Studio version that has nullptr + #define _WEBSOCKETPP_NULLPTR_TOKEN_ nullptr + #else + // assume we don't have nullptr + #define _WEBSOCKETPP_NULLPTR_TOKEN_ 0 + #endif + #endif + #endif #endif #endif // WEBSOCKETPP_COMMON_CPP11_HPP diff --git a/websocketpp/common/md5.hpp b/websocketpp/common/md5.hpp index 70eaff8..279725f 100644 --- a/websocketpp/common/md5.hpp +++ b/websocketpp/common/md5.hpp @@ -1,7 +1,8 @@ /* - md5.hpp is a reformulation of the md5.h and md5.c code to allow it to function - as a component of a header only library. This conversion was done by Peter - Thorson (webmaster@zaphoyd.com) in 2012 for the WebSocket++ project. The + md5.hpp is a reformulation of the md5.h and md5.c code from + http://www.opensource.apple.com/source/cups/cups-59/cups/md5.c to allow it to + function as a component of a header only library. This conversion was done by + Peter Thorson (webmaster@zaphoyd.com) in 2012 for the WebSocket++ project. The changes are released under the same license as the original (listed below) */ /* @@ -33,7 +34,7 @@ This code implements the MD5 Algorithm defined in RFC 1321, whose text is available at - http://www.ietf.org/rfc/rfc1321.txt + http://www.ietf.org/rfc/rfc1321.txt The code is derived from the text of the RFC, including the test suite (section A.5) but excluding the rest of Appendix A. It does not include any code or documentation that is identified in the RFC as being @@ -44,12 +45,12 @@ that follows (in reverse chronological order): 2002-04-13 lpd Removed support for non-ANSI compilers; removed - references to Ghostscript; clarified derivation from RFC 1321; - now handles byte order either statically or dynamically. + references to Ghostscript; clarified derivation from RFC 1321; + now handles byte order either statically or dynamically. 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); - added conditionalization for C++ compilation from Martin - Purschke . + added conditionalization for C++ compilation from Martin + Purschke . 1999-05-03 lpd Original version. */ @@ -79,9 +80,9 @@ typedef unsigned int md5_word_t; /* 32-bit word */ /* Define the state of the MD5 Algorithm. */ typedef struct md5_state_s { - md5_word_t count[2]; /* message length in bits, lsw first */ - md5_word_t abcd[4]; /* digest buffer */ - md5_byte_t buf[64]; /* accumulate block */ + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ } md5_state_t; /* Initialize the algorithm. */ @@ -93,7 +94,7 @@ inline void md5_append(md5_state_t *pms, md5_byte_t const * data, size_t nbytes) /* Finish the message and return the digest. */ inline void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); -#undef ZSW_MD5_BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ +#undef ZSW_MD5_BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ #ifdef ARCH_IS_BIG_ENDIAN # define ZSW_MD5_BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) #else @@ -168,8 +169,8 @@ inline void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); static void md5_process(md5_state_t *pms, md5_byte_t const * data /*[64]*/) { md5_word_t - a = pms->abcd[0], b = pms->abcd[1], - c = pms->abcd[2], d = pms->abcd[3]; + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; md5_word_t t; #if ZSW_MD5_BYTE_ORDER > 0 /* Define storage only for big-endian CPUs. */ @@ -182,51 +183,51 @@ static void md5_process(md5_state_t *pms, md5_byte_t const * data /*[64]*/) { { #if ZSW_MD5_BYTE_ORDER == 0 - /* - * Determine dynamically whether this is a big-endian or - * little-endian machine, since we can use a more efficient - * algorithm on the latter. - */ - static int const w = 1; + /* + * Determine dynamically whether this is a big-endian or + * little-endian machine, since we can use a more efficient + * algorithm on the latter. + */ + static int const w = 1; - if (*((md5_byte_t const *)&w)) /* dynamic little-endian */ + if (*((md5_byte_t const *)&w)) /* dynamic little-endian */ #endif -#if ZSW_MD5_BYTE_ORDER <= 0 /* little-endian */ - { - /* - * On little-endian machines, we can process properly aligned - * data without copying it. - */ - if (!((data - (md5_byte_t const *)0) & 3)) { - /* data are properly aligned */ - X = (md5_word_t const *)data; - } else { - /* not aligned */ - std::memcpy(xbuf, data, 64); - X = xbuf; - } - } +#if ZSW_MD5_BYTE_ORDER <= 0 /* little-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (md5_byte_t const *)0) & 3)) { + /* data are properly aligned */ + X = (md5_word_t const *)data; + } else { + /* not aligned */ + std::memcpy(xbuf, data, 64); + X = xbuf; + } + } #endif #if ZSW_MD5_BYTE_ORDER == 0 - else /* dynamic big-endian */ + else /* dynamic big-endian */ #endif -#if ZSW_MD5_BYTE_ORDER >= 0 /* big-endian */ - { - /* - * On big-endian machines, we must arrange the bytes in the - * right order. - */ - const md5_byte_t *xp = data; - int i; +#if ZSW_MD5_BYTE_ORDER >= 0 /* big-endian */ + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; # if ZSW_MD5_BYTE_ORDER == 0 - X = xbuf; /* (dynamic only) */ + X = xbuf; /* (dynamic only) */ # else -# define xbuf X /* (static only) */ +# define xbuf X /* (static only) */ # endif - for (i = 0; i < 16; ++i, xp += 4) - xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); - } + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } #endif } @@ -360,71 +361,71 @@ void md5_append(md5_state_t *pms, md5_byte_t const * data, size_t nbytes) { md5_word_t nbits = (md5_word_t)(nbytes << 3); if (nbytes <= 0) - return; + return; /* Update the message length. */ pms->count[1] += nbytes >> 29; pms->count[0] += nbits; if (pms->count[0] < nbits) - pms->count[1]++; + pms->count[1]++; /* Process an initial partial block. */ if (offset) { - int copy = (offset + nbytes > 64 ? 64 - offset : static_cast(nbytes)); + int copy = (offset + nbytes > 64 ? 64 - offset : static_cast(nbytes)); - std::memcpy(pms->buf + offset, p, copy); - if (offset + copy < 64) - return; - p += copy; - left -= copy; - md5_process(pms, pms->buf); + std::memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); } /* Process full blocks. */ for (; left >= 64; p += 64, left -= 64) - md5_process(pms, p); + md5_process(pms, p); /* Process a final partial block. */ if (left) - std::memcpy(pms->buf, p, left); + std::memcpy(pms->buf, p, left); } void md5_finish(md5_state_t *pms, md5_byte_t digest[16]) { static md5_byte_t const pad[64] = { - 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; md5_byte_t data[8]; int i; /* Save the length before padding. */ for (i = 0; i < 8; ++i) - data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); /* Pad to 56 bytes mod 64. */ md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); /* Append the length. */ md5_append(pms, data, 8); for (i = 0; i < 16; ++i) - digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); } // some convenience c++ functions inline std::string md5_hash_string(std::string const & s) { - char digest[16]; + char digest[16]; - md5_state_t state; + md5_state_t state; - md5_init(&state); - md5_append(&state, (md5_byte_t const *)s.c_str(), s.size()); - md5_finish(&state, (md5_byte_t *)digest); + md5_init(&state); + md5_append(&state, (md5_byte_t const *)s.c_str(), s.size()); + md5_finish(&state, (md5_byte_t *)digest); std::string ret; ret.resize(16); std::copy(digest,digest+16,ret.begin()); - return ret; + return ret; } const char hexval[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; diff --git a/websocketpp/common/network.hpp b/websocketpp/common/network.hpp index 6a01f14..9ba8b53 100644 --- a/websocketpp/common/network.hpp +++ b/websocketpp/common/network.hpp @@ -29,7 +29,7 @@ #define WEBSOCKETPP_COMMON_NETWORK_HPP // For ntohs and htons -#if defined(WIN32) +#if defined(_WIN32) #include #else //#include diff --git a/websocketpp/common/platforms.hpp b/websocketpp/common/platforms.hpp index 4e031aa..1196847 100644 --- a/websocketpp/common/platforms.hpp +++ b/websocketpp/common/platforms.hpp @@ -33,7 +33,7 @@ * don't fit somewhere else better. */ -#if defined(WIN32) && !defined(NOMINMAX) +#if defined(_WIN32) && !defined(NOMINMAX) // don't define min and max macros that conflict with std::min and std::max #define NOMINMAX #endif diff --git a/websocketpp/common/stdint.hpp b/websocketpp/common/stdint.hpp index 233e36f..a8ca1e3 100644 --- a/websocketpp/common/stdint.hpp +++ b/websocketpp/common/stdint.hpp @@ -32,7 +32,7 @@ #define __STDC_LIMIT_MACROS 1 #endif -#if WIN32 && (_MSC_VER < 1600) +#if defined (_WIN32) && defined (_MSC_VER) && (_MSC_VER < 1600) #include using boost::int8_t; diff --git a/websocketpp/config/core.hpp b/websocketpp/config/core.hpp index 69744a1..32ddb3e 100644 --- a/websocketpp/config/core.hpp +++ b/websocketpp/config/core.hpp @@ -91,6 +91,11 @@ struct core { /// RNG policies typedef websocketpp::random::none::int_generator rng_type; + /// Controls compile time enabling/disabling of thread syncronization + /// code Disabling can provide a minor performance improvement to single + /// threaded applications + static bool const enable_multithreading = true; + struct transport_config { typedef type::concurrency_type concurrency_type; typedef type::elog_type elog_type; @@ -98,6 +103,11 @@ struct core { typedef type::request_type request_type; typedef type::response_type response_type; + /// Controls compile time enabling/disabling of thread syncronization + /// code Disabling can provide a minor performance improvement to single + /// threaded applications + static bool const enable_multithreading = true; + /// Default timer values (in ms) /// Length of time to wait for socket pre-initialization @@ -179,7 +189,7 @@ struct core { websocketpp::log::alevel::all ^ websocketpp::log::alevel::devel; /// - static const size_t connection_read_buffer_size = 512; + static const size_t connection_read_buffer_size = 16384; /// Drop connections immediately on protocol error. /** @@ -205,6 +215,18 @@ struct core { */ static const bool silent_close = false; + /// Default maximum message size + /** + * Default value for the processor's maximum message size. Maximum message size + * determines the point at which the library will fail a connection with the + * message_too_big protocol error. + * + * The default is 32MB + * + * @since 0.4.0-alpha1 + */ + static const size_t max_message_size = 32000000; + /// Global flag for enabling/disabling extensions static const bool enable_extensions = true; diff --git a/websocketpp/config/core_client.hpp b/websocketpp/config/core_client.hpp index b732975..1b2fae4 100644 --- a/websocketpp/config/core_client.hpp +++ b/websocketpp/config/core_client.hpp @@ -34,7 +34,11 @@ #include // Concurrency +#ifndef _WEBSOCKETPP_NO_THREADING_ #include +#else +#include +#endif // Transport #include @@ -68,7 +72,11 @@ struct core_client { typedef core_client type; // Concurrency policy +#ifndef _WEBSOCKETPP_NO_THREADING_ typedef websocketpp::concurrency::basic concurrency_type; +#else + typedef websocketpp::concurrency::none concurrency_type; +#endif // HTTP Parser Policies typedef http::parser::request request_type; @@ -92,6 +100,11 @@ struct core_client { typedef websocketpp::random::random_device::int_generator rng_type; + /// Controls compile time enabling/disabling of thread syncronization code + /// Disabling can provide a minor performance improvement to single threaded + /// applications + static bool const enable_multithreading = true; + struct transport_config { typedef type::concurrency_type concurrency_type; typedef type::elog_type elog_type; @@ -99,6 +112,11 @@ struct core_client { typedef type::request_type request_type; typedef type::response_type response_type; + /// Controls compile time enabling/disabling of thread syncronization + /// code Disabling can provide a minor performance improvement to single + /// threaded applications + static bool const enable_multithreading = true; + /// Default timer values (in ms) /// Length of time to wait for socket pre-initialization @@ -180,7 +198,7 @@ struct core_client { websocketpp::log::alevel::all ^ websocketpp::log::alevel::devel; /// - static const size_t connection_read_buffer_size = 512; + static const size_t connection_read_buffer_size = 16384; /// Drop connections immediately on protocol error. /** @@ -206,6 +224,18 @@ struct core_client { */ static const bool silent_close = false; + /// Default maximum message size + /** + * Default value for the processor's maximum message size. Maximum message size + * determines the point at which the library will fail a connection with the + * message_too_big protocol error. + * + * The default is 32MB + * + * @since 0.4.0-alpha1 + */ + static const size_t max_message_size = 32000000; + /// Global flag for enabling/disabling extensions static const bool enable_extensions = true; diff --git a/websocketpp/config/debug.hpp b/websocketpp/config/debug.hpp index 0f2fc65..a708de1 100644 --- a/websocketpp/config/debug.hpp +++ b/websocketpp/config/debug.hpp @@ -92,6 +92,11 @@ struct debug_core { /// RNG policies typedef websocketpp::random::none::int_generator rng_type; + /// Controls compile time enabling/disabling of thread syncronization + /// code Disabling can provide a minor performance improvement to single + /// threaded applications + static bool const enable_multithreading = true; + struct transport_config { typedef type::concurrency_type concurrency_type; typedef type::elog_type elog_type; @@ -99,6 +104,11 @@ struct debug_core { typedef type::request_type request_type; typedef type::response_type response_type; + /// Controls compile time enabling/disabling of thread syncronization + /// code Disabling can provide a minor performance improvement to single + /// threaded applications + static bool const enable_multithreading = true; + /// Default timer values (in ms) /// Length of time to wait for socket pre-initialization @@ -180,7 +190,7 @@ struct debug_core { websocketpp::log::alevel::all; /// - static const size_t connection_read_buffer_size = 512; + static const size_t connection_read_buffer_size = 16384; /// Drop connections immediately on protocol error. /** @@ -206,6 +216,18 @@ struct debug_core { */ static const bool silent_close = false; + /// Default maximum message size + /** + * Default value for the processor's maximum message size. Maximum message size + * determines the point at which the library will fail a connection with the + * message_too_big protocol error. + * + * The default is 32MB + * + * @since 0.4.0-alpha1 + */ + static const size_t max_message_size = 32000000; + /// Global flag for enabling/disabling extensions static const bool enable_extensions = true; diff --git a/websocketpp/connection.hpp b/websocketpp/connection.hpp index 702060f..e2ebcd6 100644 --- a/websocketpp/connection.hpp +++ b/websocketpp/connection.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, Peter Thorson. All rights reserved. + * Copyright (c) 2014, 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: @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -149,6 +150,10 @@ typedef lib::function validate_handler; */ typedef lib::function http_handler; +// +typedef lib::function read_handler; +typedef lib::function write_frame_handler; + // constants related to the default WebSocket protocol versions available #ifdef _WEBSOCKETPP_INITIALIZER_LISTS_ // simplified C++11 version /// Container that stores the list of protocol versions supported @@ -216,7 +221,6 @@ template class connection : public config::transport_type::transport_con_type , public config::connection_base - , public lib::enable_shared_from_this< connection > { public: /// Type of this connection @@ -276,16 +280,32 @@ private: }; public: - explicit connection(bool is_server, std::string const & ua, alog_type& alog, + explicit connection(bool p_is_server, std::string const & ua, alog_type& alog, elog_type& elog, rng_type & rng) - : transport_con_type(is_server,alog,elog) + : transport_con_type(p_is_server, alog, elog) + , m_handle_read_frame(lib::bind( + &type::handle_read_frame, + this, + lib::placeholders::_1, + lib::placeholders::_2 + )) + , m_write_frame_handler(lib::bind( + &type::handle_write_frame, + this, + lib::placeholders::_1 + )) , m_user_agent(ua) + , m_open_handshake_timeout_dur(config::timeout_open_handshake) + , m_close_handshake_timeout_dur(config::timeout_close_handshake) + , m_pong_timeout_dur(config::timeout_pong) + , m_max_message_size(config::max_message_size) , 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) , m_write_flag(false) - , m_is_server(is_server) + , m_read_flag(true) + , m_is_server(p_is_server) , m_alog(alog) , m_elog(elog) , m_rng(rng) @@ -296,6 +316,11 @@ public: m_alog.write(log::alevel::devel,"connection constructor"); } + /// Get a shared pointer to this component + ptr get_shared() { + return lib::static_pointer_cast(transport_con_type::get_shared()); + } + /////////////////////////// // Set Handler Callbacks // /////////////////////////// @@ -433,6 +458,111 @@ public: m_message_handler = h; } + ////////////////////////////////////////// + // Connection timeouts and other limits // + ////////////////////////////////////////// + + /// Set open handshake timeout + /** + * Sets the length of time the library will wait after an opening handshake + * has been initiated before cancelling it. This can be used to prevent + * excessive wait times for outgoing clients or excessive resource usage + * from broken clients or DoS attacks on servers. + * + * Connections that time out will have their fail handlers called with the + * open_handshake_timeout error code. + * + * The default value is specified via the compile time config value + * 'timeout_open_handshake'. The default value in the core config + * is 5000ms. A value of 0 will disable the timer entirely. + * + * To be effective, the transport you are using must support timers. See + * the documentation for your transport policy for details about its + * timer support. + * + * @param dur The length of the open handshake timeout in ms + */ + void set_open_handshake_timeout(long dur) { + m_open_handshake_timeout_dur = dur; + } + + /// Set close handshake timeout + /** + * Sets the length of time the library will wait after a closing handshake + * has been initiated before cancelling it. This can be used to prevent + * excessive wait times for outgoing clients or excessive resource usage + * from broken clients or DoS attacks on servers. + * + * Connections that time out will have their close handlers called with the + * close_handshake_timeout error code. + * + * The default value is specified via the compile time config value + * 'timeout_close_handshake'. The default value in the core config + * is 5000ms. A value of 0 will disable the timer entirely. + * + * To be effective, the transport you are using must support timers. See + * the documentation for your transport policy for details about its + * timer support. + * + * @param dur The length of the close handshake timeout in ms + */ + void set_close_handshake_timeout(long dur) { + m_close_handshake_timeout_dur = dur; + } + + /// Set pong timeout + /** + * Sets the length of time the library will wait for a pong response to a + * ping. This can be used as a keepalive or to detect broken connections. + * + * Pong responses that time out will have the pong timeout handler called. + * + * The default value is specified via the compile time config value + * 'timeout_pong'. The default value in the core config + * is 5000ms. A value of 0 will disable the timer entirely. + * + * To be effective, the transport you are using must support timers. See + * the documentation for your transport policy for details about its + * timer support. + * + * @param dur The length of the pong timeout in ms + */ + void set_pong_timeout(long dur) { + m_pong_timeout_dur = dur; + } + + /// Get maximum message size + /** + * Get maximum message size. Maximum message size determines the point at which the + * connection will fail a connection with the message_too_big protocol error. + * + * The default is set by the endpoint that creates the connection. + * + * @since 0.4.0-alpha1 + */ + size_t get_max_message_size() const { + return m_max_message_size; + } + + /// Set maximum message size + /** + * Set maximum message size. Maximum message size determines the point at which the + * connection will fail a connection with the message_too_big protocol error. This + * value may be changed during the connection. + * + * The default is set by the endpoint that creates the connection. + * + * @since 0.4.0-alpha1 + * + * @param new_value The value to set as the maximum message size. + */ + void set_max_message_size(size_t new_value) { + m_max_message_size = new_value; + if (m_processor) { + m_processor->set_max_message_size(new_value); + } + } + ////////////////////////////////// // Uncategorized public methods // ////////////////////////////////// @@ -472,7 +602,7 @@ public: * frame::opcode::text */ lib::error_code send(std::string const & payload, frame::opcode::value op = - frame::opcode::TEXT); + frame::opcode::text); /// Send a message (raw array overload) /** @@ -489,7 +619,7 @@ public: * frame::opcode::binary */ lib::error_code send(void const * payload, size_t len, frame::opcode::value - op = frame::opcode::BINARY); + op = frame::opcode::binary); /// Add a message to the outgoing send queue /** @@ -520,9 +650,46 @@ public: * @return An error code */ lib::error_code interrupt(); - + /// Transport inturrupt callback void handle_interrupt(); + + /// Pause reading of new data + /** + * Signals to the connection to halt reading of new data. While reading is paused, + * the connection will stop reading from its associated socket. In turn this will + * result in TCP based flow control kicking in and slowing data flow from the remote + * endpoint. + * + * This is useful for applications that push new requests to a queue to be processed + * by another thread and need a way to signal when their request queue is full without + * blocking the network processing thread. + * + * Use `resume_reading()` to resume. + * + * If supported by the transport this is done asynchronously. As such reading may not + * stop until the current read operation completes. Typically you can expect to + * receive no more bytes after initiating a read pause than the size of the read + * buffer. + * + * If reading is paused for this connection already nothing is changed. + */ + lib::error_code pause_reading(); + + /// Pause reading callback + void handle_pause_reading(); + + /// Resume reading of new data + /** + * Signals to the connection to resume reading of new data after it was paused by + * `pause_reading()`. + * + * If reading is not paused for this connection already nothing is changed. + */ + lib::error_code resume_reading(); + + /// Resume reading callback + void handle_resume_reading(); /// Send a ping /** @@ -803,7 +970,7 @@ public: * @see replace_header * @see websocketpp::http::parser::append_header */ - void append_header(std::string const &key, std::string const & val); + void append_header(std::string const & key, std::string const & val); /// Replace a header /** @@ -997,8 +1164,8 @@ public: void handle_open_handshake_timeout(lib::error_code const & ec); void handle_close_handshake_timeout(lib::error_code const & ec); - void handle_read_frame(lib::error_code const & ec, - size_t bytes_transferred); + void handle_read_frame(lib::error_code const & ec, size_t bytes_transferred); + void read_frame(); /// Get array of WebSocket protocol versions that this connection supports. const std::vector& get_supported_versions() const; @@ -1030,7 +1197,7 @@ public: * @param ec A status code from the transport layer, zero on success, * non-zero otherwise. */ - void handle_write_frame(bool terminate, lib::error_code const & ec); + void handle_write_frame(lib::error_code const & ec); protected: void handle_transport_init(lib::error_code const & ec); @@ -1186,8 +1353,20 @@ private: */ void log_fail_result(); + /// Prints information about an arbitrary error code on the specified channel + template + void log_err(log::level l, char const * msg, error_type const & ec) { + std::stringstream s; + s << msg << " error: " << ec << " (" << ec.message() << ")"; + m_elog.write(l, s.str()); + } + + // internal handler functions + read_handler m_handle_read_frame; + write_frame_handler m_write_frame_handler; + // static settings - const std::string m_user_agent; + std::string const m_user_agent; /// Pointer to the connection handle connection_hdl m_connection_hdl; @@ -1204,6 +1383,12 @@ private: validate_handler m_validate_handler; message_handler m_message_handler; + /// constant values + long m_open_handshake_timeout_dur; + long m_close_handshake_timeout_dur; + long m_pong_timeout_dur; + size_t m_max_message_size; + /// External connection state /** * Lock: m_connection_state_lock @@ -1266,9 +1451,9 @@ private: */ std::vector m_send_buffer; - /// a pointer to hold on to the current message being written to keep it + /// a list of pointers to hold on to the messages being written to keep them /// from going out of scope before the write is complete. - message_ptr m_current_msg; + std::vector m_current_msgs; /// True if there is currently an outstanding transport write /** @@ -1276,6 +1461,9 @@ private: */ bool m_write_flag; + /// True if this connection is presently reading new data + bool m_read_flag; + // connection data request_type m_request; response_type m_response; diff --git a/websocketpp/endpoint.hpp b/websocketpp/endpoint.hpp index 3d44aef..6c08b13 100644 --- a/websocketpp/endpoint.hpp +++ b/websocketpp/endpoint.hpp @@ -87,18 +87,22 @@ public: typedef lib::shared_ptr hdl_type; - explicit endpoint(bool is_server) + explicit endpoint(bool p_is_server) : 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_open_handshake_timeout_dur(config::timeout_open_handshake) + , m_close_handshake_timeout_dur(config::timeout_close_handshake) + , m_pong_timeout_dur(config::timeout_pong) + , m_max_message_size(config::max_message_size) + , m_is_server(p_is_server) { m_alog.set_channels(config::alog_level); m_elog.set_channels(config::elog_level); - m_alog.write(log::alevel::devel,"endpoint constructor"); + m_alog.write(log::alevel::devel, "endpoint constructor"); - transport_type::init_logging(&m_alog,&m_elog); + transport_type::init_logging(&m_alog, &m_elog); } /// Returns the user agent string that this endpoint will use @@ -229,30 +233,37 @@ public: m_close_handler = h; } void set_fail_handler(fail_handler h) { + m_alog.write(log::alevel::devel,"set_fail_handler"); scoped_lock_type guard(m_mutex); m_fail_handler = h; } void set_ping_handler(ping_handler h) { + m_alog.write(log::alevel::devel,"set_ping_handler"); scoped_lock_type guard(m_mutex); m_ping_handler = h; } void set_pong_handler(pong_handler h) { + m_alog.write(log::alevel::devel,"set_pong_handler"); scoped_lock_type guard(m_mutex); m_pong_handler = h; } void set_pong_timeout_handler(pong_timeout_handler h) { + m_alog.write(log::alevel::devel,"set_pong_timeout_handler"); scoped_lock_type guard(m_mutex); m_pong_timeout_handler = h; } void set_interrupt_handler(interrupt_handler h) { + m_alog.write(log::alevel::devel,"set_interrupt_handler"); scoped_lock_type guard(m_mutex); m_interrupt_handler = h; } void set_http_handler(http_handler h) { + m_alog.write(log::alevel::devel,"set_http_handler"); scoped_lock_type guard(m_mutex); m_http_handler = h; } void set_validate_handler(validate_handler h) { + m_alog.write(log::alevel::devel,"set_validate_handler"); scoped_lock_type guard(m_mutex); m_validate_handler = h; } @@ -262,6 +273,112 @@ public: m_message_handler = h; } + ////////////////////////////////////////// + // Connection timeouts and other limits // + ////////////////////////////////////////// + + /// Set open handshake timeout + /** + * Sets the length of time the library will wait after an opening handshake + * has been initiated before cancelling it. This can be used to prevent + * excessive wait times for outgoing clients or excessive resource usage + * from broken clients or DoS attacks on servers. + * + * Connections that time out will have their fail handlers called with the + * open_handshake_timeout error code. + * + * The default value is specified via the compile time config value + * 'timeout_open_handshake'. The default value in the core config + * is 5000ms. A value of 0 will disable the timer entirely. + * + * To be effective, the transport you are using must support timers. See + * the documentation for your transport policy for details about its + * timer support. + * + * @param dur The length of the open handshake timeout in ms + */ + void set_open_handshake_timeout(long dur) { + scoped_lock_type guard(m_mutex); + m_open_handshake_timeout_dur = dur; + } + + /// Set close handshake timeout + /** + * Sets the length of time the library will wait after a closing handshake + * has been initiated before cancelling it. This can be used to prevent + * excessive wait times for outgoing clients or excessive resource usage + * from broken clients or DoS attacks on servers. + * + * Connections that time out will have their close handlers called with the + * close_handshake_timeout error code. + * + * The default value is specified via the compile time config value + * 'timeout_close_handshake'. The default value in the core config + * is 5000ms. A value of 0 will disable the timer entirely. + * + * To be effective, the transport you are using must support timers. See + * the documentation for your transport policy for details about its + * timer support. + * + * @param dur The length of the close handshake timeout in ms + */ + void set_close_handshake_timeout(long dur) { + scoped_lock_type guard(m_mutex); + m_close_handshake_timeout_dur = dur; + } + + /// Set pong timeout + /** + * Sets the length of time the library will wait for a pong response to a + * ping. This can be used as a keepalive or to detect broken connections. + * + * Pong responses that time out will have the pong timeout handler called. + * + * The default value is specified via the compile time config value + * 'timeout_pong'. The default value in the core config + * is 5000ms. A value of 0 will disable the timer entirely. + * + * To be effective, the transport you are using must support timers. See + * the documentation for your transport policy for details about its + * timer support. + * + * @param dur The length of the pong timeout in ms + */ + void set_pong_timeout(long dur) { + scoped_lock_type guard(m_mutex); + m_pong_timeout_dur = dur; + } + + /// Get default maximum message size + /** + * Get the default maximum message size that will be used for new connections created + * by this endpoint. The maximum message size determines the point at which the + * connection will fail a connection with the message_too_big protocol error. + * + * The default is set by the max_message_size value from the template config + * + * @since 0.4.0-alpha1 + */ + size_t get_max_message_size() const { + return m_max_message_size; + } + + /// Set default maximum message size + /** + * Set the default maximum message size that will be used for new connections created + * by this endpoint. Maximum message size determines the point at which the connection + * will fail a connection with the message_too_big protocol error. + * + * The default is set by the max_message_size value from the template config + * + * @since 0.4.0-alpha1 + * + * @param new_value The value to set as the maximum message size. + */ + void set_max_message_size(size_t new_value) { + m_max_message_size = new_value; + } + /*************************************/ /* Connection pass through functions */ /*************************************/ @@ -277,8 +394,63 @@ public: void interrupt(connection_hdl hdl, lib::error_code & ec); void interrupt(connection_hdl hdl); + /// Pause reading of new data (exception free) + /** + * Signals to the connection to halt reading of new data. While reading is paused, + * the connection will stop reading from its associated socket. In turn this will + * result in TCP based flow control kicking in and slowing data flow from the remote + * endpoint. + * + * This is useful for applications that push new requests to a queue to be processed + * by another thread and need a way to signal when their request queue is full without + * blocking the network processing thread. + * + * Use `resume_reading()` to resume. + * + * If supported by the transport this is done asynchronously. As such reading may not + * stop until the current read operation completes. Typically you can expect to + * receive no more bytes after initiating a read pause than the size of the read + * buffer. + * + * If reading is paused for this connection already nothing is changed. + */ + void pause_reading(connection_hdl hdl, lib::error_code & ec); + + /// Pause reading of new data + void pause_reading(connection_hdl hdl); + + /// Resume reading of new data (exception free) + /** + * Signals to the connection to resume reading of new data after it was paused by + * `pause_reading()`. + * + * If reading is not paused for this connection already nothing is changed. + */ + void resume_reading(connection_hdl hdl, lib::error_code & ec); + + /// Resume reading of new data + void resume_reading(connection_hdl hdl); + + /// Create a message and add it to the outgoing send queue (exception free) + /** + * Convenience method to send a message given a payload string and an opcode + * + * @param [in] hdl The handle identifying the connection to send via. + * @param [in] payload The payload string to generated the message with + * @param [in] op The opcode to generated the message with. + * @param [out] ec A code to fill in for errors + */ void send(connection_hdl hdl, std::string const & payload, frame::opcode::value op, lib::error_code & ec); + /// Create a message and add it to the outgoing send queue + /** + * Convenience method to send a message given a payload string and an opcode + * + * @param [in] hdl The handle identifying the connection to send via. + * @param [in] payload The payload string to generated the message with + * @param [in] op The opcode to generated the message with. + * @param [out] ec A code to fill in for errors + */ void send(connection_hdl hdl, std::string const & payload, frame::opcode::value op); @@ -372,7 +544,6 @@ public: } protected: connection_ptr create_connection(); - void remove_connection(connection_ptr con); alog_type m_alog; elog_type m_elog; @@ -391,16 +562,18 @@ private: validate_handler m_validate_handler; message_handler m_message_handler; - rng_type m_rng; + long m_open_handshake_timeout_dur; + long m_close_handshake_timeout_dur; + long m_pong_timeout_dur; + size_t m_max_message_size; - // endpoint resources - std::set m_connections; + rng_type m_rng; // static settings bool const m_is_server; // endpoint state - mutex_type m_mutex; + mutable mutex_type m_mutex; }; } // namespace websocketpp diff --git a/websocketpp/error.hpp b/websocketpp/error.hpp index d699075..45a3458 100644 --- a/websocketpp/error.hpp +++ b/websocketpp/error.hpp @@ -114,7 +114,14 @@ enum value { close_handshake_timeout, /// Invalid port in URI - invalid_port + invalid_port, + + /// An async accept operation failed because the underlying transport has been + /// requested to not listen for new connections anymore. + async_accept_not_listening, + + /// The requested operation was canceled + operation_canceled }; // enum value @@ -176,6 +183,10 @@ public: return "The closing handshake timed out"; case error::invalid_port: return "Invalid URI port"; + case error::async_accept_not_listening: + return "Async Accept not listening"; + case error::operation_canceled: + return "Operation canceled"; default: return "Unknown"; } @@ -205,9 +216,9 @@ namespace websocketpp { class exception : public std::exception { public: - exception(std::string const & msg, - error::value code = error::general) - : m_msg(msg),m_code(code) {} + exception(std::string const & msg, error::value p_code = error::general) + : m_msg(msg), m_code(p_code) {} + ~exception() throw() {} virtual char const * what() const throw() { diff --git a/websocketpp/extensions/permessage_deflate/enabled.hpp b/websocketpp/extensions/permessage_deflate/enabled.hpp index d8011a9..9e5de6a 100644 --- a/websocketpp/extensions/permessage_deflate/enabled.hpp +++ b/websocketpp/extensions/permessage_deflate/enabled.hpp @@ -28,8 +28,10 @@ #ifndef WEBSOCKETPP_PROCESSOR_EXTENSION_PERMESSAGEDEFLATE_HPP #define WEBSOCKETPP_PROCESSOR_EXTENSION_PERMESSAGEDEFLATE_HPP + #include #include +#include #include #include @@ -522,6 +524,7 @@ public: m_dstate.next_out = m_compress_buffer.get(); deflate(&m_dstate, m_flush); + output = m_compress_buffer_size - m_dstate.avail_out; out.append((char *)(m_compress_buffer.get()),output); diff --git a/websocketpp/http/constants.hpp b/websocketpp/http/constants.hpp index 3edc3eb..eac92db 100644 --- a/websocketpp/http/constants.hpp +++ b/websocketpp/http/constants.hpp @@ -53,7 +53,7 @@ namespace http { static char const header_delimiter[] = "\r\n"; /// Literal value of the HTTP header separator - static char const header_separator[] = ": "; + static char const header_separator[] = ":"; /// Literal value of an empty header static std::string const empty_header = ""; @@ -255,7 +255,7 @@ namespace http { case internal_server_error: return "Internal Server Error"; case not_implemented: - return "Not Implimented"; + return "Not Implemented"; case bad_gateway: return "Bad Gateway"; case service_unavailable: diff --git a/websocketpp/http/impl/parser.hpp b/websocketpp/http/impl/parser.hpp index 0ceac2e..d1d9081 100644 --- a/websocketpp/http/impl/parser.hpp +++ b/websocketpp/http/impl/parser.hpp @@ -147,8 +147,8 @@ inline void parser::process_header(std::string::iterator begin, throw exception("Invalid header line",status_code::bad_request); } - append_header(std::string(begin,cursor), - std::string(cursor+sizeof(header_separator)-1,end)); + append_header(strip_lws(std::string(begin,cursor)), + strip_lws(std::string(cursor+sizeof(header_separator)-1,end))); } inline std::string parser::raw_headers() const { diff --git a/websocketpp/http/impl/request.hpp b/websocketpp/http/impl/request.hpp index 804a7fa..4557df4 100644 --- a/websocketpp/http/impl/request.hpp +++ b/websocketpp/http/impl/request.hpp @@ -38,15 +38,15 @@ namespace http { namespace parser { inline bool request::parse_complete(std::istream& s) { - std::string request; + std::string req; // get status line - std::getline(s, request); + std::getline(s, req); - if (request[request.size()-1] == '\r') { - request.erase(request.end()-1); + if (req[req.size()-1] == '\r') { + req.erase(req.end()-1); - std::stringstream ss(request); + std::stringstream ss(req); std::string val; ss >> val; @@ -127,18 +127,18 @@ inline size_t request::consume(const char *buf, size_t len) { } } - begin = end+sizeof(header_delimiter)-1; + begin = end+(sizeof(header_delimiter)-1); } } inline std::string request::raw() { // TODO: validation. Make sure all required fields have been set? - std::stringstream raw; + std::stringstream ret; - raw << m_method << " " << m_uri << " " << get_version() << "\r\n"; - raw << raw_headers() << "\r\n" << m_body; + ret << m_method << " " << m_uri << " " << get_version() << "\r\n"; + ret << raw_headers() << "\r\n" << m_body; - return raw.str(); + return ret.str(); } inline void request::set_method(const std::string& method) { diff --git a/websocketpp/http/impl/response.hpp b/websocketpp/http/impl/response.hpp index bcddd17..e3a73eb 100644 --- a/websocketpp/http/impl/response.hpp +++ b/websocketpp/http/impl/response.hpp @@ -172,15 +172,15 @@ inline size_t response::consume(std::istream & s) { 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; + std::string line; // get status line - std::getline(s, response); + std::getline(s, line); - if (response[response.size()-1] == '\r') { - response.erase(response.end()-1); + if (line[line.size()-1] == '\r') { + line.erase(line.end()-1); - std::stringstream ss(response); + std::stringstream ss(line); std::string str_val; int int_val; char char_val[256]; @@ -201,14 +201,14 @@ inline bool response::parse_complete(std::istream& s) { inline std::string response::raw() const { // TODO: validation. Make sure all required fields have been set? - std::stringstream raw; + std::stringstream ret; - raw << get_version() << " " << m_status_code << " " << m_status_msg; - raw << "\r\n" << raw_headers() << "\r\n"; + ret << get_version() << " " << m_status_code << " " << m_status_msg; + ret << "\r\n" << raw_headers() << "\r\n"; - raw << m_body; + ret << m_body; - return raw.str(); + return ret.str(); } inline void response::set_status(status_code::value code) { diff --git a/websocketpp/http/parser.hpp b/websocketpp/http/parser.hpp index 0bb52c2..2819276 100644 --- a/websocketpp/http/parser.hpp +++ b/websocketpp/http/parser.hpp @@ -367,6 +367,16 @@ InputIterator extract_parameters(InputIterator begin, InputIterator end, return cursor; } +inline std::string strip_lws(std::string const & input) { + std::string::const_iterator begin = extract_all_lws(input.begin(),input.end()); + if (begin == input.end()) { + return std::string(); + } + std::string::const_reverse_iterator end = extract_all_lws(input.rbegin(),input.rend()); + + return std::string(begin,end.base()); +} + /// Base HTTP parser /** * Includes methods and data elements common to all types of HTTP messages such diff --git a/websocketpp/impl/connection_impl.hpp b/websocketpp/impl/connection_impl.hpp index c24fc40..c7a9a99 100644 --- a/websocketpp/impl/connection_impl.hpp +++ b/websocketpp/impl/connection_impl.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, Peter Thorson. All rights reserved. + * Copyright (c) 2014, 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: @@ -28,6 +28,7 @@ #ifndef WEBSOCKETPP_CONNECTION_IMPL_HPP #define WEBSOCKETPP_CONNECTION_IMPL_HPP +#include #include #include @@ -132,7 +133,7 @@ lib::error_code connection::send(typename config::message_type::ptr msg) if (needs_writing) { transport_con_type::dispatch(lib::bind( &type::write_frame, - type::shared_from_this() + type::get_shared() )); } @@ -164,15 +165,17 @@ void connection::ping(const std::string& payload, lib::error_code& ec) { m_ping_timer->cancel(); } - m_ping_timer = transport_con_type::set_timer( - config::timeout_pong, - lib::bind( - &type::handle_pong_timeout, - type::shared_from_this(), - payload, - lib::placeholders::_1 - ) - ); + if (m_pong_timeout_dur > 0) { + m_ping_timer = transport_con_type::set_timer( + m_pong_timeout_dur, + lib::bind( + &type::handle_pong_timeout, + type::get_shared(), + payload, + lib::placeholders::_1 + ) + ); + } if (!m_ping_timer) { // Our transport doesn't support timers @@ -191,7 +194,7 @@ void connection::ping(const std::string& payload, lib::error_code& ec) { if (needs_writing) { transport_con_type::dispatch(lib::bind( &type::write_frame, - type::shared_from_this() + type::get_shared() )); } @@ -254,7 +257,7 @@ void connection::pong(const std::string& payload, lib::error_code& ec) { if (needs_writing) { transport_con_type::dispatch(lib::bind( &type::write_frame, - type::shared_from_this() + type::get_shared() )); } @@ -271,8 +274,8 @@ void connection::pong(const std::string& payload) { } template -void connection::close(const close::status::value code, - const std::string & reason, lib::error_code & ec) +void connection::close(close::status::value const code, + std::string const & reason, lib::error_code & ec) { m_alog.write(log::alevel::devel,"connection close"); @@ -289,8 +292,8 @@ void connection::close(const close::status::value code, } template -void connection::close(const close::status::value code, - const std::string & reason) +void connection::close(close::status::value const code, + std::string const & reason) { lib::error_code ec; close(code,reason,ec); @@ -309,7 +312,7 @@ lib::error_code connection::interrupt() { return transport_con_type::interrupt( lib::bind( &type::handle_interrupt, - type::shared_from_this() + type::get_shared() ) ); } @@ -322,6 +325,41 @@ void connection::handle_interrupt() { } } +template +lib::error_code connection::pause_reading() { + m_alog.write(log::alevel::devel,"connection connection::pause_reading"); + return transport_con_type::dispatch( + lib::bind( + &type::handle_pause_reading, + type::get_shared() + ) + ); +} + +/// Pause reading handler. Not safe to call directly +template +void connection::handle_pause_reading() { + m_alog.write(log::alevel::devel,"connection connection::handle_pause_reading"); + m_read_flag = false; +} + +template +lib::error_code connection::resume_reading() { + m_alog.write(log::alevel::devel,"connection connection::resume_reading"); + return transport_con_type::dispatch( + lib::bind( + &type::handle_resume_reading, + type::get_shared() + ) + ); +} + +/// Resume reading helper method. Not safe to call directly +template +void connection::handle_resume_reading() { + m_read_flag = true; + read_frame(); +} @@ -466,8 +504,7 @@ connection::get_response_header(const std::string &key) { } template -void connection::set_status( - http::status_code::value code) +void connection::set_status(http::status_code::value code) { //scoped_lock_type lock(m_connection_state_lock); @@ -480,8 +517,8 @@ void connection::set_status( m_response.set_status(code); } template -void connection::set_status( - http::status_code::value code, const std::string& msg) +void connection::set_status(http::status_code::value code, + std::string const & msg) { //scoped_lock_type lock(m_connection_state_lock); @@ -494,7 +531,7 @@ void connection::set_status( m_response.set_status(code,msg); } template -void connection::set_body(const std::string& value) { +void connection::set_body(std::string const & value) { //scoped_lock_type lock(m_connection_state_lock); if (m_internal_state != istate::PROCESS_HTTP_REQUEST) { @@ -505,47 +542,71 @@ void connection::set_body(const std::string& value) { m_response.set_body(value); } + template -void connection::append_header( - const std::string &key, const std::string &val) +void connection::append_header(std::string const & key, + std::string const & val) { //scoped_lock_type lock(m_connection_state_lock); - if (m_internal_state != istate::PROCESS_HTTP_REQUEST) { - throw error::make_error_code(error::invalid_state); - //throw exception("Call to set_status from invalid state", - // error::INVALID_STATE); + if (m_is_server) { + if (m_internal_state == istate::PROCESS_HTTP_REQUEST) { + // we are setting response headers for an incoming server connection + m_response.append_header(key,val); + } else { + throw error::make_error_code(error::invalid_state); + } + } else { + if (m_internal_state == istate::USER_INIT) { + // we are setting initial headers for an outgoing client connection + m_request.append_header(key,val); + } else { + throw error::make_error_code(error::invalid_state); + } } - - m_response.append_header(key,val); } template -void connection::replace_header( - const std::string &key, const std::string &val) +void connection::replace_header(std::string const & key, + std::string const & val) { // scoped_lock_type lock(m_connection_state_lock); - if (m_internal_state != istate::PROCESS_HTTP_REQUEST) { - throw error::make_error_code(error::invalid_state); - //throw exception("Call to set_status from invalid state", - // error::INVALID_STATE); + if (m_is_server) { + if (m_internal_state == istate::PROCESS_HTTP_REQUEST) { + // we are setting response headers for an incoming server connection + m_response.replace_header(key,val); + } else { + throw error::make_error_code(error::invalid_state); + } + } else { + if (m_internal_state == istate::USER_INIT) { + // we are setting initial headers for an outgoing client connection + m_request.replace_header(key,val); + } else { + throw error::make_error_code(error::invalid_state); + } } - - m_response.replace_header(key,val); } template -void connection::remove_header( - const std::string &key) +void connection::remove_header(std::string const & key) { //scoped_lock_type lock(m_connection_state_lock); - if (m_internal_state != istate::PROCESS_HTTP_REQUEST) { - throw error::make_error_code(error::invalid_state); - //throw exception("Call to set_status from invalid state", - // error::INVALID_STATE); + if (m_is_server) { + if (m_internal_state == istate::PROCESS_HTTP_REQUEST) { + // we are setting response headers for an incoming server connection + m_response.remove_header(key); + } else { + throw error::make_error_code(error::invalid_state); + } + } else { + if (m_internal_state == istate::USER_INIT) { + // we are setting initial headers for an outgoing client connection + m_request.remove_header(key); + } else { + throw error::make_error_code(error::invalid_state); + } } - - m_response.remove_header(key); } @@ -571,7 +632,7 @@ void connection::start() { transport_con_type::init( lib::bind( &type::handle_transport_init, - type::shared_from_this(), + type::get_shared(), lib::placeholders::_1 ) ); @@ -602,7 +663,7 @@ void connection::handle_transport_init(lib::error_code const & ec) { if (ec) { std::stringstream s; - s << "handle_transport_init recieved error: "<< ec.message(); + s << "handle_transport_init received error: "<< ec.message(); m_elog.write(log::elevel::fatal,s.str()); this->terminate(ec); @@ -624,14 +685,16 @@ template void connection::read_handshake(size_t num_bytes) { m_alog.write(log::alevel::devel,"connection read"); - m_handshake_timer = transport_con_type::set_timer( - config::timeout_open_handshake, - lib::bind( - &type::handle_open_handshake_timeout, - type::shared_from_this(), - lib::placeholders::_1 - ) - ); + if (m_open_handshake_timeout_dur > 0) { + m_handshake_timer = transport_con_type::set_timer( + m_open_handshake_timeout_dur, + lib::bind( + &type::handle_open_handshake_timeout, + type::get_shared(), + lib::placeholders::_1 + ) + ); + } transport_con_type::async_read_at_least( num_bytes, @@ -639,7 +702,7 @@ void connection::read_handshake(size_t num_bytes) { config::connection_read_buffer_size, lib::bind( &type::handle_read_handshake, - type::shared_from_this(), + type::get_shared(), lib::placeholders::_1, lib::placeholders::_2 ) @@ -649,7 +712,7 @@ void connection::read_handshake(size_t num_bytes) { // All exit paths for this function need to call send_http_response() or submit // a new read request with this function as the handler. template -void connection::handle_read_handshake(const lib::error_code& ec, +void connection::handle_read_handshake(lib::error_code const & ec, size_t bytes_transferred) { m_alog.write(log::alevel::devel,"connection handle_read_handshake"); @@ -763,7 +826,7 @@ void connection::handle_read_handshake(const lib::error_code& ec, config::connection_read_buffer_size, lib::bind( &type::handle_read_handshake, - type::shared_from_this(), + type::get_shared(), lib::placeholders::_1, lib::placeholders::_2 ) @@ -789,7 +852,7 @@ void connection::send_http_response_error() { // All exit paths for this function need to call send_http_response() or submit // a new read request with this function as the handler. template -void connection::handle_read_frame(const lib::error_code& ec, +void connection::handle_read_frame(lib::error_code const & ec, size_t bytes_transferred) { //m_alog.write(log::alevel::devel,"connection handle_read_frame"); @@ -800,6 +863,8 @@ void connection::handle_read_frame(const lib::error_code& ec, ); if (ec) { + log::level echannel = log::elevel::fatal; + if (ec == transport::error::eof) { if (m_state == session::state::closed) { // we expect to get eof if the connection is closed already @@ -815,24 +880,29 @@ void connection::handle_read_frame(const lib::error_code& ec, } } if (ec == transport::error::tls_short_read) { - m_elog.write(log::elevel::rerror,"got TLS short read, killing connection for now"); - this->terminate(ec); - return; + if (m_state == session::state::closed) { + // We expect to get a TLS short read if we try to read after the + // connection is closed. If this happens ignore and exit the + // read frame path. + terminate(lib::error_code()); + return; + } + echannel = log::elevel::rerror; + } else if (ec == transport::error::action_after_shutdown) { + echannel = log::elevel::info; } - - std::stringstream s; - s << "error in handle_read_frame: " << ec.message() << " (" << ec << ")"; - m_elog.write(log::elevel::fatal,s.str()); + + log_err(echannel, "handle_read_frame", ec); this->terminate(ec); return; } // Boundaries checking. TODO: How much of this should be done? - if (bytes_transferred > config::connection_read_buffer_size) { + /*if (bytes_transferred > config::connection_read_buffer_size) { m_elog.write(log::elevel::fatal,"Fatal boundaries checking error"); this->terminate(make_error_code(error::general)); return; - } + }*/ size_t p = 0; @@ -849,7 +919,7 @@ void connection::handle_read_frame(const lib::error_code& ec, m_alog.write(log::alevel::devel,s.str()); } - lib::error_code ec; + lib::error_code consume_ec; if (m_alog.static_test(log::alevel::devel)) { std::stringstream s; @@ -860,7 +930,7 @@ void connection::handle_read_frame(const lib::error_code& ec, p += m_processor->consume( reinterpret_cast(m_buf)+p, bytes_transferred-p, - ec + consume_ec ); if (m_alog.static_test(log::alevel::devel)) { @@ -868,20 +938,22 @@ void connection::handle_read_frame(const lib::error_code& ec, 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()); + if (consume_ec) { + log_err(log::elevel::rerror, "consume", consume_ec); if (config::drop_on_protocol_error) { - this->terminate(ec); + this->terminate(consume_ec); return; } else { lib::error_code close_ec; - this->close(processor::error::to_ws(ec),ec.message(),close_ec); + this->close( + processor::error::to_ws(consume_ec), + consume_ec.message(), + close_ec + ); if (close_ec) { - m_elog.write(log::elevel::fatal, - "Failed to send a close frame after protocol error: " - +close_ec.message()); + log_err(log::elevel::fatal, "Protocol error close frame ", close_ec); this->terminate(close_ec); return; } @@ -899,13 +971,11 @@ void connection::handle_read_frame(const lib::error_code& ec, message_ptr msg = m_processor->get_message(); if (!msg) { - m_alog.write(log::alevel::devel, - "null message from m_processor"); + m_alog.write(log::alevel::devel, "null message from m_processor"); } else if (!is_control(msg->get_opcode())) { // data message, dispatch to user if (m_state != session::state::open) { - m_elog.write(log::elevel::warn, - "got non-close data frame in state closing"); + m_elog.write(log::elevel::warn, "got non-close frame while closing"); } else if (m_message_handler) { m_message_handler(m_connection_hdl, msg); } @@ -915,6 +985,16 @@ void connection::handle_read_frame(const lib::error_code& ec, } } + read_frame(); +} + +/// Issue a new transport read unless reading is paused. +template +void connection::read_frame() { + if (!m_read_flag) { + return; + } + transport_con_type::async_read_at_least( // std::min wont work with undefined static const values. // TODO: is there a more elegant way to do this? @@ -926,12 +1006,7 @@ void connection::handle_read_frame(const lib::error_code& ec, 1, m_buf, config::connection_read_buffer_size, - lib::bind( - &type::handle_read_frame, - type::shared_from_this(), - lib::placeholders::_1, - lib::placeholders::_2 - ) + m_handle_read_frame ); } @@ -947,8 +1022,7 @@ bool connection::initialize_processor() { int version = processor::get_websocket_version(m_request); if (version < 0) { - m_alog.write(log::alevel::devel, - "BAD REQUEST: can't determine version"); + m_alog.write(log::alevel::devel, "BAD REQUEST: can't determine version"); m_response.set_status(http::status_code::bad_request); return false; } @@ -962,8 +1036,7 @@ bool connection::initialize_processor() { // We don't have a processor for this version. Return bad request // with Sec-WebSocket-Version header filled with values we do accept - m_alog.write(log::alevel::devel, - "BAD REQUEST: no processor for version"); + m_alog.write(log::alevel::devel, "BAD REQUEST: no processor for version"); m_response.set_status(http::status_code::bad_request); std::stringstream ss; @@ -994,8 +1067,7 @@ bool connection::process_handshake_request() { ); if (!m_uri->get_valid()) { - m_alog.write(log::alevel::devel, - std::string("Bad request: failed to parse uri")); + m_alog.write(log::alevel::devel, "Bad request: failed to parse uri"); m_response.set_status(http::status_code::bad_request); return false; } @@ -1014,8 +1086,7 @@ bool connection::process_handshake_request() { // Validate: make sure all required elements are present. if (ec){ // Not a valid handshake request - m_alog.write(log::alevel::devel, - "BAD REQUEST (724) "+ec.message()); + m_alog.write(log::alevel::devel, "Bad request " + ec.message()); m_response.set_status(http::status_code::bad_request); return false; } @@ -1028,8 +1099,7 @@ bool connection::process_handshake_request() { if (neg_results.first) { // There was a fatal error in extension parsing that should result in // a failed connection attempt. - m_alog.write(log::alevel::devel, - "BAD REQUEST: (737) " + neg_results.first.message()); + m_alog.write(log::alevel::devel, "Bad request: " + neg_results.first.message()); m_response.set_status(http::status_code::bad_request); return false; } else { @@ -1047,8 +1117,7 @@ bool connection::process_handshake_request() { if (!m_uri->get_valid()) { - m_alog.write(log::alevel::devel, - std::string("Bad request: failed to parse uri")); + m_alog.write(log::alevel::devel, "Bad request: failed to parse uri"); m_response.set_status(http::status_code::bad_request); return false; } @@ -1072,14 +1141,14 @@ bool connection::process_handshake_request() { if (ec) { std::stringstream s; s << "Processing error: " << ec << "(" << ec.message() << ")"; - m_alog.write(log::alevel::devel,s.str()); + m_alog.write(log::alevel::devel, s.str()); m_response.set_status(http::status_code::internal_server_error); return false; } } else { // User application has rejected the handshake - m_alog.write(log::alevel::devel,"USER REJECT"); + m_alog.write(log::alevel::devel, "USER REJECT"); // Use Bad Request if the user handler did not provide a more // specific http response error code. @@ -1135,16 +1204,14 @@ void connection::send_http_response() { m_handshake_buffer.size(), lib::bind( &type::handle_send_http_response, - type::shared_from_this(), + type::get_shared(), lib::placeholders::_1 ) ); } template -void connection::handle_send_http_response( - const lib::error_code& ec) -{ +void connection::handle_send_http_response(lib::error_code const & ec) { m_alog.write(log::alevel::devel,"handle_send_http_response"); this->atomic_state_check( @@ -1153,35 +1220,34 @@ void connection::handle_send_http_response( ); if (ec) { - m_elog.write(log::elevel::rerror, - "error in handle_send_http_response: "+ec.message()); + log_err(log::elevel::rerror,"handle_send_http_response",ec); this->terminate(ec); return; } this->log_open_result(); + if (m_handshake_timer) { + m_handshake_timer->cancel(); + m_handshake_timer.reset(); + } + if (m_response.get_status_code() != http::status_code::switching_protocols) { if (m_processor) { - // if this was not a websocket connection, we have written - // the expected response and the connection can be closed. - } else { // this was a websocket connection that ended in an error std::stringstream s; s << "Handshake ended with HTTP error: " << m_response.get_status_code(); m_elog.write(log::elevel::rerror,s.str()); + } else { + // if this was not a websocket connection, we have written + // the expected response and the connection can be closed. } this->terminate(make_error_code(error::http_connection_ended)); return; } - if (m_handshake_timer) { - m_handshake_timer->cancel(); - m_handshake_timer.reset(); - } - this->atomic_state_change( istate::PROCESS_HTTP_REQUEST, istate::PROCESS_CONNECTION, @@ -1211,13 +1277,11 @@ void connection::send_http_request() { m_requested_subprotocols); if (ec) { - m_elog.write(log::elevel::fatal, - "Internal library error: processor error: "+ec.message()); + log_err(log::elevel::fatal,"Internal library error: Processor",ec); return; } } else { - m_elog.write(log::elevel::fatal, - "Internal library error: missing processor"); + m_elog.write(log::elevel::fatal,"Internal library error: missing processor"); return; } @@ -1233,32 +1297,33 @@ void connection::send_http_request() { m_handshake_buffer = m_request.raw(); if (m_alog.static_test(log::alevel::devel)) { - m_alog.write(log::alevel::devel, - "Raw Handshake request:\n"+m_handshake_buffer); + m_alog.write(log::alevel::devel,"Raw Handshake request:\n"+m_handshake_buffer); } - m_handshake_timer = transport_con_type::set_timer( - config::timeout_open_handshake, - lib::bind( - &type::handle_open_handshake_timeout, - type::shared_from_this(), - lib::placeholders::_1 - ) - ); + if (m_open_handshake_timeout_dur > 0) { + m_handshake_timer = transport_con_type::set_timer( + m_open_handshake_timeout_dur, + lib::bind( + &type::handle_open_handshake_timeout, + type::get_shared(), + lib::placeholders::_1 + ) + ); + } transport_con_type::async_write( m_handshake_buffer.data(), m_handshake_buffer.size(), lib::bind( &type::handle_send_http_request, - type::shared_from_this(), + type::get_shared(), lib::placeholders::_1 ) ); } template -void connection::handle_send_http_request(const lib::error_code& ec) { +void connection::handle_send_http_request(lib::error_code const & ec) { m_alog.write(log::alevel::devel,"handle_send_http_request"); this->atomic_state_check( @@ -1267,8 +1332,7 @@ void connection::handle_send_http_request(const lib::error_code& ec) { ); if (ec) { - m_elog.write(log::elevel::rerror, - "error in handle_send_http_request: "+ec.message()); + log_err(log::elevel::rerror,"handle_send_http_request",ec); this->terminate(ec); return; } @@ -1285,7 +1349,7 @@ void connection::handle_send_http_request(const lib::error_code& ec) { config::connection_read_buffer_size, lib::bind( &type::handle_read_http_response, - type::shared_from_this(), + type::get_shared(), lib::placeholders::_1, lib::placeholders::_2 ) @@ -1293,7 +1357,7 @@ void connection::handle_send_http_request(const lib::error_code& ec) { } template -void connection::handle_read_http_response(const lib::error_code& ec, +void connection::handle_read_http_response(lib::error_code const & ec, size_t bytes_transferred) { m_alog.write(log::alevel::devel,"handle_read_http_response"); @@ -1304,8 +1368,7 @@ void connection::handle_read_http_response(const lib::error_code& ec, ); if (ec) { - m_elog.write(log::elevel::rerror, - "error in handle_read_http_response: "+ec.message()); + log_err(log::elevel::rerror,"handle_send_http_response",ec); this->terminate(ec); return; } @@ -1323,16 +1386,18 @@ void connection::handle_read_http_response(const lib::error_code& ec, 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( + if (m_handshake_timer) { + m_handshake_timer->cancel(); + m_handshake_timer.reset(); + } + + lib::error_code validate_ec = m_processor->validate_server_handshake_response( m_request, m_response ); - if (ec) { - m_elog.write(log::elevel::rerror, - std::string("Server handshake response was invalid: ")+ - ec.message() - ); - this->terminate(ec); + if (validate_ec) { + log_err(log::elevel::rerror,"Server handshake response",validate_ec); + this->terminate(validate_ec); return; } @@ -1345,11 +1410,6 @@ void connection::handle_read_http_response(const lib::error_code& ec, "handle_read_http_response must be called from READ_HTTP_RESPONSE state" ); - if (m_handshake_timer) { - m_handshake_timer->cancel(); - m_handshake_timer.reset(); - } - this->log_open_result(); if (m_open_handler) { @@ -1370,7 +1430,7 @@ void connection::handle_read_http_response(const lib::error_code& ec, config::connection_read_buffer_size, lib::bind( &type::handle_read_http_response, - type::shared_from_this(), + type::get_shared(), lib::placeholders::_1, lib::placeholders::_2 ) @@ -1383,14 +1443,13 @@ void connection::handle_open_handshake_timeout( lib::error_code const & ec) { if (ec == transport::error::operation_aborted) { - m_alog.write(log::alevel::devel, - "asio open handshake timer cancelled"); + m_alog.write(log::alevel::devel,"open handshake timer cancelled"); } else if (ec) { m_alog.write(log::alevel::devel, - "asio open handle_open_handshake_timeout error: "+ec.message()); + "open handle_open_handshake_timeout error: "+ec.message()); // TODO: ignore or fail here? } else { - m_alog.write(log::alevel::devel, "asio open handshake timer expired"); + m_alog.write(log::alevel::devel,"open handshake timer expired"); terminate(make_error_code(error::open_handshake_timeout)); } } @@ -1400,8 +1459,7 @@ void connection::handle_close_handshake_timeout( lib::error_code const & ec) { if (ec == transport::error::operation_aborted) { - m_alog.write(log::alevel::devel, - "asio close handshake timer cancelled"); + m_alog.write(log::alevel::devel,"asio close handshake timer cancelled"); } else if (ec) { m_alog.write(log::alevel::devel, "asio open handle_close_handshake_timeout error: "+ec.message()); @@ -1413,7 +1471,7 @@ void connection::handle_close_handshake_timeout( } template -void connection::terminate(const lib::error_code & ec) { +void connection::terminate(lib::error_code const & ec) { if (m_alog.static_test(log::alevel::devel)) { m_alog.write(log::alevel::devel,"connection terminate"); } @@ -1443,10 +1501,12 @@ void connection::terminate(const lib::error_code & ec) { return; } + // TODO: choose between shutdown and close based on error code sent + transport_con_type::async_shutdown( lib::bind( &type::handle_terminate, - type::shared_from_this(), + type::get_shared(), tstat, lib::placeholders::_1 ) @@ -1455,7 +1515,7 @@ void connection::terminate(const lib::error_code & ec) { template void connection::handle_terminate(terminate_status tstat, - const lib::error_code& ec) + lib::error_code const & ec) { if (m_alog.static_test(log::alevel::devel)) { m_alog.write(log::alevel::devel,"connection handle_terminate"); @@ -1463,7 +1523,7 @@ void connection::handle_terminate(terminate_status tstat, if (ec) { // there was an error actually shutting down the connection - m_elog.write(log::elevel::rerror,ec.message()); + log_err(log::elevel::devel,"handle_terminate",ec); } // clean shutdown @@ -1486,11 +1546,10 @@ void connection::handle_terminate(terminate_status tstat, // If it does, we don't care and should catch and ignore it. if (m_termination_handler) { try { - m_termination_handler(type::shared_from_this()); - } catch (const std::exception& e) { + m_termination_handler(type::get_shared()); + } catch (std::exception const & e) { m_elog.write(log::elevel::warn, - std::string("termination_handler call failed. Reason was: ") - +e.what()); + std::string("termination_handler call failed. Reason was: ")+e.what()); } } } @@ -1510,72 +1569,104 @@ void connection::write_frame() { return; } - // Get the next message in the queue. This will return an empty - // message if the queue was empty. - m_current_msg = write_pop(); - - if (!m_current_msg) { - return; + // pull off all the messages that are ready to write. + // stop if we get a message marked terminal + message_ptr next_message = write_pop(); + while (next_message) { + m_current_msgs.push_back(next_message); + if (!next_message->get_terminal()) { + next_message = write_pop(); + } else { + next_message = message_ptr(); + } + } + + if (m_current_msgs.empty()) { + // there was nothing to send + return; + } else { + // At this point we own the next messages to be sent and are + // responsible for holding the write flag until they are + // successfully sent or there is some error + m_write_flag = true; } - - // At this point we own the next message to be sent and are - // responsible for holding the write flag until it is successfully - // sent or there is some error - m_write_flag = true; } - 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())); + typename std::vector::iterator it; + for (it = m_current_msgs.begin(); it != m_current_msgs.end(); ++it) { + std::string const & header = (*it)->get_header(); + std::string const & payload = (*it)->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())); + } + // Print detailed send stats if those log levels are enabled 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() - << " payload bytes" << std::endl; - 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)); + std::stringstream general,header,payload; + + general << "Dispatching write containing " << m_current_msgs.size() + <<" message(s) containing "; + header << "Header Bytes: \n"; + payload << "Payload Bytes: \n"; + + size_t hbytes = 0; + size_t pbytes = 0; + + for (size_t i = 0; i < m_current_msgs.size(); i++) { + hbytes += m_current_msgs[i]->get_header().size(); + pbytes += m_current_msgs[i]->get_payload().size(); + + + header << "[" << i << "] (" + << m_current_msgs[i]->get_header().size() << ") " + << utility::to_hex(m_current_msgs[i]->get_header()) << "\n"; + + if (m_alog.static_test(log::alevel::frame_payload)) { + if (m_alog.dynamic_test(log::alevel::frame_payload)) { + payload << "[" << i << "] (" + << m_current_msgs[i]->get_payload().size() << ") " + << utility::to_hex(m_current_msgs[i]->get_payload()) + << "\n"; + } + } + } + + general << hbytes << " header bytes and " << pbytes << " payload bytes"; + + m_alog.write(log::alevel::frame_header,general.str()); + m_alog.write(log::alevel::frame_header,header.str()); + m_alog.write(log::alevel::frame_payload,payload.str()); } } transport_con_type::async_write( m_send_buffer, - lib::bind( - &type::handle_write_frame, - type::shared_from_this(), - m_current_msg->get_terminal(), - lib::placeholders::_1 - ) + m_write_frame_handler ); } template -void connection::handle_write_frame(bool terminate, - const lib::error_code& ec) +void connection::handle_write_frame(lib::error_code const & ec) { if (m_alog.static_test(log::alevel::devel)) { m_alog.write(log::alevel::devel,"connection handle_write_frame"); } + bool terminal = m_current_msgs.back()->get_terminal(); + m_send_buffer.clear(); - m_current_msg.reset(); + m_current_msgs.clear(); + // TODO: recycle instead of deleting if (ec) { - m_elog.write(log::elevel::fatal,"error in handle_write_frame: "+ec.message()); + log_err(log::elevel::fatal,"handle_write_frame",ec); this->terminate(ec); return; } - if (terminate) { + if (terminal) { this->terminate(lib::error_code()); return; } @@ -1593,14 +1684,14 @@ void connection::handle_write_frame(bool terminate, if (needs_writing) { transport_con_type::dispatch(lib::bind( &type::write_frame, - type::shared_from_this() + type::get_shared() )); } } template -void connection::atomic_state_change(istate_type req, - istate_type dest, std::string msg) +void connection::atomic_state_change(istate_type req, istate_type dest, + std::string msg) { scoped_lock_type lock(m_connection_state_lock); @@ -1613,10 +1704,9 @@ void connection::atomic_state_change(istate_type req, } template -void connection::atomic_state_change( - istate_type internal_req, istate_type internal_dest, - session::state::value external_req, session::state::value external_dest, - std::string msg) +void connection::atomic_state_change(istate_type internal_req, + istate_type internal_dest, session::state::value external_req, + session::state::value external_dest, std::string msg) { scoped_lock_type lock(m_connection_state_lock); @@ -1630,8 +1720,7 @@ void connection::atomic_state_change( } template -void connection::atomic_state_check(istate_type req, - std::string msg) +void connection::atomic_state_check(istate_type req, std::string msg) { scoped_lock_type lock(m_connection_state_lock); @@ -1648,8 +1737,7 @@ const std::vector& connection::get_supported_versions() const } template -void connection::process_control_frame(typename - config::message_type::ptr msg) +void connection::process_control_frame(typename config::message_type::ptr msg) { m_alog.write(log::alevel::devel,"process_control_frame"); @@ -1670,17 +1758,16 @@ void connection::process_control_frame(typename } if (op == frame::opcode::PING) { - bool pong = true; + bool should_reply = true; if (m_ping_handler) { - pong = m_ping_handler(m_connection_hdl, msg->get_payload()); + should_reply = m_ping_handler(m_connection_hdl, msg->get_payload()); } - if (pong) { + if (should_reply) { this->pong(msg->get_payload(),ec); if (ec) { - m_elog.write(log::elevel::devel, - "Failed to send response pong: "+ec.message()); + log_err(log::elevel::devel,"Failed to send response pong",ec); } } } else if (op == frame::opcode::PONG) { @@ -1696,7 +1783,7 @@ void connection::process_control_frame(typename m_remote_close_code = close::extract_code(msg->get_payload(),ec); if (ec) { - std::stringstream s; + s.str(""); if (config::drop_on_protocol_error) { s << "Received invalid close code " << m_remote_close_code << " dropping connection per config."; @@ -1709,8 +1796,7 @@ void connection::process_control_frame(typename ec = send_close_ack(close::status::protocol_error, "Invalid close code"); if (ec) { - m_elog.write(log::elevel::devel, - "send_close_ack error: "+ec.message()); + log_err(log::elevel::devel,"send_close_ack",ec); } } return; @@ -1728,27 +1814,25 @@ void connection::process_control_frame(typename ec = send_close_ack(close::status::protocol_error, "Invalid close reason"); if (ec) { - m_elog.write(log::elevel::devel, - "send_close_ack error: "+ec.message()); + log_err(log::elevel::devel,"send_close_ack",ec); } } return; } if (m_state == session::state::open) { - std::stringstream s; + s.str(""); s << "Received close frame with code " << m_remote_close_code << " and reason " << m_remote_close_reason; m_alog.write(log::alevel::devel,s.str()); ec = send_close_ack(); if (ec) { - m_elog.write(log::elevel::devel, - "send_close_ack error: "+ec.message()); + log_err(log::elevel::devel,"send_close_ack",ec); } } else if (m_state == session::state::closing && !m_was_clean) { // ack of our close - m_alog.write(log::alevel::devel,"Got acknowledgement of close"); + m_alog.write(log::alevel::devel, "Got acknowledgement of close"); m_was_clean = true; @@ -1764,27 +1848,30 @@ void connection::process_control_frame(typename } } else { // spurious, ignore - m_elog.write(log::elevel::devel,"Got close frame in wrong state"); + m_elog.write(log::elevel::devel, "Got close frame in wrong state"); } } else { // got an invalid control opcode - m_elog.write(log::elevel::devel,"Got control frame with invalid opcode"); + m_elog.write(log::elevel::devel, "Got control frame with invalid opcode"); // initiate protocol error shutdown } } template lib::error_code connection::send_close_ack(close::status::value code, - const std::string &reason) + std::string const & reason) { return send_close_frame(code,reason,true,m_is_server); } template lib::error_code connection::send_close_frame(close::status::value code, - const std::string &reason, bool ack, bool terminal) + std::string const & reason, bool ack, bool terminal) { m_alog.write(log::alevel::devel,"send_close_frame"); + + // check for special codes + // If silent close is set, respect it and blank out close information // Otherwise use whatever has been specified in the parameters. If // parameters specifies close::status::blank then determine what to do @@ -1845,14 +1932,16 @@ lib::error_code connection::send_close_frame(close::status::value code, // Start a timer so we don't wait forever for the acknowledgement close // frame - m_handshake_timer = transport_con_type::set_timer( - config::timeout_close_handshake, - lib::bind( - &type::handle_close_handshake_timeout, - type::shared_from_this(), - lib::placeholders::_1 - ) - ); + if (m_close_handshake_timeout_dur > 0) { + m_handshake_timer = transport_con_type::set_timer( + m_close_handshake_timeout_dur, + lib::bind( + &type::handle_close_handshake_timeout, + type::get_shared(), + lib::placeholders::_1 + ) + ); + } bool needs_writing = false; { @@ -1864,7 +1953,7 @@ lib::error_code connection::send_close_frame(close::status::value code, if (needs_writing) { transport_con_type::dispatch(lib::bind( &type::write_frame, - type::shared_from_this() + type::get_shared() )); } @@ -1875,49 +1964,49 @@ template typename connection::processor_ptr connection::get_processor(int version) const { // TODO: allow disabling certain versions + + processor_ptr p; + switch (version) { case 0: - return processor_ptr( - new processor::hybi00( - transport_con_type::is_secure(), - m_is_server, - m_msg_manager - ) - ); + p.reset(new processor::hybi00( + transport_con_type::is_secure(), + m_is_server, + m_msg_manager + )); break; case 7: - return processor_ptr( - new processor::hybi07( - transport_con_type::is_secure(), - m_is_server, - m_msg_manager, - m_rng - ) - ); + p.reset(new processor::hybi07( + transport_con_type::is_secure(), + m_is_server, + m_msg_manager, + m_rng + )); break; case 8: - return processor_ptr( - new processor::hybi08( - transport_con_type::is_secure(), - m_is_server, - m_msg_manager, - m_rng - ) - ); + p.reset(new processor::hybi08( + transport_con_type::is_secure(), + m_is_server, + m_msg_manager, + m_rng + )); break; case 13: - return processor_ptr( - new processor::hybi13( - transport_con_type::is_secure(), - m_is_server, - m_msg_manager, - m_rng - ) - ); + p.reset(new processor::hybi13( + transport_con_type::is_secure(), + m_is_server, + m_msg_manager, + m_rng + )); break; default: - return processor_ptr(); + return p; } + + // Settings not configured by the constructor + p->set_max_message_size(m_max_message_size); + + return p; } template diff --git a/websocketpp/impl/endpoint_impl.hpp b/websocketpp/impl/endpoint_impl.hpp index 2a7c82d..2cd4ff2 100644 --- a/websocketpp/impl/endpoint_impl.hpp +++ b/websocketpp/impl/endpoint_impl.hpp @@ -40,6 +40,7 @@ endpoint::create_connection() { return connection_ptr(); }*/ + //scoped_lock_type guard(m_mutex); // Create a connection on the heap and manage it using a shared pointer connection_ptr con(new connection_type(m_is_server,m_user_agent,m_alog, m_elog, m_rng)); @@ -50,9 +51,6 @@ endpoint::create_connection() { // Cast that weak pointer to void* and manage it using another shared_ptr // connection_hdl hdl(reinterpret_cast(new connection_weak_ptr(con))); - // con->set_handle(hdl); - - // con->set_handle(w); // Copy default handlers from the endpoint @@ -67,13 +65,18 @@ endpoint::create_connection() { con->set_validate_handler(m_validate_handler); con->set_message_handler(m_message_handler); - con->set_termination_handler( - lib::bind( - &type::remove_connection, - this, - lib::placeholders::_1 - ) - ); + if (m_open_handshake_timeout_dur != config::timeout_open_handshake) { + con->set_open_handshake_timeout(m_open_handshake_timeout_dur); + } + if (m_close_handshake_timeout_dur != config::timeout_close_handshake) { + con->set_close_handshake_timeout(m_close_handshake_timeout_dur); + } + if (m_pong_timeout_dur != config::timeout_pong) { + con->set_pong_timeout(m_pong_timeout_dur); + } + if (m_max_message_size != config::max_message_size) { + con->set_max_message_size(m_max_message_size); + } lib::error_code ec; @@ -83,15 +86,11 @@ endpoint::create_connection() { return connection_ptr(); } - scoped_lock_type lock(m_mutex); - m_connections.insert(con); - return con; } template -void endpoint::interrupt(connection_hdl hdl, - lib::error_code & ec) +void endpoint::interrupt(connection_hdl hdl, lib::error_code & ec) { connection_ptr con = get_con_from_hdl(hdl,ec); if (ec) {return;} @@ -109,8 +108,42 @@ void endpoint::interrupt(connection_hdl hdl) { } template -void endpoint::send(connection_hdl hdl, std::string const & - payload, frame::opcode::value op, lib::error_code & ec) +void endpoint::pause_reading(connection_hdl hdl, lib::error_code & ec) +{ + connection_ptr con = get_con_from_hdl(hdl,ec); + if (ec) {return;} + + ec = con->pause_reading(); +} + +template +void endpoint::pause_reading(connection_hdl hdl) { + lib::error_code ec; + pause_reading(hdl,ec); + if (ec) { throw ec; } +} + +template +void endpoint::resume_reading(connection_hdl hdl, lib::error_code & ec) +{ + connection_ptr con = get_con_from_hdl(hdl,ec); + if (ec) {return;} + + ec = con->resume_reading(); +} + +template +void endpoint::resume_reading(connection_hdl hdl) { + lib::error_code ec; + resume_reading(hdl,ec); + if (ec) { throw ec; } +} + + + +template +void endpoint::send(connection_hdl hdl, std::string const & payload, + frame::opcode::value op, lib::error_code & ec) { connection_ptr con = get_con_from_hdl(hdl,ec); if (ec) {return;} @@ -119,8 +152,8 @@ void endpoint::send(connection_hdl hdl, std::string const & } template -void endpoint::send(connection_hdl hdl, std::string const & - payload, frame::opcode::value op) +void endpoint::send(connection_hdl hdl, std::string const & payload, + frame::opcode::value op) { lib::error_code ec; send(hdl,payload,op,ec); @@ -190,8 +223,7 @@ void endpoint::ping(connection_hdl hdl, std::string const & } template -void endpoint::ping(connection_hdl hdl, std::string const & - payload) +void endpoint::ping(connection_hdl hdl, std::string const & payload) { lib::error_code ec; ping(hdl,payload,ec); @@ -199,8 +231,8 @@ void endpoint::ping(connection_hdl hdl, std::string const & } template -void endpoint::pong(connection_hdl hdl, std::string const & - payload, lib::error_code & ec) +void endpoint::pong(connection_hdl hdl, std::string const & payload, + lib::error_code & ec) { connection_ptr con = get_con_from_hdl(hdl,ec); if (ec) {return;} @@ -208,28 +240,13 @@ void endpoint::pong(connection_hdl hdl, std::string const & } template -void endpoint::pong(connection_hdl hdl, std::string const & - payload) +void endpoint::pong(connection_hdl hdl, std::string const & payload) { lib::error_code ec; pong(hdl,payload,ec); if (ec) { throw ec; } } -template -void endpoint::remove_connection(connection_ptr con) { - std::stringstream s; - s << "remove_connection. New count: " << m_connections.size()-1; - m_alog.write(log::alevel::devel,s.str()); - - scoped_lock_type lock(m_mutex); - - // unregister the termination handler - con->set_termination_handler(termination_handler()); - - m_connections.erase(con); -} - } // namespace websocketpp #endif // WEBSOCKETPP_ENDPOINT_IMPL_HPP diff --git a/websocketpp/processors/hybi00.hpp b/websocketpp/processors/hybi00.hpp index 27d0f93..8e04a74 100644 --- a/websocketpp/processors/hybi00.hpp +++ b/websocketpp/processors/hybi00.hpp @@ -34,6 +34,7 @@ #include #include #include +#include #include @@ -57,8 +58,8 @@ public: typedef typename config::con_msg_manager_type::ptr msg_manager_ptr; - explicit hybi00(bool secure, bool server, msg_manager_ptr manager) - : processor(secure, server) + explicit hybi00(bool secure, bool p_is_server, msg_manager_ptr manager) + : processor(secure, p_is_server) , msg_hdr(0x00) , msg_ftr(0xff) , m_state(HEADER) diff --git a/websocketpp/processors/hybi07.hpp b/websocketpp/processors/hybi07.hpp index 8b2df0e..bf4956d 100644 --- a/websocketpp/processors/hybi07.hpp +++ b/websocketpp/processors/hybi07.hpp @@ -45,9 +45,8 @@ public: typedef typename config::con_msg_manager_type::ptr msg_manager_ptr; typedef typename config::rng_type rng_type; - explicit hybi07(bool secure, bool server, msg_manager_ptr manager, - rng_type& rng) - : hybi08(secure, server, manager, rng) {} + explicit hybi07(bool secure, bool p_is_server, msg_manager_ptr manager, rng_type& rng) + : hybi08(secure, p_is_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, diff --git a/websocketpp/processors/hybi08.hpp b/websocketpp/processors/hybi08.hpp index 54f2103..ba9e210 100644 --- a/websocketpp/processors/hybi08.hpp +++ b/websocketpp/processors/hybi08.hpp @@ -46,9 +46,8 @@ public: typedef typename config::con_msg_manager_type::ptr msg_manager_ptr; typedef typename config::rng_type rng_type; - explicit hybi08(bool secure, bool server, msg_manager_ptr manager, - rng_type& rng) - : hybi13(secure, server, manager, rng) {} + explicit hybi08(bool secure, bool p_is_server, msg_manager_ptr manager, rng_type& rng) + : hybi13(secure, p_is_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, diff --git a/websocketpp/processors/hybi13.hpp b/websocketpp/processors/hybi13.hpp index 15ac5c7..5df9f1a 100644 --- a/websocketpp/processors/hybi13.hpp +++ b/websocketpp/processors/hybi13.hpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -67,9 +68,8 @@ public: typedef std::pair err_str_pair; - explicit hybi13(bool secure, bool server, msg_manager_ptr manager, - rng_type& rng) - : processor(secure,server) + explicit hybi13(bool secure, bool p_is_server, msg_manager_ptr manager, rng_type& rng) + : processor(secure, p_is_server) , m_msg_manager(manager) , m_rng(rng) { @@ -370,11 +370,25 @@ public: m_current_msg = &m_control_msg; } else { if (!m_data_msg.msg_ptr) { + if (m_bytes_needed > base::m_max_message_size) { + ec = make_error_code(error::message_too_big); + break; + } + m_data_msg = msg_metadata( m_msg_manager->get_message(op,m_bytes_needed), frame::get_masking_key(m_basic_header,m_extended_header) ); } else { + // Fetch the underlying payload buffer from the data message we + // are writing into. + std::string & out = m_data_msg.msg_ptr->get_raw_payload(); + + if (out.size() + m_bytes_needed > base::m_max_message_size) { + ec = make_error_code(error::message_too_big); + break; + } + // Each frame starts a new masking key. All other state // remains between frames. m_data_msg.prepared_key = prepare_masking_key( @@ -383,7 +397,8 @@ public: m_extended_header ) ); - // TODO: reserve space in the existing message for the new bytes + + out.reserve(out.size() + m_bytes_needed); } m_current_msg = &m_data_msg; } @@ -660,27 +675,11 @@ protected: lib::error_code process_handshake_key(std::string & key) const { key.append(constants::handshake_guid); - sha1 sha; - uint32_t message_digest[5]; + unsigned char message_digest[20]; + sha1::calc(key.c_str(),key.length(),message_digest); + key = base64_encode(message_digest,20); - sha << key.c_str(); - - if (sha.get_raw_digest(message_digest)){ - // convert sha1 hash bytes to network byte order because this sha1 - // library works on ints rather than bytes - for (int i = 0; i < 5; i++) { - message_digest[i] = htonl(message_digest[i]); - } - - key = base64_encode( - reinterpret_cast(message_digest), - 20 - ); - - return lib::error_code(); - } else { - return error::make_error_code(error::sha1_library); - } + return lib::error_code(); } /// Reads bytes from buf into m_basic_header diff --git a/websocketpp/processors/processor.hpp b/websocketpp/processors/processor.hpp index 6333b5b..69b9ab1 100644 --- a/websocketpp/processors/processor.hpp +++ b/websocketpp/processors/processor.hpp @@ -159,15 +159,45 @@ public: typedef typename config::message_type::ptr message_ptr; typedef std::pair err_str_pair; - explicit processor(bool secure, bool server) + explicit processor(bool secure, bool p_is_server) : m_secure(secure) - , m_server(server) {} + , m_server(p_is_server) + , m_max_message_size(config::max_message_size) + {} virtual ~processor() {} /// Get the protocol version of this processor virtual int get_version() const = 0; + /// Get maximum message size + /** + * Get maximum message size. Maximum message size determines the point at which the + * processor will fail a connection with the message_too_big protocol error. + * + * The default is retrieved from the max_message_size value from the template config + * + * @since 0.4.0-alpha1 + */ + size_t get_max_message_size() const { + return m_max_message_size; + } + + /// Set maximum message size + /** + * Set maximum message size. Maximum message size determines the point at which the + * processor will fail a connection with the message_too_big protocol error. + * + * The default is retrieved from the max_message_size value from the template config + * + * @since 0.4.0-alpha1 + * + * @param new_value The value to set as the maximum message size. + */ + void set_max_message_size(size_t new_value) { + m_max_message_size = new_value; + } + /// Returns whether or not the permessage_compress extension is implemented /** * Compile time flag that indicates whether this processor has implemented @@ -196,8 +226,7 @@ public: * @return A status code, 0 on success, non-zero for specific sorts of * failure */ - virtual lib::error_code validate_handshake(request_type const & request) - const = 0; + virtual lib::error_code validate_handshake(request_type const & request) const = 0; /// Calculate the appropriate response for this websocket request /** @@ -236,8 +265,7 @@ public: virtual std::string get_raw(response_type const & request) const = 0; /// Return the value of the header containing the CORS origin. - virtual std::string const & get_origin(request_type const & request) - const = 0; + virtual std::string const & get_origin(request_type const & request) const = 0; /// Extracts requested subprotocols from a handshake request /** @@ -310,8 +338,7 @@ public: * Performs validation, masking, compression, etc. will return an error if * there was an error, otherwise msg will be ready to be written */ - virtual lib::error_code prepare_data_frame(message_ptr in, message_ptr out) - = 0; + virtual lib::error_code prepare_data_frame(message_ptr in, message_ptr out) = 0; /// Prepare a ping frame /** @@ -324,8 +351,8 @@ public: * * @return Status code, zero on success, non-zero on failure */ - virtual lib::error_code prepare_ping(std::string const & in, - message_ptr out) const = 0; + virtual lib::error_code prepare_ping(std::string const & in, message_ptr out) const + = 0; /// Prepare a pong frame /** @@ -338,8 +365,8 @@ public: * * @return Status code, zero on success, non-zero on failure */ - virtual lib::error_code prepare_pong(std::string const & in, - message_ptr out) const = 0; + virtual lib::error_code prepare_pong(std::string const & in, message_ptr out) const + = 0; /// Prepare a close frame /** @@ -361,6 +388,7 @@ public: protected: bool const m_secure; bool const m_server; + size_t m_max_message_size; }; } // namespace processor diff --git a/websocketpp/roles/client_endpoint.hpp b/websocketpp/roles/client_endpoint.hpp index 7ce05ec..1072a0f 100644 --- a/websocketpp/roles/client_endpoint.hpp +++ b/websocketpp/roles/client_endpoint.hpp @@ -66,8 +66,7 @@ public: explicit client() : endpoint_type(false) { - endpoint_type::m_alog.write(log::alevel::devel, - "client constructor"); + endpoint_type::m_alog.write(log::alevel::devel, "client constructor"); } /// Get a new connection @@ -77,15 +76,17 @@ public: * applying connection specific settings before performing the opening * handshake. * + * @param [in] location URI to open the connection to as a uri_ptr + * @param [out] ec An status code indicating failure reasons, if any + * * @return A connection_ptr to the new connection */ - connection_ptr get_connection(uri_ptr location, lib::error_code &ec) { + connection_ptr get_connection(uri_ptr location, lib::error_code & ec) { if (location->get_secure() && !transport_type::is_secure()) { ec = error::make_error_code(error::endpoint_not_secure); return connection_ptr(); } - // create connection connection_ptr con = endpoint_type::create_connection(); if (!con) { @@ -95,7 +96,6 @@ public: con->set_uri(location); - // Success ec = lib::error_code(); return con; } @@ -106,10 +106,12 @@ public: * suitable for passing to connect(connection_ptr). This overload allows * default construction of the uri_ptr from a standard string. * + * @param [in] u URI to open the connection to as a string + * @param [out] ec An status code indicating failure reasons, if any + * * @return A connection_ptr to the new connection */ - connection_ptr get_connection(const std::string& u, lib::error_code &ec) { - // parse uri + connection_ptr get_connection(std::string const & u, lib::error_code & ec) { uri_ptr location(new uri(u)); if (!location->get_valid()) { @@ -137,35 +139,17 @@ public: lib::bind( &type::handle_connect, this, - lib::placeholders::_1, - lib::placeholders::_2 + con, + lib::placeholders::_1 ) ); return con; } - - - - // connect(...) private: // handle_connect - void handle_connect(connection_hdl hdl, const lib::error_code & ec) { - lib::error_code hdl_ec; - connection_ptr con = endpoint_type::get_con_from_hdl(hdl,hdl_ec); - - if (hdl_ec == error::bad_connection) { - endpoint_type::m_elog.write(log::elevel::fatal, - "handle_connect got an invalid handle back"); - } else if (hdl_ec) { - // There was some other unknown error attempting to convert the hdl - // to a connection. - endpoint_type::m_elog.write(log::elevel::fatal, - "handle_connect error in get_con_from_hdl: "+hdl_ec.message()); - //con->terminate(); - } else if (ec) { - // TODO - // Set connection's failure reasons + void handle_connect(connection_ptr con, lib::error_code const & ec) { + if (ec) { con->terminate(ec); endpoint_type::m_elog.write(log::elevel::rerror, diff --git a/websocketpp/roles/server_endpoint.hpp b/websocketpp/roles/server_endpoint.hpp index f2bb7ef..88ceae7 100644 --- a/websocketpp/roles/server_endpoint.hpp +++ b/websocketpp/roles/server_endpoint.hpp @@ -64,69 +64,101 @@ public: /// Type of the endpoint component of this server typedef endpoint endpoint_type; - - // TODO: clean up these types - explicit server() : endpoint_type(true) { - endpoint_type::m_alog.write(log::alevel::devel, - "server constructor"); + endpoint_type::m_alog.write(log::alevel::devel, "server constructor"); } - // return an initialized connection_ptr. Call start() on this object to - // begin the processing loop. + /// Create and initialize a new connection + /** + * The connection will be initialized and ready to begin. Call its start() + * method to begin the processing loop. + * + * Note: The connection must either be started or terminated using + * connection::terminate in order to avoid memory leaks. + * + * @return A pointer to the new connection. + */ connection_ptr get_connection() { - connection_ptr con = endpoint_type::create_connection(); - - return con; + return endpoint_type::create_connection(); } - // Starts the server's async connection acceptance loop. - void start_accept() { + /// Starts the server's async connection acceptance loop (exception free) + /** + * Initiates the server connection acceptance loop. Must be called after + * listen. This method will have no effect until the underlying io_service + * starts running. It may be called after the io_service is already running. + * + * Refer to documentation for the transport policy you are using for + * instructions on how to stop this acceptance loop. + * + * @param [out] ec A status code indicating an error, if any. + */ + void start_accept(lib::error_code & ec) { + if (!transport_type::is_listening()) { + ec = error::make_error_code(error::async_accept_not_listening); + return; + } + + ec = lib::error_code(); connection_ptr con = get_connection(); - + transport_type::async_accept( lib::static_pointer_cast(con), - lib::bind( - &type::handle_accept, - this, - lib::placeholders::_1, - lib::placeholders::_2 - ) + lib::bind(&type::handle_accept,this,con,lib::placeholders::_1), + ec ); + + if (ec && con) { + // If the connection was constructed but the accept failed, + // terminate the connection to prevent memory leaks + con->terminate(lib::error_code()); + } } - void handle_accept(connection_hdl hdl, const lib::error_code& ec) { - lib::error_code hdl_ec; - connection_ptr con = endpoint_type::get_con_from_hdl(hdl,hdl_ec); + /// Starts the server's async connection acceptance loop + /** + * Initiates the server connection acceptance loop. Must be called after + * listen. This method will have no effect until the underlying io_service + * starts running. It may be called after the io_service is already running. + * + * Refer to documentation for the transport policy you are using for + * instructions on how to stop this acceptance loop. + */ + void start_accept() { + lib::error_code ec; + start_accept(ec); + if (ec) { + throw ec; + } + } - if (hdl_ec == error::bad_connection) { - // The connection we were trying to connect went out of scope - // This really shouldn't happen - endpoint_type::m_elog.write(log::elevel::fatal, - "handle_accept got an invalid handle back"); - //con->terminate(); - } else if (hdl_ec) { - // There was some other unknown error attempting to convert the hdl - // to a connection. - endpoint_type::m_elog.write(log::elevel::fatal, - "handle_accept error in get_con_from_hdl: "+hdl_ec.message()); - //con->terminate(); - } else { - if (ec) { - con->terminate(ec); + /// Handler callback for start_accept + void handle_accept(connection_ptr con, lib::error_code const & ec) { + if (ec) { + con->terminate(ec); - endpoint_type::m_elog.write(log::elevel::rerror, + if (ec == error::operation_canceled) { + endpoint_type::m_elog.write(log::elevel::info, "handle_accept error: "+ec.message()); } else { - con->start(); + endpoint_type::m_elog.write(log::elevel::rerror, + "handle_accept error: "+ec.message()); } + } else { + con->start(); } - // TODO: are there cases where we should terminate this loop? - start_accept(); + lib::error_code start_ec; + start_accept(start_ec); + if (start_ec == error::async_accept_not_listening) { + endpoint_type::m_elog.write(log::elevel::info, + "Stopping acceptance of new connections because the underlying transport is no longer listening."); + } else if (start_ec) { + endpoint_type::m_elog.write(log::elevel::rerror, + "Restarting async_accept loop failed: "+ec.message()); + } } -private: }; } // namespace websocketpp diff --git a/websocketpp/sha1/license.txt b/websocketpp/sha1/license.txt deleted file mode 100755 index 8401f0e..0000000 --- a/websocketpp/sha1/license.txt +++ /dev/null @@ -1,14 +0,0 @@ -Copyright (C) 1998, 2009 -Paul E. Jones - -Freeware Public License (FPL) - -This software is licensed as "freeware." Permission to distribute -this software in source and binary forms, including incorporation -into other products, is hereby granted without a fee. THIS SOFTWARE -IS PROVIDED 'AS IS' AND WITHOUT ANY EXPRESSED OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHOR SHALL NOT BE HELD -LIABLE FOR ANY DAMAGES RESULTING FROM THE USE OF THIS SOFTWARE, EITHER -DIRECTLY OR INDIRECTLY, INCLUDING, BUT NOT LIMITED TO, LOSS OF DATA -OR DATA BEING RENDERED INACCURATE. diff --git a/websocketpp/sha1/sha1.hpp b/websocketpp/sha1/sha1.hpp index 154196a..76a9eaf 100755 --- a/websocketpp/sha1/sha1.hpp +++ b/websocketpp/sha1/sha1.hpp @@ -1,406 +1,184 @@ -/* - * sha1.hpp - * - * Copyright (C) 1998, 2009 - * Paul E. Jones - * All Rights Reserved. - * - * Modifications were done in 2012-13 by Peter Thorson (webmaster@zaphoyd.com) - * to allow header only usage of the library and use C++ features to better - * support C++ usage. These changes are distributed under the original freeware - * license included below - * - * Freeware Public License (FPL) - * - * This software is licensed as "freeware." Permission to distribute - * this software in source and binary forms, including incorporation - * into other products, is hereby granted without a fee. THIS SOFTWARE - * IS PROVIDED 'AS IS' AND WITHOUT ANY EXPRESSED OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHOR SHALL NOT BE HELD - * LIABLE FOR ANY DAMAGES RESULTING FROM THE USE OF THIS SOFTWARE, EITHER - * DIRECTLY OR INDIRECTLY, INCLUDING, BUT NOT LIMITED TO, LOSS OF DATA - * OR DATA BEING RENDERED INACCURATE. - * - ***************************************************************************** - * - * Description: - * This class implements the Secure Hashing Standard as defined - * in FIPS PUB 180-1 published April 17, 1995. - * - * Many of the variable names in this class, especially the single - * character names, were used because those were the names used - * in the publication. - * - * The Secure Hashing Standard, which uses the Secure Hashing - * Algorithm (SHA), produces a 160-bit message digest for a - * given data stream. In theory, it is highly improbable that - * two messages will produce the same message digest. Therefore, - * this algorithm can serve as a means of providing a "fingerprint" - * for a message. - * - * Portability Issues: - * SHA-1 is defined in terms of 32-bit "words". This code was - * written with the expectation that the processor has at least - * a 32-bit machine word size. If the machine word size is larger, - * the code should still function properly. One caveat to that - * is that the input functions taking characters and character arrays - * assume that only 8 bits of information are stored in each character. - * - * Caveats: - * SHA-1 is designed to work with messages less than 2^64 bits long. - * Although SHA-1 allows a message digest to be generated for - * messages of any number of bits less than 2^64, this implementation - * only works with messages with a length that is a multiple of 8 - * bits. - * - ***************************************************************************** - */ - -#ifndef _SHA1_H_ -#define _SHA1_H_ - -#include - -namespace websocketpp { - -/// Provides SHA1 hashing functionality -class sha1 { -public: - sha1() { - reset(); - } - - virtual ~sha1() {} - - /// Re-initialize the class - void reset() { - length_low = 0; - length_high = 0; - message_block_index = 0; - - H[0] = 0x67452301; - H[1] = 0xEFCDAB89; - H[2] = 0x98BADCFE; - H[3] = 0x10325476; - H[4] = 0xC3D2E1F0; - - computed = false; - corrupted = false; - } - - /// Extract the message digest as a raw integer array - /** - * @param [out] message_digest_array Integer array to store the message in - * @return Whether or not the extraction was sucessful - */ - bool get_raw_digest(uint32_t * message_digest_array) { - if (corrupted) { - return false; - } - - if (!computed) { - pad_message(); - computed = true; - } - - for (int i = 0; i < 5; i++) { - message_digest_array[i] = H[i]; - } - - return true; - } - - /// Provide input to SHA1 - /** - * @param [in] message_array The input bytes - * @param [in] length Length of the message array - */ - void input(unsigned char const * message_array, uint32_t length) { - if (!length) { - return; - } - - if (computed || corrupted) { - corrupted = true; - return; - } - - while (length-- && !corrupted) { -// Suppresses Visual Studio code analysis for write overrun. It doesn't know the -// index into Message_Block is protected by checking length. -// -// TODO: is there a more compatible way to write the original code to avoid -// this sort of warning? -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(suppress: 6386) -#endif - message_block[message_block_index++] = (*message_array & 0xFF); -#ifdef _MSC_VER -#pragma warning(pop) -#endif - - length_low += 8; - length_low &= 0xFFFFFFFF; // Force it to 32 bits - if (length_low == 0) { - length_high++; - length_high &= 0xFFFFFFFF; // Force it to 32 bits - if (length_high == 0) { - corrupted = true; // Message is too long - } - } - - if (message_block_index == 64) { - process_message_block(); - } - - message_array++; - } - } - - /// Provide input to SHA1 - /** - * Overload with signed message array - * - * @param [in] message_array The input bytes - * @param [in] length Length of the message array - */ - void input(char const * message_array, uint32_t length) { - input(reinterpret_cast(message_array), length); - } - - /// Provide input to SHA1 - /** - * Overload with a single unsigned char - * - * @param [in] message_element The character to input - */ - void input(unsigned char message_element) { - input(&message_element, 1); - } - - /// Provide input to SHA1 - /** - * Overload with a single signed char - * - * @param [in] message_element The character to input - */ - void input(char message_element) { - input(reinterpret_cast(&message_element), 1); - } - - /// Provide input to SHA1 - /** - * Overload via signed char array stream input - * - * @param [in] message_array The character array to input - */ - sha1& operator<<(char const * message_array) { - char const * p = message_array; - - while(*p) { - input(*p); - p++; - } - - return *this; - } - - /// Provide input to SHA1 - /** - * Overload via unsigned char array stream input - * - * @param [in] message_array The character array to input - */ - sha1& operator<<(unsigned char const * message_array) { - unsigned char const * p = message_array; - - while(*p) { - input(*p); - p++; - } - - return *this; - } - - /// Provide input to SHA1 - /** - * Overload via signed char stream input - * - * @param [in] message_element The character to input - */ - sha1& operator<<(char message_element) { - input((unsigned char *) &message_element, 1); - - return *this; - } - - /// Provide input to SHA1 - /** - * Overload via unsigned char stream input - * - * @param [in] message_element The character to input - */ - sha1& operator<<(unsigned char message_element) { - input(&message_element, 1); - - return *this; - } -private: - /// Process the next 512 bits of the message - /** - * Many of the variable names in this function, especially the single - * character names, were used because those were the names used - * in the publication. - */ - void process_message_block() { - // Constants defined for SHA-1 - uint32_t const K[] = { 0x5A827999, - 0x6ED9EBA1, - 0x8F1BBCDC, - 0xCA62C1D6 }; - uint32_t temp; // Temporary word value - uint32_t W[80]; // Word sequence - uint32_t A, B, C, D, E; // Word buffers - - // Initialize the first 16 words in the array W - for (int t = 0; t < 16; t++) { - W[t] = ((uint32_t) message_block[t * 4]) << 24; - W[t] |= ((uint32_t) message_block[t * 4 + 1]) << 16; - W[t] |= ((uint32_t) message_block[t * 4 + 2]) << 8; - W[t] |= ((uint32_t) message_block[t * 4 + 3]); - } - - for (int t = 16; t < 80; t++) { - W[t] = CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); - } - - A = H[0]; - B = H[1]; - C = H[2]; - D = H[3]; - E = H[4]; - - for (int t = 0; t < 20; t++) { - temp = CircularShift(5,A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; - temp &= 0xFFFFFFFF; - E = D; - D = C; - C = CircularShift(30,B); - B = A; - A = temp; - } - - for (int t = 20; t < 40; t++) { - temp = CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1]; - temp &= 0xFFFFFFFF; - E = D; - D = C; - C = CircularShift(30,B); - B = A; - A = temp; - } - - for (int t = 40; t < 60; t++) { - temp = CircularShift(5,A) + - ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; - temp &= 0xFFFFFFFF; - E = D; - D = C; - C = CircularShift(30,B); - B = A; - A = temp; - } - - for (int t = 60; t < 80; t++) { - temp = CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3]; - temp &= 0xFFFFFFFF; - E = D; - D = C; - C = CircularShift(30,B); - B = A; - A = temp; - } - - H[0] = (H[0] + A) & 0xFFFFFFFF; - H[1] = (H[1] + B) & 0xFFFFFFFF; - H[2] = (H[2] + C) & 0xFFFFFFFF; - H[3] = (H[3] + D) & 0xFFFFFFFF; - H[4] = (H[4] + E) & 0xFFFFFFFF; - - message_block_index = 0; - } - - /// Pads the current message block to 512 bits - /** - * According to the standard, the message must be padded to an even - * 512 bits. The first padding bit must be a '1'. The last 64 bits - * represent the length of the original message. All bits in between - * should be 0. This function will pad the message according to those - * rules by filling the message_block array accordingly. It will also - * call ProcessMessageBlock() appropriately. When it returns, it - * can be assumed that the message digest has been computed. - */ - void pad_message() { - // Check to see if the current message block is too small to hold - // the initial padding bits and length. If so, we will pad the - // block, process it, and then continue padding into a second block. - if (message_block_index > 55) { - message_block[message_block_index++] = 0x80; - while(message_block_index < 64) { - message_block[message_block_index++] = 0; - } - - process_message_block(); - - while(message_block_index < 56) { - message_block[message_block_index++] = 0; - } - } else { - message_block[message_block_index++] = 0x80; - while(message_block_index < 56) { - message_block[message_block_index++] = 0; - } - - } - - // Store the message length as the last 8 octets - message_block[56] = (length_high >> 24) & 0xFF; - message_block[57] = (length_high >> 16) & 0xFF; - message_block[58] = (length_high >> 8) & 0xFF; - message_block[59] = (length_high) & 0xFF; - message_block[60] = (length_low >> 24) & 0xFF; - message_block[61] = (length_low >> 16) & 0xFF; - message_block[62] = (length_low >> 8) & 0xFF; - message_block[63] = (length_low) & 0xFF; - - process_message_block(); - } - - /// Performs a circular left shift operation - /** - * @param [in] bits How many bits to shift - * @param [in] word The word to shift - * @return The shifted word - */ - inline uint32_t CircularShift(int bits, uint32_t word) { - return ((word << bits) & 0xFFFFFFFF) | ((word & 0xFFFFFFFF) >> (32-bits)); - } - - uint32_t H[5]; // Message digest buffers - - uint32_t length_low; // Message length in bits - uint32_t length_high; // Message length in bits - - unsigned char message_block[64]; // 512-bit message blocks - int message_block_index; // Index into message block array - - bool computed; // Is the digest computed? - bool corrupted; // Is the message digest corruped? - -}; - -} // namespace websocketpp - -#endif // _SHA1_H_ +/* +***** +sha1.hpp is a repackaging of the sha1.cpp and sha1.h files from the shallsha1 +library (http://code.google.com/p/smallsha1/) into a single header suitable for +use as a header only library. This conversion was done by Peter Thorson +(webmaster@zaphoyd.com) in 2013. All modifications to the code are redistributed +under the same license as the original, which is listed below. +***** + + Copyright (c) 2011, Micael Hildenborg + 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 Micael Hildenborg 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 Micael Hildenborg ''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 Micael Hildenborg 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 SHA1_DEFINED +#define SHA1_DEFINED + +namespace websocketpp { +namespace sha1 { + + namespace // local + { + // Rotate an integer value to left. + inline const unsigned int rol(const unsigned int value, + const unsigned int steps) + { + return ((value << steps) | (value >> (32 - steps))); + } + + // Sets the first 16 integers in the buffert to zero. + // Used for clearing the W buffert. + inline void clearWBuffert(unsigned int* buffert) + { + for (int pos = 16; --pos >= 0;) + { + buffert[pos] = 0; + } + } + + inline void innerHash(unsigned int* result, unsigned int* w) + { + unsigned int a = result[0]; + unsigned int b = result[1]; + unsigned int c = result[2]; + unsigned int d = result[3]; + unsigned int e = result[4]; + + int round = 0; + + #define sha1macro(func,val) \ + { \ + const unsigned int t = rol(a, 5) + (func) + e + val + w[round]; \ + e = d; \ + d = c; \ + c = rol(b, 30); \ + b = a; \ + a = t; \ + } + + while (round < 16) + { + sha1macro((b & c) | (~b & d), 0x5a827999) + ++round; + } + while (round < 20) + { + w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); + sha1macro((b & c) | (~b & d), 0x5a827999) + ++round; + } + while (round < 40) + { + w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); + sha1macro(b ^ c ^ d, 0x6ed9eba1) + ++round; + } + while (round < 60) + { + w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); + sha1macro((b & c) | (b & d) | (c & d), 0x8f1bbcdc) + ++round; + } + while (round < 80) + { + w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); + sha1macro(b ^ c ^ d, 0xca62c1d6) + ++round; + } + + #undef sha1macro + + result[0] += a; + result[1] += b; + result[2] += c; + result[3] += d; + result[4] += e; + } + } // namespace + + /** + @param src points to any kind of data to be hashed. + @param bytelength the number of bytes to hash from the src pointer. + @param hash should point to a buffer of at least 20 bytes of size for storing the sha1 result in. + */ + inline void calc(const void* src, const int bytelength, unsigned char* hash) { + // Init the result array. + unsigned int result[5] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 }; + + // Cast the void src pointer to be the byte array we can work with. + const unsigned char* sarray = (const unsigned char*) src; + + // The reusable round buffer + unsigned int w[80]; + + // Loop through all complete 64byte blocks. + const int endOfFullBlocks = bytelength - 64; + int endCurrentBlock; + int currentBlock = 0; + + while (currentBlock <= endOfFullBlocks) + { + endCurrentBlock = currentBlock + 64; + + // Init the round buffer with the 64 byte block data. + for (int roundPos = 0; currentBlock < endCurrentBlock; currentBlock += 4) + { + // This line will swap endian on big endian and keep endian on little endian. + w[roundPos++] = (unsigned int) sarray[currentBlock + 3] + | (((unsigned int) sarray[currentBlock + 2]) << 8) + | (((unsigned int) sarray[currentBlock + 1]) << 16) + | (((unsigned int) sarray[currentBlock]) << 24); + } + innerHash(result, w); + } + + // Handle the last and not full 64 byte block if existing. + endCurrentBlock = bytelength - currentBlock; + clearWBuffert(w); + int lastBlockBytes = 0; + for (;lastBlockBytes < endCurrentBlock; ++lastBlockBytes) + { + w[lastBlockBytes >> 2] |= (unsigned int) sarray[lastBlockBytes + currentBlock] << ((3 - (lastBlockBytes & 3)) << 3); + } + w[lastBlockBytes >> 2] |= 0x80 << ((3 - (lastBlockBytes & 3)) << 3); + if (endCurrentBlock >= 56) + { + innerHash(result, w); + clearWBuffert(w); + } + w[15] = bytelength << 3; + innerHash(result, w); + + // Store hash in result pointer, and make sure we get in in the correct order on both endian models. + for (int hashByte = 20; --hashByte >= 0;) + { + hash[hashByte] = (result[hashByte >> 2] >> (((3 - hashByte) & 0x3) << 3)) & 0xff; + } + } + +} // namespace sha1 +} // namespace websocketpp + +#endif // SHA1_DEFINED diff --git a/websocketpp/transport/asio/base.hpp b/websocketpp/transport/asio/base.hpp index a868ad9..28ac933 100644 --- a/websocketpp/transport/asio/base.hpp +++ b/websocketpp/transport/asio/base.hpp @@ -34,6 +34,10 @@ #include +#include +#include +#include + #include namespace websocketpp { @@ -45,9 +49,133 @@ namespace transport { */ namespace asio { +// + +// Class to manage the memory to be used for handler-based custom allocation. +// It contains a single block of memory which may be returned for allocation +// requests. If the memory is in use when an allocation request is made, the +// allocator delegates allocation to the global heap. +class handler_allocator + : private boost::noncopyable +{ +public: + handler_allocator() + : in_use_(false) + { + } + + void* allocate(std::size_t size) + { + if (!in_use_ && size < storage_.size) + { + in_use_ = true; + return storage_.address(); + } + else + { + return ::operator new(size); + } + } + + void deallocate(void* pointer) + { + if (pointer == storage_.address()) + { + in_use_ = false; + } + else + { + ::operator delete(pointer); + } + } + +private: + // Storage space used for handler-based custom memory allocation. + boost::aligned_storage<1024> storage_; + + // Whether the handler-based custom allocation storage has been used. + bool in_use_; +}; + +// Wrapper class template for handler objects to allow handler memory +// allocation to be customised. Calls to operator() are forwarded to the +// encapsulated handler. +template +class custom_alloc_handler +{ +public: + custom_alloc_handler(handler_allocator& a, Handler h) + : allocator_(a), + handler_(h) + { + } + + template + void operator()(Arg1 arg1) + { + handler_(arg1); + } + + template + void operator()(Arg1 arg1, Arg2 arg2) + { + handler_(arg1, arg2); + } + + friend void* asio_handler_allocate(std::size_t size, + custom_alloc_handler* this_handler) + { + return this_handler->allocator_.allocate(size); + } + + friend void asio_handler_deallocate(void* pointer, std::size_t /*size*/, + custom_alloc_handler* this_handler) + { + this_handler->allocator_.deallocate(pointer); + } + +private: + handler_allocator& allocator_; + Handler handler_; +}; + +// Helper function to wrap a handler object to add custom allocation. +template +inline custom_alloc_handler make_custom_alloc_handler( + handler_allocator& a, Handler h) +{ + return custom_alloc_handler(a, h); +} + + + + + + + +// Forward declaration of class endpoint so that it can be friended/referenced +// before being included. +template +class endpoint; + typedef lib::function socket_shutdown_handler; +typedef lib::function async_read_handler; + +typedef lib::function async_write_handler; + +typedef lib::function pre_init_handler; + +// handle_timer: dynamic parameters, multiple copies +// handle_proxy_write +// handle_proxy_read +// handle_async_write +// handle_pre_init + + /// Asio transport errors namespace error { enum value { diff --git a/websocketpp/transport/asio/connection.hpp b/websocketpp/transport/asio/connection.hpp index e775dd3..cd78fd2 100644 --- a/websocketpp/transport/asio/connection.hpp +++ b/websocketpp/transport/asio/connection.hpp @@ -28,6 +28,7 @@ #ifndef WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP #define WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP +#include #include #include #include @@ -81,9 +82,16 @@ public: /// Type of a pointer to the ASIO io_service being used typedef boost::asio::io_service* io_service_ptr; + /// Type of a pointer to the ASIO io_service::strand being used + typedef lib::shared_ptr strand_ptr; /// Type of a pointer to the ASIO timer class typedef lib::shared_ptr timer_ptr; + // connection is friends with its associated endpoint to allow the endpoint + // to call private/protected utility methods that we don't want to expose + // to the public api. + friend class endpoint; + // generate and manage our own io_service explicit connection(bool is_server, alog_type& alog, elog_type& elog) : m_is_server(is_server) @@ -93,35 +101,56 @@ public: m_alog.write(log::alevel::devel,"asio con transport constructor"); } + /// Get a shared pointer to this component + ptr get_shared() { + return lib::static_pointer_cast(socket_con_type::get_shared()); + } + bool is_secure() const { return socket_con_type::is_secure(); } - /// Finish constructing the transport + /// Sets the tcp pre init handler /** - * init_asio is called once immediately after construction to initialize - * boost::asio components to the io_service + * The tcp pre init handler is called after the raw tcp connection has been + * established but before any additional wrappers (proxy connects, TLS + * handshakes, etc) have been performed. * - * 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. + * @since 0.4.0-alpha1 * - * @param io_service A pointer to the io_service to register with this - * connection - * - * @return Status code for the success or failure of the initialization + * @param h The handler to call on tcp pre init. */ - lib::error_code 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; - - //m_strand.reset(new boost::asio::strand(*io_service)); - - return socket_con_type::init_asio(io_service, m_is_server); + void set_tcp_pre_init_handler(tcp_init_handler h) { + m_tcp_pre_init_handler = h; } + /// Sets the tcp pre init handler (deprecated) + /** + * The tcp pre init handler is called after the raw tcp connection has been + * established but before any additional wrappers (proxy connects, TLS + * handshakes, etc) have been performed. + * + * @deprecated Use set_tcp_pre_init_handler instead + * + * @param h The handler to call on tcp pre init. + */ void set_tcp_init_handler(tcp_init_handler h) { - m_tcp_init_handler = h; + set_tcp_pre_init_handler(h); + } + + /// Sets the tcp post init handler + /** + * The tcp post init handler is called after the tcp connection has been + * established and all additional wrappers (proxy connects, TLS handshakes, + * etc have been performed. This is fired before any bytes are read or any + * WebSocket specific handshake logic has been performed. + * + * @since 0.4.0-alpha1 + * + * @param h The handler to call on tcp post init. + */ + void set_tcp_post_init_handler(tcp_init_handler h) { + m_tcp_post_init_handler = h; } /// Set the proxy to connect through (exception free) @@ -246,26 +275,6 @@ public: 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(); - } - /// Call back a function after a period of time. /** * Sets a timer that calls back a function after the specified period of @@ -288,15 +297,21 @@ public: ) ); - new_timer->async_wait( - lib::bind( - &type::handle_timer, - this, + if (config::enable_multithreading) { + new_timer->async_wait(m_strand->wrap(lib::bind( + &type::handle_timer, get_shared(), new_timer, callback, lib::placeholders::_1 - ) - ); + ))); + } else { + new_timer->async_wait(lib::bind( + &type::handle_timer, get_shared(), + new_timer, + callback, + lib::placeholders::_1 + )); + } return new_timer; } @@ -306,14 +321,14 @@ public: * The timer pointer is included to ensure the timer isn't destroyed until * after it has expired. * + * TODO: candidate for protected status + * * @param t Pointer to the timer in question - * * @param callback The function to call back - * * @param ec The status code */ - void handle_timer(timer_ptr t, timer_handler callback, const - boost::system::error_code& ec) + void handle_timer(timer_ptr t, timer_handler callback, + boost::system::error_code const & ec) { if (ec) { if (ec == boost::asio::error::operation_aborted) { @@ -327,6 +342,11 @@ public: } } protected: + /// Get a pointer to this connection's strand + strand_ptr get_strand() { + return m_strand; + } + /// Initialize transport for reading /** * init_asio is called once immediately after construction to initialize @@ -352,39 +372,106 @@ protected: // TODO: pre-init timeout. Right now no implemented socket policies // actually have an asyncronous pre-init + m_init_handler = callback; + socket_con_type::pre_init( lib::bind( &type::handle_pre_init, - this, - callback, + get_shared(), lib::placeholders::_1 ) ); } - void handle_pre_init(init_handler callback, const lib::error_code& ec) { + /// initialize the proxy buffers and http parsers + /** + * + * @param authority The address of the server we want the proxy to tunnel to + * in the format of a URI authority (host:port) + * + * @return Status code indicating what errors occurred, if any + */ + lib::error_code proxy_init(std::string const & authority) { + if (!m_proxy_data) { + return websocketpp::error::make_error_code( + websocketpp::error::invalid_state); + } + m_proxy_data->req.set_version("HTTP/1.1"); + m_proxy_data->req.set_method("CONNECT"); + + m_proxy_data->req.set_uri(authority); + m_proxy_data->req.replace_header("Host",authority); + + return lib::error_code(); + } + + /// Finish constructing the transport + /** + * init_asio is called once immediately after construction to initialize + * boost::asio components to the io_service. + * + * @param io_service A pointer to the io_service to register with this + * connection + * + * @return Status code for the success or failure of the initialization + */ + lib::error_code init_asio (io_service_ptr io_service) { + m_io_service = io_service; + + if (config::enable_multithreading) { + m_strand.reset(new boost::asio::strand(*io_service)); + + m_async_read_handler = m_strand->wrap(lib::bind( + &type::handle_async_read, get_shared(),lib::placeholders::_1, + lib::placeholders::_2)); + + m_async_write_handler = m_strand->wrap(lib::bind( + &type::handle_async_write, get_shared(),lib::placeholders::_1, + lib::placeholders::_2)); + } else { + m_async_read_handler = lib::bind(&type::handle_async_read, + get_shared(), lib::placeholders::_1, lib::placeholders::_2); + + m_async_write_handler = lib::bind(&type::handle_async_write, + get_shared(), lib::placeholders::_1, lib::placeholders::_2); + } + + lib::error_code ec = socket_con_type::init_asio(io_service, m_strand, + m_is_server); + + if (ec) { + // reset the handlers to break the circular reference: + // this->handler->this + m_async_read_handler = _WEBSOCKETPP_NULLPTR_TOKEN_; + m_async_write_handler = _WEBSOCKETPP_NULLPTR_TOKEN_; + } + + return ec; + } + + void handle_pre_init(lib::error_code const & ec) { if (m_alog.static_test(log::alevel::devel)) { m_alog.write(log::alevel::devel,"asio connection handle pre_init"); } - if (m_tcp_init_handler) { - m_tcp_init_handler(m_connection_hdl); + if (m_tcp_pre_init_handler) { + m_tcp_pre_init_handler(m_connection_hdl); } if (ec) { - callback(ec); + m_init_handler(ec); } // If we have a proxy set issue a proxy connect, otherwise skip to // post_init if (!m_proxy.empty()) { - proxy_write(callback); + proxy_write(); } else { - post_init(callback); + post_init(); } } - void post_init(init_handler callback) { + void post_init() { if (m_alog.static_test(log::alevel::devel)) { m_alog.write(log::alevel::devel,"asio connection post_init"); } @@ -394,9 +481,9 @@ protected: config::timeout_socket_post_init, lib::bind( &type::handle_post_init_timeout, - this, + get_shared(), post_timer, - callback, + m_init_handler, lib::placeholders::_1 ) ); @@ -404,16 +491,16 @@ protected: socket_con_type::post_init( lib::bind( &type::handle_post_init, - this, + get_shared(), post_timer, - callback, + m_init_handler, lib::placeholders::_1 ) ); } void handle_post_init_timeout(timer_ptr post_timer, init_handler callback, - const lib::error_code& ec) + lib::error_code const & ec) { lib::error_code ret_ec; @@ -439,8 +526,8 @@ protected: callback(ret_ec); } - void handle_post_init(timer_ptr post_timer, init_handler callback, const - lib::error_code& ec) + void handle_post_init(timer_ptr post_timer, init_handler callback, + lib::error_code const & ec) { if (ec == transport::error::operation_aborted || post_timer->expires_from_now().is_negative()) @@ -455,10 +542,14 @@ protected: m_alog.write(log::alevel::devel,"asio connection handle_post_init"); } + if (m_tcp_post_init_handler) { + m_tcp_post_init_handler(m_connection_hdl); + } + callback(ec); } - void proxy_write(init_handler callback) { + void proxy_write() { if (m_alog.static_test(log::alevel::devel)) { m_alog.write(log::alevel::devel,"asio connection proxy_write"); } @@ -466,7 +557,7 @@ protected: 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)); + m_init_handler(make_error_code(error::general)); return; } @@ -482,26 +573,38 @@ protected: m_proxy_data->timeout_proxy, lib::bind( &type::handle_proxy_timeout, - this, - callback, + get_shared(), + m_init_handler, lib::placeholders::_1 ) ); // Send proxy request - boost::asio::async_write( - socket_con_type::get_next_layer(), - m_bufs, - lib::bind( - &type::handle_proxy_write, - this, - callback, - lib::placeholders::_1 - ) - ); + if (config::enable_multithreading) { + boost::asio::async_write( + socket_con_type::get_next_layer(), + m_bufs, + m_strand->wrap(lib::bind( + &type::handle_proxy_write, get_shared(), + m_init_handler, + lib::placeholders::_1 + )) + ); + } else { + boost::asio::async_write( + socket_con_type::get_next_layer(), + m_bufs, + lib::bind( + &type::handle_proxy_write, get_shared(), + m_init_handler, + lib::placeholders::_1 + ) + ); + } } - void handle_proxy_timeout(init_handler callback, const lib::error_code & ec) { + void handle_proxy_timeout(init_handler callback, lib::error_code const & ec) + { if (ec == transport::error::operation_aborted) { m_alog.write(log::alevel::devel, "asio handle_proxy_write timer cancelled"); @@ -517,11 +620,12 @@ protected: } } - void handle_proxy_write(init_handler callback, const - boost::system::error_code& ec) + void handle_proxy_write(init_handler callback, + boost::system::error_code const & ec) { if (m_alog.static_test(log::alevel::devel)) { - m_alog.write(log::alevel::devel,"asio connection handle_proxy_write"); + m_alog.write(log::alevel::devel, + "asio connection handle_proxy_write"); } m_bufs.clear(); @@ -559,25 +663,37 @@ protected: return; } - boost::asio::async_read_until( - socket_con_type::get_next_layer(), - m_proxy_data->read_buf, - "\r\n\r\n", - lib::bind( - &type::handle_proxy_read, - this, - callback, - lib::placeholders::_1, - lib::placeholders::_2 - ) - ); + if (config::enable_multithreading) { + boost::asio::async_read_until( + socket_con_type::get_next_layer(), + m_proxy_data->read_buf, + "\r\n\r\n", + m_strand->wrap(lib::bind( + &type::handle_proxy_read, get_shared(), + callback, + lib::placeholders::_1, lib::placeholders::_2 + )) + ); + } else { + boost::asio::async_read_until( + socket_con_type::get_next_layer(), + m_proxy_data->read_buf, + "\r\n\r\n", + lib::bind( + &type::handle_proxy_read, get_shared(), + callback, + lib::placeholders::_1, lib::placeholders::_2 + ) + ); + } } - void handle_proxy_read(init_handler callback, const - boost::system::error_code& ec, size_t bytes_transferred) + void handle_proxy_read(init_handler callback, + boost::system::error_code const & ec, size_t bytes_transferred) { if (m_alog.static_test(log::alevel::devel)) { - m_alog.write(log::alevel::devel,"asio connection handle_proxy_read"); + m_alog.write(log::alevel::devel, + "asio connection handle_proxy_read"); } // Timer expired or the operation was aborted for some reason. @@ -645,7 +761,7 @@ protected: m_proxy_data.reset(); // Continue with post proxy initialization - post_init(callback); + post_init(); } } @@ -663,93 +779,141 @@ protected: m_alog.write(log::alevel::devel,s.str()); } - if (num_bytes > len) { + if (!m_async_read_handler) { + m_alog.write(log::alevel::devel, + "async_read_at_least called after async_shutdown"); + handler(make_error_code(transport::error::action_after_shutdown),0); + return; + } + + // TODO: safety vs speed ? + // maybe move into an if devel block + /*if (num_bytes > len) { m_elog.write(log::elevel::devel, "asio async_read_at_least error::invalid_num_bytes"); handler(make_error_code(transport::error::invalid_num_bytes), size_t(0)); return; + }*/ + + m_read_handler = handler; + + if (!m_read_handler) { + m_alog.write(log::alevel::devel, + "asio con async_read_at_least called with bad handler"); } 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 + make_custom_alloc_handler( + m_read_handler_allocator, + m_async_read_handler ) ); } - void handle_async_read(read_handler handler, const - boost::system::error_code& ec, size_t bytes_transferred) + void handle_async_read(boost::system::error_code const & ec, + size_t bytes_transferred) { - if (!ec) { - handler(lib::error_code(), bytes_transferred); - return; - } + m_alog.write(log::alevel::devel, "asio con handle_async_read"); // translate boost error codes into more lib::error_codes + lib::error_code tec; if (ec == boost::asio::error::eof) { - handler(make_error_code(transport::error::eof), - bytes_transferred); - } else if (ec.value() == 335544539) { - handler(make_error_code(transport::error::tls_short_read), - bytes_transferred); + tec = make_error_code(transport::error::eof); + } else if (ec) { + // We don't know much more about the error at this point. As our + // socket/security policy if it knows more: + tec = socket_con_type::translate_ec(ec); + + if (tec == transport::error::tls_error || + tec == transport::error::pass_through) + { + // These are aggregate/catch all errors. Log some human readable + // information to the info channel to give library users some + // more details about why the upstream method may have failed. + log_err(log::elevel::info,"asio async_read_at_least",ec); + } + } + if (m_read_handler) { + m_read_handler(tec,bytes_transferred); + // TODO: why does this line break things? + //m_read_handler = _WEBSOCKETPP_NULLPTR_TOKEN_; } else { - log_err(log::elevel::info,"asio async_read_at_least",ec); - handler(make_error_code(transport::error::pass_through), - bytes_transferred); + // This can happen in cases where the connection is terminated while + // the transport is waiting on a read. + m_alog.write(log::alevel::devel, + "handle_async_read called with null read handler"); } } void async_write(const char* buf, size_t len, write_handler handler) { - m_bufs.push_back(boost::asio::buffer(buf,len)); + if (!m_async_write_handler) { + m_alog.write(log::alevel::devel, + "async_write (single) called after async_shutdown"); + handler(make_error_code(transport::error::action_after_shutdown)); + return; + } + + m_bufs.push_back(boost::asio::buffer(buf,len)); + + m_write_handler = handler; boost::asio::async_write( socket_con_type::get_socket(), m_bufs, - lib::bind( - &type::handle_async_write, - this, - handler, - lib::placeholders::_1 + make_custom_alloc_handler( + m_write_handler_allocator, + m_async_write_handler ) ); } void async_write(const std::vector& bufs, write_handler handler) { + if (!m_async_write_handler) { + m_alog.write(log::alevel::devel, + "async_write (vector) called after async_shutdown"); + handler(make_error_code(transport::error::action_after_shutdown)); + return; + } std::vector::const_iterator it; for (it = bufs.begin(); it != bufs.end(); ++it) { m_bufs.push_back(boost::asio::buffer((*it).buf,(*it).len)); } + m_write_handler = handler; + boost::asio::async_write( socket_con_type::get_socket(), m_bufs, - lib::bind( - &type::handle_async_write, - this, - handler, - lib::placeholders::_1 + make_custom_alloc_handler( + m_write_handler_allocator, + m_async_write_handler ) ); } - void handle_async_write(write_handler handler, const - boost::system::error_code& ec) + void handle_async_write(boost::system::error_code const & ec, + size_t bytes_transferred) { m_bufs.clear(); + lib::error_code tec; if (ec) { log_err(log::elevel::info,"asio async_write",ec); - handler(make_error_code(transport::error::pass_through)); + tec = make_error_code(transport::error::pass_through); + } + if (m_write_handler) { + m_write_handler(tec); + // TODO: why does this line break things? + //m_write_handler = _WEBSOCKETPP_NULLPTR_TOKEN_; } else { - handler(lib::error_code()); + // This can happen in cases where the connection is terminated while + // the transport is waiting on a read. + m_alog.write(log::alevel::devel, + "handle_async_write called with null write handler"); } } @@ -768,16 +932,22 @@ protected: /// Trigger the on_interrupt handler /** * This needs to be thread safe - * - * Might need a strand at some point? */ lib::error_code interrupt(interrupt_handler handler) { - m_io_service->post(handler); + if (config::enable_multithreading) { + m_io_service->post(m_strand->wrap(handler)); + } else { + m_io_service->post(handler); + } return lib::error_code(); } lib::error_code dispatch(dispatch_handler handler) { - m_io_service->post(handler); + if (config::enable_multithreading) { + m_io_service->post(m_strand->wrap(handler)); + } else { + m_io_service->post(handler); + } return lib::error_code(); } @@ -791,12 +961,22 @@ protected: m_alog.write(log::alevel::devel,"asio connection async_shutdown"); } + // Reset cached handlers now that we won't be reading or writing anymore + // These cached handlers store shared pointers to this connection and + // will leak the connection if not destroyed. + m_async_read_handler = _WEBSOCKETPP_NULLPTR_TOKEN_; + m_async_write_handler = _WEBSOCKETPP_NULLPTR_TOKEN_; + m_init_handler = _WEBSOCKETPP_NULLPTR_TOKEN_; + + m_read_handler = _WEBSOCKETPP_NULLPTR_TOKEN_; + m_write_handler = _WEBSOCKETPP_NULLPTR_TOKEN_; + timer_ptr shutdown_timer; shutdown_timer = set_timer( config::timeout_socket_shutdown, lib::bind( &type::handle_async_shutdown_timeout, - this, + get_shared(), shutdown_timer, callback, lib::placeholders::_1 @@ -806,7 +986,7 @@ protected: socket_con_type::async_shutdown( lib::bind( &type::handle_async_shutdown, - this, + get_shared(), shutdown_timer, callback, lib::placeholders::_1 @@ -815,7 +995,7 @@ protected: } void handle_async_shutdown_timeout(timer_ptr shutdown_timer, init_handler - callback, const lib::error_code& ec) + callback, lib::error_code const & ec) { lib::error_code ret_ec; @@ -826,7 +1006,7 @@ protected: return; } - log_err(log::elevel::devel,"asio handle_async_socket_shutdown",ec); + log_err(log::elevel::devel,"asio handle_async_shutdown_timeout",ec); ret_ec = ec; } else { ret_ec = make_error_code(transport::error::timeout); @@ -839,7 +1019,7 @@ protected: } void handle_async_shutdown(timer_ptr shutdown_timer, shutdown_handler - callback, const boost::system::error_code & ec) + callback, boost::system::error_code const & ec) { if (ec == boost::asio::error::operation_aborted || shutdown_timer->expires_from_now().is_negative()) @@ -850,25 +1030,37 @@ protected: shutdown_timer->cancel(); + lib::error_code tec; if (ec) { - log_err(log::elevel::info,"asio async_shutdown",ec); if (ec == boost::asio::error::not_connected) { // The socket was already closed when we tried to close it. This // happens periodically (usually if a read or write fails // earlier and if it is a real error will be caught at another // level of the stack. - callback(lib::error_code()); } else { - callback(make_error_code(transport::error::pass_through)); + // We don't know anything more about this error, give our + // socket/security policy a crack at it. + tec = socket_con_type::translate_ec(ec); + + if (tec == transport::error::tls_short_read) { + // TLS short read at this point is somewhat expected if both + // sides try and end the connection at the same time or if + // SSLv2 is being used. In general there is nothing that can + // be done here other than a low level development log. + } else { + // all other errors are effectively pass through errors of + // some sort so print some detail on the info channel for + // library users to look up if needed. + log_err(log::elevel::info,"asio async_shutdown",ec); + } } } else { if (m_alog.static_test(log::alevel::devel)) { m_alog.write(log::alevel::devel, "asio con handle_async_shutdown"); } - - callback(lib::error_code()); } + callback(tec); } private: /// Convenience method for logging the code and message for an error_code @@ -899,12 +1091,25 @@ private: lib::shared_ptr m_proxy_data; // transport resources - io_service_ptr m_io_service; - connection_hdl m_connection_hdl; + io_service_ptr m_io_service; + strand_ptr m_strand; + connection_hdl m_connection_hdl; + std::vector m_bufs; // Handlers - tcp_init_handler m_tcp_init_handler; + tcp_init_handler m_tcp_pre_init_handler; + tcp_init_handler m_tcp_post_init_handler; + + handler_allocator m_read_handler_allocator; + handler_allocator m_write_handler_allocator; + + read_handler m_read_handler; + write_handler m_write_handler; + init_handler m_init_handler; + + async_read_handler m_async_read_handler; + async_write_handler m_async_write_handler; }; diff --git a/websocketpp/transport/asio/endpoint.hpp b/websocketpp/transport/asio/endpoint.hpp index 08bb2aa..a7bf8a3 100644 --- a/websocketpp/transport/asio/endpoint.hpp +++ b/websocketpp/transport/asio/endpoint.hpp @@ -84,10 +84,14 @@ public: typedef lib::shared_ptr resolver_ptr; /// Type of timer handle typedef lib::shared_ptr timer_ptr; + /// Type of a shared pointer to an io_service work object + typedef lib::shared_ptr work_ptr; // generate and manage our own io_service explicit endpoint() : m_external_io_service(false) + , m_listen_backlog(0) + , m_reuse_addr(false) , m_state(UNINITIALIZED) { //std::cout << "transport::asio::endpoint constructor" << std::endl; @@ -119,6 +123,8 @@ public: : m_io_service(src.m_io_service) , m_external_io_service(src.m_external_io_service) , m_acceptor(src.m_acceptor) + , m_listen_backlog(boost::asio::socket_base::max_connections) + , m_reuse_addr(src.m_reuse_addr) , m_state(src.m_state) { src.m_io_service = NULL; @@ -132,11 +138,14 @@ public: m_io_service = rhs.m_io_service; m_external_io_service = rhs.m_external_io_service; m_acceptor = rhs.m_acceptor; + m_listen_backlog = rhs.m_listen_backlog + m_reuse_addr = rhs.m_reuse_addr; m_state = rhs.m_state; rhs.m_io_service = NULL; rhs.m_external_io_service = false; rhs.m_acceptor = NULL; + rhs.m_listen_backlog = boost::asio::socket_base::max_connections; rhs.m_state = UNINITIALIZED; } return *this; @@ -216,6 +225,90 @@ public: m_external_io_service = false; } + /// Sets the tcp pre init handler + /** + * The tcp pre init handler is called after the raw tcp connection has been + * established but before any additional wrappers (proxy connects, TLS + * handshakes, etc) have been performed. + * + * @since 0.4.0-alpha1 + * + * @param h The handler to call on tcp pre init. + */ + void set_tcp_pre_init_handler(tcp_init_handler h) { + m_tcp_pre_init_handler = h; + } + + /// Sets the tcp pre init handler (deprecated) + /** + * The tcp pre init handler is called after the raw tcp connection has been + * established but before any additional wrappers (proxy connects, TLS + * handshakes, etc) have been performed. + * + * @deprecated Use set_tcp_pre_init_handler instead + * + * @param h The handler to call on tcp pre init. + */ + void set_tcp_init_handler(tcp_init_handler h) { + set_tcp_pre_init_handler(h); + } + + /// Sets the tcp post init handler + /** + * The tcp post init handler is called after the tcp connection has been + * established and all additional wrappers (proxy connects, TLS handshakes, + * etc have been performed. This is fired before any bytes are read or any + * WebSocket specific handshake logic has been performed. + * + * @since 0.4.0-alpha1 + * + * @param h The handler to call on tcp post init. + */ + void set_tcp_post_init_handler(tcp_init_handler h) { + m_tcp_post_init_handler = h; + } + + /// Sets the maximum length of the queue of pending connections. + /** + * Sets the maximum length of the queue of pending connections. Increasing + * this will allow WebSocket++ to queue additional incoming connections. + * Setting it higher may prevent failed connections at high connection rates + * but may cause additional latency. + * + * For this value to take effect you may need to adjust operating system + * settings. + * + * New values affect future calls to listen only. + * + * A value of zero will use the operating system default. This is the + * default value. + * + * @since 0.4.0-alpha1 + * + * @param backlog The maximum length of the queue of pending connections + */ + void set_listen_backlog(int backlog) { + m_listen_backlog = backlog; + } + + /// Sets whether or not to use the SO_REUSEADDR flag when opening a listening socket + /** + * Specifies whether or not to use the SO_REUSEADDR TCP socket option. What this flag + * does depends on your operating system. Please consult operating system + * documentation for more details. + * + * New values affect future calls to listen only. + * + * The default is false. + * + * @since 0.4.0-alpha1 + * + * @param value Whether or not to use the SO_REUSEADDR option + */ + void set_reuse_addr(bool value) { + m_reuse_addr = value; + } + /// Retrieve a reference to the endpoint's io_service /** * The io_service may be an internal or external one. This may be used to @@ -231,20 +324,6 @@ public: return *m_io_service; } - /// Sets the tcp init handler - /** - * The tcp init handler is called after the tcp connection has been - * established. - * - * @see WebSocket++ handler documentation for more information about - * handlers. - * - * @param h The handler to call on tcp init. - */ - void set_tcp_init_handler(tcp_init_handler h) { - m_tcp_init_handler = h; - } - /// Set up endpoint for listening manually (exception free) /** * Bind the internal acceptor using the specified settings. The endpoint @@ -265,12 +344,25 @@ public: m_alog->write(log::alevel::devel,"asio::listen"); - m_acceptor->open(ep.protocol()); - m_acceptor->set_option(boost::asio::socket_base::reuse_address(true)); - m_acceptor->bind(ep); - m_acceptor->listen(); - m_state = LISTENING; - ec = lib::error_code(); + boost::system::error_code bec; + + m_acceptor->open(ep.protocol(),bec); + if (!bec) { + m_acceptor->set_option(boost::asio::socket_base::reuse_address(m_reuse_addr),bec); + } + if (!bec) { + m_acceptor->bind(ep,bec); + } + if (!bec) { + m_acceptor->listen(m_listen_backlog,bec); + } + if (bec) { + log_err(log::elevel::info,"asio listen",bec); + ec = make_error_code(error::pass_through); + } else { + m_state = LISTENING; + ec = lib::error_code(); + } } /// Set up endpoint for listening manually @@ -456,6 +548,14 @@ public: } } + /// Check if the endpoint is listening + /** + * @return Whether or not the endpoint is listening. + */ + bool is_listening() const { + return (m_state == LISTENING); + } + /// wraps the run method of the internal io_service object std::size_t run() { return m_io_service->run(); @@ -494,6 +594,34 @@ public: return m_io_service->stopped(); } + /// Marks the endpoint as perpetual, stopping it from exiting when empty + /** + * Marks the endpoint as perpetual. Perpetual endpoints will not + * automatically exit when they run out of connections to process. To stop + * a perpetual endpoint call `end_perpetual`. + * + * An endpoint may be marked perpetual at any time by any thread. It must be + * called either before the endpoint has run out of work or before it was + * started + * + * @since 0.4.0-alpha1 + */ + void start_perpetual() { + m_work.reset(new boost::asio::io_service::work(*m_io_service)); + } + + /// Clears the endpoint's perpetual flag, allowing it to exit when empty + /** + * Clears the endpoint's perpetual flag. This will cause the endpoint's run + * method to exit normally when it runs out of connections. If there are + * currently active connections it will not end until they are complete. + * + * @since 0.4.0-alpha1 + */ + void stop_perpetual() { + m_work.reset(); + } + /// Call back a function after a period of time. /** * Sets a timer that calls back a function after the specified period of @@ -563,25 +691,34 @@ public: lib::error_code & ec) { if (m_state != LISTENING) { - m_elog->write(log::elevel::library, - "asio::async_accept called from the wrong state"); using websocketpp::error::make_error_code; - ec = make_error_code(websocketpp::error::invalid_state); + ec = make_error_code(websocketpp::error::async_accept_not_listening); return; } m_alog->write(log::alevel::devel, "asio::async_accept"); - m_acceptor->async_accept( - tcon->get_raw_socket(), - lib::bind( - &type::handle_accept, - this, - tcon->get_handle(), - callback, - lib::placeholders::_1 - ) - ); + if (config::enable_multithreading) { + m_acceptor->async_accept( + tcon->get_raw_socket(), + tcon->get_strand()->wrap(lib::bind( + &type::handle_accept, + this, + callback, + lib::placeholders::_1 + )) + ); + } else { + m_acceptor->async_accept( + tcon->get_raw_socket(), + lib::bind( + &type::handle_accept, + this, + callback, + lib::placeholders::_1 + ) + ); + } } /// Accept the next connection attempt and assign it to con. @@ -612,18 +749,23 @@ protected: m_elog = e; } - void handle_accept(connection_hdl hdl, accept_handler callback, - const boost::system::error_code& error) + void handle_accept(accept_handler callback, boost::system::error_code const + & boost_ec) { - if (error) { - //con->terminate(); - // TODO: Better translation of errors at this point - callback(hdl,make_error_code(error::pass_through)); - return; + lib::error_code ret_ec; + + m_alog->write(log::alevel::devel, "asio::handle_accept"); + + if (boost_ec) { + if (boost_ec == boost::system::errc::operation_canceled) { + ret_ec = make_error_code(websocketpp::error::operation_canceled); + } else { + log_err(log::elevel::info,"asio handle_accept",boost_ec); + ret_ec = make_error_code(error::pass_through); + } } - //con->start(); - callback(hdl,lib::error_code()); + callback(ret_ec); } /// Initiate a new connection @@ -649,13 +791,13 @@ protected: uri_ptr pu(new uri(proxy)); if (!pu->get_valid()) { - cb(tcon->get_handle(),make_error_code(error::proxy_invalid)); + cb(make_error_code(error::proxy_invalid)); return; } ec = tcon->proxy_init(u->get_authority()); if (ec) { - cb(tcon->get_handle(),ec); + cb(ec); return; } @@ -672,34 +814,48 @@ protected: timer_ptr dns_timer; - dns_timer = set_timer( + dns_timer = tcon->set_timer( config::timeout_dns_resolve, lib::bind( &type::handle_resolve_timeout, this, - tcon, dns_timer, cb, lib::placeholders::_1 ) ); - m_resolver->async_resolve( - query, - lib::bind( - &type::handle_resolve, - this, - tcon, - dns_timer, - cb, - lib::placeholders::_1, - lib::placeholders::_2 - ) - ); + if (config::enable_multithreading) { + m_resolver->async_resolve( + query, + tcon->get_strand()->wrap(lib::bind( + &type::handle_resolve, + this, + tcon, + dns_timer, + cb, + lib::placeholders::_1, + lib::placeholders::_2 + )) + ); + } else { + m_resolver->async_resolve( + query, + lib::bind( + &type::handle_resolve, + this, + tcon, + dns_timer, + cb, + lib::placeholders::_1, + lib::placeholders::_2 + ) + ); + } } - void handle_resolve_timeout(transport_con_ptr tcon, timer_ptr dns_timer, - connect_handler callback, const lib::error_code & ec) + void handle_resolve_timeout(timer_ptr dns_timer, connect_handler callback, + lib::error_code const & ec) { lib::error_code ret_ec; @@ -718,11 +874,11 @@ protected: m_alog->write(log::alevel::devel,"DNS resolution timed out"); m_resolver->cancel(); - callback(tcon->get_handle(),ret_ec); + callback(ret_ec); } void handle_resolve(transport_con_ptr tcon, timer_ptr dns_timer, - connect_handler callback, const boost::system::error_code& ec, + connect_handler callback, boost::system::error_code const & ec, boost::asio::ip::tcp::resolver::iterator iterator) { if (ec == boost::asio::error::operation_aborted || @@ -736,7 +892,7 @@ protected: if (ec) { log_err(log::elevel::info,"asio async_resolve",ec); - callback(tcon->get_handle(),make_error_code(error::pass_through)); + callback(make_error_code(error::pass_through)); return; } @@ -756,10 +912,10 @@ protected: timer_ptr con_timer; - con_timer = set_timer( + con_timer = tcon->set_timer( config::timeout_connect, lib::bind( - &type::handle_resolve_timeout, + &type::handle_connect_timeout, this, tcon, con_timer, @@ -768,22 +924,37 @@ protected: ) ); - boost::asio::async_connect( - tcon->get_raw_socket(), - iterator, - lib::bind( - &type::handle_connect, - this, - tcon, - con_timer, - callback, - lib::placeholders::_1 - ) - ); + if (config::enable_multithreading) { + boost::asio::async_connect( + tcon->get_raw_socket(), + iterator, + tcon->get_strand()->wrap(lib::bind( + &type::handle_connect, + this, + tcon, + con_timer, + callback, + lib::placeholders::_1 + )) + ); + } else { + boost::asio::async_connect( + tcon->get_raw_socket(), + iterator, + lib::bind( + &type::handle_connect, + this, + tcon, + con_timer, + callback, + lib::placeholders::_1 + ) + ); + } } void handle_connect_timeout(transport_con_ptr tcon, timer_ptr con_timer, - connect_handler callback, const lib::error_code & ec) + connect_handler callback, lib::error_code const & ec) { lib::error_code ret_ec; @@ -802,11 +973,11 @@ protected: m_alog->write(log::alevel::devel,"TCP connect timed out"); tcon->cancel_socket(); - callback(tcon->get_handle(),ret_ec); + callback(ret_ec); } void handle_connect(transport_con_ptr tcon, timer_ptr con_timer, - connect_handler callback, const boost::system::error_code& ec) + connect_handler callback, boost::system::error_code const & ec) { if (ec == boost::asio::error::operation_aborted || con_timer->expires_from_now().is_negative()) @@ -819,7 +990,7 @@ protected: if (ec) { log_err(log::elevel::info,"asio async_connect",ec); - callback(tcon->get_handle(),make_error_code(error::pass_through)); + callback(make_error_code(error::pass_through)); return; } @@ -828,11 +999,7 @@ protected: "Async connect to "+tcon->get_remote_endpoint()+" successful."); } - callback(tcon->get_handle(),lib::error_code()); - } - - bool is_listening() const { - return (m_state == LISTENING); + callback(lib::error_code()); } /// Initialize a connection @@ -858,7 +1025,8 @@ protected: ec = tcon->init_asio(m_io_service); if (ec) {return ec;} - tcon->set_tcp_init_handler(m_tcp_init_handler); + tcon->set_tcp_pre_init_handler(m_tcp_pre_init_handler); + tcon->set_tcp_post_init_handler(m_tcp_post_init_handler); return lib::error_code(); } @@ -878,13 +1046,19 @@ private: }; // Handlers - tcp_init_handler m_tcp_init_handler; + tcp_init_handler m_tcp_pre_init_handler; + tcp_init_handler m_tcp_post_init_handler; // Network Resources io_service_ptr m_io_service; bool m_external_io_service; acceptor_ptr m_acceptor; resolver_ptr m_resolver; + work_ptr m_work; + + // Network constants + int m_listen_backlog; + bool m_reuse_addr; elog_type* m_elog; alog_type* m_alog; diff --git a/websocketpp/transport/asio/security/base.hpp b/websocketpp/transport/asio/security/base.hpp index 7e00379..7003452 100644 --- a/websocketpp/transport/asio/security/base.hpp +++ b/websocketpp/transport/asio/security/base.hpp @@ -95,7 +95,10 @@ namespace error { pass_through, /// Required tls_init handler not present - missing_tls_init_handler + missing_tls_init_handler, + + /// TLS Handshake Failed + tls_handshake_failed, }; } // namespace error @@ -119,9 +122,11 @@ public: case error::tls_handshake_timeout: return "TLS handshake timed out"; case error::pass_through: - return "Pass through from underlying library"; + return "Pass through from socket policy"; case error::missing_tls_init_handler: return "Required tls_init handler not present."; + case error::tls_handshake_failed: + return "TLS handshake failed"; default: return "Unknown"; } diff --git a/websocketpp/transport/asio/security/none.hpp b/websocketpp/transport/asio/security/none.hpp index c0df412..96bf868 100644 --- a/websocketpp/transport/asio/security/none.hpp +++ b/websocketpp/transport/asio/security/none.hpp @@ -28,6 +28,7 @@ #ifndef WEBSOCKETPP_TRANSPORT_SECURITY_NONE_HPP #define WEBSOCKETPP_TRANSPORT_SECURITY_NONE_HPP +#include #include #include @@ -48,7 +49,7 @@ typedef lib::function * transport::asio::basic_socket::connection implements a connection socket * component using Boost ASIO ip::tcp::socket. */ -class connection { +class connection : public lib::enable_shared_from_this { public: /// Type of this connection socket component typedef connection type; @@ -57,6 +58,8 @@ public: /// Type of a pointer to the ASIO io_service being used typedef boost::asio::io_service* io_service_ptr; + /// Type of a pointer to the ASIO io_service strand being used + typedef lib::shared_ptr strand_ptr; /// Type of a shared pointer to the socket being used. typedef lib::shared_ptr socket_ptr; @@ -65,6 +68,11 @@ public: // << std::endl; } + /// Get a shared pointer to this component + ptr get_shared() { + return shared_from_this(); + } + /// Check whether or not this connection is secure /** * @return Whether or not this connection is secure @@ -146,7 +154,9 @@ protected: * @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) { + lib::error_code init_asio (io_service_ptr service, strand_ptr strand, + bool is_server) + { if (m_state != UNINITIALIZED) { return socket::make_error_code(socket::error::invalid_state); } @@ -219,6 +229,23 @@ protected: lib::error_code get_ec() const { return lib::error_code(); } + + /// Translate any security policy specific information about an error code + /** + * Translate_ec takes a boost error code and attempts to convert its value + * to an appropriate websocketpp error code. The plain socket policy does + * not presently provide any additional information so all errors will be + * reported as the generic transport pass_through error. + * + * @since 0.4.0-beta1 + * + * @param ec The error code to translate_ec + * @return The translated error code + */ + lib::error_code translate_ec(boost::system::error_code ec) { + // We don't know any more information about this error so pass through + return make_error_code(transport::error::pass_through); + } private: enum state { UNINITIALIZED = 0, diff --git a/websocketpp/transport/asio/security/tls.hpp b/websocketpp/transport/asio/security/tls.hpp index a99275d..9b884ce 100644 --- a/websocketpp/transport/asio/security/tls.hpp +++ b/websocketpp/transport/asio/security/tls.hpp @@ -55,7 +55,7 @@ typedef lib::function(connection_hdl) * transport::asio::tls_socket::connection implements a secure connection socket * component that uses Boost ASIO's ssl::stream to wrap an ip::tcp::socket. */ -class connection { +class connection : public lib::enable_shared_from_this { public: /// Type of this connection socket component typedef connection type; @@ -68,6 +68,8 @@ public: 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; + /// Type of a pointer to the ASIO io_service strand being used + typedef lib::shared_ptr strand_ptr; /// Type of a shared pointer to the ASIO TLS context being used typedef lib::shared_ptr context_ptr; @@ -78,6 +80,11 @@ public: // << std::endl; } + /// Get a shared pointer to this component + ptr get_shared() { + return shared_from_this(); + } + /// Check whether or not this connection is secure /** * @return Whether or not this connection is secure @@ -169,9 +176,12 @@ protected: * boost::asio components to the io_service * * @param service A pointer to the endpoint's io_service + * @param strand A pointer to the connection's 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) { + lib::error_code init_asio (io_service_ptr service, strand_ptr strand, + bool is_server) + { if (!m_tls_init_handler) { return socket::make_error_code(socket::error::missing_tls_init_handler); } @@ -183,6 +193,7 @@ protected: m_socket.reset(new socket_type(*service,*m_context)); m_io_service = service; + m_strand = strand; m_is_server = is_server; return lib::error_code(); @@ -217,15 +228,25 @@ protected: m_ec = socket::make_error_code(socket::error::tls_handshake_timeout); // TLS handshake - m_socket->async_handshake( - get_handshake_type(), - lib::bind( - &type::handle_init, - this, - callback, - lib::placeholders::_1 - ) - ); + if (m_strand) { + m_socket->async_handshake( + get_handshake_type(), + m_strand->wrap(lib::bind( + &type::handle_init, get_shared(), + callback, + lib::placeholders::_1 + )) + ); + } else { + m_socket->async_handshake( + get_handshake_type(), + lib::bind( + &type::handle_init, get_shared(), + callback, + lib::placeholders::_1 + ) + ); + } } /// Sets the connection handle @@ -239,11 +260,10 @@ protected: m_hdl = hdl; } - void handle_init(init_handler callback, const - boost::system::error_code& ec) + void handle_init(init_handler callback,boost::system::error_code const & ec) { if (ec) { - m_ec = socket::make_error_code(socket::error::pass_through); + m_ec = socket::make_error_code(socket::error::tls_handshake_failed); } else { m_ec = lib::error_code(); } @@ -263,6 +283,37 @@ protected: void async_shutdown(socket_shutdown_handler callback) { m_socket->async_shutdown(callback); } + + /// Translate any security policy specific information about an error code + /** + * Translate_ec takes a boost error code and attempts to convert its value + * to an appropriate websocketpp error code. Any error that is determined to + * be related to TLS but does not have a more specific websocketpp error + * code is returned under the catch all error "tls_error". + * + * Non-TLS related errors are returned as the transport generic pass_through + * error. + * + * @since 0.4.0-beta1 + * + * @param ec The error code to translate_ec + * @return The translated error code + */ + lib::error_code translate_ec(boost::system::error_code ec) { + if (ec.category() == boost::asio::error::get_ssl_category()) { + if (ERR_GET_REASON(ec.value()) == SSL_R_SHORT_READ) { + return make_error_code(transport::error::tls_short_read); + } else { + // We know it is a TLS related error, but otherwise don't know + // more. Pass through as TLS generic. + return make_error_code(transport::error::tls_error); + } + } else { + // We don't know any more information about this error so pass + // through + return make_error_code(transport::error::pass_through); + } + } private: socket_type::handshake_type get_handshake_type() { if (m_is_server) { @@ -273,6 +324,7 @@ private: } io_service_ptr m_io_service; + strand_ptr m_strand; context_ptr m_context; socket_ptr m_socket; bool m_is_server; diff --git a/websocketpp/transport/base/connection.hpp b/websocketpp/transport/base/connection.hpp index 1db6310..f4f2b50 100644 --- a/websocketpp/transport/base/connection.hpp +++ b/websocketpp/transport/base/connection.hpp @@ -169,7 +169,13 @@ enum value { tls_short_read, /// Timer expired - timeout + timeout, + + /// read or write after shutdown + action_after_shutdown, + + /// Other TLS error + tls_error, }; class category : public lib::error_category { @@ -198,6 +204,10 @@ class category : public lib::error_category { return "TLS Short Read"; case timeout: return "Timer Expired"; + case action_after_shutdown: + return "A transport action was requested after shutdown"; + case tls_error: + return "Generic TLS related error"; default: return "Unknown"; } diff --git a/websocketpp/transport/base/endpoint.hpp b/websocketpp/transport/base/endpoint.hpp index df1dc45..1fefd48 100644 --- a/websocketpp/transport/base/endpoint.hpp +++ b/websocketpp/transport/base/endpoint.hpp @@ -71,12 +71,10 @@ namespace websocketpp { namespace transport { /// The type and signature of the callback passed to the accept method -typedef lib::function - accept_handler; +typedef lib::function accept_handler; /// The type and signature of the callback passed to the connect method -typedef lib::function - connect_handler; +typedef lib::function connect_handler; } // namespace transport } // namespace websocketpp diff --git a/websocketpp/transport/iostream/connection.hpp b/websocketpp/transport/iostream/connection.hpp index 98443b4..4a1994d 100644 --- a/websocketpp/transport/iostream/connection.hpp +++ b/websocketpp/transport/iostream/connection.hpp @@ -28,8 +28,9 @@ #ifndef WEBSOCKETPP_TRANSPORT_IOSTREAM_CON_HPP #define WEBSOCKETPP_TRANSPORT_IOSTREAM_CON_HPP -#include #include +#include +#include #include #include @@ -49,7 +50,7 @@ struct timer { }; template -class connection { +class connection : public lib::enable_shared_from_this< connection > { public: /// Type of this connection transport component typedef connection type; @@ -81,6 +82,11 @@ public: m_alog.write(log::alevel::devel,"iostream con transport constructor"); } + /// Get a shared pointer to this component + ptr get_shared() { + return type::shared_from_this(); + } + /// Register a std::ostream with the transport for writing output /** * Register a std::ostream with the transport. All future writes will be @@ -127,14 +133,62 @@ public: * Copies bytes from buf into WebSocket++'s input buffers. Bytes will be * copied from the supplied buffer to fulfill 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 + * pending reads read_some will return immediately. Not all of the bytes may * be able to be read in one call + * + * + * @since 0.3.0-alpha4 + * + * @param buf Char buffer to read into the websocket + * @param len Length of buf + * @return The number of characters from buf actually read. */ - size_t readsome(char const * buf, size_t len) { + size_t read_some(char const * buf, size_t len) { // this serializes calls to external read. scoped_lock_type lock(m_read_mutex); - return this->readsome_impl(buf,len); + return this->read_some_impl(buf,len); + } + + /// Manual input supply (DEPRECATED) + /** + * @deprecated DEPRECATED in favor of read_some() + * @see read_some() + */ + size_t readsome(char const * buf, size_t len) { + return this->read_some(buf,len); + } + + /// Signal EOF + /** + * Signals to the transport that data stream being read has reached EOF and + * that no more bytes may be read or written to/from the transport. + * + * @since 0.3.0-alpha4 + */ + void eof() { + // this serializes calls to external read. + scoped_lock_type lock(m_read_mutex); + + if (m_reading) { + complete_read(make_error_code(transport::error::eof)); + } + } + + /// Signal transport error + /** + * Signals to the transport that a fatal data stream error has occurred and + * that no more bytes may be read or written to/from the transport. + * + * @since 0.3.0-alpha4 + */ + void fatal_error() { + // this serializes calls to external read. + scoped_lock_type lock(m_read_mutex); + + if (m_reading) { + complete_read(make_error_code(transport::error::pass_through)); + } } /// Set whether or not this connection is secure @@ -408,18 +462,18 @@ private: // TODO: error handling if (in.bad()) { m_reading = false; - m_read_handler(make_error_code(error::bad_stream), m_cursor); + complete_read(make_error_code(error::bad_stream)); } if (m_cursor >= m_bytes_needed) { m_reading = false; - m_read_handler(lib::error_code(), m_cursor); + complete_read(lib::error_code()); } } } - size_t readsome_impl(char const * buf, size_t len) { - m_alog.write(log::alevel::devel,"iostream_con readsome"); + size_t read_some_impl(char const * buf, size_t len) { + m_alog.write(log::alevel::devel,"iostream_con read_some"); if (!m_reading) { m_elog.write(log::elevel::devel,"write while not reading"); @@ -428,18 +482,42 @@ private: size_t bytes_to_copy = std::min(len,m_len-m_cursor); - std::copy(buf,buf+bytes_to_copy,m_buf); + std::copy(buf,buf+bytes_to_copy,m_buf+m_cursor); m_cursor += bytes_to_copy; if (m_cursor >= m_bytes_needed) { - m_reading = false; - m_read_handler(lib::error_code(), m_cursor); + complete_read(lib::error_code()); } return bytes_to_copy; } + /// Signal that a requested read is complete + /** + * Sets the reading flag to false and returns the handler that should be + * called back with the result of the read. The cursor position that is sent + * is whatever the value of m_cursor is. + * + * It MUST NOT be called when m_reading is false. + * it MUST be called while holding the read lock + * + * It is important to use this method rather than directly setting/calling + * m_read_handler back because this function makes sure to delete the + * locally stored handler which contains shared pointers that will otherwise + * cause circular reference based memory leaks. + * + * @param ec The error code to forward to the read handler + */ + void complete_read(lib::error_code const & ec) { + m_reading = false; + + read_handler handler = m_read_handler; + m_read_handler = read_handler(); + + handler(ec,m_cursor); + } + // 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 91ea6fc..ff37866 100644 --- a/websocketpp/transport/iostream/endpoint.hpp +++ b/websocketpp/transport/iostream/endpoint.hpp @@ -141,7 +141,7 @@ protected: * @param cb The function to call back with the results when complete. */ void async_connect(transport_con_ptr tcon, uri_ptr u, connect_handler cb) { - cb(tcon->get_handle(),lib::error_code()); + cb(lib::error_code()); } /// Initialize a connection diff --git a/websocketpp/uri.hpp b/websocketpp/uri.hpp index 2bfa1f7..b0895d5 100644 --- a/websocketpp/uri.hpp +++ b/websocketpp/uri.hpp @@ -47,13 +47,13 @@ static uint16_t const uri_default_secure_port = 443; class uri { public: - explicit uri(std::string const & uri) : m_valid(false) { + explicit uri(std::string const & uri_string) : m_valid(false) { std::string::const_iterator it; std::string::const_iterator temp; int state = 0; - it = uri.begin(); + it = uri_string.begin(); if (std::equal(it,it+6,"wss://")) { m_secure = true; @@ -88,14 +88,14 @@ public: //temp = std::find(it,it2,']'); temp = it; - while (temp != uri.end()) { + while (temp != uri_string.end()) { if (*temp == ']') { break; } ++temp; } - if (temp == uri.end()) { + if (temp == uri_string.end()) { return; } else { // validate IPv6 literal parts @@ -103,7 +103,7 @@ public: m_host.append(it,temp); } it = temp+1; - if (it == uri.end()) { + if (it == uri_string.end()) { state = 2; } else if (*it == '/') { state = 2; @@ -119,7 +119,7 @@ public: // IPv4 or hostname // extract until : or / while (state == 0) { - if (it == uri.end()) { + if (it == uri_string.end()) { state = 2; break; } else if (*it == '/') { @@ -137,8 +137,11 @@ public: // parse port std::string port = ""; while (state == 1) { - if (it == uri.end()) { - state = 3; + if (it == uri_string.end()) { + // state is not used after this point presently. + // this should be re-enabled if it ever is needed in a future + // refactoring + //state = 3; break; } else if (*it == '/') { state = 3; @@ -156,7 +159,7 @@ public: } m_resource = "/"; - m_resource.append(it,uri.end()); + m_resource.append(it,uri_string.end()); m_valid = true; @@ -279,7 +282,22 @@ public: return s.str(); } - // get query? + /// Return the query portion + /** + * Returns the query portion (after the ?) of the URI or an empty string if + * there is none. + * + * @return query portion of the URI. + */ + std::string get_query() const { + std::size_t found = m_resource.find('?'); + if (found != std::string::npos) { + return m_resource.substr(found + 1); + } else { + return ""; + } + } + // get fragment // hi <3 diff --git a/websocketpp/utf8_validator.hpp b/websocketpp/utf8_validator.hpp index b0c5664..528ede1 100644 --- a/websocketpp/utf8_validator.hpp +++ b/websocketpp/utf8_validator.hpp @@ -1,5 +1,30 @@ -// Copyright (c) 2008-2009 Bjoern Hoehrmann -// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. +/* + * The following code is adapted from code originally written by Bjoern + * Hoehrmann . See + * http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + * + * The original license: + * + * Copyright (c) 2008-2009 Bjoern Hoehrmann + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. +*/ #ifndef UTF8_VALIDATOR_HPP #define UTF8_VALIDATOR_HPP diff --git a/websocketpp/utilities.hpp b/websocketpp/utilities.hpp index 0774566..8f08efe 100644 --- a/websocketpp/utilities.hpp +++ b/websocketpp/utilities.hpp @@ -78,7 +78,7 @@ struct ci_less : std::binary_function { : public std::binary_function { bool operator() (unsigned char const & c1, unsigned char const & c2) const { - return std::tolower (c1) < std::tolower (c2); + return tolower (c1) < tolower (c2); } }; bool operator() (std::string const & s1, std::string const & s2) const { diff --git a/websocketpp/version.hpp b/websocketpp/version.hpp index 6c77010..e6c9cbf 100644 --- a/websocketpp/version.hpp +++ b/websocketpp/version.hpp @@ -50,10 +50,10 @@ static int const patch_version = 0; * This is a textual flag indicating the type and number for pre-release * versions (dev, alpha, beta, rc). This will be blank for release versions. */ -static char const prerelease_flag[] = "alpha3"; +static char const prerelease_flag[] = "alpha4"; /// Default user agent string -static char const user_agent[] = "WebSocket++/0.3.0-alpha3"; +static char const user_agent[] = "WebSocket++/0.3.0-alpha4"; } // namespace websocketpp