From fbc9db2b5d3e4fc8c620da994c45cc76eb444858 Mon Sep 17 00:00:00 2001 From: Peter Thorson Date: Sun, 2 Mar 2014 12:06:37 -0600 Subject: [PATCH] work on close step --- tutorials/app_client_tutorial/chapter1.md | 30 ++- tutorials/app_client_tutorial/step5.cpp | 147 ++++++--------- tutorials/app_client_tutorial/step5a.cpp | 219 ++++++++++++++++++++++ 3 files changed, 297 insertions(+), 99 deletions(-) create mode 100644 tutorials/app_client_tutorial/step5a.cpp diff --git a/tutorials/app_client_tutorial/chapter1.md b/tutorials/app_client_tutorial/chapter1.md index 41047f6378..f2f4296bf8 100644 --- a/tutorials/app_client_tutorial/chapter1.md +++ b/tutorials/app_client_tutorial/chapter1.md @@ -528,11 +528,26 @@ int main() { _Closing connections_ +This step adds a command that allows you to close a WebSocket connection and adjusts the quit command so that it cleanly closes all outstanding connections before quitting. + +#### WebSocket++ tools for connection closing + +> ###### Terminology: WebSocket close codes & reasons +> The WebSocket close handshake involves an exchange of optional machine readable close codes and human readable reason strings. Each endpoint sends independent close details. The codes are short integers. The reasons are UTF8 text strings of at most 125 characters. More details about valid close code ranges and the meaning of each code can be found at https://tools.ietf.org/html/rfc6455#section-7.4 + +WebSocket++ offers + +The `websocketpp::close::status` namespace contains named constants for all of the IANA defined close codes. It also contains some special values. + +#### Add close handler + +The `connection_metadata::on_close` method is added. This method retrieves the close code and reason from the closing handshake and stores it in the local error reason field. + - endpoint::close - terminology: WebSocket close codes - Setting a close handler -### Step 5 +### Step 6 _Sending and receiving messages_ @@ -540,7 +555,11 @@ _Sending and receiving messages_ - terminology: WebSocket opcodes, text vs binary messages - Receiving a message -### Step 6 +### Step 7 + +_Using TLS / Secure WebSockets_ + +### Step 8 _Intermediate level features_ @@ -550,11 +569,8 @@ _Intermediate level features_ - Proxies? - Setting user agent - Setting Origin - -### Step 7 - -_Using TLS / Secure WebSockets_ - +- Timers and security +- Close behavior diff --git a/tutorials/app_client_tutorial/step5.cpp b/tutorials/app_client_tutorial/step5.cpp index 3cbffd2c02..a7c127234f 100644 --- a/tutorials/app_client_tutorial/step5.cpp +++ b/tutorials/app_client_tutorial/step5.cpp @@ -14,6 +14,8 @@ 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) @@ -21,19 +23,19 @@ public: , m_uri(uri) , m_server("N/A") {} - - connection_metadata() - : m_id(-1) {} - + 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(); } @@ -41,23 +43,12 @@ public: m_status = "Closed"; client::connection_ptr con = c->get_con_from_hdl(hdl); std::stringstream s; - s << "close code: " << con->get_remote_close_code() << ", close reason: " << con->get_remote_close_reason(); + 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 set_error(std::string const & err) { - m_error_reason = err; - m_status = "Error"; - } - - int get_id() const { - return m_id; - } - - websocketpp::connection_hdl get_hdl() const { - return m_hdl; - } - + friend std::ostream & operator<< (std::ostream & out, connection_metadata const & data); private: int m_id; @@ -69,15 +60,11 @@ private: }; std::ostream & operator<< (std::ostream & out, connection_metadata const & data) { - if (data.m_id == -1) { - out << "> Invalid connection"; - } else { - out << "> URI: " << data.m_uri << "\n" - << "> Status: " << data.m_status << "\n" - << "> Remote Server: " << data.m_server << "\n" - << "> Error/close reason: " << data.m_error_reason; - } - + out << "> URI: " << data.m_uri << "\n" + << "> Status: " << data.m_status << "\n" + << "> Remote Server: " << (data.m_server.empty() ? "None Specified" : data.m_server) << "\n" + << "> Error/close reason: " << (data.m_error_reason.empty() ? "N/A" : data.m_error_reason); + return out; } @@ -86,88 +73,59 @@ public: websocket_endpoint () : m_next_id(0) { m_endpoint.clear_access_channels(websocketpp::log::alevel::all); m_endpoint.clear_error_channels(websocketpp::log::elevel::all); - + m_endpoint.init_asio(); m_endpoint.start_perpetual(); m_thread.reset(new websocketpp::lib::thread(&client::run, &m_endpoint)); } - - ~websocket_endpoint() { - m_endpoint.stop_perpetual(); - - for (con_list::const_iterator it = m_connection_list.begin(); it != m_connection_list.end(); ++it) { - 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() << std::endl; - } - } - - m_thread->join(); - } - - // copy constructors - // assignment operator - + int connect(std::string const & uri) { websocketpp::lib::error_code ec; - int new_id = m_next_id++; client::connection_ptr con = m_endpoint.get_connection(uri, ec); - + if (ec) { std::cout << "> Connect initialization error: " << ec.message() << std::endl; return -1; } - - m_connection_list[new_id] = connection_metadata(new_id, con->get_handle(), uri); - - using websocketpp::lib::placeholders::_1; - using websocketpp::lib::bind; - con->set_open_handler(bind( + + int new_id = m_next_id++; + connection_metadata::ptr metadata_ptr(new connection_metadata(new_id, con->get_handle(), uri)); + m_connection_list[new_id] = metadata_ptr; + + con->set_open_handler(websocketpp::lib::bind( &connection_metadata::on_open, - &m_connection_list[new_id], + metadata_ptr, &m_endpoint, - ::_1 + websocketpp::lib::placeholders::_1 )); - con->set_fail_handler(bind(&connection_metadata::on_fail,&m_connection_list[new_id],&m_endpoint,::_1)); - con->set_close_handler(bind(&connection_metadata::on_close,&m_connection_list[new_id],&m_endpoint,::_1)); - + con->set_fail_handler(websocketpp::lib::bind( + &connection_metadata::on_fail, + metadata_ptr, + &m_endpoint, + websocketpp::lib::placeholders::_1 + )); + m_endpoint.connect(con); return new_id; } - - void close(int id, websocketpp::close::status::value code) { - websocketpp::lib::error_code ec; - - con_list::iterator metadata_it = m_connection_list.find(id); - if (metadata_it == m_connection_list.end()) { - std::cout << "> No connection found with id " << id << std::endl; - return; - } - - m_endpoint.close(metadata_it->second.get_hdl(),code, "", ec); - if (ec) { - std::cout << "> Error initiating close: " << ec.message() << std::endl; - } - } - - connection_metadata get_metadata(int id) const { + + 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(); + return connection_metadata::ptr(); } else { return metadata_it->second; } } private: - typedef std::map con_list; - + typedef std::map con_list; + client m_endpoint; websocketpp::lib::shared_ptr m_thread; - + con_list m_connection_list; int m_next_id; }; @@ -184,25 +142,27 @@ int main() { if (input == "quit") { done = true; } else if (input == "help") { - std::cout + std::cout << "\nCommand List:\n" << "connect \n" - << "close \n" << "show \n" << "help: Display this help text\n" << "quit: Exit the program\n" << std::endl; } else if (input.substr(0,7) == "connect") { int id = endpoint.connect(input.substr(8)); - std::cout << "> Created connection with id " << id << std::endl; - } else if (input.substr(0,5) == "close") { - int id = atoi(input.substr(6).c_str()); - - endpoint.close(id, websocketpp::close::status::normal); + if (id != -1) { + std::cout << "> Created connection with id " << id << std::endl; + } } else if (input.substr(0,4) == "show") { int id = atoi(input.substr(5).c_str()); - - std::cout << endpoint.get_metadata(id) << std::endl; + + 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; } @@ -216,4 +176,7 @@ int main() { 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 -*/ \ No newline at end of file + +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/app_client_tutorial/step5a.cpp b/tutorials/app_client_tutorial/step5a.cpp new file mode 100644 index 0000000000..3cbffd2c02 --- /dev/null +++ b/tutorials/app_client_tutorial/step5a.cpp @@ -0,0 +1,219 @@ +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +typedef websocketpp::client client; + +class connection_metadata { +public: + 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") + {} + + connection_metadata() + : m_id(-1) {} + + 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_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() << ", close reason: " << con->get_remote_close_reason(); + m_error_reason = s.str(); + } + + void set_error(std::string const & err) { + m_error_reason = err; + m_status = "Error"; + } + + int get_id() const { + return m_id; + } + + websocketpp::connection_hdl get_hdl() const { + return m_hdl; + } + + friend std::ostream & operator<< (std::ostream & out, connection_metadata const & data); +private: + int m_id; + websocketpp::connection_hdl m_hdl; + std::string m_status; + std::string m_uri; + std::string m_server; + std::string m_error_reason; +}; + +std::ostream & operator<< (std::ostream & out, connection_metadata const & data) { + if (data.m_id == -1) { + out << "> Invalid connection"; + } else { + out << "> URI: " << data.m_uri << "\n" + << "> Status: " << data.m_status << "\n" + << "> Remote Server: " << data.m_server << "\n" + << "> Error/close reason: " << data.m_error_reason; + } + + return out; +} + +class websocket_endpoint { +public: + websocket_endpoint () : m_next_id(0) { + m_endpoint.clear_access_channels(websocketpp::log::alevel::all); + m_endpoint.clear_error_channels(websocketpp::log::elevel::all); + + m_endpoint.init_asio(); + m_endpoint.start_perpetual(); + + m_thread.reset(new websocketpp::lib::thread(&client::run, &m_endpoint)); + } + + ~websocket_endpoint() { + m_endpoint.stop_perpetual(); + + for (con_list::const_iterator it = m_connection_list.begin(); it != m_connection_list.end(); ++it) { + 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() << std::endl; + } + } + + m_thread->join(); + } + + // copy constructors + // assignment operator + + int connect(std::string const & uri) { + websocketpp::lib::error_code ec; + int new_id = m_next_id++; + + client::connection_ptr con = m_endpoint.get_connection(uri, ec); + + if (ec) { + std::cout << "> Connect initialization error: " << ec.message() << std::endl; + return -1; + } + + m_connection_list[new_id] = connection_metadata(new_id, con->get_handle(), uri); + + using websocketpp::lib::placeholders::_1; + using websocketpp::lib::bind; + con->set_open_handler(bind( + &connection_metadata::on_open, + &m_connection_list[new_id], + &m_endpoint, + ::_1 + )); + con->set_fail_handler(bind(&connection_metadata::on_fail,&m_connection_list[new_id],&m_endpoint,::_1)); + con->set_close_handler(bind(&connection_metadata::on_close,&m_connection_list[new_id],&m_endpoint,::_1)); + + m_endpoint.connect(con); + + return new_id; + } + + void close(int id, websocketpp::close::status::value code) { + websocketpp::lib::error_code ec; + + con_list::iterator metadata_it = m_connection_list.find(id); + if (metadata_it == m_connection_list.end()) { + std::cout << "> No connection found with id " << id << std::endl; + return; + } + + m_endpoint.close(metadata_it->second.get_hdl(),code, "", ec); + if (ec) { + std::cout << "> Error initiating close: " << ec.message() << std::endl; + } + } + + connection_metadata 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(); + } else { + return metadata_it->second; + } + } +private: + typedef std::map con_list; + + client m_endpoint; + websocketpp::lib::shared_ptr m_thread; + + con_list m_connection_list; + int m_next_id; +}; + +int main() { + bool done = false; + std::string input; + websocket_endpoint endpoint; + + while (!done) { + std::cout << "Enter Command: "; + std::getline(std::cin, input); + + if (input == "quit") { + done = true; + } else if (input == "help") { + std::cout + << "\nCommand List:\n" + << "connect \n" + << "close \n" + << "show \n" + << "help: Display this help text\n" + << "quit: Exit the program\n" + << std::endl; + } else if (input.substr(0,7) == "connect") { + int id = endpoint.connect(input.substr(8)); + std::cout << "> Created connection with id " << id << std::endl; + } else if (input.substr(0,5) == "close") { + int id = atoi(input.substr(6).c_str()); + + endpoint.close(id, websocketpp::close::status::normal); + } else if (input.substr(0,4) == "show") { + int id = atoi(input.substr(5).c_str()); + + std::cout << endpoint.get_metadata(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 +*/ \ No newline at end of file