diff --git a/.gitignore b/.gitignore index 413af3a6f..558a1b3d7 100644 --- a/.gitignore +++ b/.gitignore @@ -32,11 +32,15 @@ install_manifest.txt *.ncb */Debug/* */*/Debug/* +bin/Debug */Release/* */*/Release/* */RelWithDebInfo/* */*/RelWithDebInfo/* +# explicitly allow this path with /debug/ in it +!websocketpp/transport/debug/* + objs_shared/ objs_static/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 4efcb389d..9ccb1f24c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ project (websocketpp) cmake_minimum_required (VERSION 2.6) set (WEBSOCKETPP_MAJOR_VERSION 0) -set (WEBSOCKETPP_MINOR_VERSION 4) +set (WEBSOCKETPP_MINOR_VERSION 5) set (WEBSOCKETPP_PATCH_VERSION 0) set (WEBSOCKETPP_VERSION ${WEBSOCKETPP_MAJOR_VERSION}.${WEBSOCKETPP_MINOR_VERSION}.${WEBSOCKETPP_PATCH_VERSION}) diff --git a/Doxyfile b/Doxyfile index b37846173..d9de24838 100644 --- a/Doxyfile +++ b/Doxyfile @@ -33,7 +33,7 @@ PROJECT_NAME = "websocketpp" # if some version control system is used. -PROJECT_NUMBER = "0.4.0" +PROJECT_NUMBER = "0.5.0" # Using the PROJECT_BRIEF tag one can provide an optional one line description @@ -974,7 +974,7 @@ HTML_COLORSTYLE_GAMMA = 148 # page will contain the date and time when the page was generated. Setting # this to NO can help when comparing the output of multiple runs. -HTML_TIMESTAMP = YES +HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the diff --git a/SConstruct b/SConstruct index 2f35a055c..e01ff04e9 100644 --- a/SConstruct +++ b/SConstruct @@ -110,6 +110,19 @@ elif env['CXX'].startswith('clang++'): env.Append(CCFLAGS = ['-Wshadow']) env.Append(CCFLAGS = ['-Wunused-parameter']) + env.Append(CCFLAGS = ['-Wsometimes-uninitialized']) + env.Append(CCFLAGS = ['-Wuninitialized']) + + #env.Append(CCFLAGS = ['-Weverything']) + #env.Append(CCFLAGS = ['-Wno-documentation']) + #env.Append(CCFLAGS = ['-Wno-weak-vtables']) + #env.Append(CCFLAGS = ['-Wno-global-constructors']) + #env.Append(CCFLAGS = ['-Wno-sign-conversion']) + #env.Append(CCFLAGS = ['-Wno-exit-time-destructors']) + + + + # Wpadded # Wsign-conversion @@ -239,6 +252,9 @@ debug_server = SConscript('#/examples/debug_server/SConscript',variant_dir = bui # subprotocol_server subprotocol_server = SConscript('#/examples/subprotocol_server/SConscript',variant_dir = builddir + 'subprotocol_server',duplicate = 0) +# telemetry_server +telemetry_server = SConscript('#/examples/telemetry_server/SConscript',variant_dir = builddir + 'telemetry_server',duplicate = 0) + if not env['PLATFORM'].startswith('win'): # iostream_server iostream_server = SConscript('#/examples/iostream_server/SConscript',variant_dir = builddir + 'iostream_server',duplicate = 0) diff --git a/changelog.md b/changelog.md index 4ab768077..c7eed0497 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,46 @@ +HEAD + +0.5.0 - 2015-01-22 +- BREAKING UTILITY CHANGE: Deprecated methods `http::parser::parse_headers`, + `http::response::parse_complete`, and `http::request::parse_complete` have + been removed. +- Security: Disabled SSLv3 in example servers. +- Feature: Adds basic support for accessing HTTP request bodies in the http + handler. #181 +- Feature: Adds the ability to register a shutdown handler when using the + iostream transport. This provides a clean interface for triggering the shut + down of external sockets and other cleanup without hooking in to higher level + WebSocket handlers. +- Feature: Adds the ability to register a write handler when using the iostream + transport. This handler can be used to handle transport output in place of + registering an ostream to write to. +- Feature: Adds a new logging policy that outputs to syslog. #386 Thank you Tom + Hughes for submitting the initial version of this policy. +- Improvement: Message payload logging now prints text for text messages rather + than binary. +- Improvement: Overhaul of handshake state machine. Should make it impossible + for exceptions to bubble out of transport methods like `io_service::run`. +- Improvement: Overhaul of handshake error reporting. Fail handler error codes + will be more detailed and precise. Adds new [fail] and [http] logging channels + that log failed websocket connections and successful HTTP connections + respectively. A new aggregate channel package, `alevel::access_core`, allows + enabling connect, disconnect, fail, and http together. Successful HTTP + connections will no longer trigger a fail handler. +- Improvement: Ability to terminate connection during an http handler to cleanly + suppress the default outgoing HTTP response. +- Documentation: Add Sending & Receiving Messages step to chapter one of the + `utility_client` tutorial. Update `utility_client` example to match. +- Cleanup: Removes unused files & STL includes. Adds required STL includes. + Normalizes include order. +- Bug: Fixes a fatal state error when a handshake response is completed + immediately after that handshake times out. #389 +- Bug: MinGW fixes; C++11 feature detection, localtime use. #393 Thank you + Schebb for reporting, code, and testing. +- Bug: Fixes an issue where `websocketpp::exception::what()` could return an out + of scope pointer. #397 Thank you fabioang for reporting. +- Bug: Fixes an issue where endpoints were not reset properly after a call to + `endpoint::listen` failed. #390 Thank you wyyqyl for reporting. + 0.4.0 - 2014-11-04 - BREAKING API CHANGE: All WebSocket++ methods now throw an exception of type `websocketpp::exception` which derives from `std::exception`. This normalizes @@ -174,7 +217,7 @@ - Change default HTTP response error code when no http_handler is defined from 500/Internal Server Error to 426/Upgrade Required - Remove timezone from logger timestamp to work around issues with the Windows - implimentation of strftime. Thank you breyed for testing and code. #257 + implementation of strftime. Thank you breyed for testing and code. #257 - Switch integer literals to char literals to improve VCPP compatibility. Thank you breyed for testing and code. #257 - Add MSVCPP warning suppression for the bundled SHA1 library. Thank you breyed diff --git a/examples/broadcast_server/broadcast_server.cpp b/examples/broadcast_server/broadcast_server.cpp index dc17250d6..e87e879ee 100644 --- a/examples/broadcast_server/broadcast_server.cpp +++ b/examples/broadcast_server/broadcast_server.cpp @@ -3,6 +3,7 @@ #include #include +#include /*#include #include @@ -66,10 +67,6 @@ class broadcast_server { m_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; } } @@ -130,7 +127,7 @@ class broadcast_server { } } private: - typedef std::set> con_list; + typedef std::set > con_list; server m_server; con_list m_connections; @@ -153,7 +150,7 @@ int main() { t.join(); - } catch (std::exception & e) { + } catch (websocketpp::exception const & e) { std::cout << e.what() << std::endl; } } diff --git a/examples/debug_client/debug_client.cpp b/examples/debug_client/debug_client.cpp index d8d84b185..c6be2c111 100644 --- a/examples/debug_client/debug_client.cpp +++ b/examples/debug_client/debug_client.cpp @@ -36,7 +36,7 @@ #include #include -typedef websocketpp::client client; +typedef websocketpp::client client; using websocketpp::lib::placeholders::_1; using websocketpp::lib::placeholders::_2; @@ -63,10 +63,11 @@ class perftest { // 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_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)); + m_endpoint.set_fail_handler(bind(&type::on_fail,this,::_1)); } void start(std::string uri) { @@ -97,6 +98,7 @@ class perftest { try { ctx->set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | + boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::single_dh_use); } catch (std::exception& e) { std::cout << e.what() << std::endl; @@ -104,6 +106,18 @@ class perftest { return ctx; } + void on_fail(websocketpp::connection_hdl hdl) { + client::connection_ptr con = m_endpoint.get_con_from_hdl(hdl); + + std::cout << "Fail handler" << std::endl; + std::cout << con->get_state() << std::endl; + std::cout << con->get_local_close_code() << std::endl; + std::cout << con->get_local_close_reason() << std::endl; + std::cout << con->get_remote_close_code() << std::endl; + std::cout << con->get_remote_close_reason() << std::endl; + std::cout << con->get_ec() << " - " << con->get_ec().message() << std::endl; + } + void on_open(websocketpp::connection_hdl hdl) { m_open = std::chrono::high_resolution_clock::now(); m_endpoint.send(hdl, "", websocketpp::frame::opcode::text); diff --git a/examples/debug_server/debug_server.cpp b/examples/debug_server/debug_server.cpp index 68b320e58..111a394bb 100644 --- a/examples/debug_server/debug_server.cpp +++ b/examples/debug_server/debug_server.cpp @@ -31,11 +31,60 @@ #include +// Custom logger +#include + #include #include -typedef websocketpp::server server; +//////////////////////////////////////////////////////////////////////////////// +///////////////// Custom Config for debugging custom policies ////////////////// +//////////////////////////////////////////////////////////////////////////////// + +struct debug_custom : public websocketpp::config::debug_asio { + typedef debug_custom type; + typedef debug_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; + + /// Custom Logging policies + /*typedef websocketpp::log::syslog elog_type; + typedef websocketpp::log::syslog alog_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 long timeout_open_handshake = 0; +}; + +//////////////////////////////////////////////////////////////////////////////// + +typedef websocketpp::server server; using websocketpp::lib::placeholders::_1; using websocketpp::lib::placeholders::_2; @@ -44,6 +93,33 @@ using websocketpp::lib::bind; // pull out the type of messages sent by our config typedef server::message_ptr message_ptr; +bool validate(server *, websocketpp::connection_hdl) { + //sleep(6); + return true; +} + +void on_http(server* s, websocketpp::connection_hdl hdl) { + server::connection_ptr con = s->get_con_from_hdl(hdl); + + std::string res = con->get_request_body(); + + std::stringstream ss; + ss << "got HTTP request with " << res.size() << " bytes of body data."; + + con->set_body(ss.str()); + con->set_status(websocketpp::http::status_code::ok); +} + +void on_fail(server* s, websocketpp::connection_hdl hdl) { + server::connection_ptr con = s->get_con_from_hdl(hdl); + + std::cout << "Fail handler: " << con->get_ec() << " " << con->get_ec().message() << std::endl; +} + +void on_close(websocketpp::connection_hdl) { + std::cout << "Close handler" << std::endl; +} + // 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() @@ -74,6 +150,12 @@ int main() { // Register our message handler echo_server.set_message_handler(bind(&on_message,&echo_server,::_1,::_2)); + echo_server.set_http_handler(bind(&on_http,&echo_server,::_1)); + echo_server.set_fail_handler(bind(&on_fail,&echo_server,::_1)); + echo_server.set_close_handler(&on_close); + + echo_server.set_validate_handler(bind(&validate,&echo_server,::_1)); + // Listen on port 9012 echo_server.listen(9012); diff --git a/examples/echo_server/echo_server.cpp b/examples/echo_server/echo_server.cpp index 59fc1fcb1..5ec576f94 100644 --- a/examples/echo_server/echo_server.cpp +++ b/examples/echo_server/echo_server.cpp @@ -50,10 +50,8 @@ int main() { // Start the ASIO io_service run loop echo_server.run(); - } catch (const std::exception & e) { + } catch (websocketpp::exception const & 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/echo_server_both/echo_server_both.cpp b/examples/echo_server_both/echo_server_both.cpp index ee78567b3..b277d5cf1 100644 --- a/examples/echo_server_both/echo_server_both.cpp +++ b/examples/echo_server_both/echo_server_both.cpp @@ -46,6 +46,7 @@ context_ptr on_tls_init(websocketpp::connection_hdl hdl) { try { ctx->set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | + boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::single_dh_use); ctx->set_password_callback(bind(&get_password)); ctx->use_certificate_chain_file("server.pem"); diff --git a/examples/echo_server_tls/echo_server_tls.cpp b/examples/echo_server_tls/echo_server_tls.cpp index 6523a77cc..8408ebccc 100644 --- a/examples/echo_server_tls/echo_server_tls.cpp +++ b/examples/echo_server_tls/echo_server_tls.cpp @@ -38,6 +38,7 @@ context_ptr on_tls_init(websocketpp::connection_hdl hdl) { try { ctx->set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | + boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::single_dh_use); ctx->set_password_callback(bind(&get_password)); ctx->use_certificate_chain_file("server.pem"); diff --git a/examples/iostream_server/iostream_server.cpp b/examples/iostream_server/iostream_server.cpp index 0114fbf0c..6843659cd 100644 --- a/examples/iostream_server/iostream_server.cpp +++ b/examples/iostream_server/iostream_server.cpp @@ -82,12 +82,8 @@ int main() { } con->eof(); } - } catch (const std::exception & e) { + } catch (websocketpp::exception const & e) { std::cout << e.what() << std::endl; - } catch (websocketpp::lib::error_code e) { - std::cout << e.message() << std::endl; - } catch (...) { - std::cout << "other exception" << std::endl; } log.close(); } diff --git a/examples/sip_client/sip_client.cpp b/examples/sip_client/sip_client.cpp index 0854955ee..66fa85784 100644 --- a/examples/sip_client/sip_client.cpp +++ b/examples/sip_client/sip_client.cpp @@ -78,11 +78,7 @@ int main(int argc, char* argv[]) { std::cout << "done" << std::endl; - } catch (const std::exception & e) { + } catch (websocketpp::exception const & 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/subprotocol_server/subprotocol_server.cpp b/examples/subprotocol_server/subprotocol_server.cpp index ecf8fecbe..9593c0152 100644 --- a/examples/subprotocol_server/subprotocol_server.cpp +++ b/examples/subprotocol_server/subprotocol_server.cpp @@ -42,11 +42,7 @@ int main() { s.start_accept(); s.run(); - } catch (const std::exception & e) { + } catch (websocketpp::exception const & 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/telemetry_server/CMakeLists.txt b/examples/telemetry_server/CMakeLists.txt new file mode 100644 index 000000000..7ee569b07 --- /dev/null +++ b/examples/telemetry_server/CMakeLists.txt @@ -0,0 +1,10 @@ + +file (GLOB SOURCE_FILES *.cpp) +file (GLOB HEADER_FILES *.hpp) + +init_target (telemetry_server) + +build_executable (${TARGET_NAME} ${SOURCE_FILES} ${HEADER_FILES}) + +link_boost () +final_target () diff --git a/examples/telemetry_server/SConscript b/examples/telemetry_server/SConscript new file mode 100644 index 000000000..1b8ff2295 --- /dev/null +++ b/examples/telemetry_server/SConscript @@ -0,0 +1,23 @@ +## Main development example +## + +Import('env') +Import('env_cpp11') +Import('boostlibs') +Import('platform_libs') +Import('polyfill_libs') + +env = env.Clone () +env_cpp11 = env_cpp11.Clone () + +prgs = [] + +# if a C++11 environment is 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('telemetry_server', ["telemetry_server.cpp"], LIBS = ALL_LIBS) +else: + ALL_LIBS = boostlibs(['system'],env) + [platform_libs] + [polyfill_libs] + prgs += env.Program('telemetry_server', ["telemetry_server.cpp"], LIBS = ALL_LIBS) + +Return('prgs') diff --git a/examples/telemetry_server/index.html b/examples/telemetry_server/index.html new file mode 100644 index 000000000..def50dd6a --- /dev/null +++ b/examples/telemetry_server/index.html @@ -0,0 +1,85 @@ + + + +WebSocket++ Telemetry Client + + + + + + + +
+
+
+ +
+
+
+ + + diff --git a/examples/telemetry_server/telemetry_server.cpp b/examples/telemetry_server/telemetry_server.cpp new file mode 100644 index 000000000..80a791343 --- /dev/null +++ b/examples/telemetry_server/telemetry_server.cpp @@ -0,0 +1,204 @@ +#include + +#include + +#include +#include +#include +#include +#include + +/** + * The telemetry server accepts connections and sends a message every second to + * each client containing an integer count. This example can be used as the + * basis for programs that expose a stream of telemetry data for logging, + * dashboards, etc. + * + * This example uses the timer based concurrency method and is self contained + * and singled threaded. Refer to telemetry client for an example of a similar + * telemetry setup using threads rather than timers. + * + * This example also includes an example simple HTTP server that serves a web + * dashboard displaying the count. This simple design is suitable for use + * delivering a small number of files to a small number of clients. It is ideal + * for cases like embedded dashboards that don't want the complexity of an extra + * HTTP server to serve static files. + * + * This design *will* fall over under high traffic or DoS conditions. In such + * cases you are much better off proxying to a real HTTP server for the http + * requests. + */ +class telemetry_server { +public: + typedef websocketpp::connection_hdl connection_hdl; + typedef websocketpp::server server; + typedef websocketpp::lib::lock_guard scoped_lock; + + telemetry_server() : m_count(0) { + // set up access channels to only log interesting things + m_endpoint.clear_access_channels(websocketpp::log::alevel::all); + m_endpoint.set_access_channels(websocketpp::log::alevel::access_core); + m_endpoint.set_access_channels(websocketpp::log::alevel::app); + + // Initialize the Asio transport policy + m_endpoint.init_asio(); + + // Bind the handlers we are using + using websocketpp::lib::placeholders::_1; + using websocketpp::lib::bind; + m_endpoint.set_open_handler(bind(&telemetry_server::on_open,this,::_1)); + m_endpoint.set_close_handler(bind(&telemetry_server::on_close,this,::_1)); + m_endpoint.set_http_handler(bind(&telemetry_server::on_http,this,::_1)); + } + + void run(std::string docroot, uint16_t port) { + std::stringstream ss; + ss << "Running telemetry server on port "<< port <<" using docroot=" << docroot; + m_endpoint.get_alog().write(websocketpp::log::alevel::app,ss.str()); + + m_docroot = docroot; + + // listen on specified port + m_endpoint.listen(port); + + // Start the server accept loop + m_endpoint.start_accept(); + + // Set the initial timer to start telemetry + set_timer(); + + // Start the ASIO io_service run loop + try { + m_endpoint.run(); + } catch (websocketpp::exception const & e) { + std::cout << e.what() << std::endl; + } + } + + void set_timer() { + m_timer = m_endpoint.set_timer( + 1000, + websocketpp::lib::bind( + &telemetry_server::on_timer, + this, + websocketpp::lib::placeholders::_1 + ) + ); + } + + void on_timer(websocketpp::lib::error_code const & ec) { + if (ec) { + // there was an error, stop telemetry + m_endpoint.get_alog().write(websocketpp::log::alevel::app, + "Timer Error: "+ec.message()); + return; + } + + std::stringstream val; + val << "count is " << m_count++; + + // Broadcast count to all connections + con_list::iterator it; + for (it = m_connections.begin(); it != m_connections.end(); ++it) { + m_endpoint.send(*it,val.str(),websocketpp::frame::opcode::text); + } + + // set timer for next telemetry check + set_timer(); + } + + void on_http(connection_hdl hdl) { + // Upgrade our connection handle to a full connection_ptr + server::connection_ptr con = m_endpoint.get_con_from_hdl(hdl); + + std::ifstream file; + std::string filename = con->get_uri()->get_resource(); + std::string response; + + m_endpoint.get_alog().write(websocketpp::log::alevel::app, + "http request1: "+filename); + + if (filename == "/") { + filename = m_docroot+"index.html"; + } else { + filename = m_docroot+filename.substr(1); + } + + m_endpoint.get_alog().write(websocketpp::log::alevel::app, + "http request2: "+filename); + + file.open(filename.c_str(), std::ios::in); + if (!file) { + // 404 error + std::stringstream ss; + + ss << "" + << "Error 404 (Resource not found)" + << "

Error 404

" + << "

The requested URL " << filename << " was not found on this server.

" + << ""; + + con->set_body(ss.str()); + con->set_status(websocketpp::http::status_code::not_found); + return; + } + + file.seekg(0, std::ios::end); + response.reserve(file.tellg()); + file.seekg(0, std::ios::beg); + + response.assign((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + + con->set_body(response); + con->set_status(websocketpp::http::status_code::ok); + } + + void on_open(connection_hdl hdl) { + m_connections.insert(hdl); + } + + void on_close(connection_hdl hdl) { + m_connections.erase(hdl); + } +private: + typedef std::set> con_list; + + server m_endpoint; + con_list m_connections; + server::timer_ptr m_timer; + + std::string m_docroot; + + // Telemetry data + uint64_t m_count; +}; + +int main(int argc, char* argv[]) { + telemetry_server s; + + std::string docroot; + uint16_t port = 9002; + + if (argc == 1) { + std::cout << "Usage: telemetry_server [documentroot] [port]" << std::endl; + return 1; + } + + if (argc >= 2) { + docroot = std::string(argv[1]); + } + + if (argc >= 3) { + int i = atoi(argv[2]); + if (i <= 0 || i > 65535) { + std::cout << "invalid port" << std::endl; + return 1; + } + + port = uint16_t(i); + } + + s.run(docroot, port); + return 0; +} \ No newline at end of file diff --git a/examples/testee_client/testee_client.cpp b/examples/testee_client/testee_client.cpp index 57f50436a..d191df579 100644 --- a/examples/testee_client/testee_client.cpp +++ b/examples/testee_client/testee_client.cpp @@ -74,11 +74,7 @@ int main(int argc, char* argv[]) { std::cout << "done" << std::endl; - } catch (const std::exception & e) { + } catch (websocketpp::exception const & 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/testee_server/testee_server.cpp b/examples/testee_server/testee_server.cpp index 1c9910e8c..d44368c88 100644 --- a/examples/testee_server/testee_server.cpp +++ b/examples/testee_server/testee_server.cpp @@ -132,11 +132,7 @@ int main(int argc, char * argv[]) { } } - } catch (const std::exception & e) { + } catch (websocketpp::exception const & e) { std::cout << "exception: " << e.what() << std::endl; - } catch (websocketpp::lib::error_code e) { - std::cout << "error code: " << e.message() << std::endl; - } catch (...) { - std::cout << "other exception" << std::endl; } } diff --git a/examples/utility_client/utility_client.cpp b/examples/utility_client/utility_client.cpp index 4f0b8900e..ee13f16a5 100644 --- a/examples/utility_client/utility_client.cpp +++ b/examples/utility_client/utility_client.cpp @@ -79,6 +79,14 @@ class connection_metadata { m_error_reason = s.str(); } + void on_message(websocketpp::connection_hdl, client::message_ptr msg) { + if (msg->get_opcode() == websocketpp::frame::opcode::text) { + m_messages.push_back("<< " + msg->get_payload()); + } else { + m_messages.push_back("<< " + websocketpp::utility::to_hex(msg->get_payload())); + } + } + websocketpp::connection_hdl get_hdl() const { return m_hdl; } @@ -91,6 +99,10 @@ class connection_metadata { return m_status; } + void record_sent_message(std::string message) { + m_messages.push_back(">> " + message); + } + friend std::ostream & operator<< (std::ostream & out, connection_metadata const & data); private: int m_id; @@ -99,13 +111,20 @@ class connection_metadata { std::string m_uri; std::string m_server; std::string m_error_reason; + std::vector m_messages; }; 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); + << "> Error/close reason: " << (data.m_error_reason.empty() ? "N/A" : data.m_error_reason) << "\n"; + out << "> Messages Processed: (" << data.m_messages.size() << ") \n"; + + std::vector::const_iterator it; + for (it = data.m_messages.begin(); it != data.m_messages.end(); ++it) { + out << *it << "\n"; + } return out; } @@ -176,6 +195,12 @@ class websocket_endpoint { &m_endpoint, websocketpp::lib::placeholders::_1 )); + con->set_message_handler(websocketpp::lib::bind( + &connection_metadata::on_message, + metadata_ptr, + websocketpp::lib::placeholders::_1, + websocketpp::lib::placeholders::_2 + )); m_endpoint.connect(con); @@ -197,6 +222,24 @@ class websocket_endpoint { } } + void send(int id, std::string message) { + 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.send(metadata_it->second->get_hdl(), message, websocketpp::frame::opcode::text, ec); + if (ec) { + std::cout << "> Error sending message: " << ec.message() << std::endl; + return; + } + + metadata_it->second->record_sent_message(message); + } + 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()) { @@ -230,6 +273,7 @@ int main() { std::cout << "\nCommand List:\n" << "connect \n" + << "send \n" << "close [] []\n" << "show \n" << "help: Display this help text\n" @@ -240,6 +284,17 @@ int main() { if (id != -1) { std::cout << "> Created connection with id " << id << std::endl; } + } else if (input.substr(0,4) == "send") { + std::stringstream ss(input); + + std::string cmd; + int id; + std::string message = ""; + + ss >> cmd >> id; + std::getline(ss,message); + + endpoint.send(id, message); } else if (input.substr(0,5) == "close") { std::stringstream ss(input); @@ -252,7 +307,7 @@ int main() { std::getline(ss,reason); endpoint.close(id, close_code, reason); - } else if (input.substr(0,4) == "show") { + } else if (input.substr(0,4) == "show") { int id = atoi(input.substr(5).c_str()); connection_metadata::ptr metadata = endpoint.get_metadata(id); diff --git a/readme.md b/readme.md index 2afd14e94..aa0cde262 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -WebSocket++ (0.4.0) +WebSocket++ (0.5.0) ========================== WebSocket++ is a header only C++ library that implements RFC6455 The WebSocket diff --git a/test/connection/connection.cpp b/test/connection/connection.cpp index 3f107b348..0c67bd193 100644 --- a/test/connection/connection.cpp +++ b/test/connection/connection.cpp @@ -30,6 +30,10 @@ #include "connection_tu2.hpp" +// Include special debugging transport +//#include +#include + // NOTE: these tests currently test against hardcoded output values. I am not // sure how problematic this will be. If issues arise like order of headers the // output should be parsed by http::response and have values checked directly @@ -79,6 +83,54 @@ struct stub_config : public websocketpp::config::core { typedef connection_extension connection_base; }; +struct debug_config_client : public websocketpp::config::core { + typedef debug_config_client type; + + 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 websocketpp::random::none::int_generator rng_type; + + struct transport_config { + typedef type::concurrency_type concurrency_type; + typedef type::elog_type elog_type; + typedef type::alog_type alog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; + + /// 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) + static const long timeout_socket_pre_init = 5000; + static const long timeout_proxy = 5000; + static const long timeout_socket_post_init = 5000; + static const long timeout_connect = 5000; + static const long timeout_socket_shutdown = 5000; + }; + + /// Transport Endpoint Component + typedef websocketpp::transport::debug::endpoint + transport_type; + + typedef core::endpoint_base endpoint_base; + typedef connection_extension connection_base; + + static const websocketpp::log::level elog_level = websocketpp::log::elevel::none; + static const websocketpp::log::level alog_level = websocketpp::log::alevel::none; +}; + struct connection_setup { connection_setup(bool p_is_server) : c(p_is_server, "", alog, elog, rng) {} @@ -89,6 +141,9 @@ struct connection_setup { websocketpp::connection c; }; +typedef websocketpp::client debug_client; +typedef websocketpp::server debug_server; + /*void echo_func(server* s, websocketpp::connection_hdl hdl, message_ptr msg) { s->send(hdl, msg->get_payload(), msg->get_opcode()); }*/ @@ -112,6 +167,29 @@ void http_func(server* s, websocketpp::connection_hdl hdl) { con->set_status(websocketpp::http::status_code::ok); } +void check_on_fail(server* s, websocketpp::lib::error_code ec, bool & called, + websocketpp::connection_hdl hdl) +{ + server::connection_ptr con = s->get_con_from_hdl(hdl); + + BOOST_CHECK_EQUAL(ec, con->get_ec()); + called = true; +} + +void on_open_print(server* s, websocketpp::connection_hdl hdl) +{ + server::connection_ptr con = s->get_con_from_hdl(hdl); + + std::cout << con->get_uri() << std::endl; +} + +void fail_on_open(websocketpp::connection_hdl) { + BOOST_CHECK(false); +} +void fail_on_http(websocketpp::connection_hdl) { + BOOST_CHECK(false); +} + BOOST_AUTO_TEST_CASE( connection_extensions ) { connection_setup env(true); @@ -215,6 +293,80 @@ BOOST_AUTO_TEST_CASE( set_max_message_size ) { BOOST_CHECK_EQUAL(run_server_test(s,input), output); } +BOOST_AUTO_TEST_CASE( websocket_fail_parse_error ) { + std::string input = "asdf\r\n\r\n"; + + server s; + websocketpp::lib::error_code ec = make_error_code(websocketpp::error::http_parse_error); + bool called = false; + s.set_fail_handler(bind(&check_on_fail,&s,ec,websocketpp::lib::ref(called),::_1)); + + run_server_test(s,input,false); + BOOST_CHECK(called); +} + +BOOST_AUTO_TEST_CASE( websocket_fail_invalid_version ) { + std::string input = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: foo\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example.com\r\n\r\n"; + + server s; + websocketpp::lib::error_code ec = make_error_code(websocketpp::error::invalid_version); + bool called = false; + s.set_fail_handler(bind(&check_on_fail,&s,ec,websocketpp::lib::ref(called),::_1)); + + run_server_test(s,input,false); + BOOST_CHECK(called); +} + +BOOST_AUTO_TEST_CASE( websocket_fail_unsupported_version ) { + std::string input = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 12\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example.com\r\n\r\n"; + + server s; + websocketpp::lib::error_code ec = make_error_code(websocketpp::error::unsupported_version); + bool called = false; + s.set_fail_handler(bind(&check_on_fail,&s,ec,websocketpp::lib::ref(called),::_1)); + + run_server_test(s,input,false); + BOOST_CHECK(called); +} + +/*BOOST_AUTO_TEST_CASE( websocket_fail_invalid_uri ) { + std::string input = "GET http://345.123.123.123/foo HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example.com\r\n\r\n"; + + server s; + websocketpp::lib::error_code ec = make_error_code(websocketpp::error::unsupported_version); + bool called = false; + s.set_fail_handler(bind(&check_on_fail,&s,ec,websocketpp::lib::ref(called),::_1)); + s.set_open_handler(bind(&on_open_print,&s,::_1)); + + std::cout << run_server_test(s,input,true) << std::endl; + BOOST_CHECK(called); +} + +BOOST_AUTO_TEST_CASE( websocket_fail_invalid_uri_http ) { + std::string input = "GET http://345.123.123.123/foo HTTP/1.1\r\nHost: www.example.com\r\nOrigin: http://www.example.com\r\n\r\n"; + + server s; + websocketpp::lib::error_code ec = make_error_code(websocketpp::error::unsupported_version); + bool called = false; + s.set_fail_handler(bind(&check_on_fail,&s,ec,websocketpp::lib::ref(called),::_1)); + s.set_open_handler(bind(&on_open_print,&s,::_1)); + + std::cout << run_server_test(s,input,true) << std::endl; + BOOST_CHECK(called); +}*/ + +BOOST_AUTO_TEST_CASE( websocket_fail_upgrade_required ) { + std::string input = "GET /foo/bar HTTP/1.1\r\nHost: www.example.com\r\nOrigin: http://www.example.com\r\n\r\n"; + + server s; + websocketpp::lib::error_code ec = make_error_code(websocketpp::error::upgrade_required); + bool called = false; + s.set_fail_handler(bind(&check_on_fail,&s,ec,websocketpp::lib::ref(called),::_1)); + + run_server_test(s,input,false); + BOOST_CHECK(called); +} + // 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 @@ -239,3 +391,88 @@ BOOST_AUTO_TEST_CASE( basic_text_message ) { BOOST_CHECK( run_server_test(input) == output); } */ + + + + + +BOOST_AUTO_TEST_CASE( client_handshake_timeout_race1 ) { + debug_client c; + + websocketpp::lib::error_code ec; + debug_client::connection_ptr con = c.get_connection("ws://localhost:9002", ec); + + BOOST_CHECK(!ec); + + // This test the case where a handshake times out immediately before the + // handler that would have completed it gets invoked. This situation happens + // when clients are connecting to overloaded servers and on servers that are + // overloaded. + c.connect(con); + + con->expire_timer(websocketpp::lib::error_code()); + // Fullfil the write to simulate the write completing immediately after + // timer expires + con->fullfil_write(); + + BOOST_CHECK_EQUAL(con->get_ec(), make_error_code(websocketpp::error::open_handshake_timeout)); +} + +BOOST_AUTO_TEST_CASE( client_handshake_timeout_race2 ) { + debug_client c; + + websocketpp::lib::error_code ec; + debug_client::connection_ptr con = c.get_connection("ws://localhost:9002", ec); + + BOOST_CHECK(!ec); + + std::string output = "HTTP/1.1 101 Switching Protocols\r\nConnection: upgrade\r\nSec-WebSocket-Accept: ICX+Yqv66kxgM0FcWaLWlFLwTAI=\r\nServer: foo\r\nUpgrade: websocket\r\n\r\n"; + + // This test the case where a handshake times out immediately before the + // handler that would have completed it gets invoked. This situation happens + // when clients are connecting to overloaded servers and on servers that are + // overloaded. + c.connect(con); + con->fullfil_write(); + + con->expire_timer(websocketpp::lib::error_code()); + // Read valid handshake to simulate receiving the handshake response + // immediately after the timer expires + con->read_all(output.data(),output.size()); + + BOOST_CHECK_EQUAL(con->get_ec(), make_error_code(websocketpp::error::open_handshake_timeout)); +} + +BOOST_AUTO_TEST_CASE( server_handshake_timeout_race1 ) { + debug_server s; + + 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: AAAAAAAAAAAAAAAAAAAAAA==\r\n\r\n"; + + debug_server::connection_ptr con = s.get_connection(); + con->start(); + + con->expire_timer(websocketpp::lib::error_code()); + // Read handshake immediately after timer expire + con->read_all(input.data(), input.size()); + + BOOST_CHECK_EQUAL(con->get_ec(), make_error_code(websocketpp::error::open_handshake_timeout)); +} + +BOOST_AUTO_TEST_CASE( server_handshake_timeout_race2 ) { + debug_server s; + + 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: AAAAAAAAAAAAAAAAAAAAAA==\r\n\r\n"; + + debug_server::connection_ptr con = s.get_connection(); + con->start(); + + con->read_all(input.data(), input.size()); + + con->expire_timer(websocketpp::lib::error_code()); + // Complete write immediately after timer expire + con->fullfil_write(); + + BOOST_CHECK_EQUAL(con->get_ec(), make_error_code(websocketpp::error::open_handshake_timeout)); +} + + diff --git a/test/connection/connection_tu2.cpp b/test/connection/connection_tu2.cpp index 2a1c0163e..540962486 100644 --- a/test/connection/connection_tu2.cpp +++ b/test/connection/connection_tu2.cpp @@ -31,17 +31,22 @@ void echo_func(server* s, websocketpp::connection_hdl hdl, message_ptr msg) { s->send(hdl, msg->get_payload(), msg->get_opcode()); } -std::string run_server_test(std::string input) { +std::string run_server_test(std::string input, bool log) { server test_server; - return run_server_test(test_server,input); + return run_server_test(test_server,input,log); } -std::string run_server_test(server & s, std::string input) { +std::string run_server_test(server & s, std::string input, bool log) { server::connection_ptr con; std::stringstream output; - s.clear_access_channels(websocketpp::log::alevel::all); - s.clear_error_channels(websocketpp::log::elevel::all); + if (log) { + s.set_access_channels(websocketpp::log::alevel::all); + s.set_error_channels(websocketpp::log::elevel::all); + } else { + s.clear_access_channels(websocketpp::log::alevel::all); + s.clear_error_channels(websocketpp::log::elevel::all); + } s.register_ostream(&output); diff --git a/test/connection/connection_tu2.hpp b/test/connection/connection_tu2.hpp index b54afa1ff..8990ce00c 100644 --- a/test/connection/connection_tu2.hpp +++ b/test/connection/connection_tu2.hpp @@ -30,6 +30,7 @@ // Test Environment: // server, no TLS, no locks, iostream based transport +#include #include #include #include @@ -46,5 +47,5 @@ using websocketpp::lib::placeholders::_2; using websocketpp::lib::bind; void echo_func(server* s, websocketpp::connection_hdl hdl, message_ptr msg); -std::string run_server_test(std::string input); -std::string run_server_test(server & s, std::string input); +std::string run_server_test(std::string input, bool log = false); +std::string run_server_test(server & s, std::string input, bool log = false); diff --git a/test/endpoint/endpoint.cpp b/test/endpoint/endpoint.cpp index 79cbe6290..08b0560b5 100644 --- a/test/endpoint/endpoint.cpp +++ b/test/endpoint/endpoint.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Peter Thorson. All rights reserved. + * Copyright (c) 2015, 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: @@ -94,8 +94,33 @@ struct stub_config : public websocketpp::config::core { BOOST_AUTO_TEST_CASE( endpoint_extensions ) { websocketpp::server s; - BOOST_CHECK( s.extension_value == 5 ); - BOOST_CHECK( s.extension_method() == 5 ); + BOOST_CHECK_EQUAL( s.extension_value, 5 ); + BOOST_CHECK_EQUAL( s.extension_method(), 5 ); - BOOST_CHECK( s.is_server() == true ); + BOOST_CHECK( s.is_server() ); +} + +BOOST_AUTO_TEST_CASE( listen_after_listen_failure ) { + using websocketpp::transport::asio::error::make_error_code; + using websocketpp::transport::asio::error::pass_through; + + websocketpp::server server1; + websocketpp::server server2; + + websocketpp::lib::error_code ec; + + server1.init_asio(); + server2.init_asio(); + + boost::asio::ip::tcp::endpoint ep1(boost::asio::ip::address::from_string("127.0.0.1"), 12345); + boost::asio::ip::tcp::endpoint ep2(boost::asio::ip::address::from_string("127.0.0.1"), 23456); + + server1.listen(ep1, ec); + BOOST_CHECK(!ec); + + server2.listen(ep1, ec); + BOOST_REQUIRE_EQUAL(ec, make_error_code(pass_through)); + + server2.listen(ep2, ec); + BOOST_CHECK(!ec); } diff --git a/test/http/parser.cpp b/test/http/parser.cpp index d8a551a5e..73e109e67 100644 --- a/test/http/parser.cpp +++ b/test/http/parser.cpp @@ -28,6 +28,7 @@ #define BOOST_TEST_MODULE http_parser #include +#include #include #include @@ -479,6 +480,62 @@ BOOST_AUTO_TEST_CASE( basic_request ) { BOOST_CHECK( r.get_header("Host") == "www.example.com" ); } +BOOST_AUTO_TEST_CASE( basic_request_with_body ) { + websocketpp::http::parser::request r; + + std::string raw = "GET / HTTP/1.1\r\nHost: www.example.com\r\nContent-Length: 5\r\n\r\nabcdef"; + + 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, 65 ); + BOOST_CHECK( r.ready() == true ); + 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_CHECK_EQUAL( r.get_header("Content-Length"), "5" ); + BOOST_CHECK_EQUAL( r.get_body(), "abcde" ); +} + +BOOST_AUTO_TEST_CASE( basic_request_with_body_split ) { + websocketpp::http::parser::request r; + + std::string raw = "GET / HTTP/1.1\r\nHost: www.example.com\r\nContent-Length: 6\r\n\r\nabc"; + std::string raw2 = "def"; + + bool exception = false; + size_t pos = 0; + + try { + pos += r.consume(raw.c_str(),raw.size()); + pos += r.consume(raw2.c_str(),raw2.size()); + } catch (std::exception &e) { + exception = true; + std::cout << e.what() << std::endl; + } + + BOOST_CHECK( exception == false ); + BOOST_CHECK_EQUAL( pos, 66 ); + BOOST_CHECK( r.ready() == true ); + 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_CHECK_EQUAL( r.get_header("Content-Length"), "6" ); + BOOST_CHECK_EQUAL( r.get_body(), "abcdef" ); +} + + + BOOST_AUTO_TEST_CASE( trailing_body_characters ) { websocketpp::http::parser::request r; @@ -594,6 +651,27 @@ BOOST_AUTO_TEST_CASE( max_header_len_split ) { BOOST_CHECK( exception == true ); } +BOOST_AUTO_TEST_CASE( max_body_len ) { + websocketpp::http::parser::request r; + + r.set_max_body_size(5); + + std::string raw = "GET / HTTP/1.1\r\nHost: www.example.com\r\nContent-Length: 6\r\n\r\nabcdef"; + + bool exception = false; + size_t pos = 0; + + try { + pos += r.consume(raw.c_str(),raw.size()); + } catch (websocketpp::http::exception const & e) { + exception = true; + BOOST_CHECK_EQUAL(e.m_error_code,websocketpp::http::status_code::request_entity_too_large); + } + + BOOST_CHECK_EQUAL(r.get_max_body_size(),5); + BOOST_CHECK( exception == true ); +} + BOOST_AUTO_TEST_CASE( firefox_full_request ) { websocketpp::http::parser::request r; diff --git a/test/transport/integration.cpp b/test/transport/integration.cpp index 2aee269df..1d2e7e795 100644 --- a/test/transport/integration.cpp +++ b/test/transport/integration.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 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: @@ -273,7 +273,8 @@ void run_dummy_client(std::string port) { } } -bool on_ping(websocketpp::connection_hdl, std::string) { +bool on_ping(server * s, websocketpp::connection_hdl, std::string) { + s->get_alog().write(websocketpp::log::alevel::app,"got ping"); return false; } @@ -291,7 +292,9 @@ void stop_on_close(server * s, websocketpp::connection_hdl hdl) { template void ping_on_open(T * c, std::string payload, websocketpp::connection_hdl hdl) { typename T::connection_ptr con = c->get_con_from_hdl(hdl); - con->ping(payload); + websocketpp::lib::error_code ec; + con->ping(payload,ec); + BOOST_CHECK_EQUAL(ec, websocketpp::lib::error_code()); } void fail_on_pong(websocketpp::connection_hdl, std::string) { @@ -382,7 +385,7 @@ BOOST_AUTO_TEST_CASE( pong_timeout ) { server s; client c; - s.set_ping_handler(on_ping); + s.set_ping_handler(bind(&on_ping, &s,::_1,::_2)); s.set_close_handler(bind(&stop_on_close,&s,::_1)); c.set_fail_handler(bind(&check_ec,&c, @@ -395,7 +398,7 @@ BOOST_AUTO_TEST_CASE( pong_timeout ) { websocketpp::lib::error_code(),::_1)); websocketpp::lib::thread sthread(websocketpp::lib::bind(&run_server,&s,9005,false)); - websocketpp::lib::thread tthread(websocketpp::lib::bind(&run_test_timer,6)); + websocketpp::lib::thread tthread(websocketpp::lib::bind(&run_test_timer,10)); tthread.detach(); run_client(c, "http://localhost:9005",false); @@ -413,7 +416,7 @@ BOOST_AUTO_TEST_CASE( client_open_handshake_timeout ) { websocketpp::error::open_handshake_timeout,::_1)); websocketpp::lib::thread sthread(websocketpp::lib::bind(&run_dummy_server,9005)); - websocketpp::lib::thread tthread(websocketpp::lib::bind(&run_test_timer,6)); + websocketpp::lib::thread tthread(websocketpp::lib::bind(&run_test_timer,10)); sthread.detach(); tthread.detach(); @@ -430,7 +433,7 @@ BOOST_AUTO_TEST_CASE( server_open_handshake_timeout ) { websocketpp::error::open_handshake_timeout,::_1)); websocketpp::lib::thread sthread(websocketpp::lib::bind(&run_server,&s,9005,false)); - websocketpp::lib::thread tthread(websocketpp::lib::bind(&run_test_timer,6)); + websocketpp::lib::thread tthread(websocketpp::lib::bind(&run_test_timer,10)); tthread.detach(); run_dummy_client("9005"); @@ -453,7 +456,7 @@ BOOST_AUTO_TEST_CASE( client_self_initiated_close_handshake_timeout ) { websocketpp::error::close_handshake_timeout,::_1)); websocketpp::lib::thread sthread(websocketpp::lib::bind(&run_server,&s,9005,false)); - websocketpp::lib::thread tthread(websocketpp::lib::bind(&run_test_timer,6)); + websocketpp::lib::thread tthread(websocketpp::lib::bind(&run_test_timer,10)); tthread.detach(); run_client(c, "http://localhost:9005", false); @@ -485,7 +488,7 @@ BOOST_AUTO_TEST_CASE( server_self_initiated_close_handshake_timeout ) { c.set_open_handler(bind(&delay,::_1,1)); websocketpp::lib::thread sthread(websocketpp::lib::bind(&run_server,&s,9005,false)); - websocketpp::lib::thread tthread(websocketpp::lib::bind(&run_test_timer,6)); + websocketpp::lib::thread tthread(websocketpp::lib::bind(&run_test_timer,10)); tthread.detach(); run_client(c, "http://localhost:9005",false); @@ -563,7 +566,7 @@ BOOST_AUTO_TEST_CASE( stop_listening ) { 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)); + websocketpp::lib::thread tthread(websocketpp::lib::bind(&run_test_timer,5)); tthread.detach(); run_client(c, "http://localhost:9005",false); @@ -575,17 +578,17 @@ 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 @@ -603,4 +606,4 @@ BOOST_AUTO_TEST_CASE( pause_reading ) { 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 89ae9a94a..49465d6ba 100644 --- a/test/transport/iostream/connection.cpp +++ b/test/transport/iostream/connection.cpp @@ -142,6 +142,20 @@ struct stub_con : public iostream_con { indef_read(); } + void shutdown() { + iostream_con::async_shutdown( + websocketpp::lib::bind( + &stub_con::handle_async_shutdown, + type::get_shared(), + websocketpp::lib::placeholders::_1 + ) + ); + } + + void handle_async_shutdown(websocketpp::lib::error_code const & e) { + ec = e; + } + websocketpp::lib::error_code ec; size_t indef_read_size; char * indef_read_buf; @@ -156,11 +170,11 @@ config::elog_type elogger; BOOST_AUTO_TEST_CASE( const_methods ) { 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() ); + BOOST_CHECK_EQUAL( con->get_remote_endpoint(), "iostream transport" ); } -BOOST_AUTO_TEST_CASE( write_before_ostream_set ) { +BOOST_AUTO_TEST_CASE( write_before_output_method_set ) { stub_con::ptr con(new stub_con(true,alogger,elogger)); con->write("foo"); @@ -168,10 +182,10 @@ BOOST_AUTO_TEST_CASE( write_before_ostream_set ) { std::vector bufs; con->write(bufs); - BOOST_CHECK( con->ec == make_error_code(websocketpp::transport::iostream::error::output_stream_required) ); + BOOST_CHECK_EQUAL( con->ec, make_error_code(websocketpp::transport::iostream::error::output_stream_required) ); } -BOOST_AUTO_TEST_CASE( async_write ) { +BOOST_AUTO_TEST_CASE( async_write_ostream ) { stub_con::ptr con(new stub_con(true,alogger,elogger)); std::stringstream output; @@ -181,10 +195,43 @@ BOOST_AUTO_TEST_CASE( async_write ) { con->write("foo"); BOOST_CHECK( !con->ec ); - BOOST_CHECK( output.str() == "foo" ); + BOOST_CHECK_EQUAL( output.str(), "foo" ); +} + +websocketpp::lib::error_code write_handler(std::string & o, websocketpp::connection_hdl, char const * buf, size_t len) { + o += std::string(buf,len); + return websocketpp::lib::error_code(); +} + +websocketpp::lib::error_code write_handler_error(websocketpp::connection_hdl, char const *, size_t) { + return make_error_code(websocketpp::transport::error::general); +} + +BOOST_AUTO_TEST_CASE( async_write_handler ) { + stub_con::ptr con(new stub_con(true,alogger,elogger)); + std::string output; + + con->set_write_handler(websocketpp::lib::bind( + &write_handler, + websocketpp::lib::ref(output), + websocketpp::lib::placeholders::_1, + websocketpp::lib::placeholders::_2, + websocketpp::lib::placeholders::_3 + )); + con->write("foo"); + BOOST_CHECK( !con->ec ); + BOOST_CHECK_EQUAL(output, "foo"); } -BOOST_AUTO_TEST_CASE( async_write_vector_0 ) { +BOOST_AUTO_TEST_CASE( async_write_handler_error ) { + stub_con::ptr con(new stub_con(true,alogger,elogger)); + + con->set_write_handler(&write_handler_error); + con->write("foo"); + BOOST_CHECK_EQUAL( con->ec, make_error_code(websocketpp::transport::error::general) ); +} + +BOOST_AUTO_TEST_CASE( async_write_vector_0_ostream ) { std::stringstream output; stub_con::ptr con(new stub_con(true,alogger,elogger)); @@ -195,10 +242,31 @@ BOOST_AUTO_TEST_CASE( async_write_vector_0 ) { con->write(bufs); BOOST_CHECK( !con->ec ); - BOOST_CHECK( output.str() == "" ); + BOOST_CHECK_EQUAL( output.str(), "" ); +} + +BOOST_AUTO_TEST_CASE( async_write_vector_0_write_handler ) { + std::string output; + + stub_con::ptr con(new stub_con(true,alogger,elogger)); + + con->set_write_handler(websocketpp::lib::bind( + &write_handler, + websocketpp::lib::ref(output), + websocketpp::lib::placeholders::_1, + websocketpp::lib::placeholders::_2, + websocketpp::lib::placeholders::_3 + )); + + std::vector bufs; + + con->write(bufs); + + BOOST_CHECK( !con->ec ); + BOOST_CHECK_EQUAL( output, "" ); } -BOOST_AUTO_TEST_CASE( async_write_vector_1 ) { +BOOST_AUTO_TEST_CASE( async_write_vector_1_ostream ) { std::stringstream output; stub_con::ptr con(new stub_con(true,alogger,elogger)); @@ -213,10 +281,34 @@ BOOST_AUTO_TEST_CASE( async_write_vector_1 ) { con->write(bufs); BOOST_CHECK( !con->ec ); - BOOST_CHECK( output.str() == "foo" ); + BOOST_CHECK_EQUAL( output.str(), "foo" ); } -BOOST_AUTO_TEST_CASE( async_write_vector_2 ) { +BOOST_AUTO_TEST_CASE( async_write_vector_1_write_handler ) { + std::string output; + + stub_con::ptr con(new stub_con(true,alogger,elogger)); + con->set_write_handler(websocketpp::lib::bind( + &write_handler, + websocketpp::lib::ref(output), + websocketpp::lib::placeholders::_1, + websocketpp::lib::placeholders::_2, + websocketpp::lib::placeholders::_3 + )); + + std::vector bufs; + + std::string foo = "foo"; + + bufs.push_back(websocketpp::transport::buffer(foo.data(),foo.size())); + + con->write(bufs); + + BOOST_CHECK( !con->ec ); + BOOST_CHECK_EQUAL( output, "foo" ); +} + +BOOST_AUTO_TEST_CASE( async_write_vector_2_ostream ) { std::stringstream output; stub_con::ptr con(new stub_con(true,alogger,elogger)); @@ -233,7 +325,33 @@ BOOST_AUTO_TEST_CASE( async_write_vector_2 ) { con->write(bufs); BOOST_CHECK( !con->ec ); - BOOST_CHECK( output.str() == "foobar" ); + BOOST_CHECK_EQUAL( output.str(), "foobar" ); +} + +BOOST_AUTO_TEST_CASE( async_write_vector_2_write_handler ) { + std::string output; + + stub_con::ptr con(new stub_con(true,alogger,elogger)); + con->set_write_handler(websocketpp::lib::bind( + &write_handler, + websocketpp::lib::ref(output), + websocketpp::lib::placeholders::_1, + websocketpp::lib::placeholders::_2, + websocketpp::lib::placeholders::_3 + )); + + std::vector bufs; + + std::string foo = "foo"; + std::string bar = "bar"; + + bufs.push_back(websocketpp::transport::buffer(foo.data(),foo.size())); + bufs.push_back(websocketpp::transport::buffer(bar.data(),bar.size())); + + con->write(bufs); + + BOOST_CHECK( !con->ec ); + BOOST_CHECK_EQUAL( output, "foobar" ); } BOOST_AUTO_TEST_CASE( async_read_at_least_too_much ) { @@ -242,7 +360,7 @@ BOOST_AUTO_TEST_CASE( async_read_at_least_too_much ) { 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) ); + BOOST_CHECK_EQUAL( con->ec, make_error_code(websocketpp::transport::iostream::error::invalid_num_bytes) ); } BOOST_AUTO_TEST_CASE( async_read_at_least_double_read ) { @@ -252,7 +370,7 @@ BOOST_AUTO_TEST_CASE( async_read_at_least_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_CHECK_EQUAL( con->ec, make_error_code(websocketpp::transport::iostream::error::double_read) ); } BOOST_AUTO_TEST_CASE( async_read_at_least ) { @@ -263,32 +381,32 @@ BOOST_AUTO_TEST_CASE( async_read_at_least ) { memset(buf,'x',10); con->async_read_at_least(5,buf,10); - BOOST_CHECK( con->ec == make_error_code(websocketpp::error::test) ); + BOOST_CHECK_EQUAL( con->ec, make_error_code(websocketpp::error::test) ); std::stringstream channel; channel << "abcd"; channel >> *con; - BOOST_CHECK( channel.tellg() == -1 ); - BOOST_CHECK( con->ec == make_error_code(websocketpp::error::test) ); + BOOST_CHECK_EQUAL( channel.tellg(), -1 ); + BOOST_CHECK_EQUAL( con->ec, make_error_code(websocketpp::error::test) ); std::stringstream channel2; channel2 << "e"; channel2 >> *con; - BOOST_CHECK( channel2.tellg() == -1 ); + BOOST_CHECK_EQUAL( channel2.tellg(), -1 ); BOOST_CHECK( !con->ec ); - BOOST_CHECK( std::string(buf,10) == "abcdexxxxx" ); + BOOST_CHECK_EQUAL( std::string(buf,10), "abcdexxxxx" ); std::stringstream channel3; channel3 << "f"; channel3 >> *con; - BOOST_CHECK( channel3.tellg() == 0 ); + BOOST_CHECK_EQUAL( channel3.tellg(), 0 ); BOOST_CHECK( !con->ec ); - BOOST_CHECK( std::string(buf,10) == "abcdexxxxx" ); + BOOST_CHECK_EQUAL( std::string(buf,10), "abcdexxxxx" ); con->async_read_at_least(1,buf+5,5); channel3 >> *con; - BOOST_CHECK( channel3.tellg() == -1 ); + BOOST_CHECK_EQUAL( channel3.tellg(), -1 ); BOOST_CHECK( !con->ec ); - BOOST_CHECK( std::string(buf,10) == "abcdefxxxx" ); + BOOST_CHECK_EQUAL( std::string(buf,10), "abcdefxxxx" ); } BOOST_AUTO_TEST_CASE( async_read_at_least2 ) { @@ -299,20 +417,20 @@ BOOST_AUTO_TEST_CASE( async_read_at_least2 ) { 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->ec, make_error_code(websocketpp::error::test) ); std::stringstream channel; channel << "abcdefg"; channel >> *con; - BOOST_CHECK( channel.tellg() == 5 ); + BOOST_CHECK_EQUAL( channel.tellg(), 5 ); BOOST_CHECK( !con->ec ); - BOOST_CHECK( std::string(buf,10) == "abcdexxxxx" ); + BOOST_CHECK_EQUAL( std::string(buf,10), "abcdexxxxx" ); con->async_read_at_least(1,buf+5,5); channel >> *con; - BOOST_CHECK( channel.tellg() == -1 ); + BOOST_CHECK_EQUAL( channel.tellg(), -1 ); BOOST_CHECK( !con->ec ); - BOOST_CHECK( std::string(buf,10) == "abcdefgxxx" ); + BOOST_CHECK_EQUAL( std::string(buf,10), "abcdefgxxx" ); } void timer_callback_stub(websocketpp::lib::error_code const &) {} @@ -332,7 +450,7 @@ BOOST_AUTO_TEST_CASE( async_read_at_least_read_some ) { 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->ec, make_error_code(websocketpp::error::test) ); char input[10] = "abcdefg"; BOOST_CHECK_EQUAL(con->read_some(input,5), 5); @@ -356,7 +474,7 @@ BOOST_AUTO_TEST_CASE( async_read_at_least_read_some_indef ) { memset(buf,'x',20); con->async_read_indef(5,buf,5); - BOOST_CHECK( con->ec == make_error_code(websocketpp::error::test) ); + BOOST_CHECK_EQUAL( con->ec, make_error_code(websocketpp::error::test) ); // here we expect to return early from read some because the outstanding // read was for 5 bytes and we were called with 10. @@ -381,7 +499,7 @@ BOOST_AUTO_TEST_CASE( async_read_at_least_read_all ) { memset(buf,'x',20); con->async_read_indef(5,buf,5); - BOOST_CHECK( con->ec == make_error_code(websocketpp::error::test) ); + BOOST_CHECK_EQUAL( con->ec, make_error_code(websocketpp::error::test) ); char input[11] = "aaaaabbbbb"; BOOST_CHECK_EQUAL(con->read_all(input,10), 10); @@ -394,7 +512,7 @@ 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) ); + BOOST_CHECK_EQUAL( con->ec, make_error_code(websocketpp::error::test) ); con->eof(); BOOST_CHECK_EQUAL( con->ec, make_error_code(websocketpp::transport::error::eof) ); } @@ -403,11 +521,31 @@ 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) ); + BOOST_CHECK_EQUAL( 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( shutdown ) { + stub_con::ptr con(new stub_con(true,alogger,elogger)); + BOOST_CHECK_EQUAL( con->ec, make_error_code(websocketpp::error::test) ); + con->shutdown(); + BOOST_CHECK_EQUAL( con->ec, websocketpp::lib::error_code() ); +} + +websocketpp::lib::error_code sd_handler(websocketpp::connection_hdl) { + return make_error_code(websocketpp::transport::error::general); +} + +BOOST_AUTO_TEST_CASE( shutdown_handler ) { + stub_con::ptr con(new stub_con(true,alogger,elogger)); + + con->set_shutdown_handler(&sd_handler); + BOOST_CHECK_EQUAL( con->ec, make_error_code(websocketpp::error::test) ); + con->shutdown(); + BOOST_CHECK_EQUAL( con->ec, make_error_code(websocketpp::transport::error::general) ); +} + BOOST_AUTO_TEST_CASE( shared_pointer_memory_cleanup ) { stub_con::ptr con(new stub_con(true,alogger,elogger)); @@ -416,7 +554,7 @@ BOOST_AUTO_TEST_CASE( shared_pointer_memory_cleanup ) { 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->ec, make_error_code(websocketpp::error::test) ); BOOST_CHECK_EQUAL(con.use_count(), 2); char input[10] = "foo"; diff --git a/test/utility/SConscript b/test/utility/SConscript index e2f08ddec..80e9ef6c8 100644 --- a/test/utility/SConscript +++ b/test/utility/SConscript @@ -16,11 +16,13 @@ objs = env.Object('uri_boost.o', ["uri.cpp"], LIBS = BOOST_LIBS) objs += env.Object('utilities_boost.o', ["utilities.cpp"], LIBS = BOOST_LIBS) objs += env.Object('close_boost.o', ["close.cpp"], LIBS = BOOST_LIBS) objs += env.Object('sha1_boost.o', ["sha1.cpp"], LIBS = BOOST_LIBS) +objs += env.Object('error_boost.o', ["error.cpp"], LIBS = BOOST_LIBS) prgs = env.Program('test_uri_boost', ["uri_boost.o"], LIBS = BOOST_LIBS) prgs += env.Program('test_utility_boost', ["utilities_boost.o"], LIBS = BOOST_LIBS) prgs += env.Program('test_frame', ["frame.cpp"], LIBS = BOOST_LIBS) prgs += env.Program('test_close_boost', ["close_boost.o"], LIBS = BOOST_LIBS) prgs += env.Program('test_sha1_boost', ["sha1_boost.o"], LIBS = BOOST_LIBS) +prgs += env.Program('test_error_boost', ["error_boost.o"], LIBS = BOOST_LIBS) if env_cpp11.has_key('WSPP_CPP11_ENABLED'): BOOST_LIBS_CPP11 = boostlibs(['unit_test_framework'],env_cpp11) + [platform_libs] + [polyfill_libs] @@ -28,9 +30,11 @@ if env_cpp11.has_key('WSPP_CPP11_ENABLED'): objs += env_cpp11.Object('uri_stl.o', ["uri.cpp"], LIBS = BOOST_LIBS_CPP11) objs += env_cpp11.Object('close_stl.o', ["close.cpp"], LIBS = BOOST_LIBS_CPP11) objs += env_cpp11.Object('sha1_stl.o', ["sha1.cpp"], LIBS = BOOST_LIBS_CPP11) + objs += env_cpp11.Object('error_stl.o', ["error.cpp"], LIBS = BOOST_LIBS_CPP11) prgs += env_cpp11.Program('test_utility_stl', ["utilities_stl.o"], LIBS = BOOST_LIBS_CPP11) prgs += env_cpp11.Program('test_uri_stl', ["uri_stl.o"], LIBS = BOOST_LIBS_CPP11) prgs += env_cpp11.Program('test_close_stl', ["close_stl.o"], LIBS = BOOST_LIBS_CPP11) prgs += env_cpp11.Program('test_sha1_stl', ["sha1_stl.o"], LIBS = BOOST_LIBS_CPP11) + prgs += env_cpp11.Program('test_error_stl', ["error_stl.o"], LIBS = BOOST_LIBS_CPP11) Return('prgs') diff --git a/websocketpp/error_container.hpp b/test/utility/error.cpp similarity index 60% rename from websocketpp/error_container.hpp rename to test/utility/error.cpp index 5a84f7aa6..1379a95f4 100644 --- a/websocketpp/error_container.hpp +++ b/test/utility/error.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Peter Thorson. All rights reserved. + * Copyright (c) 2015, 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: @@ -24,48 +24,31 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ +//#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE error +#include -#ifndef WEBSOCKETPP_ERROR_MESSAGE_HPP -#define WEBSOCKETPP_ERROR_MESSAGE_HPP +#include -namespace websocketpp { +BOOST_AUTO_TEST_CASE( constructing_exceptions ) { + websocketpp::lib::error_code test_ec = websocketpp::error::make_error_code(websocketpp::error::test); + websocketpp::lib::error_code general_ec = websocketpp::error::make_error_code(websocketpp::error::general); -/** - * The transport::security::* classes are a set of security/socket related - * policies and support code for the ASIO transport types. - */ -class error_msg { -public: - const std::string& get_msg() const { - return m_error_msg; - } - - void set_msg(const std::string& msg) { - m_error_msg = msg; - } + websocketpp::exception b("foo"); + websocketpp::exception c("foo",test_ec); + websocketpp::exception d(""); + websocketpp::exception e("",test_ec); - void append_msg(const std::string& msg) { - m_error_msg.append(msg); - } + BOOST_CHECK_EQUAL(b.what(),"foo"); + BOOST_CHECK_EQUAL(b.code(),general_ec); - template - void set_msg(const T& thing) { - std::stringsteam val; - val << thing; - this->set_msg(val.str()); - } + BOOST_CHECK_EQUAL(c.what(),"foo"); + BOOST_CHECK_EQUAL(c.code(),test_ec); - template - void append_msg(const T& thing) { - std::stringsteam val; - val << thing; - this->append_msg(val.str()); - } -private: - // error resources - std::string m_error_msg; -}; + BOOST_CHECK_EQUAL(d.what(),"Generic error"); + BOOST_CHECK_EQUAL(d.code(),general_ec); -} // namespace websocketpp + BOOST_CHECK_EQUAL(e.what(),"Test Error"); + BOOST_CHECK_EQUAL(e.code(),test_ec); +} -#endif // WEBSOCKETPP_ERROR_MESSAGE_HPP diff --git a/tutorials/chat_tutorial/chat_tutorial.md b/tutorials/chat_tutorial/chat_tutorial.md index e50fca54a..042837c6f 100644 --- a/tutorials/chat_tutorial/chat_tutorial.md +++ b/tutorials/chat_tutorial/chat_tutorial.md @@ -2,7 +2,7 @@ Chat Tutorial ============= Goals of this tutorial: -- Impliment a realtime websocket chat server +- Implement a realtime websocket chat server Server diff --git a/tutorials/utility_client/step6.cpp b/tutorials/utility_client/step6.cpp new file mode 100644 index 000000000..6cd3ef50b --- /dev/null +++ b/tutorials/utility_client/step6.cpp @@ -0,0 +1,335 @@ +/* + * 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(); + } + + void on_message(websocketpp::connection_hdl, client::message_ptr msg) { + if (msg->get_opcode() == websocketpp::frame::opcode::text) { + m_messages.push_back("<< " + msg->get_payload()); + } else { + m_messages.push_back("<< " + websocketpp::utility::to_hex(msg->get_payload())); + } + } + + websocketpp::connection_hdl get_hdl() const { + return m_hdl; + } + + int get_id() const { + return m_id; + } + + std::string get_status() const { + return m_status; + } + + void record_sent_message(std::string message) { + m_messages.push_back(">> " + 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::vector m_messages; +}; + +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) << "\n"; + out << "> Messages Processed: (" << data.m_messages.size() << ") \n"; + + std::vector::const_iterator it; + for (it = data.m_messages.begin(); it != data.m_messages.end(); ++it) { + out << *it << "\n"; + } + + 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 = websocketpp::lib::make_shared(&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 = websocketpp::lib::make_shared(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 + )); + con->set_message_handler(websocketpp::lib::bind( + &connection_metadata::on_message, + metadata_ptr, + websocketpp::lib::placeholders::_1, + websocketpp::lib::placeholders::_2 + )); + + 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; + } + } + + void send(int id, std::string message) { + 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.send(metadata_it->second->get_hdl(), message, websocketpp::frame::opcode::text, ec); + if (ec) { + std::cout << "> Error sending message: " << ec.message() << std::endl; + return; + } + + metadata_it->second->record_sent_message(message); + } + + 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" + << "send \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,4) == "send") { + std::stringstream ss(input); + + std::string cmd; + int id; + std::string message = ""; + + ss >> cmd >> id; + std::getline(ss,message); + + endpoint.send(id, message); + } 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 index f90516a65..5490f571b 100644 --- a/tutorials/utility_client/utility_client.md +++ b/tutorials/utility_client/utility_client.md @@ -221,7 +221,7 @@ int main() { _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. +This step adds two new commands to utility_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 @@ -263,7 +263,7 @@ A new WebSocket connection is initiated via a three step process. First, a conne > **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. +> For clarity about error handling the utility_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. @@ -286,7 +286,7 @@ Next, the connection request is configured. For this step the only configuration > > 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. +`utility_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. @@ -591,7 +591,7 @@ void close(int id, websocketpp::close::status::value code) { #### 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. +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::close` 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. @@ -654,7 +654,7 @@ Enter Command: close 0 1001 example message Enter Command: show 0 > URI: ws://localhost:9002 > Status: Closed -> Remote Server: WebSocket++/0.3.0-alpha4 +> Remote Server: WebSocket++/0.4.0 > Error/close reason: close code: 1001 (Going away), close reason: example message Enter Command: connect ws://localhost:9002 > Created connection with id 1 @@ -668,9 +668,146 @@ Enter Command: quit _Sending and receiving messages_ -- Sending a messages -- terminology: WebSocket opcodes, text vs binary messages -- Receiving a message +This step adds a command to send a message on a given connection and updates the show command to print a transcript of all sent and received messages for that connection. + +> ###### Terminology: WebSocket message types (opcodes) +> WebSocket messages have types indicated by their opcode. The protocol currently specifies two different opcodes for data messages, text and binary. Text messages represent UTF8 text and will be validated as such. Binary messages represent raw binary bytes and are passed through directly with no validation. +> +> WebSocket++ provides the values `websocketpp::frame::opcode::text` and `websocketpp::frame::opcode::binary` that can be used to direct how outgoing messages should be sent and to check how incoming messages are formatted. + +#### Sending Messages + +Messages are sent using `endpoint::send`. This is a thread safe method that may be called from anywhere to queue a message for sending on the specified connection. There are three send overloads for use with different scenarios. + +Each method takes a `connection_hdl` to indicate which connection to send the message on as well as a `frame::opcode::value` to indicate which opcode to label the message as. All overloads are also available with an exception free varient that fills in a a status/error code instead of throwing. + +The first overload, `connection_hdl hdl, std::string const & payload, frame::opcode::value op`, takes a `std::string`. The string contents are copied into an internal buffer and can be safely modified after calling send. + +The second overload, `connection_hdl hdl, void const * payload, size_t len, frame::opcode::value op`, takes a void * buffer and length. The buffer contents are copied and can be safely modified after calling send. + +The third overload, `connection_hdl hdl, message_ptr msg`, takes a WebSocket++ `message_ptr`. This overload allows a message to be constructed in place before the call to send. It also may allow a single message buffer to be sent multiple times, including to multiple connections, without copying. Whether or not this actually happens depends on other factors such as whether compression is enabled. The contents of the message buffer may not be safely modified after being sent. + +> ###### Terminology: Outgoing WebSocket message queueing & flow control +> In many configurations, such as when the Asio based transport is in use, WebSocket++ is an asynchronous system. As such the `endpoint::send` method may return before any bytes are actually written to the outgoing socket. In cases where send is called multiple times in quick succession messages may be coalesced and sent in the same operation or even the same TCP packet. When this happens the message boundaries are preserved (each call to send will produce a separate message). +> +> In the case of applications that call send from inside a handler this means that no messages will be written to the socket until that handler returns. If you are planning to send many messages in this manor or need a message to be written on the wire before continuing you should look into using multiple threads or the built in timer/interrupt handler functionality. +> +> If the outgoing socket link is slow messages may build up in this queue. You can use `connection::get_buffered_amount` to query the current size of the written message queue to decide if you want to change your sending behavior. + +#### Add send method to `websocket_endpoint` + +Like the close method, send will start by looking up the given connection ID in the connection list. Next a send request is sent to the connection's handle with the specified WebSocket message and the text opcode. Finally, we record the sent message with our connection metadata object so later our show connection command can print a list of messages sent. + +```cpp +void send(int id, std::string message) { + 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.send(metadata_it->second->get_hdl(), message, websocketpp::frame::opcode::text, ec); + if (ec) { + std::cout << "> Error sending message: " << ec.message() << std::endl; + return; + } + + metadata_it->second->record_sent_message(message); +} +``` + +#### Add send option to the command loop and help message + +A send option is added to the command loop. It takes a connection ID and a text message to send. An entry is also added to the help system to describe how the new command may be used. + +```cpp +else if (input.substr(0,4) == "send") { + std::stringstream ss(input); + + std::string cmd; + int id; + std::string message = ""; + + ss >> cmd >> id; + std::getline(ss,message); + + endpoint.send(id, message); +} +``` + +#### Add glue to `connection_metadata` for storing sent messages + +In order to store messages sent on this connection some code is added to `connection_metadata`. This includes a new data member `std::vector m_messages` to keep track of all messages sent and received as well as a method for adding a sent message in that list: + +```cpp +void record_sent_message(std::string message) { + m_messages.push_back(">> " + message); +} +``` + +Finally the connection metadata output operator is updated to also print a list of processed messages: + +```cpp +out << "> Messages Processed: (" << data.m_messages.size() << ") \n"; + +std::vector::const_iterator it; +for (it = data.m_messages.begin(); it != data.m_messages.end(); ++it) { + out << *it << "\n"; +} +``` + +#### Receiving Messages + +Messages are received by registering a message handler. This handler will be called once per message received and its signature is `void on_message(websocketpp::connection_hdl hdl, endpoint::message_ptr msg)`. The `connection_hdl`, like the similar parameter from the other handlers is a handle for the connection that the message was received on. The `message_ptr` is a pointer to an object that can be queried for the message payload, opcode, and other metadata. Note that the message_ptr type, as well as its underlying message type, is dependent on how your endpoint is configured and may be different for different configs. + +#### Add a message handler to method to `connection_metadata` + +The message receiving behave that we are implementing will be to collect all messages sent and received and to print them in order when the show connection command is run. The sent messages are already being added to that list. Now we add a message handler that pushes received messages to the list as well. Text messages are pushed as-is. Binary messages are first converted to printable hexadecimal format. + +```cpp +void on_message(websocketpp::connection_hdl hdl, client::message_ptr msg) { + if (msg->get_opcode() == websocketpp::frame::opcode::text) { + m_messages.push_back(msg->get_payload()); + } else { + m_messages.push_back(websocketpp::utility::to_hex(msg->get_payload())); + } +} +``` + +In order to have this handler called when new messages are received we also register it with our connection. Note that unlike most other handlers, the message handler has two parameters and thus needs two placeholders. + +```cpp +con->set_message_handler(websocketpp::lib::bind( + &connection_metadata::on_message, + metadata_ptr, + websocketpp::lib::placeholders::_1, + websocketpp::lib::placeholders::_2 +)); +``` + +#### Build + +There are no changes to the build instructions from step 5 + +#### Run + +In this example run we are connecting to the WebSocket++ example echo_server. This server will repeat any message we send back to it. You can also try testing this with the echo server at `ws://echo.websocket.org` with similar results. + +``` +Enter Command: connect ws://localhost:9002 +> Created connection with id 0 +Enter Command: send 0 example message +Enter Command: show 0 +> URI: ws://localhost:9002 +> Status: Open +> Remote Server: WebSocket++/0.4.0 +> Error/close reason: N/A +> Messages Processed: (2) +>> example message +<< example message +``` ### Step 7 diff --git a/websocketpp/close.hpp b/websocketpp/close.hpp index 53ad4f1c6..f8e769436 100644 --- a/websocketpp/close.hpp +++ b/websocketpp/close.hpp @@ -150,6 +150,21 @@ namespace status { * illegal on the wire. */ static value const tls_handshake = 1015; + + /// A generic subprotocol error + /** + * Indicates that a subprotocol error occurred. Typically this involves + * receiving a message that is not formatted as a valid message for the + * subprotocol in use. + */ + static value const subprotocol_error = 3000; + + /// A invalid subprotocol data + /** + * Indicates that data was received that violated the specification of the + * subprotocol in use. + */ + static value const invalid_subprotocol_data = 3001; /// First value in range reserved for future protocol use static value const rsv_start = 1016; @@ -235,6 +250,10 @@ namespace status { return "Internal endpoint error"; case tls_handshake: return "TLS handshake failure"; + case subprotocol_error: + return "Generic subprotocol error"; + case invalid_subprotocol_data: + return "Invalid subprotocol data"; default: return "Unknown"; } diff --git a/websocketpp/common/cpp11.hpp b/websocketpp/common/cpp11.hpp index c317e9a40..6c79d644d 100644 --- a/websocketpp/common/cpp11.hpp +++ b/websocketpp/common/cpp11.hpp @@ -51,6 +51,11 @@ // C++11 compiler via the __cplusplus macro or the user/build system // supplies one of the two preprocessor defines below: + // This is defined to allow other WebSocket++ common headers to enable + // C++11 features when they are detected by this file rather than + // duplicating the above logic in every common header. + #define _WEBSOCKETPP_CPP11_INTERNAL_ + // _WEBSOCKETPP_CPP11_STRICT_ // // This define reports to WebSocket++ that 100% of the language and library diff --git a/websocketpp/common/functional.hpp b/websocketpp/common/functional.hpp index cc451bfac..d332dd15e 100644 --- a/websocketpp/common/functional.hpp +++ b/websocketpp/common/functional.hpp @@ -28,12 +28,27 @@ #ifndef WEBSOCKETPP_COMMON_FUNCTIONAL_HPP #define WEBSOCKETPP_COMMON_FUNCTIONAL_HPP -#if defined _WEBSOCKETPP_CPP11_STL_ && !defined _WEBSOCKETPP_NO_CPP11_FUNCTIONAL_ +#include + +// If we've determined that we're in full C++11 mode and the user hasn't +// explicitly disabled the use of C++11 functional header, then prefer it to +// boost. +#if defined _WEBSOCKETPP_CPP11_INTERNAL_ && !defined _WEBSOCKETPP_NO_CPP11_FUNCTIONAL_ + #ifndef _WEBSOCKETPP_CPP11_FUNCTIONAL_ + #define _WEBSOCKETPP_CPP11_FUNCTIONAL_ + #endif +#endif + +// If we're on Visual Studio 2010 or higher and haven't explicitly disabled +// the use of C++11 functional header then prefer it to boost. +#if defined(_MSC_VER) && _MSC_VER >= 1600 && !defined _WEBSOCKETPP_NO_CPP11_FUNCTIONAL_ #ifndef _WEBSOCKETPP_CPP11_FUNCTIONAL_ #define _WEBSOCKETPP_CPP11_FUNCTIONAL_ #endif #endif + + #ifdef _WEBSOCKETPP_CPP11_FUNCTIONAL_ #include #else @@ -52,14 +67,14 @@ namespace lib { using std::bind; using std::ref; namespace placeholders = std::placeholders; - + // There are some cases where a C++11 compiler balks at using std::ref // but a C++03 compiler using boost function requires boost::ref. As such // lib::ref is not useful in these cases. Instead this macro allows the use // of boost::ref in the case of a boost compile or no reference wrapper at // all in the case of a C++11 compile #define _WEBSOCKETPP_REF(x) x - + template void clear_function(T & x) { x = nullptr; @@ -72,11 +87,12 @@ namespace lib { /// \todo this feels hacky, is there a better way? using ::_1; using ::_2; + using ::_3; } - + // See above definition for more details on what this is and why it exists #define _WEBSOCKETPP_REF(x) boost::ref(x) - + template void clear_function(T & x) { x.clear(); diff --git a/websocketpp/common/memory.hpp b/websocketpp/common/memory.hpp index fba4c6072..52cd85b57 100644 --- a/websocketpp/common/memory.hpp +++ b/websocketpp/common/memory.hpp @@ -28,12 +28,27 @@ #ifndef WEBSOCKETPP_COMMON_MEMORY_HPP #define WEBSOCKETPP_COMMON_MEMORY_HPP -#if defined _WEBSOCKETPP_CPP11_STL_ && !defined _WEBSOCKETPP_NO_CPP11_MEMORY_ +#include + +// If we've determined that we're in full C++11 mode and the user hasn't +// explicitly disabled the use of C++11 memory header, then prefer it to +// boost. +#if defined _WEBSOCKETPP_CPP11_INTERNAL_ && !defined _WEBSOCKETPP_NO_CPP11_MEMORY_ + #ifndef _WEBSOCKETPP_CPP11_MEMORY_ + #define _WEBSOCKETPP_CPP11_MEMORY_ + #endif +#endif + +// If we're on Visual Studio 2010 or higher and haven't explicitly disabled +// the use of C++11 functional header then prefer it to boost. +#if defined(_MSC_VER) && _MSC_VER >= 1600 && !defined _WEBSOCKETPP_NO_CPP11_MEMORY_ #ifndef _WEBSOCKETPP_CPP11_MEMORY_ #define _WEBSOCKETPP_CPP11_MEMORY_ #endif #endif + + #ifdef _WEBSOCKETPP_CPP11_MEMORY_ #include #else diff --git a/websocketpp/common/network.hpp b/websocketpp/common/network.hpp index ac9064408..3f9839665 100644 --- a/websocketpp/common/network.hpp +++ b/websocketpp/common/network.hpp @@ -42,7 +42,7 @@ namespace net { inline bool is_little_endian() { short int val = 0x1; - char *ptr = (char*)&val; + char *ptr = reinterpret_cast(&val); return (ptr[0] == 1); } diff --git a/websocketpp/common/platforms.hpp b/websocketpp/common/platforms.hpp index d934a0390..877985777 100644 --- a/websocketpp/common/platforms.hpp +++ b/websocketpp/common/platforms.hpp @@ -38,4 +38,9 @@ #define NOMINMAX #endif +// Bump up the variadic parameter max for Visual Studio 2012 +#if defined(_MSC_VER) && _MSC_VER == 1700 + #define _VARIADIC_MAX 8 +#endif + #endif // WEBSOCKETPP_COMMON_PLATFORMS_HPP diff --git a/websocketpp/common/random.hpp b/websocketpp/common/random.hpp index fd29c42d3..ddf996941 100644 --- a/websocketpp/common/random.hpp +++ b/websocketpp/common/random.hpp @@ -28,12 +28,28 @@ #ifndef WEBSOCKETPP_COMMON_RANDOM_DEVICE_HPP #define WEBSOCKETPP_COMMON_RANDOM_DEVICE_HPP -#if defined _WEBSOCKETPP_CPP11_STL_ && !defined _WEBSOCKETPP_NO_CPP11_RANDOM_DEVICE_ +#include + +// If we've determined that we're in full C++11 mode and the user hasn't +// explicitly disabled the use of C++11 random header, then prefer it to +// boost. +#if defined _WEBSOCKETPP_CPP11_INTERNAL_ && !defined _WEBSOCKETPP_NO_CPP11_RANDOM_DEVICE_ #ifndef _WEBSOCKETPP_CPP11_RANDOM_DEVICE_ #define _WEBSOCKETPP_CPP11_RANDOM_DEVICE_ #endif #endif + +// If we're on Visual Studio 2010 or higher and haven't explicitly disabled +// the use of C++11 random header then prefer it to boost. +#if defined(_MSC_VER) && _MSC_VER >= 1600 && !defined _WEBSOCKETPP_NO_CPP11_MEMORY_ + #ifndef _WEBSOCKETPP_CPP11_MEMORY_ + #define _WEBSOCKETPP_CPP11_MEMORY_ + #endif +#endif + + + #ifdef _WEBSOCKETPP_CPP11_RANDOM_DEVICE_ #include #else diff --git a/websocketpp/common/system_error.hpp b/websocketpp/common/system_error.hpp index 4bfa73dc5..4abe1732f 100644 --- a/websocketpp/common/system_error.hpp +++ b/websocketpp/common/system_error.hpp @@ -28,12 +28,28 @@ #ifndef WEBSOCKETPP_COMMON_SYSTEM_ERROR_HPP #define WEBSOCKETPP_COMMON_SYSTEM_ERROR_HPP -#if defined _WEBSOCKETPP_CPP11_STL_ && !defined _WEBSOCKETPP_NO_CPP11_SYSTEM_ERROR_ + +#include + +// If we've determined that we're in full C++11 mode and the user hasn't +// explicitly disabled the use of C++11 system_error header, then prefer it to +// boost. +#if defined _WEBSOCKETPP_CPP11_INTERNAL_ && !defined _WEBSOCKETPP_NO_CPP11_SYSTEM_ERROR_ #ifndef _WEBSOCKETPP_CPP11_SYSTEM_ERROR_ #define _WEBSOCKETPP_CPP11_SYSTEM_ERROR_ #endif #endif +// If we're on Visual Studio 2010 or higher and haven't explicitly disabled +// the use of C++11 system_error header then prefer it to boost. +#if defined(_MSC_VER) && _MSC_VER >= 1600 && !defined _WEBSOCKETPP_NO_CPP11_SYSTEM_ERROR_ + #ifndef _WEBSOCKETPP_CPP11_SYSTEM_ERROR_ + #define _WEBSOCKETPP_CPP11_SYSTEM_ERROR_ + #endif +#endif + + + #ifdef _WEBSOCKETPP_CPP11_SYSTEM_ERROR_ #include #else diff --git a/websocketpp/common/thread.hpp b/websocketpp/common/thread.hpp index 30fb6a420..fa87396f9 100644 --- a/websocketpp/common/thread.hpp +++ b/websocketpp/common/thread.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Peter Thorson. All rights reserved. + * Copyright (c) 2015, 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,9 +28,18 @@ #ifndef WEBSOCKETPP_COMMON_THREAD_HPP #define WEBSOCKETPP_COMMON_THREAD_HPP -#if defined _WEBSOCKETPP_CPP11_STL_ && !defined _WEBSOCKETPP_NO_CPP11_THREAD_ - #ifndef _WEBSOCKETPP_CPP11_THREAD_ - #define _WEBSOCKETPP_CPP11_THREAD_ +#include + +// If we autodetect C++11 and haven't been explicitly instructed to not use +// C++11 threads, then set the defines that instructs the rest of this header +// to use C++11 and +#if defined _WEBSOCKETPP_CPP11_INTERNAL_ && !defined _WEBSOCKETPP_NO_CPP11_THREAD_ + // MinGW by default does not support C++11 thread/mutex so even if the + // internal check for C++11 passes, ignore it if we are on MinGW + #if (!defined(__MINGW32__) && !defined(__MINGW64__)) + #ifndef _WEBSOCKETPP_CPP11_THREAD_ + #define _WEBSOCKETPP_CPP11_THREAD_ + #endif #endif #endif diff --git a/websocketpp/common/time.hpp b/websocketpp/common/time.hpp index 1f01c311b..571688e1b 100644 --- a/websocketpp/common/time.hpp +++ b/websocketpp/common/time.hpp @@ -28,7 +28,7 @@ #ifndef WEBSOCKETPP_COMMON_TIME_HPP #define WEBSOCKETPP_COMMON_TIME_HPP -#include +#include namespace websocketpp { namespace lib { @@ -40,7 +40,9 @@ namespace lib { /// Thread safe cross platform localtime inline std::tm localtime(std::time_t const & time) { std::tm tm_snapshot; -#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) +#if (defined(__MINGW32__) || defined(__MINGW64__)) + memcpy(&tm_snapshot, ::localtime(&time), sizeof(std::tm)); +#elif (defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) localtime_s(&tm_snapshot, &time); #else localtime_r(&time, &tm_snapshot); // POSIX diff --git a/websocketpp/config/core.hpp b/websocketpp/config/core.hpp index c35bff762..a95b4021d 100644 --- a/websocketpp/config/core.hpp +++ b/websocketpp/config/core.hpp @@ -226,6 +226,18 @@ struct core { * @since 0.3.0 */ static const size_t max_message_size = 32000000; + + /// Default maximum http body size + /** + * Default value for the http parser's maximum body size. Maximum body size + * determines the point at which the library will abort reading an HTTP + * connection with the 413/request entity too large error. + * + * The default is 32MB + * + * @since 0.5.0 + */ + static const size_t max_http_body_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 fcbac10ac..dadf8a4e7 100644 --- a/websocketpp/config/core_client.hpp +++ b/websocketpp/config/core_client.hpp @@ -236,6 +236,18 @@ struct core_client { */ static const size_t max_message_size = 32000000; + /// Default maximum http body size + /** + * Default value for the http parser's maximum body size. Maximum body size + * determines the point at which the library will abort reading an HTTP + * connection with the 413/request entity too large error. + * + * The default is 32MB + * + * @since 0.5.0 + */ + static const size_t max_http_body_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 9f90360ae..223f72fbb 100644 --- a/websocketpp/config/debug.hpp +++ b/websocketpp/config/debug.hpp @@ -228,6 +228,18 @@ struct debug_core { */ static const size_t max_message_size = 32000000; + /// Default maximum http body size + /** + * Default value for the http parser's maximum body size. Maximum body size + * determines the point at which the library will abort reading an HTTP + * connection with the 413/request entity too large error. + * + * The default is 32MB + * + * @since 0.5.0 + */ + static const size_t max_http_body_size = 32000000; + /// Global flag for enabling/disabling extensions static const bool enable_extensions = true; diff --git a/websocketpp/config/minimal_server.hpp b/websocketpp/config/minimal_server.hpp index f689da9e6..dd1aedb9d 100644 --- a/websocketpp/config/minimal_server.hpp +++ b/websocketpp/config/minimal_server.hpp @@ -112,10 +112,8 @@ struct minimal_server { endpoint_msg_manager_type; /// Logging policies - typedef websocketpp::log::stub elog_type; - typedef websocketpp::log::stub alog_type; + typedef websocketpp::log::stub elog_type; + typedef websocketpp::log::stub alog_type; /// RNG policies typedef websocketpp::random::none::int_generator rng_type; @@ -256,6 +254,18 @@ struct minimal_server { */ static const size_t max_message_size = 32000000; + /// Default maximum http body size + /** + * Default value for the http parser's maximum body size. Maximum body size + * determines the point at which the library will abort reading an HTTP + * connection with the 413/request entity too large error. + * + * The default is 32MB + * + * @since 0.5.0 + */ + static const size_t max_http_body_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 37880ce1c..954683279 100644 --- a/websocketpp/connection.hpp +++ b/websocketpp/connection.hpp @@ -29,18 +29,18 @@ #define WEBSOCKETPP_CONNECTION_HPP #include -#include -#include -#include #include #include -#include + #include #include #include +#include + +#include +#include +#include -#include -#include #include #include #include @@ -119,7 +119,7 @@ typedef lib::function pong_timeout_handler; /** * The validate handler is called after a WebSocket handshake has been received * and processed but before it has been accepted. This gives the application a - * chance to impliment connection details specific policies for accepting + * chance to implement connection details specific policies for accepting * connections and the ability to negotiate extensions and subprotocols. * * The validate handler return value indicates whether or not the connection @@ -311,6 +311,7 @@ class connection , m_rng(rng) , m_local_close_code(close::status::abnormal_close) , m_remote_close_code(close::status::abnormal_close) + , m_is_http(false) , m_was_clean(false) { m_alog.write(log::alevel::devel,"connection constructor"); @@ -533,8 +534,8 @@ class connection /// 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. + * Get maximum message size. Maximum message size determines the point at + * which the connection will fail with the message_too_big protocol error. * * The default is set by the endpoint that creates the connection. * @@ -546,9 +547,9 @@ class connection /// 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. + * Set maximum message size. Maximum message size determines the point at + * which the connection will fail 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. * @@ -562,6 +563,38 @@ class connection m_processor->set_max_message_size(new_value); } } + + /// Get maximum HTTP message body size + /** + * Get maximum HTTP message body size. Maximum message body size determines + * the point at which the connection will stop reading an HTTP request whose + * body is too large. + * + * The default is set by the endpoint that creates the connection. + * + * @since 0.5.0 + * + * @return The maximum HTTP message body size + */ + size_t get_max_http_body_size() const { + return m_request.get_max_body_size(); + } + + /// Set maximum HTTP message body size + /** + * Set maximum HTTP message body size. Maximum message body size determines + * the point at which the connection will stop reading an HTTP request whose + * body is too large. + * + * The default is set by the endpoint that creates the connection. + * + * @since 0.5.0 + * + * @param new_value The value to set as the maximum message size. + */ + void set_max_http_body_size(size_t new_value) { + m_request.set_max_body_size(new_value); + } ////////////////////////////////// // Uncategorized public methods // @@ -899,7 +932,18 @@ class connection * @param key Name of the header to get * @return The value of the header */ - std::string const & get_request_header(std::string const & key); + std::string const & get_request_header(std::string const & key) const; + + /// Retrieve a request body + /** + * Retrieve the value of the request body. This value is typically used with + * PUT and POST requests to upload files or other data. Only HTTP + * connections will ever have bodies. WebSocket connection's will always + * have blank bodies. + * + * @return The value of the request body. + */ + std::string const & get_request_body() const; /// Retrieve a response header /** @@ -908,7 +952,7 @@ class connection * @param key Name of the header to get * @return The value of the header */ - std::string const & get_response_header(std::string const & key); + std::string const & get_response_header(std::string const & key) const; /// Set response status code and message /** @@ -1168,7 +1212,7 @@ class connection void read_frame(); /// Get array of WebSocket protocol versions that this connection supports. - const std::vector& get_supported_versions() const; + std::vector const & get_supported_versions() const; /// Sets the handler for a terminating connection. Should only be used /// internally by the endpoint class. @@ -1202,60 +1246,21 @@ class connection void handle_transport_init(lib::error_code const & ec); /// Set m_processor based on information in m_request. Set m_response - /// status and return false on error. - bool initialize_processor(); + /// status and return an error code indicating status. + lib::error_code initialize_processor(); /// Perform WebSocket handshake validation of m_request using m_processor. - /// set m_response and return false on error. - bool process_handshake_request(); - - /// Atomically change the internal connection state. - /** - * @param req The required starting state. If the internal state does not - * match req an exception is thrown. - * - * @param dest The state to change to. - * - * @param msg The message to include in the exception thrown - */ - void atomic_state_change(istate_type req, istate_type dest, - std::string msg); - - /// Atomically change the internal and external connection state. - /** - * @param ireq The required starting internal state. If the internal state - * does not match ireq an exception is thrown. - * - * @param idest The internal state to change to. - * - * @param ereq The required starting external state. If the external state - * does not match ereq an exception is thrown. - * - * @param edest The external state to change to. - * - * @param msg The message to include in the exception thrown - */ - void atomic_state_change(istate_type ireq, istate_type idest, - session::state::value ereq, session::state::value edest, - std::string msg); - - /// Atomically read and compared the internal state. - /** - * @param req The state to test against. If the internal state does not - * match req an exception is thrown. - * - * @param msg The message to include in the exception thrown - */ - void atomic_state_check(istate_type req, std::string msg); + /// set m_response and return an error code indicating status. + lib::error_code process_handshake_request(); private: /// Completes m_response, serializes it, and sends it out on the wire. - void send_http_response(); + void send_http_response(lib::error_code const & ec); /// Sends an opening WebSocket connect request void send_http_request(); /// Alternate path for send_http_response in error conditions - void send_http_response_error(); + void send_http_response_error(lib::error_code const & ec); /// Process control message /** @@ -1352,6 +1357,12 @@ class connection * Includes: error code and message for why it was failed */ void log_fail_result(); + + /// Prints information about HTTP connections + /** + * Includes: TODO + */ + void log_http_result(); /// Prints information about an arbitrary error code on the specified channel template @@ -1495,6 +1506,10 @@ class connection /// Detailed internal error code lib::error_code m_ec; + + /// A flag that gets set once it is determined that the connection is an + /// HTTP connection and not a WebSocket one. + bool m_is_http; bool m_was_clean; diff --git a/websocketpp/endpoint.hpp b/websocketpp/endpoint.hpp index 3bbe9da18..467f0d822 100644 --- a/websocketpp/endpoint.hpp +++ b/websocketpp/endpoint.hpp @@ -29,10 +29,11 @@ #define WEBSOCKETPP_ENDPOINT_HPP #include + #include #include -#include +#include namespace websocketpp { @@ -84,8 +85,6 @@ class endpoint : public config::transport_type, public config::endpoint_base { // TODO: organize these typedef typename connection_type::termination_handler termination_handler; - typedef lib::shared_ptr hdl_type; - explicit endpoint(bool p_is_server) : m_alog(config::alog_level, log::channel_type_hint::access) , m_elog(config::elog_level, log::channel_type_hint::error) @@ -94,6 +93,7 @@ class endpoint : public config::transport_type, public config::endpoint_base { , 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_max_http_body_size(config::max_http_body_size) , m_is_server(p_is_server) { m_alog.set_channels(config::alog_level); @@ -350,9 +350,10 @@ class endpoint : public config::transport_type, public config::endpoint_base { /// 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. + * 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 * @@ -364,9 +365,10 @@ class endpoint : public config::transport_type, public config::endpoint_base { /// 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. + * 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 * @@ -378,6 +380,40 @@ class endpoint : public config::transport_type, public config::endpoint_base { m_max_message_size = new_value; } + /// Get maximum HTTP message body size + /** + * Get maximum HTTP message body size. Maximum message body size determines + * the point at which the connection will stop reading an HTTP request whose + * body is too large. + * + * The default is set by the max_http_body_size value from the template + * config + * + * @since 0.5.0 + * + * @return The maximum HTTP message body size + */ + size_t get_max_http_body_size() const { + return m_max_http_body_size; + } + + /// Set maximum HTTP message body size + /** + * Set maximum HTTP message body size. Maximum message body size determines + * the point at which the connection will stop reading an HTTP request whose + * body is too large. + * + * The default is set by the max_http_body_size value from the template + * config + * + * @since 0.5.0 + * + * @param new_value The value to set as the maximum message size. + */ + void get_max_http_body_size(size_t new_value) { + m_max_http_body_size = new_value; + } + /*************************************/ /* Connection pass through functions */ /*************************************/ @@ -395,21 +431,21 @@ class endpoint : public config::transport_type, public config::endpoint_base { /// 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. + * 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. + * 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 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. */ @@ -420,8 +456,8 @@ class endpoint : public config::transport_type, public config::endpoint_base { /// Resume reading of new data (exception free) /** - * Signals to the connection to resume reading of new data after it was paused by - * `pause_reading()`. + * 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. */ @@ -565,6 +601,7 @@ class endpoint : public config::transport_type, public config::endpoint_base { long m_close_handshake_timeout_dur; long m_pong_timeout_dur; size_t m_max_message_size; + size_t m_max_http_body_size; rng_type m_rng; diff --git a/websocketpp/error.hpp b/websocketpp/error.hpp index 6fff53075..81fff8733 100644 --- a/websocketpp/error.hpp +++ b/websocketpp/error.hpp @@ -28,7 +28,9 @@ #ifndef WEBSOCKETPP_ERROR_HPP #define WEBSOCKETPP_ERROR_HPP +#include #include +#include #include #include @@ -122,7 +124,23 @@ enum value { async_accept_not_listening, /// The requested operation was canceled - operation_canceled + operation_canceled, + + /// Connection rejected + rejected, + + /// Upgrade Required. This happens if an HTTP request is made to a + /// WebSocket++ server that doesn't implement an http handler + upgrade_required, + + /// Invalid WebSocket protocol version + invalid_version, + + /// Unsupported WebSocket protocol version + unsupported_version, + + /// HTTP parse error + http_parse_error }; // enum value @@ -188,6 +206,16 @@ class category : public lib::error_category { return "Async Accept not listening"; case error::operation_canceled: return "Operation canceled"; + case error::rejected: + return "Connection rejected"; + case error::upgrade_required: + return "Upgrade required"; + case error::invalid_version: + return "Invalid version"; + case error::unsupported_version: + return "Unsupported version"; + case error::http_parse_error: + return "HTTP parse error"; default: return "Unknown"; } @@ -218,7 +246,7 @@ namespace websocketpp { class exception : public std::exception { public: exception(std::string const & msg, lib::error_code ec = make_error_code(error::general)) - : m_msg(msg), m_code(ec) + : m_msg(msg.empty() ? ec.message() : msg), m_code(ec) {} explicit exception(lib::error_code ec) @@ -228,11 +256,7 @@ class exception : public std::exception { ~exception() throw() {} virtual char const * what() const throw() { - if (m_msg.empty()) { - return m_code.message().c_str(); - } else { - return m_msg.c_str(); - } + return m_msg.c_str(); } lib::error_code code() const throw() { diff --git a/websocketpp/extensions/permessage_deflate/enabled.hpp b/websocketpp/extensions/permessage_deflate/enabled.hpp index 00a255d9b..73b9b3072 100644 --- a/websocketpp/extensions/permessage_deflate/enabled.hpp +++ b/websocketpp/extensions/permessage_deflate/enabled.hpp @@ -46,13 +46,13 @@ namespace websocketpp { namespace extensions { -/// Implimentation of the draft permessage-deflate WebSocket extension +/// Implementation of the draft permessage-deflate WebSocket extension /** * ### permessage-deflate interface * * **is_implemented**\n * `bool is_implemented()`\n - * Returns whether or not the object impliments the extension or not + * Returns whether or not the object implements the extension or not * * **is_enabled**\n * `bool is_enabled()`\n @@ -287,11 +287,11 @@ class enabled { return lib::error_code(); } - /// Test if this object impliments the permessage-deflate specification + /// Test if this object implements the permessage-deflate specification /** - * Because this object does impliment it, it will always return true. + * Because this object does implieent it, it will always return true. * - * @return Whether or not this object impliments permessage-deflate + * @return Whether or not this object implements permessage-deflate */ bool is_implemented() const { return true; diff --git a/websocketpp/frame.hpp b/websocketpp/frame.hpp index c22d35844..8a173375a 100644 --- a/websocketpp/frame.hpp +++ b/websocketpp/frame.hpp @@ -29,6 +29,7 @@ #define WEBSOCKETPP_FRAME_HPP #include +#include #include #include diff --git a/websocketpp/http/constants.hpp b/websocketpp/http/constants.hpp index 22af5f2c4..10e726d0b 100644 --- a/websocketpp/http/constants.hpp +++ b/websocketpp/http/constants.hpp @@ -28,9 +28,11 @@ #ifndef HTTP_CONSTANTS_HPP #define HTTP_CONSTANTS_HPP +#include #include #include #include +#include namespace websocketpp { /// HTTP handling support @@ -61,6 +63,9 @@ namespace http { /// Maximum size in bytes before rejecting an HTTP header as too big. size_t const max_header_size = 16000; + + /// Default Maximum size in bytes for HTTP message bodies. + size_t const max_body_size = 32000000; /// Number of bytes to use for temporary istream read buffers size_t const istream_buffer = 512; diff --git a/websocketpp/http/impl/parser.hpp b/websocketpp/http/impl/parser.hpp index a8f8d665f..92799a406 100644 --- a/websocketpp/http/impl/parser.hpp +++ b/websocketpp/http/impl/parser.hpp @@ -29,6 +29,8 @@ #define HTTP_PARSER_IMPL_HPP #include +#include +#include #include #include @@ -93,6 +95,9 @@ inline void parser::set_body(std::string const & value) { return; } + // TODO: should this method respect the max size? If so how should errors + // be indicated? + std::stringstream len; len << value.size(); replace_header("Content-Length", len.str()); @@ -111,26 +116,46 @@ inline bool parser::parse_parameter_list(std::string const & in, return (it == in.begin()); } -inline bool parser::parse_headers(std::istream & s) { - std::string header; - std::string::size_type end; - - // get headers - while (std::getline(s, header) && header != "\r") { - if (header[header.size()-1] != '\r') { - continue; // ignore malformed header lines? - } else { - header.erase(header.end()-1); - } - - end = header.find(header_separator,0); - - if (end != std::string::npos) { - append_header(header.substr(0,end),header.substr(end+2)); +inline bool parser::prepare_body() { + if (get_header("Content-Length") != "") { + std::string const & cl_header = get_header("Content-Length"); + char * end; + + // TODO: not 100% sure what the compatibility of this method is. Also, + // I believe this will only work up to 32bit sizes. Is there a need for + // > 4GiB HTTP payloads? + m_body_bytes_needed = std::strtoul(cl_header.c_str(),&end,10); + + if (m_body_bytes_needed > m_body_bytes_max) { + throw exception("HTTP message body too large", + status_code::request_entity_too_large); } + + m_body_encoding = body_encoding::plain; + return true; + } else if (get_header("Transfer-Encoding") == "chunked") { + // TODO + //m_body_encoding = body_encoding::chunked; + return false; + } else { + return false; } +} - return true; +inline size_t parser::process_body(char const * buf, size_t len) { + if (m_body_encoding == body_encoding::plain) { + size_t processed = (std::min)(m_body_bytes_needed,len); + m_body.append(buf,processed); + m_body_bytes_needed -= processed; + return processed; + } else if (m_body_encoding == body_encoding::chunked) { + // TODO: + throw exception("Unexpected body encoding", + status_code::internal_server_error); + } else { + throw exception("Unexpected body encoding", + status_code::internal_server_error); + } } inline void parser::process_header(std::string::iterator begin, diff --git a/websocketpp/http/impl/request.hpp b/websocketpp/http/impl/request.hpp index f7cad4f5e..581629556 100644 --- a/websocketpp/http/impl/request.hpp +++ b/websocketpp/http/impl/request.hpp @@ -30,6 +30,7 @@ #include #include +#include #include @@ -37,35 +38,18 @@ namespace websocketpp { namespace http { namespace parser { -inline bool request::parse_complete(std::istream& s) { - std::string req; - - // get status line - std::getline(s, req); - - if (req[req.size()-1] == '\r') { - req.erase(req.end()-1); - - std::stringstream ss(req); - std::string val; - - ss >> val; - set_method(val); - - ss >> val; - set_uri(val); - - ss >> val; - set_version(val); - } else { - return false; - } - - return parse_headers(s); -} - -inline size_t request::consume(const char *buf, size_t len) { +inline size_t request::consume(char const * buf, size_t len) { + size_t bytes_processed; + if (m_ready) {return 0;} + + if (m_body_bytes_needed > 0) { + bytes_processed = process_body(buf,len); + if (body_ready()) { + m_ready = true; + } + return bytes_processed; + } if (m_buf->size() + len > max_header_size) { // exceeded max header size @@ -107,9 +91,8 @@ inline size_t request::consume(const char *buf, size_t len) { if (m_method.empty() || get_header("Host") == "") { throw exception("Incomplete Request",status_code::bad_request); } - m_ready = true; - size_t bytes_processed = ( + bytes_processed = ( len - static_cast(m_buf->end()-end) + sizeof(header_delimiter) - 1 ); @@ -117,8 +100,22 @@ inline size_t request::consume(const char *buf, size_t len) { // frees memory used temporarily during request parsing m_buf.reset(); - // return number of bytes processed (starting bytes - bytes left) - return bytes_processed; + // if this was not an upgrade request and has a content length + // continue capturing content-length bytes and expose them as a + // request body. + + if (prepare_body()) { + bytes_processed += process_body(buf+bytes_processed,len-bytes_processed); + if (body_ready()) { + m_ready = true; + } + return bytes_processed; + } else { + m_ready = true; + + // return number of bytes processed (starting bytes - bytes left) + return bytes_processed; + } } else { if (m_method.empty()) { this->process(begin,end); @@ -131,7 +128,7 @@ inline size_t request::consume(const char *buf, size_t len) { } } -inline std::string request::raw() { +inline std::string request::raw() const { // TODO: validation. Make sure all required fields have been set? std::stringstream ret; @@ -141,7 +138,17 @@ inline std::string request::raw() { return ret.str(); } -inline void request::set_method(const std::string& method) { +inline std::string request::raw_head() const { + // TODO: validation. Make sure all required fields have been set? + std::stringstream ret; + + ret << m_method << " " << m_uri << " " << get_version() << "\r\n"; + ret << raw_headers() << "\r\n"; + + return ret.str(); +} + +inline void request::set_method(std::string const & method) { if (std::find_if(method.begin(),method.end(),is_not_token_char) != method.end()) { throw exception("Invalid method token.",status_code::bad_request); } @@ -149,14 +156,7 @@ inline void request::set_method(const std::string& method) { m_method = method; } -/// Set HTTP body -/** - * Sets the body of the HTTP object and fills in the appropriate content length - * header - * - * @param value The value to set the body to. - */ -inline void request::set_uri(const std::string& uri) { +inline void request::set_uri(std::string const & uri) { // TODO: validation? m_uri = uri; } diff --git a/websocketpp/http/impl/response.hpp b/websocketpp/http/impl/response.hpp index 6a99e81c4..4bc9de548 100644 --- a/websocketpp/http/impl/response.hpp +++ b/websocketpp/http/impl/response.hpp @@ -29,7 +29,9 @@ #define HTTP_PARSER_RESPONSE_IMPL_HPP #include +#include #include +#include #include @@ -37,7 +39,7 @@ namespace websocketpp { namespace http { namespace parser { -inline size_t response::consume(const char *buf, size_t len) { +inline size_t response::consume(char const * buf, size_t len) { if (m_state == DONE) {return 0;} if (m_state == BODY) { @@ -170,34 +172,6 @@ inline size_t response::consume(std::istream & s) { return total; } -inline bool response::parse_complete(std::istream& s) { - // parse a complete header (ie \r\n\r\n MUST be in the input stream) - std::string line; - - // get status line - std::getline(s, line); - - if (line[line.size()-1] == '\r') { - line.erase(line.end()-1); - - std::stringstream ss(line); - std::string str_val; - int int_val; - char char_val[256]; - - ss >> str_val; - set_version(str_val); - - ss >> int_val; - ss.getline(char_val,256); - set_status(status_code::value(int_val),std::string(char_val)); - } else { - return false; - } - - return parse_headers(s); -} - inline std::string response::raw() const { // TODO: validation. Make sure all required fields have been set? @@ -217,7 +191,7 @@ inline void response::set_status(status_code::value code) { m_status_msg = get_string(code); } -inline void response::set_status(status_code::value code, const std::string& +inline void response::set_status(status_code::value code, std::string const & msg) { // TODO: validation? @@ -255,7 +229,7 @@ inline void response::process(std::string::iterator begin, set_status(status_code::value(code),std::string(cursor_end+1,end)); } -inline size_t response::process_body(const char *buf, size_t len) { +inline size_t response::process_body(char const * buf, size_t len) { // If no content length was set then we read forever and never set m_ready if (m_read == 0) { //m_body.append(buf,len); diff --git a/websocketpp/http/parser.hpp b/websocketpp/http/parser.hpp index cc81f63c8..eb7ac1442 100644 --- a/websocketpp/http/parser.hpp +++ b/websocketpp/http/parser.hpp @@ -29,8 +29,9 @@ #define HTTP_PARSER_HPP #include -#include #include +#include +#include #include #include @@ -48,6 +49,14 @@ namespace state { }; } +namespace body_encoding { + enum value { + unknown, + plain, + chunked + }; +} + typedef std::map header_list; /// Read and return the next token in the stream @@ -384,6 +393,11 @@ inline std::string strip_lws(std::string const & input) { */ class parser { public: + parser() + : m_body_bytes_needed(0) + , m_body_bytes_max(max_body_size) + , m_body_encoding(body_encoding::unknown) {} + /// Get the HTTP version string /** * @return The version string for this parser @@ -467,12 +481,11 @@ class parser { */ void remove_header(std::string const & key); - /// Set HTTP body + /// Get HTTP body /** - * Sets the body of the HTTP object and fills in the appropriate content - * length header. + * Gets the body of the HTTP object * - * @param [in] value The value to set the body to. + * @return The body of the HTTP message. */ std::string const & get_body() const { return m_body; @@ -489,6 +502,32 @@ class parser { */ void set_body(std::string const & value); + /// Get body size limit + /** + * Retrieves the maximum number of bytes to parse & buffer before canceling + * a request. + * + * @since 0.5.0 + * + * @return The maximum length of a message body. + */ + size_t get_max_body_size() const { + return m_body_bytes_max; + } + + /// Set body size limit + /** + * Set the maximum number of bytes to parse and buffer before canceling a + * request. + * + * @since 0.5.0 + * + * @param value The size to set the max body length to. + */ + void set_max_body_size(size_t value) { + m_body_bytes_max = value; + } + /// Extract an HTTP parameter list from a string. /** * @param [in] in The input string. @@ -498,22 +537,53 @@ class parser { bool parse_parameter_list(std::string const & in, parameter_list & out) const; protected: - /// Parse headers from an istream + /// Process a header line /** - * @deprecated Use process_header instead. + * @todo Update this method to be exception free. * - * @param [in] s The istream to extract headers from. + * @param [in] begin An iterator to the beginning of the sequence. + * @param [in] end An iterator to the end of the sequence. */ - bool parse_headers(std::istream & s); + void process_header(std::string::iterator begin, std::string::iterator end); - /// Process a header line + /// Prepare the parser to begin parsing body data /** - * @todo Update this method to be exception free. + * Inspects headers to determine if the message has a body that needs to be + * read. If so, sets up the necessary state, otherwise returns false. If + * this method returns true and loading the message body is desired call + * `process_body` until it returns zero bytes or an error. + * + * Must not be called until after all headers have been processed. + * + * @since 0.5.0 + * + * @return True if more bytes are needed to load the body, false otherwise. + */ + bool prepare_body(); + + /// Process body data + /** + * Parses body data. + * + * @since 0.5.0 * * @param [in] begin An iterator to the beginning of the sequence. * @param [in] end An iterator to the end of the sequence. + * @return The number of bytes processed */ - void process_header(std::string::iterator begin, std::string::iterator end); + size_t process_body(char const * buf, size_t len); + + /// Check if the parser is done parsing the body + /** + * Behavior before a call to `prepare_body` is undefined. + * + * @since 0.5.0 + * + * @return True if the message body has been completed loaded. + */ + bool body_ready() const { + return (m_body_bytes_needed == 0); + } /// Generate and return the HTTP headers as a string /** @@ -526,7 +596,11 @@ class parser { std::string m_version; header_list m_headers; - std::string m_body; + + std::string m_body; + size_t m_body_bytes_needed; + size_t m_body_bytes_max; + body_encoding::value m_body_encoding; }; } // namespace parser diff --git a/websocketpp/http/request.hpp b/websocketpp/http/request.hpp index 4a5990ac8..3355c99b8 100644 --- a/websocketpp/http/request.hpp +++ b/websocketpp/http/request.hpp @@ -28,6 +28,8 @@ #ifndef HTTP_PARSER_REQUEST_HPP #define HTTP_PARSER_REQUEST_HPP +#include + #include #include @@ -54,9 +56,6 @@ class request : public parser { : m_buf(lib::make_shared()) , m_ready(false) {} - /// DEPRECATED parse a complete header (\r\n\r\n MUST be in the istream) - bool parse_complete(std::istream& s); - /// Process bytes in the input buffer /** * Process up to len bytes from input buffer buf. Returns the number of @@ -77,29 +76,32 @@ class request : public parser { * @param len Size of byte buffer * @return Number of bytes processed. */ - size_t consume(const char *buf, size_t len); + size_t consume(char const * buf, size_t len); /// Returns whether or not the request is ready for reading. bool ready() const { return m_ready; } - /// Returns the full raw request - std::string raw(); + /// Returns the full raw request (including the body) + std::string raw() const; + + /// Returns the raw request headers only (similar to an HTTP HEAD request) + std::string raw_head() const; /// Set the HTTP method. Must be a valid HTTP token - void set_method(const std::string& method); + void set_method(std::string const & method); /// Return the request method - const std::string& get_method() const { + std::string const & get_method() const { return m_method; } /// Set the HTTP uri. Must be a valid HTTP uri - void set_uri(const std::string& uri); + void set_uri(std::string const & uri); /// Return the requested URI - const std::string& get_uri() const { + std::string const & get_uri() const { return m_uri; } diff --git a/websocketpp/http/response.hpp b/websocketpp/http/response.hpp index 3825a5589..e724a3d3a 100644 --- a/websocketpp/http/response.hpp +++ b/websocketpp/http/response.hpp @@ -28,6 +28,9 @@ #ifndef HTTP_PARSER_RESPONSE_HPP #define HTTP_PARSER_RESPONSE_HPP +#include +#include + #include namespace websocketpp { @@ -82,8 +85,28 @@ class response : public parser { * @param len Size of byte buffer * @return Number of bytes processed. */ - size_t consume(const char *buf, size_t len); + size_t consume(char const * buf, size_t len); + /// Process bytes in the input buffer (istream version) + /** + * Process bytes from istream s. Returns the number of bytes processed. + * Bytes left unprocessed means bytes left over after the final header + * delimiters. + * + * Consume is a streaming processor. It may be called multiple times on one + * response and the full headers need not be available before processing can + * begin. If the end of the response was reached during this call to consume + * the ready flag will be set. Further calls to consume once ready will be + * ignored. + * + * Consume will throw an http::exception in the case of an error. Typical + * error reasons include malformed responses, incomplete responses, and max + * header size being reached. + * + * @param buf Pointer to byte buffer + * @param len Size of byte buffer + * @return Number of bytes processed. + */ size_t consume(std::istream & s); /// Returns true if the response is ready. @@ -99,9 +122,6 @@ class response : public parser { return (m_state == BODY || m_state == DONE); } - /// DEPRECATED parse a complete response from a pre-delimited istream - bool parse_complete(std::istream& s); - /// Returns the full raw response std::string raw() const; @@ -126,7 +146,7 @@ class response : public parser { * @param code Code to set * @param msg Message to set */ - void set_status(status_code::value code, const std::string& msg); + void set_status(status_code::value code, std::string const & msg); /// Return the response status code status_code::value get_status_code() const { @@ -142,7 +162,7 @@ class response : public parser { void process(std::string::iterator begin, std::string::iterator end); /// Helper function for processing body bytes - size_t process_body(const char *buf, size_t len); + size_t process_body(char const * buf, size_t len); enum state { RESPONSE_LINE = 0, diff --git a/websocketpp/impl/connection_impl.hpp b/websocketpp/impl/connection_impl.hpp index 1f29f4eff..55832d01a 100644 --- a/websocketpp/impl/connection_impl.hpp +++ b/websocketpp/impl/connection_impl.hpp @@ -28,16 +28,23 @@ #ifndef WEBSOCKETPP_CONNECTION_IMPL_HPP #define WEBSOCKETPP_CONNECTION_IMPL_HPP -#include -#include - -#include - #include #include #include #include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + namespace websocketpp { namespace istate = session::internal_state; @@ -55,7 +62,7 @@ void connection::set_termination_handler( } template -const std::string& connection::get_origin() const { +std::string const & connection::get_origin() const { //scoped_lock_type lock(m_connection_state_lock); return m_processor->get_origin(m_request); } @@ -73,7 +80,7 @@ session::state::value connection::get_state() const { } template -lib::error_code connection::send(const std::string& payload, +lib::error_code connection::send(std::string const & payload, frame::opcode::value op) { message_ptr msg = m_msg_manager->get_message(op,payload.size()); @@ -83,7 +90,7 @@ lib::error_code connection::send(const std::string& payload, } template -lib::error_code connection::send(const void* payload, size_t len, +lib::error_code connection::send(void const * payload, size_t len, frame::opcode::value op) { message_ptr msg = m_msg_manager->get_message(op,len); @@ -98,10 +105,12 @@ lib::error_code connection::send(typename config::message_type::ptr msg) if (m_alog.static_test(log::alevel::devel)) { m_alog.write(log::alevel::devel,"connection send"); } - // TODO: - if (m_state != session::state::open) { - return error::make_error_code(error::invalid_state); + { + scoped_lock_type lock(m_connection_state_lock); + if (m_state != session::state::open) { + return error::make_error_code(error::invalid_state); + } } message_ptr outgoing_msg; @@ -142,14 +151,20 @@ lib::error_code connection::send(typename config::message_type::ptr msg) } template -void connection::ping(const std::string& payload, lib::error_code& ec) { +void connection::ping(std::string const& payload, lib::error_code& ec) { if (m_alog.static_test(log::alevel::devel)) { m_alog.write(log::alevel::devel,"connection ping"); } - if (m_state != session::state::open) { - ec = error::make_error_code(error::invalid_state); - return; + { + scoped_lock_type lock(m_connection_state_lock); + if (m_state != session::state::open) { + std::stringstream ss; + ss << "connection::ping called from invalid state " << m_state; + m_alog.write(log::alevel::devel,ss.str()); + ec = error::make_error_code(error::invalid_state); + return; + } } message_ptr msg = m_msg_manager->get_message(); @@ -233,14 +248,20 @@ void connection::handle_pong_timeout(std::string payload, } template -void connection::pong(const std::string& payload, lib::error_code& ec) { +void connection::pong(std::string const& payload, lib::error_code& ec) { if (m_alog.static_test(log::alevel::devel)) { m_alog.write(log::alevel::devel,"connection pong"); } - if (m_state != session::state::open) { - ec = error::make_error_code(error::invalid_state); - return; + { + scoped_lock_type lock(m_connection_state_lock); + if (m_state != session::state::open) { + std::stringstream ss; + ss << "connection::pong called from invalid state " << m_state; + m_alog.write(log::alevel::devel,ss.str()); + ec = error::make_error_code(error::invalid_state); + return; + } } message_ptr msg = m_msg_manager->get_message(); @@ -286,15 +307,17 @@ void connection::close(close::status::value const code, m_alog.write(log::alevel::devel,"connection close"); } + // Truncate reason to maximum size allowable in a close frame. + std::string tr(reason,0,std::min(reason.size(), + frame::limits::close_reason_size)); + + scoped_lock_type lock(m_connection_state_lock); + if (m_state != session::state::open) { ec = error::make_error_code(error::invalid_state); return; } - // Truncate reason to maximum size allowable in a close frame. - std::string tr(reason,0,std::min(reason.size(), - frame::limits::close_reason_size)); - ec = this->send_close_frame(code,tr,false,close::status::terminal(code)); } @@ -385,13 +408,13 @@ bool connection::get_secure() const { } template -const std::string& connection::get_host() const { +std::string const & connection::get_host() const { //scoped_lock_type lock(m_connection_state_lock); return m_uri->get_host(); } template -const std::string& connection::get_resource() const { +std::string const & connection::get_resource() const { //scoped_lock_type lock(m_connection_state_lock); return m_uri->get_resource(); } @@ -420,12 +443,12 @@ void connection::set_uri(uri_ptr uri) { template -const std::string & connection::get_subprotocol() const { +std::string const & connection::get_subprotocol() const { return m_subprotocol; } template -const std::vector & +std::vector const & connection::get_requested_subprotocols() const { return m_requested_subprotocols; } @@ -499,35 +522,36 @@ void connection::select_subprotocol(std::string const & value) { template -const std::string & -connection::get_request_header(std::string const & key) { +std::string const & +connection::get_request_header(std::string const & key) const { return m_request.get_header(key); } template -const std::string & -connection::get_response_header(std::string const & key) { +std::string const & +connection::get_request_body() const { + return m_request.get_body(); +} + +template +std::string const & +connection::get_response_header(std::string const & key) const { return m_response.get_header(key); } template void connection::set_status(http::status_code::value code) { - //scoped_lock_type lock(m_connection_state_lock); - if (m_internal_state != istate::PROCESS_HTTP_REQUEST) { throw exception("Call to set_status from invalid state", error::make_error_code(error::invalid_state)); } - m_response.set_status(code); } template void connection::set_status(http::status_code::value code, std::string const & msg) { - //scoped_lock_type lock(m_connection_state_lock); - if (m_internal_state != istate::PROCESS_HTTP_REQUEST) { throw exception("Call to set_status from invalid state", error::make_error_code(error::invalid_state)); @@ -537,8 +561,6 @@ void connection::set_status(http::status_code::value code, } template void connection::set_body(std::string const & value) { - //scoped_lock_type lock(m_connection_state_lock); - if (m_internal_state != istate::PROCESS_HTTP_REQUEST) { throw exception("Call to set_status from invalid state", error::make_error_code(error::invalid_state)); @@ -551,8 +573,6 @@ template void connection::append_header(std::string const & key, std::string const & val) { - //scoped_lock_type lock(m_connection_state_lock); - if (m_is_server) { if (m_internal_state == istate::PROCESS_HTTP_REQUEST) { // we are setting response headers for an incoming server connection @@ -575,8 +595,6 @@ template void connection::replace_header(std::string const & key, std::string const & val) { - // scoped_lock_type lock(m_connection_state_lock); - if (m_is_server) { if (m_internal_state == istate::PROCESS_HTTP_REQUEST) { // we are setting response headers for an incoming server connection @@ -598,8 +616,6 @@ void connection::replace_header(std::string const & key, template void connection::remove_header(std::string const & key) { - //scoped_lock_type lock(m_connection_state_lock); - if (m_is_server) { if (m_internal_state == istate::PROCESS_HTTP_REQUEST) { // we are setting response headers for an incoming server connection @@ -630,11 +646,13 @@ template void connection::start() { m_alog.write(log::alevel::devel,"connection start"); - this->atomic_state_change( - istate::USER_INIT, - istate::TRANSPORT_INIT, - "Start must be called from user init state" - ); + if (m_internal_state != istate::USER_INIT) { + m_alog.write(log::alevel::devel,"Start called in invalid state"); + this->terminate(error::make_error_code(error::invalid_state)); + return; + } + + m_internal_state = istate::TRANSPORT_INIT; // Depending on how the transport implements init this function may return // immediately and call handle_transport_init later or call @@ -652,39 +670,31 @@ template void connection::handle_transport_init(lib::error_code const & ec) { m_alog.write(log::alevel::devel,"connection handle_transport_init"); - { - scoped_lock_type lock(m_connection_state_lock); + lib::error_code ecm = ec; - if (m_internal_state != istate::TRANSPORT_INIT) { - throw exception("handle_transport_init must be called from transport init state", - error::make_error_code(error::invalid_state)); - } - - if (!ec) { - // unless there was a transport error, advance internal state. - if (m_is_server) { - m_internal_state = istate::READ_HTTP_REQUEST; - } else { - m_internal_state = istate::WRITE_HTTP_REQUEST; - } - } + if (m_internal_state != istate::TRANSPORT_INIT) { + m_alog.write(log::alevel::devel, + "handle_transport_init must be called from transport init state"); + ecm = error::make_error_code(error::invalid_state); } - if (ec) { + if (ecm) { std::stringstream s; - s << "handle_transport_init received error: "<< ec.message(); - m_elog.write(log::elevel::fatal,s.str()); + s << "handle_transport_init received error: "<< ecm.message(); + m_elog.write(log::elevel::rerror,s.str()); - this->terminate(ec); + this->terminate(ecm); return; } // At this point the transport is ready to read and write bytes. if (m_is_server) { + m_internal_state = istate::READ_HTTP_REQUEST; this->read_handshake(1); } else { // We are a client. Set the processor to the version specified in the // config file and send a handshake request. + m_internal_state = istate::WRITE_HTTP_REQUEST; m_processor = get_processor(config::client_version); this->send_http_request(); } @@ -726,24 +736,37 @@ void connection::handle_read_handshake(lib::error_code const & ec, { m_alog.write(log::alevel::devel,"connection handle_read_handshake"); - this->atomic_state_check( - istate::READ_HTTP_REQUEST, - "handle_read_handshake must be called from READ_HTTP_REQUEST state" - ); + lib::error_code ecm = ec; - if (ec) { - if (ec == transport::error::eof) { - // we expect to get eof if the connection is closed already - if (m_state == session::state::closed) { - m_alog.write(log::alevel::devel,"got eof from closed con"); - return; - } + if (!ecm) { + scoped_lock_type lock(m_connection_state_lock); + + if (m_state == session::state::connecting) { + if (m_internal_state != istate::READ_HTTP_REQUEST) { + ecm = error::make_error_code(error::invalid_state); + } + } else if (m_state == session::state::closed) { + // The connection was canceled while the response was being sent, + // usually by the handshake timer. This is basically expected + // (though hopefully rare) and there is nothing we can do so ignore. + m_alog.write(log::alevel::devel, + "handle_read_handshake invoked after connection was closed"); + return; + } else { + ecm = error::make_error_code(error::invalid_state); } + } - std::stringstream s; - s << "error in handle_read_handshake: "<< ec.message(); - m_elog.write(log::elevel::fatal,s.str()); - this->terminate(ec); + if (ecm) { + if (ecm == transport::error::eof && m_state == session::state::closed) { + // we expect to get eof if the connection is closed already + m_alog.write(log::alevel::devel, + "got (expected) eof/state error from closed con"); + return; + } + + log_err(log::elevel::rerror,"handle_read_handshake",ecm); + this->terminate(ecm); return; } @@ -761,13 +784,13 @@ void connection::handle_read_handshake(lib::error_code const & ec, // All HTTP exceptions will result in this request failing and an error // response being returned. No more bytes will be read in this con. m_response.set_status(e.m_error_code,e.m_error_msg); - this->send_http_response_error(); + this->send_http_response_error(error::make_error_code(error::http_parse_error)); return; } // More paranoid boundaries checking. // TODO: Is this overkill? - if (bytes_processed > config::connection_read_buffer_size) { + if (bytes_processed > bytes_transferred) { m_elog.write(log::elevel::fatal,"Fatal boundaries checking error."); this->terminate(make_error_code(error::general)); return; @@ -781,8 +804,9 @@ void connection::handle_read_handshake(lib::error_code const & ec, } if (m_request.ready()) { - if (!this->initialize_processor()) { - this->send_http_response_error(); + lib::error_code processor_ec = this->initialize_processor(); + if (processor_ec) { + this->send_http_response_error(processor_ec); return; } @@ -799,7 +823,7 @@ void connection::handle_read_handshake(lib::error_code const & ec, // TODO: need more bytes m_alog.write(log::alevel::devel,"short key3 read"); m_response.set_status(http::status_code::internal_server_error); - this->send_http_response_error(); + this->send_http_response_error(processor::error::make_error_code(processor::error::short_key3)); return; } } @@ -818,15 +842,12 @@ void connection::handle_read_handshake(lib::error_code const & ec, std::copy(m_buf+bytes_processed,m_buf+bytes_transferred,m_buf); m_buf_cursor = bytes_transferred-bytes_processed; - this->atomic_state_change( - istate::READ_HTTP_REQUEST, - istate::PROCESS_HTTP_REQUEST, - "send_http_response must be called from READ_HTTP_REQUEST state" - ); + m_internal_state = istate::PROCESS_HTTP_REQUEST; + // We have the complete request. Process it. - this->process_handshake_request(); - this->send_http_response(); + lib::error_code handshake_ec = this->process_handshake_request(); + this->send_http_response(handshake_ec); } else { // read at least 1 more byte transport_con_type::async_read_at_least( @@ -849,13 +870,17 @@ void connection::handle_read_handshake(lib::error_code const & ec, // sure if the hybi00 key3 bytes need to be read). This method sets the correct // state and calls send_http_response template -void connection::send_http_response_error() { - this->atomic_state_change( - istate::READ_HTTP_REQUEST, - istate::PROCESS_HTTP_REQUEST, - "send_http_response must be called from READ_HTTP_REQUEST state" - ); - this->send_http_response(); +void connection::send_http_response_error(lib::error_code const & ec) { + if (m_internal_state != istate::READ_HTTP_REQUEST) { + m_alog.write(log::alevel::devel, + "send_http_response_error called in invalid state"); + this->terminate(error::make_error_code(error::invalid_state)); + return; + } + + m_internal_state = istate::PROCESS_HTTP_REQUEST; + + this->send_http_response(ec); } // All exit paths for this function need to call send_http_response() or submit @@ -866,15 +891,16 @@ void connection::handle_read_frame(lib::error_code const & ec, { //m_alog.write(log::alevel::devel,"connection handle_read_frame"); - this->atomic_state_check( - istate::PROCESS_CONNECTION, - "handle_read_frame must be called from PROCESS_CONNECTION state" - ); + lib::error_code ecm = ec; - if (ec) { - log::level echannel = log::elevel::fatal; + if (!ecm && m_internal_state != istate::PROCESS_CONNECTION) { + ecm = error::make_error_code(error::invalid_state); + } + + if (ecm) { + log::level echannel = log::elevel::rerror; - if (ec == transport::error::eof) { + if (ecm == transport::error::eof) { if (m_state == session::state::closed) { // we expect to get eof if the connection is closed already // just ignore it @@ -887,8 +913,17 @@ void connection::handle_read_frame(lib::error_code const & ec, terminate(lib::error_code()); return; } - } - if (ec == transport::error::tls_short_read) { + } else if (ecm == error::invalid_state) { + // In general, invalid state errors in the closed state are the + // result of handlers that were in the system already when the state + // changed and should be ignored as they pose no problems and there + // is nothing useful that we can do about them. + if (m_state == session::state::closed) { + m_alog.write(log::alevel::devel, + "handle_read_frame: got invalid istate in closed state"); + return; + } + } else if (ecm == transport::error::tls_short_read) { 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 @@ -897,12 +932,12 @@ void connection::handle_read_frame(lib::error_code const & ec, return; } echannel = log::elevel::rerror; - } else if (ec == transport::error::action_after_shutdown) { + } else if (ecm == transport::error::action_after_shutdown) { echannel = log::elevel::info; } - log_err(echannel, "handle_read_frame", ec); - this->terminate(ec); + log_err(echannel, "handle_read_frame", ecm); + this->terminate(ecm); return; } @@ -1014,12 +1049,12 @@ void connection::read_frame() { } template -bool connection::initialize_processor() { +lib::error_code connection::initialize_processor() { m_alog.write(log::alevel::devel,"initialize_processor"); // if it isn't a websocket handshake nothing to do. if (!processor::is_websocket_handshake(m_request)) { - return true; + return lib::error_code(); } int version = processor::get_websocket_version(m_request); @@ -1027,14 +1062,14 @@ bool connection::initialize_processor() { if (version < 0) { m_alog.write(log::alevel::devel, "BAD REQUEST: can't determine version"); m_response.set_status(http::status_code::bad_request); - return false; + return error::make_error_code(error::invalid_version); } m_processor = get_processor(version); // if the processor is not null we are done if (m_processor) { - return true; + return lib::error_code(); } // We don't have a processor for this version. Return bad request @@ -1052,11 +1087,11 @@ bool connection::initialize_processor() { } m_response.replace_header("Sec-WebSocket-Version",ss.str()); - return false; + return error::make_error_code(error::unsupported_version); } template -bool connection::process_handshake_request() { +lib::error_code connection::process_handshake_request() { m_alog.write(log::alevel::devel,"process handshake request"); if (!processor::is_websocket_handshake(m_request)) { @@ -1072,16 +1107,21 @@ bool connection::process_handshake_request() { if (!m_uri->get_valid()) { m_alog.write(log::alevel::devel, "Bad request: failed to parse uri"); m_response.set_status(http::status_code::bad_request); - return false; + return error::make_error_code(error::invalid_uri); } if (m_http_handler) { + m_is_http = true; m_http_handler(m_connection_hdl); + if (m_state == session::state::closed) { + return error::make_error_code(error::http_connection_ended); + } } else { set_status(http::status_code::upgrade_required); + return error::make_error_code(error::upgrade_required); } - return true; + return lib::error_code(); } lib::error_code ec = m_processor->validate_handshake(m_request); @@ -1091,7 +1131,7 @@ bool connection::process_handshake_request() { // Not a valid handshake request m_alog.write(log::alevel::devel, "Bad request " + ec.message()); m_response.set_status(http::status_code::bad_request); - return false; + return ec; } // Read extension parameters and set up values necessary for the end user @@ -1104,7 +1144,7 @@ bool connection::process_handshake_request() { // a failed connection attempt. m_alog.write(log::alevel::devel, "Bad request: " + neg_results.first.message()); m_response.set_status(http::status_code::bad_request); - return false; + return neg_results.first; } else { // extension negotiation succeeded, set response header accordingly // we don't send an empty extensions header because it breaks many @@ -1122,7 +1162,7 @@ bool connection::process_handshake_request() { if (!m_uri->get_valid()) { m_alog.write(log::alevel::devel, "Bad request: failed to parse uri"); m_response.set_status(http::status_code::bad_request); - return false; + return error::make_error_code(error::invalid_uri); } // extract subprotocols @@ -1147,7 +1187,7 @@ bool connection::process_handshake_request() { m_alog.write(log::alevel::devel, s.str()); m_response.set_status(http::status_code::internal_server_error); - return false; + return ec; } } else { // User application has rejected the handshake @@ -1159,19 +1199,27 @@ bool connection::process_handshake_request() { if (m_response.get_status_code() == http::status_code::uninitialized) { m_response.set_status(http::status_code::bad_request); } - - return false; + + return error::make_error_code(error::rejected); } - return true; + return lib::error_code(); } template -void connection::send_http_response() { +void connection::send_http_response(lib::error_code const & ec) { m_alog.write(log::alevel::devel,"connection send_http_response"); + if (ec == error::make_error_code(error::http_connection_ended)) { + m_alog.write(log::alevel::http,"An HTTP handler took over the connection."); + return; + } + if (m_response.get_status_code() == http::status_code::uninitialized) { m_response.set_status(http::status_code::internal_server_error); + m_ec = error::make_error_code(error::general); + } else { + m_ec = ec; } m_response.set_version("HTTP/1.1"); @@ -1217,18 +1265,39 @@ template 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( - istate::PROCESS_HTTP_REQUEST, - "handle_send_http_response must be called from PROCESS_HTTP_REQUEST state" - ); + lib::error_code ecm = ec; - if (ec) { - log_err(log::elevel::rerror,"handle_send_http_response",ec); - this->terminate(ec); - return; + if (!ecm) { + scoped_lock_type lock(m_connection_state_lock); + + if (m_state == session::state::connecting) { + if (m_internal_state != istate::PROCESS_HTTP_REQUEST) { + ecm = error::make_error_code(error::invalid_state); + } + } else if (m_state == session::state::closed) { + // The connection was canceled while the response was being sent, + // usually by the handshake timer. This is basically expected + // (though hopefully rare) and there is nothing we can do so ignore. + m_alog.write(log::alevel::devel, + "handle_send_http_response invoked after connection was closed"); + return; + } else { + ecm = error::make_error_code(error::invalid_state); + } } - this->log_open_result(); + if (ecm) { + if (ecm == transport::error::eof && m_state == session::state::closed) { + // we expect to get eof if the connection is closed already + m_alog.write(log::alevel::devel, + "got (expected) eof/state error from closed con"); + return; + } + + log_err(log::elevel::rerror,"handle_send_http_response",ecm); + this->terminate(ecm); + return; + } if (m_handshake_timer) { m_handshake_timer->cancel(); @@ -1237,8 +1306,11 @@ void connection::handle_send_http_response(lib::error_code const & ec) { if (m_response.get_status_code() != http::status_code::switching_protocols) { - if (m_processor) { - // this was a websocket connection that ended in an error + /*if (m_processor || m_ec == error::http_parse_error || + m_ec == error::invalid_version || m_ec == error::unsupported_version + || m_ec == error::upgrade_required) + {*/ + if (!m_is_http) { std::stringstream s; s << "Handshake ended with HTTP error: " << m_response.get_status_code(); @@ -1246,18 +1318,24 @@ void connection::handle_send_http_response(lib::error_code const & ec) { } 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)); + + this->log_http_result(); + + if (m_ec) { + m_alog.write(log::alevel::devel, + "got to writing HTTP results with m_ec set: "+m_ec.message()); + } + m_ec = make_error_code(error::http_connection_ended); + } + + this->terminate(m_ec); return; } - this->atomic_state_change( - istate::PROCESS_HTTP_REQUEST, - istate::PROCESS_CONNECTION, - session::state::connecting, - session::state::open, - "handle_send_http_response must be called from PROCESS_HTTP_REQUEST state" - ); + this->log_open_result(); + + m_internal_state = istate::PROCESS_CONNECTION; + m_state = session::state::open; if (m_open_handler) { m_open_handler(m_connection_hdl); @@ -1329,22 +1407,41 @@ template 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( - istate::WRITE_HTTP_REQUEST, - "handle_send_http_request must be called from WRITE_HTTP_REQUEST state" - ); + lib::error_code ecm = ec; - if (ec) { - log_err(log::elevel::rerror,"handle_send_http_request",ec); - this->terminate(ec); - return; + if (!ecm) { + scoped_lock_type lock(m_connection_state_lock); + + if (m_state == session::state::connecting) { + if (m_internal_state != istate::WRITE_HTTP_REQUEST) { + ecm = error::make_error_code(error::invalid_state); + } else { + m_internal_state = istate::READ_HTTP_RESPONSE; + } + } else if (m_state == session::state::closed) { + // The connection was canceled while the response was being sent, + // usually by the handshake timer. This is basically expected + // (though hopefully rare) and there is nothing we can do so ignore. + m_alog.write(log::alevel::devel, + "handle_send_http_request invoked after connection was closed"); + return; + } else { + ecm = error::make_error_code(error::invalid_state); + } } - this->atomic_state_change( - istate::WRITE_HTTP_REQUEST, - istate::READ_HTTP_RESPONSE, - "handle_send_http_request must be called from WRITE_HTTP_REQUEST state" - ); + if (ecm) { + if (ecm == transport::error::eof && m_state == session::state::closed) { + // we expect to get eof if the connection is closed already + m_alog.write(log::alevel::devel, + "got (expected) eof/state error from closed con"); + return; + } + + log_err(log::elevel::rerror,"handle_send_http_request",ecm); + this->terminate(ecm); + return; + } transport_con_type::async_read_at_least( 1, @@ -1365,16 +1462,40 @@ void connection::handle_read_http_response(lib::error_code const & ec, { m_alog.write(log::alevel::devel,"handle_read_http_response"); - this->atomic_state_check( - istate::READ_HTTP_RESPONSE, - "handle_read_http_response must be called from READ_HTTP_RESPONSE state" - ); + lib::error_code ecm = ec; - if (ec) { - log_err(log::elevel::rerror,"handle_send_http_response",ec); - this->terminate(ec); + if (!ecm) { + scoped_lock_type lock(m_connection_state_lock); + + if (m_state == session::state::connecting) { + if (m_internal_state != istate::READ_HTTP_RESPONSE) { + ecm = error::make_error_code(error::invalid_state); + } + } else if (m_state == session::state::closed) { + // The connection was canceled while the response was being sent, + // usually by the handshake timer. This is basically expected + // (though hopefully rare) and there is nothing we can do so ignore. + m_alog.write(log::alevel::devel, + "handle_read_http_response invoked after connection was closed"); + return; + } else { + ecm = error::make_error_code(error::invalid_state); + } + } + + if (ecm) { + if (ecm == transport::error::eof && m_state == session::state::closed) { + // we expect to get eof if the connection is closed already + m_alog.write(log::alevel::devel, + "got (expected) eof/state error from closed con"); + return; + } + + log_err(log::elevel::rerror,"handle_read_http_response",ecm); + this->terminate(ecm); return; } + size_t bytes_processed = 0; // TODO: refactor this to use error codes rather than exceptions try { @@ -1404,14 +1525,9 @@ void connection::handle_read_http_response(lib::error_code const & ec, return; } - // response is valid, connection can now be assumed to be open - this->atomic_state_change( - istate::READ_HTTP_RESPONSE, - istate::PROCESS_CONNECTION, - session::state::connecting, - session::state::open, - "handle_read_http_response must be called from READ_HTTP_RESPONSE state" - ); + // response is valid, connection can now be assumed to be open + m_internal_state = istate::PROCESS_CONNECTION; + m_state = session::state::open; this->log_open_result(); @@ -1492,9 +1608,16 @@ void connection::terminate(lib::error_code const & ec) { m_local_close_reason = ec.message(); } + // TODO: does this need a mutex? if (m_state == session::state::connecting) { m_state = session::state::closed; tstat = failed; + + // Log fail result here before socket is shut down and we can't get + // the remote address, etc anymore + if (m_ec != error::http_connection_ended) { + log_fail_result(); + } } else if (m_state != session::state::closed) { m_state = session::state::closed; tstat = closed; @@ -1531,10 +1654,11 @@ void connection::handle_terminate(terminate_status tstat, // clean shutdown if (tstat == failed) { - if (m_fail_handler) { - m_fail_handler(m_connection_hdl); + if (m_ec != error::http_connection_ended) { + if (m_fail_handler) { + m_fail_handler(m_connection_hdl); + } } - log_fail_result(); } else if (tstat == closed) { if (m_close_handler) { m_close_handler(m_connection_hdl); @@ -1629,8 +1753,11 @@ void connection::write_frame() { 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()) + << m_current_msgs[i]->get_payload().size() << ") ["<get_opcode()<<"] " + << (m_current_msgs[i]->get_opcode() == frame::opcode::text ? + m_current_msgs[i]->get_payload() : + utility::to_hex(m_current_msgs[i]->get_payload()) + ) << "\n"; } } @@ -1693,45 +1820,7 @@ void connection::handle_write_frame(lib::error_code const & ec) } template -void connection::atomic_state_change(istate_type req, istate_type dest, - std::string msg) -{ - scoped_lock_type lock(m_connection_state_lock); - - if (m_internal_state != req) { - throw exception(msg,error::make_error_code(error::invalid_state)); - } - - m_internal_state = dest; -} - -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) -{ - scoped_lock_type lock(m_connection_state_lock); - - if (m_internal_state != internal_req || m_state != external_req) { - throw exception(msg,error::make_error_code(error::invalid_state)); - } - - m_internal_state = internal_dest; - m_state = external_dest; -} - -template -void connection::atomic_state_check(istate_type req, std::string msg) -{ - scoped_lock_type lock(m_connection_state_lock); - - if (m_internal_state != req) { - throw exception(msg,error::make_error_code(error::invalid_state)); - } -} - -template -const std::vector& connection::get_supported_versions() const +std::vector const & connection::get_supported_versions() const { return versions_supported; } @@ -2108,9 +2197,69 @@ void connection::log_close_result() template void connection::log_fail_result() { - // TODO: include more information about the connection? - // should this be filed under connect rather than disconnect? - m_alog.write(log::alevel::disconnect,"Failed: "+m_ec.message()); + std::stringstream s; + + int version = processor::get_websocket_version(m_request); + + // Connection Type + s << "WebSocket Connection "; + + // Remote endpoint address & WebSocket version + s << transport_con_type::get_remote_endpoint(); + if (version < 0) { + s << " -"; + } else { + s << " v" << version; + } + + // User Agent + std::string ua = m_request.get_header("User-Agent"); + if (ua == "") { + s << " \"\" "; + } else { + // check if there are any quotes in the user agent + s << " \"" << utility::string_replace_all(ua,"\"","\\\"") << "\" "; + } + + // URI + s << (m_uri ? m_uri->get_resource() : "-"); + + // HTTP Status code + s << " " << m_response.get_status_code(); + + // WebSocket++ error code & reason + s << " " << m_ec << " " << m_ec.message(); + + m_alog.write(log::alevel::fail,s.str()); +} + +template +void connection::log_http_result() { + std::stringstream s; + + if (processor::is_websocket_handshake(m_request)) { + m_alog.write(log::alevel::devel,"Call to log_http_result for WebSocket"); + return; + } + + // Connection Type + s << (m_request.get_header("host") == "" ? "-" : m_request.get_header("host")) + << " " << transport_con_type::get_remote_endpoint() + << " \"" << m_request.get_method() + << " " << (m_uri ? m_uri->get_resource() : "-") + << " " << m_request.get_version() << "\" " << m_response.get_status_code() + << " " << m_response.get_body().size(); + + // User Agent + std::string ua = m_request.get_header("User-Agent"); + if (ua == "") { + s << " \"\" "; + } else { + // check if there are any quotes in the user agent + s << " \"" << utility::string_replace_all(ua,"\"","\\\"") << "\" "; + } + + m_alog.write(log::alevel::http,s.str()); } } // namespace websocketpp diff --git a/websocketpp/impl/endpoint_impl.hpp b/websocketpp/impl/endpoint_impl.hpp index ca2a47e9f..5895199a0 100644 --- a/websocketpp/impl/endpoint_impl.hpp +++ b/websocketpp/impl/endpoint_impl.hpp @@ -28,6 +28,8 @@ #ifndef WEBSOCKETPP_ENDPOINT_IMPL_HPP #define WEBSOCKETPP_ENDPOINT_IMPL_HPP +#include + namespace websocketpp { template @@ -77,6 +79,7 @@ endpoint::create_connection() { if (m_max_message_size != config::max_message_size) { con->set_max_message_size(m_max_message_size); } + con->set_max_http_body_size(m_max_http_body_size); lib::error_code ec; diff --git a/websocketpp/impl/utilities_impl.hpp b/websocketpp/impl/utilities_impl.hpp index 3682a5013..6f86e22f5 100644 --- a/websocketpp/impl/utilities_impl.hpp +++ b/websocketpp/impl/utilities_impl.hpp @@ -28,6 +28,9 @@ #ifndef WEBSOCKETPP_UTILITIES_IMPL_HPP #define WEBSOCKETPP_UTILITIES_IMPL_HPP +#include +#include + namespace websocketpp { namespace utility { @@ -37,7 +40,7 @@ inline std::string to_lower(std::string const & in) { return out; } -inline std::string to_hex(const std::string& input) { +inline std::string to_hex(std::string const & input) { std::string output; std::string hex = "0123456789ABCDEF"; @@ -50,7 +53,7 @@ inline std::string to_hex(const std::string& input) { return output; } -inline std::string to_hex(const uint8_t* input,size_t length) { +inline std::string to_hex(uint8_t const * input, size_t length) { std::string output; std::string hex = "0123456789ABCDEF"; @@ -67,8 +70,8 @@ inline std::string to_hex(const char* input,size_t length) { return to_hex(reinterpret_cast(input),length); } -inline std::string string_replace_all(std::string subject, const std::string& - search, const std::string& replace) +inline std::string string_replace_all(std::string subject, std::string const & + search, std::string const & replace) { size_t pos = 0; while((pos = subject.find(search, pos)) != std::string::npos) { diff --git a/websocketpp/logger/basic.hpp b/websocketpp/logger/basic.hpp index 1005774c0..95099a834 100644 --- a/websocketpp/logger/basic.hpp +++ b/websocketpp/logger/basic.hpp @@ -40,14 +40,16 @@ * */ -#include -#include -#include +#include #include #include #include -#include + +#include +#include +#include +#include namespace websocketpp { namespace log { @@ -56,23 +58,23 @@ namespace log { template class basic { public: - basic(channel_type_hint::value h = + basic(channel_type_hint::value h = channel_type_hint::access) : m_static_channels(0xffffffff) , m_dynamic_channels(0) , m_out(h == channel_type_hint::error ? &std::cerr : &std::cout) {} - + basic(std::ostream * out) : m_static_channels(0xffffffff) , m_dynamic_channels(0) , m_out(out) {} - basic(level c, channel_type_hint::value h = + basic(level c, channel_type_hint::value h = channel_type_hint::access) : m_static_channels(c) , m_dynamic_channels(0) , m_out(h == channel_type_hint::error ? &std::cerr : &std::cout) {} - + basic(level c, std::ostream * out) : m_static_channels(c) , m_dynamic_channels(0) @@ -97,6 +99,11 @@ class basic { m_dynamic_channels &= ~channels; } + /// Write a string message to the given channel + /** + * @param channel The channel to write to + * @param msg The message to write + */ void write(level channel, std::string const & msg) { scoped_lock_type lock(m_lock); if (!this->dynamic_test(channel)) { return; } @@ -106,6 +113,11 @@ class basic { m_out->flush(); } + /// Write a cstring message to the given channel + /** + * @param channel The channel to write to + * @param msg The message to write + */ void write(level channel, char const * msg) { scoped_lock_type lock(m_lock); if (!this->dynamic_test(channel)) { return; } @@ -122,10 +134,13 @@ class basic { bool dynamic_test(level channel) { return ((channel & m_dynamic_channels) != 0); } -private: + +protected: typedef typename concurrency::scoped_lock_type scoped_lock_type; typedef typename concurrency::mutex_type mutex_type; + mutex_type m_lock; +private: // The timestamp does not include the time zone, because on Windows with the // default registry settings, the time zone would be written out in full, // which would be obnoxiously verbose. @@ -143,8 +158,6 @@ class basic { #endif } - mutex_type m_lock; - level const m_static_channels; level m_dynamic_channels; std::ostream * m_out; diff --git a/websocketpp/logger/levels.hpp b/websocketpp/logger/levels.hpp index a774aadfb..cd7ccd690 100644 --- a/websocketpp/logger/levels.hpp +++ b/websocketpp/logger/levels.hpp @@ -141,6 +141,13 @@ struct alevel { static level const devel = 0x400; /// Special channel for application specific logs. Not used by the library. static level const app = 0x800; + /// Access related to HTTP requests + static level const http = 0x1000; + /// One line for each failed WebSocket connection with details + static level const fail = 0x2000; + /// Aggregate package representing the commonly used core access channels + /// Connect, Disconnect, Fail, and HTTP + static level const access_core = 0x00003003; /// Special aggregate value representing "all levels" static level const all = 0xffffffff; @@ -180,6 +187,10 @@ struct alevel { return "devel"; case app: return "application"; + case http: + return "http"; + case fail: + return "fail"; default: return "unknown"; } diff --git a/websocketpp/logger/stub.hpp b/websocketpp/logger/stub.hpp index d9ba6ff92..2db6da7df 100644 --- a/websocketpp/logger/stub.hpp +++ b/websocketpp/logger/stub.hpp @@ -28,10 +28,11 @@ #ifndef WEBSOCKETPP_LOGGER_STUB_HPP #define WEBSOCKETPP_LOGGER_STUB_HPP -#include +#include #include -#include + +#include namespace websocketpp { namespace log { @@ -44,7 +45,7 @@ class stub { * @param hint A channel type specific hint for how to construct the logger */ explicit stub(channel_type_hint::value) {} - + /// Construct the logger /** * @param default_channels A set of channels to statically enable @@ -55,16 +56,16 @@ class stub { /// Dynamically enable the given list of channels /** - * All operations on the stub logger are no-ops and all arguments are + * All operations on the stub logger are no-ops and all arguments are * ignored * * @param channels The package of channels to enable */ void set_channels(level) {} - + /// Dynamically disable the given list of channels /** - * All operations on the stub logger are no-ops and all arguments are + * All operations on the stub logger are no-ops and all arguments are * ignored * * @param channels The package of channels to disable @@ -75,23 +76,23 @@ class stub { /** * Writing on the stub logger is a no-op and all arguments are ignored * - * @param channel The package of channels to write to + * @param channel The channel to write to * @param msg The message to write */ void write(level, std::string const &) {} - + /// Write a cstring message to the given channel /** * Writing on the stub logger is a no-op and all arguments are ignored * - * @param channel The package of channels to write to + * @param channel The channel to write to * @param msg The message to write */ void write(level, char const *) {} /// Test whether a channel is statically enabled /** - * The stub logger has no channels so all arguments are ignored and + * The stub logger has no channels so all arguments are ignored and * `static_test` always returns false. * * @param channel The package of channels to test @@ -99,10 +100,10 @@ class stub { _WEBSOCKETPP_CONSTEXPR_TOKEN_ bool static_test(level) const { return false; } - + /// Test whether a channel is dynamically enabled /** - * The stub logger has no channels so all arguments are ignored and + * The stub logger has no channels so all arguments are ignored and * `dynamic_test` always returns false. * * @param channel The package of channels to test diff --git a/websocketpp/logger/syslog.hpp b/websocketpp/logger/syslog.hpp new file mode 100644 index 000000000..513abee4a --- /dev/null +++ b/websocketpp/logger/syslog.hpp @@ -0,0 +1,146 @@ +/* + * 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. + * + * + * The initial version of this logging policy was contributed to the WebSocket++ + * project by Tom Hughes. + */ + +#ifndef WEBSOCKETPP_LOGGER_SYSLOG_HPP +#define WEBSOCKETPP_LOGGER_SYSLOG_HPP + +#include + +#include + +#include +#include + +namespace websocketpp { +namespace log { + +/// Basic logger that outputs to syslog +template +class syslog : public basic { +public: + typedef basic base; + + /// Construct the logger + /** + * @param hint A channel type specific hint for how to construct the logger + */ + syslog(channel_type_hint::value hint = + channel_type_hint::access) + : basic(hint), m_channel_type_hint(hint) {} + + /// Construct the logger + /** + * @param channels A set of channels to statically enable + * @param hint A channel type specific hint for how to construct the logger + */ + syslog(level channels, channel_type_hint::value hint = + channel_type_hint::access) + : basic(channels, hint), m_channel_type_hint(hint) {} + + /// Write a string message to the given channel + /** + * @param channel The channel to write to + * @param msg The message to write + */ + void write(level channel, std::string const & msg) { + write(channel, msg.c_str()); + } + + /// Write a cstring message to the given channel + /** + * @param channel The channel to write to + * @param msg The message to write + */ + void write(level channel, char const * msg) { + scoped_lock_type lock(base::m_lock); + if (!this->dynamic_test(channel)) { return; } + ::syslog(syslog_priority(channel), "[%s] %s", + names::channel_name(channel), msg); + } +private: + typedef typename base::scoped_lock_type scoped_lock_type; + + /// The default level is used for all access logs and any error logs that + /// don't trivially map to one of the standard syslog levels. + static int const default_level = LOG_INFO; + + /// retrieve the syslog priority code given a WebSocket++ channel + /** + * @param channel The level to look up + * @return The syslog level associated with `channel` + */ + int syslog_priority(level channel) const { + if (m_channel_type_hint == channel_type_hint::access) { + return syslog_priority_access(channel); + } else { + return syslog_priority_error(channel); + } + } + + /// retrieve the syslog priority code given a WebSocket++ error channel + /** + * @param channel The level to look up + * @return The syslog level associated with `channel` + */ + int syslog_priority_error(level channel) const { + switch (channel) { + case elevel::devel: + return LOG_DEBUG; + case elevel::library: + return LOG_DEBUG; + case elevel::info: + return LOG_INFO; + case elevel::warn: + return LOG_WARNING; + case elevel::rerror: + return LOG_ERR; + case elevel::fatal: + return LOG_CRIT; + default: + return default_level; + } + } + + /// retrieve the syslog priority code given a WebSocket++ access channel + /** + * @param channel The level to look up + * @return The syslog level associated with `channel` + */ + _WEBSOCKETPP_CONSTEXPR_TOKEN_ int syslog_priority_access(level) const { + return default_level; + } + + channel_type_hint::value m_channel_type_hint; +}; + +} // log +} // websocketpp + +#endif // WEBSOCKETPP_LOGGER_SYSLOG_HPP diff --git a/websocketpp/message_buffer/message.hpp b/websocketpp/message_buffer/message.hpp index fd70d6bd8..b127ee254 100644 --- a/websocketpp/message_buffer/message.hpp +++ b/websocketpp/message_buffer/message.hpp @@ -43,7 +43,7 @@ namespace message_buffer { * * # connection_message_manager: * An object that manages all of the message_buffers associated with a given - * connection. Impliments the get_message_buffer(size) method that returns + * connection. Implements the get_message_buffer(size) method that returns * a message buffer at least size bytes long. * * Message buffers are reference counted with shared ownership semantics. Once @@ -54,7 +54,7 @@ namespace message_buffer { * in the manager. * * # endpoint_message_manager: - * An object that manages connection_message_managers. Impliments the + * An object that manages connection_message_managers. Implements the * get_message_manager() method. This is used once by each connection to * request the message manager that they are supposed to use to manage message * buffers for their own use. diff --git a/websocketpp/message_buffer/pool.hpp b/websocketpp/message_buffer/pool.hpp index 7b82fd21c..3af9e02a2 100644 --- a/websocketpp/message_buffer/pool.hpp +++ b/websocketpp/message_buffer/pool.hpp @@ -42,7 +42,7 @@ namespace message_buffer { * * # connection_message_manager: * An object that manages all of the message_buffers associated with a given - * connection. Impliments the get_message_buffer(size) method that returns + * connection. Implements the get_message_buffer(size) method that returns * a message buffer at least size bytes long. * * Message buffers are reference counted with shared ownership semantics. Once @@ -53,7 +53,7 @@ namespace message_buffer { * in the manager. * * # endpoint_message_manager: - * An object that manages connection_message_managers. Impliments the + * An object that manages connection_message_managers. Implements the * get_message_manager() method. This is used once by each connection to * request the message manager that they are supposed to use to manage message * buffers for their own use. diff --git a/websocketpp/processors/base.hpp b/websocketpp/processors/base.hpp index 66cfc6aac..ddb8b81a4 100644 --- a/websocketpp/processors/base.hpp +++ b/websocketpp/processors/base.hpp @@ -28,14 +28,13 @@ #ifndef WEBSOCKETPP_PROCESSOR_BASE_HPP #define WEBSOCKETPP_PROCESSOR_BASE_HPP -#include -#include - #include #include #include -#include +#include +#include + #include namespace websocketpp { @@ -151,7 +150,11 @@ enum processor_errors { extension_parse_error, /// Extension related operation was ignored because extensions are disabled - extensions_disabled + extensions_disabled, + + /// Short Ke3 read. Hybi00 requires a third key to be read from the 8 bytes + /// after the handshake. Less than 8 bytes were read. + short_key3 }; /// Category for processor errors @@ -223,6 +226,8 @@ class processor_category : public lib::error_category { return "Error parsing extension header"; case error::extensions_disabled: return "Extensions are disabled"; + case error::short_key3: + return "Short Hybi00 Key 3 read"; default: return "Unknown"; } diff --git a/websocketpp/processors/hybi00.hpp b/websocketpp/processors/hybi00.hpp index 704f82661..991813922 100644 --- a/websocketpp/processors/hybi00.hpp +++ b/websocketpp/processors/hybi00.hpp @@ -28,8 +28,6 @@ #ifndef WEBSOCKETPP_PROCESSOR_HYBI00_HPP #define WEBSOCKETPP_PROCESSOR_HYBI00_HPP -#include - #include #include #include @@ -38,6 +36,11 @@ #include +#include +#include +#include +#include + namespace websocketpp { namespace processor { @@ -108,7 +111,7 @@ class hybi00 : public processor { // if it is less the final key will almost certainly be wrong. // TODO: decide if it is best to silently fail here or produce some sort // of warning or exception. - const std::string& key3 = req.get_header("Sec-WebSocket-Key3"); + std::string const & key3 = req.get_header("Sec-WebSocket-Key3"); std::copy(key3.c_str(), key3.c_str()+(std::min)(static_cast(8), key3.size()), &key_final[8]); @@ -364,7 +367,7 @@ class hybi00 : public processor { * @param out The message buffer to prepare the pong in. * @return Status code, zero on success, non-zero on failure */ - lib::error_code prepare_pong(const std::string &, message_ptr) const + lib::error_code prepare_pong(std::string const &, message_ptr) const { return lib::error_code(error::no_protocol_support); } diff --git a/websocketpp/processors/hybi07.hpp b/websocketpp/processors/hybi07.hpp index 59b6f5596..14b67c21a 100644 --- a/websocketpp/processors/hybi07.hpp +++ b/websocketpp/processors/hybi07.hpp @@ -30,6 +30,9 @@ #include +#include +#include + namespace websocketpp { namespace processor { diff --git a/websocketpp/processors/hybi08.hpp b/websocketpp/processors/hybi08.hpp index 7e9337d82..15f6e6586 100644 --- a/websocketpp/processors/hybi08.hpp +++ b/websocketpp/processors/hybi08.hpp @@ -30,6 +30,9 @@ #include +#include +#include + namespace websocketpp { namespace processor { @@ -68,7 +71,7 @@ class hybi08 : public hybi13 { return 8; } - const std::string& get_origin(request_type const & r) const { + std::string const & get_origin(request_type const & r) const { return r.get_header("Sec-WebSocket-Origin"); } private: diff --git a/websocketpp/processors/hybi13.hpp b/websocketpp/processors/hybi13.hpp index cb98286c5..51ce3403d 100644 --- a/websocketpp/processors/hybi13.hpp +++ b/websocketpp/processors/hybi13.hpp @@ -28,19 +28,20 @@ #ifndef WEBSOCKETPP_PROCESSOR_HYBI13_HPP #define WEBSOCKETPP_PROCESSOR_HYBI13_HPP -#include +#include #include -#include -#include -#include #include -#include - +#include #include #include +#include +#include + +#include +#include #include #include #include @@ -160,8 +161,8 @@ class hybi13 : public processor { * generic struct if other user input parameters to the processed handshake * are found. */ - lib::error_code process_handshake(request_type const & request, const - std::string & subprotocol, response_type& response) const + lib::error_code process_handshake(request_type const & request, + std::string const & subprotocol, response_type & response) const { std::string server_key = request.get_header("Sec-WebSocket-Key"); @@ -588,6 +589,7 @@ class hybi13 : public processor { } out->set_prepared(true); + out->set_opcode(op); return lib::error_code(); } diff --git a/websocketpp/processors/processor.hpp b/websocketpp/processors/processor.hpp index dc174a49a..99b64cae8 100644 --- a/websocketpp/processors/processor.hpp +++ b/websocketpp/processors/processor.hpp @@ -35,8 +35,10 @@ #include #include -#include +#include #include +#include +#include namespace websocketpp { /// Processors encapsulate the protocol rules specific to each WebSocket version @@ -103,6 +105,10 @@ bool is_websocket_handshake(request_type& r) { */ template int get_websocket_version(request_type& r) { + if (!r.ready()) { + return -2; + } + if (r.get_header("Sec-WebSocket-Version") == "") { return 0; } diff --git a/websocketpp/roles/client_endpoint.hpp b/websocketpp/roles/client_endpoint.hpp index 22c7da106..d5a9f0038 100644 --- a/websocketpp/roles/client_endpoint.hpp +++ b/websocketpp/roles/client_endpoint.hpp @@ -29,12 +29,14 @@ #define WEBSOCKETPP_CLIENT_ENDPOINT_HPP #include + #include -#include +#include -namespace websocketpp { +#include +namespace websocketpp { /// Client endpoint role based on the given config /** diff --git a/websocketpp/roles/server_endpoint.hpp b/websocketpp/roles/server_endpoint.hpp index e92cbf0ae..45a4bef9e 100644 --- a/websocketpp/roles/server_endpoint.hpp +++ b/websocketpp/roles/server_endpoint.hpp @@ -29,13 +29,13 @@ #define WEBSOCKETPP_SERVER_ENDPOINT_HPP #include + #include -#include +#include namespace websocketpp { - /// Server endpoint role based on the given config /** * diff --git a/websocketpp/transport/asio/connection.hpp b/websocketpp/transport/asio/connection.hpp index f4341019f..0be40f6b4 100644 --- a/websocketpp/transport/asio/connection.hpp +++ b/websocketpp/transport/asio/connection.hpp @@ -28,22 +28,27 @@ #ifndef WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP #define WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP -#include -#include -#include -#include -#include -#include #include + #include +#include +#include + #include #include +#include +#include +#include +#include + #include #include +#include #include +#include #include namespace websocketpp { @@ -243,7 +248,7 @@ class connection : public config::socket_type::socket_con_type { if (ec) { throw exception(ec); } } - const std::string & get_proxy() const { + std::string const & get_proxy() const { return m_proxy; } @@ -899,7 +904,7 @@ class connection : public config::socket_type::socket_con_type { ); } - void async_write(const std::vector& bufs, write_handler handler) { + void async_write(std::vector const & bufs, write_handler handler) { if (!m_async_write_handler) { m_alog.write(log::alevel::devel, "async_write (vector) called after async_shutdown"); diff --git a/websocketpp/transport/asio/endpoint.hpp b/websocketpp/transport/asio/endpoint.hpp index 9ce58256b..ef3b07db4 100644 --- a/websocketpp/transport/asio/endpoint.hpp +++ b/websocketpp/transport/asio/endpoint.hpp @@ -28,16 +28,22 @@ #ifndef WEBSOCKETPP_TRANSPORT_ASIO_HPP #define WEBSOCKETPP_TRANSPORT_ASIO_HPP -#include -#include #include #include #include +#include +#include + +#include + #include #include #include +#include +#include + namespace websocketpp { namespace transport { namespace asio { @@ -179,7 +185,7 @@ class endpoint : public config::socket_type { m_external_io_service = true; m_acceptor = lib::make_shared( lib::ref(*m_io_service)); - + m_state = READY; ec = lib::error_code(); } @@ -289,10 +295,10 @@ class endpoint : public config::socket_type { void set_listen_backlog(int backlog) { m_listen_backlog = backlog; } - + /// Sets whether to use the SO_REUSEADDR flag when opening listening sockets /** - * Specifies whether or not to use the SO_REUSEADDR TCP socket option. What + * 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. * @@ -356,6 +362,9 @@ class endpoint : public config::socket_type { m_acceptor->listen(m_listen_backlog,bec); } if (bec) { + if (m_acceptor->is_open()) { + m_acceptor->close(); + } log_err(log::elevel::info,"asio listen",bec); ec = make_error_code(error::pass_through); } else { diff --git a/websocketpp/transport/asio/security/base.hpp b/websocketpp/transport/asio/security/base.hpp index 0b4941704..543a3533c 100644 --- a/websocketpp/transport/asio/security/base.hpp +++ b/websocketpp/transport/asio/security/base.hpp @@ -97,14 +97,14 @@ namespace error { missing_tls_init_handler, /// TLS Handshake Failed - tls_handshake_failed, + tls_handshake_failed }; } // namespace error /// Error category related to asio transport socket policies class socket_category : public lib::error_category { public: - const char *name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { + char const * name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { return "websocketpp.transport.asio.socket"; } @@ -132,7 +132,7 @@ class socket_category : public lib::error_category { } }; -inline const lib::error_category& get_socket_category() { +inline lib::error_category const & get_socket_category() { static socket_category instance; return instance; } diff --git a/websocketpp/transport/asio/security/none.hpp b/websocketpp/transport/asio/security/none.hpp index 17ddbf6fe..14b6f8b5f 100644 --- a/websocketpp/transport/asio/security/none.hpp +++ b/websocketpp/transport/asio/security/none.hpp @@ -28,11 +28,13 @@ #ifndef WEBSOCKETPP_TRANSPORT_SECURITY_NONE_HPP #define WEBSOCKETPP_TRANSPORT_SECURITY_NONE_HPP -#include #include +#include + #include +#include #include namespace websocketpp { diff --git a/websocketpp/transport/asio/security/tls.hpp b/websocketpp/transport/asio/security/tls.hpp index 68f4198eb..8434a5c3d 100644 --- a/websocketpp/transport/asio/security/tls.hpp +++ b/websocketpp/transport/asio/security/tls.hpp @@ -29,6 +29,7 @@ #define WEBSOCKETPP_TRANSPORT_SECURITY_TLS_HPP #include + #include #include #include @@ -37,6 +38,7 @@ #include #include +#include #include namespace websocketpp { diff --git a/websocketpp/transport/base/connection.hpp b/websocketpp/transport/base/connection.hpp index 774c60d08..f76d40913 100644 --- a/websocketpp/transport/base/connection.hpp +++ b/websocketpp/transport/base/connection.hpp @@ -31,9 +31,10 @@ #include #include #include -#include #include +#include + namespace websocketpp { /// Transport policies provide network connectivity and timers /** @@ -176,7 +177,7 @@ enum value { action_after_shutdown, /// Other TLS error - tls_error, + tls_error }; class category : public lib::error_category { diff --git a/websocketpp/transport/base/endpoint.hpp b/websocketpp/transport/base/endpoint.hpp index 43bbdf46f..3b4b0d6db 100644 --- a/websocketpp/transport/base/endpoint.hpp +++ b/websocketpp/transport/base/endpoint.hpp @@ -28,13 +28,8 @@ #ifndef WEBSOCKETPP_TRANSPORT_BASE_HPP #define WEBSOCKETPP_TRANSPORT_BASE_HPP -#include -#include #include #include -#include - -#include namespace websocketpp { /// Transport policies provide network connectivity and timers diff --git a/websocketpp/transport/debug/base.hpp b/websocketpp/transport/debug/base.hpp new file mode 100644 index 000000000..2e477b501 --- /dev/null +++ b/websocketpp/transport/debug/base.hpp @@ -0,0 +1,104 @@ +/* + * 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. + * + */ + +#ifndef WEBSOCKETPP_TRANSPORT_DEBUG_BASE_HPP +#define WEBSOCKETPP_TRANSPORT_DEBUG_BASE_HPP + +#include +#include + +#include + +namespace websocketpp { +namespace transport { +/// Debug transport policy that is used for various mocking and stubbing duties +/// in unit tests. +namespace debug { + +/// debug transport errors +namespace error { +enum value { + /// Catch-all error for transport policy errors that don't fit in other + /// categories + general = 1, + + /// not implemented + not_implemented, + + invalid_num_bytes, + + double_read +}; + +/// debug transport error category +class category : public lib::error_category { + public: + category() {} + + char const * name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { + return "websocketpp.transport.debug"; + } + + std::string message(int value) const { + switch(value) { + case general: + return "Generic stub transport policy error"; + case not_implemented: + return "feature not implemented"; + case invalid_num_bytes: + return "Invalid number of bytes"; + case double_read: + return "Read while another read was outstanding"; + default: + return "Unknown"; + } + } +}; + +/// Get a reference to a static copy of the debug transport error category +inline lib::error_category const & get_category() { + static category instance; + return instance; +} + +/// Get an error code with the given value and the debug transport category +inline lib::error_code make_error_code(error::value e) { + return lib::error_code(static_cast(e), get_category()); +} + +} // namespace error +} // namespace debug +} // namespace transport +} // namespace websocketpp +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_ +template<> struct is_error_code_enum +{ + static bool const value = true; +}; +_WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_ + +#endif // WEBSOCKETPP_TRANSPORT_DEBUG_BASE_HPP diff --git a/websocketpp/transport/debug/connection.hpp b/websocketpp/transport/debug/connection.hpp new file mode 100644 index 000000000..7b8c29cbd --- /dev/null +++ b/websocketpp/transport/debug/connection.hpp @@ -0,0 +1,397 @@ +/* + * 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. + * + */ + +#ifndef WEBSOCKETPP_TRANSPORT_DEBUG_CON_HPP +#define WEBSOCKETPP_TRANSPORT_DEBUG_CON_HPP + +#include + +#include + +#include + +#include +#include +#include + +#include +#include + +namespace websocketpp { +namespace transport { +namespace debug { + +/// Empty timer class to stub out for timer functionality that stub +/// transport doesn't support +struct timer { + void cancel() {} +}; + +template +class connection : public lib::enable_shared_from_this< connection > { +public: + /// Type of this connection transport component + typedef connection type; + /// Type of a shared pointer to this connection transport component + typedef lib::shared_ptr ptr; + + /// transport concurrency policy + typedef typename config::concurrency_type concurrency_type; + /// Type of this transport's access logging policy + typedef typename config::alog_type alog_type; + /// Type of this transport's error logging policy + typedef typename config::elog_type elog_type; + + // Concurrency policy types + typedef typename concurrency_type::scoped_lock_type scoped_lock_type; + typedef typename concurrency_type::mutex_type mutex_type; + + typedef lib::shared_ptr timer_ptr; + + explicit connection(bool is_server, alog_type & alog, elog_type & elog) + : m_reading(false), m_is_server(is_server), m_alog(alog), m_elog(elog) + { + m_alog.write(log::alevel::devel,"debug con transport constructor"); + } + + /// Get a shared pointer to this component + ptr get_shared() { + return type::shared_from_this(); + } + + /// Set whether or not this connection is secure + /** + * Todo: docs + * + * @since 0.3.0-alpha4 + * + * @param value Whether or not this connection is secure. + */ + void set_secure(bool) {} + + /// Tests whether or not the underlying transport is secure + /** + * TODO: docs + * + * @return Whether or not the underlying transport is secure + */ + bool is_secure() const { + return false; + } + + /// Set human readable remote endpoint address + /** + * Sets the remote endpoint address returned by `get_remote_endpoint`. This + * value should be a human readable string that describes the remote + * endpoint. Typically an IP address or hostname, perhaps with a port. But + * may be something else depending on the nature of the underlying + * transport. + * + * If none is set a default is returned. + * + * @since 0.3.0-alpha4 + * + * @param value The remote endpoint address to set. + */ + void set_remote_endpoint(std::string) {} + + /// Get human readable remote endpoint address + /** + * TODO: docs + * + * This value is used in access and error logs and is available to the end + * application for including in user facing interfaces and messages. + * + * @return A string identifying the address of the remote endpoint + */ + std::string get_remote_endpoint() const { + return "unknown (debug transport)"; + } + + /// Get the connection handle + /** + * @return The handle for this connection. + */ + connection_hdl get_handle() const { + return connection_hdl(); + } + + /// Call back a function after a period of time. + /** + * Timers are not implemented in this transport. The timer pointer will + * always be empty. The handler will never be called. + * + * @param duration Length of time to wait in milliseconds + * @param callback The function to call back when the timer has expired + * @return A handle that can be used to cancel the timer if it is no longer + * needed. + */ + timer_ptr set_timer(long, timer_handler handler) { + m_alog.write(log::alevel::devel,"debug connection set timer"); + m_timer_handler = handler; + return timer_ptr(); + } + + /// Manual input supply (read all) + /** + * Similar to read_some, but continues to read until all bytes in the + * supplied buffer have been read or the connection runs out of read + * requests. + * + * This method still may not read all of the bytes in the input buffer. if + * it doesn't it indicates that the connection was most likely closed or + * is in an error state where it is no longer accepting new input. + * + * @since 0.3.0 + * + * @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 read_all(char const * buf, size_t len) { + size_t total_read = 0; + size_t temp_read = 0; + + do { + temp_read = this->read_some_impl(buf+total_read,len-total_read); + total_read += temp_read; + } while (temp_read != 0 && total_read < len); + + return total_read; + } + + // debug stuff to invoke the async handlers + void expire_timer(lib::error_code const & ec) { + m_timer_handler(ec); + } + + void fullfil_write() { + m_write_handler(lib::error_code()); + } +protected: + /// Initialize the connection transport + /** + * Initialize the connection's transport component. + * + * @param handler The `init_handler` to call when initialization is done + */ + void init(init_handler handler) { + m_alog.write(log::alevel::devel,"debug connection init"); + handler(lib::error_code()); + } + + /// Initiate an async_read for at least num_bytes bytes into buf + /** + * Initiates an async_read request for at least num_bytes bytes. The input + * will be read into buf. A maximum of len bytes will be input. When the + * operation is complete, handler will be called with the status and number + * of bytes read. + * + * This method may or may not call handler from within the initial call. The + * application should be prepared to accept either. + * + * The application should never call this method a second time before it has + * been called back for the first read. If this is done, the second read + * will be called back immediately with a double_read error. + * + * If num_bytes or len are zero handler will be called back immediately + * indicating success. + * + * @param num_bytes Don't call handler until at least this many bytes have + * been read. + * @param buf The buffer to read bytes into + * @param len The size of buf. At maximum, this many bytes will be read. + * @param handler The callback to invoke when the operation is complete or + * ends in an error + */ + void async_read_at_least(size_t num_bytes, char * buf, size_t len, + read_handler handler) + { + std::stringstream s; + s << "debug_con async_read_at_least: " << num_bytes; + m_alog.write(log::alevel::devel,s.str()); + + if (num_bytes > len) { + handler(make_error_code(error::invalid_num_bytes),size_t(0)); + return; + } + + if (m_reading == true) { + handler(make_error_code(error::double_read),size_t(0)); + return; + } + + if (num_bytes == 0 || len == 0) { + handler(lib::error_code(),size_t(0)); + return; + } + + m_buf = buf; + m_len = len; + m_bytes_needed = num_bytes; + m_read_handler = handler; + m_cursor = 0; + m_reading = true; + } + + /// Asyncronous Transport Write + /** + * Write len bytes in buf to the output stream. Call handler to report + * success or failure. handler may or may not be called during async_write, + * but it must be safe for this to happen. + * + * Will return 0 on success. + * + * @param buf buffer to read bytes from + * @param len number of bytes to write + * @param handler Callback to invoke with operation status. + */ + void async_write(char const *, size_t, write_handler handler) { + m_alog.write(log::alevel::devel,"debug_con async_write"); + m_write_handler = handler; + } + + /// Asyncronous Transport Write (scatter-gather) + /** + * Write a sequence of buffers to the output stream. Call handler to report + * success or failure. handler may or may not be called during async_write, + * but it must be safe for this to happen. + * + * Will return 0 on success. + * + * @param bufs vector of buffers to write + * @param handler Callback to invoke with operation status. + */ + void async_write(std::vector const &, write_handler handler) { + m_alog.write(log::alevel::devel,"debug_con async_write buffer list"); + m_write_handler = handler; + } + + /// Set Connection Handle + /** + * @param hdl The new handle + */ + void set_handle(connection_hdl) {} + + /// Call given handler back within the transport's event system (if present) + /** + * Invoke a callback within the transport's event system if it has one. If + * it doesn't, the handler will be invoked immediately before this function + * returns. + * + * @param handler The callback to invoke + * + * @return Whether or not the transport was able to register the handler for + * callback. + */ + lib::error_code dispatch(dispatch_handler handler) { + handler(); + return lib::error_code(); + } + + /// Perform cleanup on socket shutdown_handler + /** + * @param h The `shutdown_handler` to call back when complete + */ + void async_shutdown(shutdown_handler handler) { + handler(lib::error_code()); + } + + size_t read_some_impl(char const * buf, size_t len) { + m_alog.write(log::alevel::devel,"debug_con read_some"); + + if (!m_reading) { + m_elog.write(log::elevel::devel,"write while not reading"); + return 0; + } + + size_t bytes_to_copy = (std::min)(len,m_len-m_cursor); + + std::copy(buf,buf+bytes_to_copy,m_buf+m_cursor); + + m_cursor += bytes_to_copy; + + if (m_cursor >= m_bytes_needed) { + 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); + } +private: + timer_handler m_timer_handler; + + // Read space (Protected by m_read_mutex) + char * m_buf; + size_t m_len; + size_t m_bytes_needed; + read_handler m_read_handler; + size_t m_cursor; + + // transport resources + connection_hdl m_connection_hdl; + write_handler m_write_handler; + shutdown_handler m_shutdown_handler; + + bool m_reading; + bool const m_is_server; + bool m_is_secure; + alog_type & m_alog; + elog_type & m_elog; + std::string m_remote_endpoint; +}; + + +} // namespace debug +} // namespace transport +} // namespace websocketpp + +#endif // WEBSOCKETPP_TRANSPORT_DEBUG_CON_HPP diff --git a/websocketpp/transport/debug/endpoint.hpp b/websocketpp/transport/debug/endpoint.hpp new file mode 100644 index 000000000..1cca70c59 --- /dev/null +++ b/websocketpp/transport/debug/endpoint.hpp @@ -0,0 +1,140 @@ +/* + * 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. + * + */ + +#ifndef WEBSOCKETPP_TRANSPORT_DEBUG_HPP +#define WEBSOCKETPP_TRANSPORT_DEBUG_HPP + +#include +#include + +#include +#include + +namespace websocketpp { +namespace transport { +namespace debug { + +template +class endpoint { +public: + /// Type of this endpoint transport component + typedef endpoint type; + /// Type of a pointer to this endpoint transport component + typedef lib::shared_ptr ptr; + + /// Type of this endpoint's concurrency policy + typedef typename config::concurrency_type concurrency_type; + /// Type of this endpoint's error logging policy + typedef typename config::elog_type elog_type; + /// Type of this endpoint's access logging policy + typedef typename config::alog_type alog_type; + + /// Type of this endpoint transport component's associated connection + /// transport component. + typedef debug::connection transport_con_type; + /// Type of a shared pointer to this endpoint transport component's + /// associated connection transport component + typedef typename transport_con_type::ptr transport_con_ptr; + + // generate and manage our own io_service + explicit endpoint() + { + //std::cout << "transport::iostream::endpoint constructor" << std::endl; + } + + /// Set whether or not endpoint can create secure connections + /** + * TODO: docs + * + * Setting this value only indicates whether or not the endpoint is capable + * of producing and managing secure connections. Connections produced by + * this endpoint must also be individually flagged as secure if they are. + * + * @since 0.3.0-alpha4 + * + * @param value Whether or not the endpoint can create secure connections. + */ + void set_secure(bool) {} + + /// Tests whether or not the underlying transport is secure + /** + * TODO: docs + * + * @return Whether or not the underlying transport is secure + */ + bool is_secure() const { + return false; + } +protected: + /// Initialize logging + /** + * The loggers are located in the main endpoint class. As such, the + * transport doesn't have direct access to them. This method is called + * by the endpoint constructor to allow shared logging from the transport + * component. These are raw pointers to member variables of the endpoint. + * In particular, they cannot be used in the transport constructor as they + * haven't been constructed yet, and cannot be used in the transport + * destructor as they will have been destroyed by then. + * + * @param a A pointer to the access logger to use. + * @param e A pointer to the error logger to use. + */ + void init_logging(alog_type *, elog_type *) {} + + /// Initiate a new connection + /** + * @param tcon A pointer to the transport connection component of the + * connection to connect. + * @param u A URI pointer to the URI to connect to. + * @param cb The function to call back with the results when complete. + */ + void async_connect(transport_con_ptr, uri_ptr, connect_handler cb) { + cb(lib::error_code()); + } + + /// Initialize a connection + /** + * Init is called by an endpoint once for each newly created connection. + * It's purpose is to give the transport policy the chance to perform any + * transport specific initialization that couldn't be done via the default + * constructor. + * + * @param tcon A pointer to the transport portion of the connection. + * @return A status code indicating the success or failure of the operation + */ + lib::error_code init(transport_con_ptr) { + return lib::error_code(); + } +private: + +}; + +} // namespace debug +} // namespace transport +} // namespace websocketpp + +#endif // WEBSOCKETPP_TRANSPORT_DEBUG_HPP diff --git a/websocketpp/transport/iostream/base.hpp b/websocketpp/transport/iostream/base.hpp index a80169fea..0103856a9 100644 --- a/websocketpp/transport/iostream/base.hpp +++ b/websocketpp/transport/iostream/base.hpp @@ -30,6 +30,8 @@ #include #include +#include +#include #include @@ -38,6 +40,14 @@ namespace transport { /// Transport policy that uses STL iostream for I/O and does not support timers namespace iostream { +/// The type and signature of the callback used by iostream transport to write +typedef lib::function + write_handler; + +/// The type and signature of the callback used by iostream transport to signal +/// a transport shutdown. +typedef lib::function shutdown_handler; + /// iostream transport errors namespace error { enum value { diff --git a/websocketpp/transport/iostream/connection.hpp b/websocketpp/transport/iostream/connection.hpp index cbd802ec7..c0d090118 100644 --- a/websocketpp/transport/iostream/connection.hpp +++ b/websocketpp/transport/iostream/connection.hpp @@ -28,15 +28,20 @@ #ifndef WEBSOCKETPP_TRANSPORT_IOSTREAM_CON_HPP #define WEBSOCKETPP_TRANSPORT_IOSTREAM_CON_HPP +#include + +#include + +#include + #include #include #include -#include - -#include -#include +#include +#include #include +#include #include namespace websocketpp { @@ -307,6 +312,44 @@ class connection : public lib::enable_shared_from_this< connection > { timer_ptr set_timer(long, timer_handler) { return timer_ptr(); } + + /// Sets the write handler + /** + * The write handler is called when the iostream transport receives data + * that needs to be written to the appropriate output location. This handler + * can be used in place of registering an ostream for output. + * + * The signature of the handler is + * `lib::error_code (connection_hdl, char const *, size_t)` The + * code returned will be reported and logged by the core library. + * + * @since 0.5.0 + * + * @param h The handler to call on connection shutdown. + */ + void set_write_handler(write_handler h) { + m_write_handler = h; + } + + /// Sets the shutdown handler + /** + * The shutdown handler is called when the iostream transport receives a + * notification from the core library that it is finished with all read and + * write operations and that the underlying transport can be cleaned up. + * + * If you are using iostream transport with another socket library, this is + * a good time to close/shutdown the socket for this connection. + * + * The signature of the handler is `lib::error_code (connection_hdl)`. The + * code returned will be reported and logged by the core library. + * + * @since 0.5.0 + * + * @param h The handler to call on connection shutdown. + */ + void set_shutdown_handler(shutdown_handler h) { + m_shutdown_handler = h; + } protected: /// Initialize the connection transport /** @@ -375,7 +418,7 @@ class connection : public lib::enable_shared_from_this< connection > { /// Asyncronous Transport Write /** - * Write len bytes in buf to the output stream. Call handler to report + * Write len bytes in buf to the output method. Call handler to report * success or failure. handler may or may not be called during async_write, * but it must be safe for this to happen. * @@ -383,31 +426,40 @@ class connection : public lib::enable_shared_from_this< connection > { * output_stream_required: No output stream was registered to write to * bad_stream: a ostream pass through error * + * This method will attempt to write to the registered ostream first. If an + * ostream is not registered it will use the write handler. If neither are + * registered then an error is passed up to the connection. + * * @param buf buffer to read bytes from * @param len number of bytes to write * @param handler Callback to invoke with operation status. */ - void async_write(char const * buf, size_t len, write_handler handler) { + void async_write(char const * buf, size_t len, transport::write_handler + handler) + { m_alog.write(log::alevel::devel,"iostream_con async_write"); // TODO: lock transport state? - if (!m_output_stream) { - handler(make_error_code(error::output_stream_required)); - return; - } - - m_output_stream->write(buf,len); + lib::error_code ec; - if (m_output_stream->bad()) { - handler(make_error_code(error::bad_stream)); + if (m_output_stream) { + m_output_stream->write(buf,len); + + if (m_output_stream->bad()) { + ec = make_error_code(error::bad_stream); + } + } else if (m_write_handler) { + ec = m_write_handler(m_connection_hdl, buf, len); } else { - handler(lib::error_code()); + ec = make_error_code(error::output_stream_required); } + + handler(ec); } /// Asyncronous Transport Write (scatter-gather) /** - * Write a sequence of buffers to the output stream. Call handler to report + * Write a sequence of buffers to the output method. Call handler to report * success or failure. handler may or may not be called during async_write, * but it must be safe for this to happen. * @@ -415,28 +467,43 @@ class connection : public lib::enable_shared_from_this< connection > { * output_stream_required: No output stream was registered to write to * bad_stream: a ostream pass through error * + * This method will attempt to write to the registered ostream first. If an + * ostream is not registered it will use the write handler. If neither are + * registered then an error is passed up to the connection. + * * @param bufs vector of buffers to write * @param handler Callback to invoke with operation status. */ - void async_write(std::vector const & bufs, write_handler handler) { + void async_write(std::vector const & bufs, transport::write_handler + handler) + { m_alog.write(log::alevel::devel,"iostream_con async_write buffer list"); // TODO: lock transport state? - if (!m_output_stream) { - handler(make_error_code(error::output_stream_required)); - return; - } + lib::error_code ec; - std::vector::const_iterator it; - for (it = bufs.begin(); it != bufs.end(); it++) { - m_output_stream->write((*it).buf,(*it).len); + if (m_output_stream) { + std::vector::const_iterator it; + for (it = bufs.begin(); it != bufs.end(); it++) { + m_output_stream->write((*it).buf,(*it).len); - if (m_output_stream->bad()) { - handler(make_error_code(error::bad_stream)); + if (m_output_stream->bad()) { + ec = make_error_code(error::bad_stream); + break; + } + } + } else if (m_write_handler) { + std::vector::const_iterator it; + for (it = bufs.begin(); it != bufs.end(); it++) { + ec = m_write_handler(m_connection_hdl, (*it).buf, (*it).len); + if (ec) {break;} } + + } else { + ec = make_error_code(error::output_stream_required); } - - handler(lib::error_code()); + + handler(ec); } /// Set Connection Handle @@ -465,10 +532,20 @@ class connection : public lib::enable_shared_from_this< connection > { /// Perform cleanup on socket shutdown_handler /** - * @param h The `shutdown_handler` to call back when complete + * If a shutdown handler is set, call it and pass through its return error + * code. Otherwise assume there is nothing to do and pass through a success + * code. + * + * @param handler The `shutdown_handler` to call back when complete */ - void async_shutdown(shutdown_handler handler) { - handler(lib::error_code()); + void async_shutdown(transport::shutdown_handler handler) { + lib::error_code ec; + + if (m_shutdown_handler) { + ec = m_shutdown_handler(m_connection_hdl); + } + + handler(ec); } private: void read(std::istream &in) { @@ -558,6 +635,8 @@ class connection : public lib::enable_shared_from_this< connection > { // transport resources std::ostream * m_output_stream; connection_hdl m_connection_hdl; + write_handler m_write_handler; + shutdown_handler m_shutdown_handler; bool m_reading; bool const m_is_server; diff --git a/websocketpp/transport/iostream/endpoint.hpp b/websocketpp/transport/iostream/endpoint.hpp index 6a83b3b7e..14ec65370 100644 --- a/websocketpp/transport/iostream/endpoint.hpp +++ b/websocketpp/transport/iostream/endpoint.hpp @@ -28,13 +28,15 @@ #ifndef WEBSOCKETPP_TRANSPORT_IOSTREAM_HPP #define WEBSOCKETPP_TRANSPORT_IOSTREAM_HPP -#include -#include - #include #include -#include +#include +#include + +#include + +#include namespace websocketpp { namespace transport { @@ -114,6 +116,44 @@ class endpoint { bool is_secure() const { return m_is_secure; } + + /// Sets the write handler + /** + * The write handler is called when the iostream transport receives data + * that needs to be written to the appropriate output location. This handler + * can be used in place of registering an ostream for output. + * + * The signature of the handler is + * `lib::error_code (connection_hdl, char const *, size_t)` The + * code returned will be reported and logged by the core library. + * + * @since 0.5.0 + * + * @param h The handler to call on connection shutdown. + */ + void set_write_handler(write_handler h) { + m_write_handler = h; + } + + /// Sets the shutdown handler + /** + * The shutdown handler is called when the iostream transport receives a + * notification from the core library that it is finished with all read and + * write operations and that the underlying transport can be cleaned up. + * + * If you are using iostream transport with another socket library, this is + * a good time to close/shutdown the socket for this connection. + * + * The signature of the handler is lib::error_code (connection_hdl). The + * code returned will be reported and logged by the core library. + * + * @since 0.5.0 + * + * @param h The handler to call on connection shutdown. + */ + void set_shutdown_handler(shutdown_handler h) { + m_shutdown_handler = h; + } protected: /// Initialize logging /** @@ -156,10 +196,19 @@ class endpoint { */ lib::error_code init(transport_con_ptr tcon) { tcon->register_ostream(m_output_stream); + if (m_shutdown_handler) { + tcon->set_shutdown_handler(m_shutdown_handler); + } + if (m_write_handler) { + tcon->set_write_handler(m_write_handler); + } return lib::error_code(); } private: std::ostream * m_output_stream; + shutdown_handler m_shutdown_handler; + write_handler m_write_handler; + elog_type * m_elog; alog_type * m_alog; bool m_is_secure; diff --git a/websocketpp/transport/stub/base.hpp b/websocketpp/transport/stub/base.hpp index 864372a45..754981e29 100644 --- a/websocketpp/transport/stub/base.hpp +++ b/websocketpp/transport/stub/base.hpp @@ -45,11 +45,11 @@ enum value { /// categories general = 1, - /// not implimented - not_implimented + /// not implemented + not_implemented }; -/// iostream transport error category +/// stub transport error category class category : public lib::error_category { public: category() {} @@ -62,8 +62,8 @@ class category : public lib::error_category { switch(value) { case general: return "Generic stub transport policy error"; - case not_implimented: - return "feature not implimented"; + case not_implemented: + return "feature not implemented"; default: return "Unknown"; } diff --git a/websocketpp/transport/stub/connection.hpp b/websocketpp/transport/stub/connection.hpp index ce3bdccd6..3e501a963 100644 --- a/websocketpp/transport/stub/connection.hpp +++ b/websocketpp/transport/stub/connection.hpp @@ -28,13 +28,18 @@ #ifndef WEBSOCKETPP_TRANSPORT_STUB_CON_HPP #define WEBSOCKETPP_TRANSPORT_STUB_CON_HPP +#include + +#include + +#include + #include #include #include -#include -#include -#include +#include +#include namespace websocketpp { namespace transport { @@ -68,6 +73,7 @@ class connection : public lib::enable_shared_from_this< connection > { typedef lib::shared_ptr timer_ptr; explicit connection(bool is_server, alog_type & alog, elog_type & elog) + : m_alog(alog), m_elog(elog) { m_alog.write(log::alevel::devel,"stub con transport constructor"); } @@ -156,7 +162,7 @@ class connection : public lib::enable_shared_from_this< connection > { */ void init(init_handler handler) { m_alog.write(log::alevel::devel,"stub connection init"); - handler(make_error_code(error::not_implimented)); + handler(make_error_code(error::not_implemented)); } /// Initiate an async_read for at least num_bytes bytes into buf @@ -183,11 +189,11 @@ class connection : public lib::enable_shared_from_this< connection > { * @param handler The callback to invoke when the operation is complete or * ends in an error */ - void async_read_at_least(size_t num_bytes, char *buf, size_t len, + void async_read_at_least(size_t num_bytes, char * buf, size_t len, read_handler handler) { m_alog.write(log::alevel::devel, "stub_con async_read_at_least"); - handler(make_error_code(error::not_implimented)); + handler(make_error_code(error::not_implemented), 0); } /// Asyncronous Transport Write @@ -204,7 +210,7 @@ class connection : public lib::enable_shared_from_this< connection > { */ void async_write(char const * buf, size_t len, write_handler handler) { m_alog.write(log::alevel::devel,"stub_con async_write"); - handler(make_error_code(error::not_implimented)); + handler(make_error_code(error::not_implemented)); } /// Asyncronous Transport Write (scatter-gather) @@ -220,7 +226,7 @@ class connection : public lib::enable_shared_from_this< connection > { */ void async_write(std::vector const & bufs, write_handler handler) { m_alog.write(log::alevel::devel,"stub_con async_write buffer list"); - handler(make_error_code(error::not_implimented)); + handler(make_error_code(error::not_implemented)); } /// Set Connection Handle @@ -254,6 +260,8 @@ class connection : public lib::enable_shared_from_this< connection > { } private: // member variables! + alog_type & m_alog; + elog_type & m_elog; }; diff --git a/websocketpp/transport/stub/endpoint.hpp b/websocketpp/transport/stub/endpoint.hpp index 7be76d7f8..3bbb78f35 100644 --- a/websocketpp/transport/stub/endpoint.hpp +++ b/websocketpp/transport/stub/endpoint.hpp @@ -55,7 +55,7 @@ class endpoint { /// Type of this endpoint transport component's associated connection /// transport component. - typedef iostream::connection transport_con_type; + typedef stub::connection transport_con_type; /// Type of a shared pointer to this endpoint transport component's /// associated connection transport component typedef typename transport_con_type::ptr transport_con_ptr; @@ -113,7 +113,7 @@ class endpoint { * @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(make_error_code(error::not_implimented)); + cb(make_error_code(error::not_implemented)); } /// Initialize a connection @@ -127,7 +127,7 @@ class endpoint { * @return A status code indicating the success or failure of the operation */ lib::error_code init(transport_con_ptr tcon) { - cb(make_error_code(error::not_implimented)); + return make_error_code(error::not_implemented); } private: diff --git a/websocketpp/uri.hpp b/websocketpp/uri.hpp index ff8285ba5..e2cd8c837 100644 --- a/websocketpp/uri.hpp +++ b/websocketpp/uri.hpp @@ -28,11 +28,11 @@ #ifndef WEBSOCKETPP_URI_HPP #define WEBSOCKETPP_URI_HPP -#include #include +#include + #include -#include #include #include diff --git a/websocketpp/utf8_validator.hpp b/websocketpp/utf8_validator.hpp index 528ede18b..c057da629 100644 --- a/websocketpp/utf8_validator.hpp +++ b/websocketpp/utf8_validator.hpp @@ -31,6 +31,8 @@ #include +#include + namespace websocketpp { namespace utf8_validator { diff --git a/websocketpp/version.hpp b/websocketpp/version.hpp index a9a80297e..519fa681a 100644 --- a/websocketpp/version.hpp +++ b/websocketpp/version.hpp @@ -42,7 +42,7 @@ namespace websocketpp { /// Library major version number static int const major_version = 0; /// Library minor version number -static int const minor_version = 4; +static int const minor_version = 5; /// Library patch version number static int const patch_version = 0; /// Library pre-release flag @@ -53,7 +53,7 @@ static int const patch_version = 0; static char const prerelease_flag[] = ""; /// Default user agent string -static char const user_agent[] = "WebSocket++/0.4.0"; +static char const user_agent[] = "WebSocket++/0.5.0"; } // namespace websocketpp