diff --git a/tutorials/app_client_tutorial/chapter1.md b/tutorials/app_client_tutorial/chapter1.md index f2f4296bf8..fd9ad80ed0 100644 --- a/tutorials/app_client_tutorial/chapter1.md +++ b/tutorials/app_client_tutorial/chapter1.md @@ -535,17 +535,114 @@ This step adds a command that allows you to close a WebSocket connection and adj > ###### 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 includes free functions to determine whether a value is reserved or invalid and to convert a code to a human readable text representation. -The `websocketpp::close::status` namespace contains named constants for all of the IANA defined close codes. It also contains some special values. +During the close handler call WebSocket++ connections offer the following methods for accessing close handshake information: + +- `connection::get_remote_close_code()`: Get the close code as reported by the remote endpoint +- `connection::get_remote_close_reason()`: Get the close reason as reported by the remote endpoint +- `connection::get_local_close_code()`: Get the close code that this endpoint sent. +- `connection::get_local_close_reason()`: Get the close reason that this endpoint sent. +- `connection::get_ec()`: Get a more detailed/specific WebSocket++ `error_code` indicating what library error (if any) ultimately resulted in the connection closure. + +*Note:* there are some special close codes that will report a code that was not actually sent on the wire. For example 1005/"no close code" indicates that the endpoint omitted a close code entirely and 1006/"abnormal close" indicates that there was a problem that resulted in the connection closing without having performed a close handshake. #### Add close handler The `connection_metadata::on_close` method is added. This method retrieves the close code and reason from the closing handshake and stores it in the local error reason field. -- endpoint::close -- terminology: WebSocket close codes -- Setting a close handler +```cpp +void on_close(client * c, websocketpp::connection_hdl hdl) { + m_status = "Closed"; + client::connection_ptr con = c->get_con_from_hdl(hdl); + std::stringstream s; + s << "close code: " << con->get_remote_close_code() << " (" + << websocketpp::close::status::get_string(con->get_remote_close_code()) + << "), close reason: " << con->get_remote_close_reason(); + m_error_reason = s.str(); +} +``` + +Similarly to `on_open` and `on_fail`, websocket_endpoint::connect registers this close handler when a new connection is made. + +#### Add close method to `websocket_endpoint` + +This method starts by looking up the given connection ID in the connection list. If it isn't found an error is printed. Next a close request is sent to the connection's handle with the specified WebSocket close code. This is done by calling `endpoint::close`. This is a thread safe method that is used to asynchronously dispatch a close signal to the connection with the given handle. When the operation is complete the connection's close handler will be triggered. + +```cpp +void close(int id, websocketpp::close::status::value code) { + websocketpp::lib::error_code ec; + + con_list::iterator metadata_it = m_connection_list.find(id); + if (metadata_it == m_connection_list.end()) { + std::cout << "> No connection found with id " << id << std::endl; + return; + } + + m_endpoint.close(metadata_it->second->get_hdl(), code, "", ec); + if (ec) { + std::cout << "> Error initiating close: " << ec.message() << std::endl; + } +} +``` + +#### Add close option to the command loop and help message + +A close option is added to the command loop. It takes a connection ID and optionally a close code and a close reason. If no code is specified the default of 1000/Normal is used. If no reason is specified, none is sent. The endpoint::send method will do some error checking and abort the close request if you try and send an invalid code or a reason with invalid UTF8 formatting. Reason strings longer than 125 characters will be truncated. + +An entry is also added to the help system to describe how the new command may be used. + +#### Close all outstanding connections in `websocket_endpoint` destructor + +Until now quitting the program left outstanding connections and the WebSocket++ network thread in a lurch. Now that we have a method of closing connections we can clean this up properly. + +The destructor for `websocket_endpoint` now stops perpetual mode (so the run thread exits after the last connection is closed) and iterates through the list of open connections and requests a clean close for each. Finally, the run thread is joined which causes the program to wait until those connection closes complete. + +```cpp +~websocket_endpoint() { + m_endpoint.stop_perpetual(); + + for (con_list::const_iterator it = m_connection_list.begin(); it != m_connection_list.end(); ++it) { + if (it->second->get_status() != "open") { + // Only close open connections + continue; + } + + std::cout << "> Closing connection " << it->second->get_id() << std::endl; + + websocketpp::lib::error_code ec; + m_endpoint.close(it->second->get_hdl(), websocketpp::close::status::going_away, "", ec); + if (ec) { + std::cout << "> Error closing connection " << it->second->get_id() << ": " << ec.message() << std::endl; + } + } + + m_thread->join(); +} +``` + +#### Build + +There are no changes to the build instructions from step 4 + +#### Run + +``` +Enter Command: connect ws://localhost:9002 +> Created connection with id 0 +Enter Command: close 0 1001 example message +Enter Command: show 0 +> URI: ws://localhost:9002 +> Status: Closed +> Remote Server: WebSocket++/0.3.0-alpha4 +> Error/close reason: close code: 1001 (Going away), close reason: example message +Enter Command: connect ws://localhost:9002 +> Created connection with id 1 +Enter Command: close 1 1006 +> Error initiating close: Invalid close code used +Enter Command: quit +> Closing connection 1 +``` ### Step 6 diff --git a/tutorials/app_client_tutorial/step5.cpp b/tutorials/app_client_tutorial/step5.cpp index a7c127234f..4e47ea93d1 100644 --- a/tutorials/app_client_tutorial/step5.cpp +++ b/tutorials/app_client_tutorial/step5.cpp @@ -49,6 +49,18 @@ public: m_error_reason = s.str(); } + websocketpp::connection_hdl get_hdl() const { + return m_hdl; + } + + int get_id() const { + return m_id; + } + + std::string get_status() const { + return m_status; + } + friend std::ostream & operator<< (std::ostream & out, connection_metadata const & data); private: int m_id; @@ -80,6 +92,27 @@ public: m_thread.reset(new websocketpp::lib::thread(&client::run, &m_endpoint)); } + ~websocket_endpoint() { + m_endpoint.stop_perpetual(); + + for (con_list::const_iterator it = m_connection_list.begin(); it != m_connection_list.end(); ++it) { + if (it->second->get_status() != "Open") { + // Only close open connections + continue; + } + + std::cout << "> Closing connection " << it->second->get_id() << std::endl; + + websocketpp::lib::error_code ec; + m_endpoint.close(it->second->get_hdl(), websocketpp::close::status::going_away, "", ec); + if (ec) { + std::cout << "> Error closing connection " << it->second->get_id() << ": " << ec.message() << std::endl; + } + } + + m_thread->join(); + } + int connect(std::string const & uri) { websocketpp::lib::error_code ec; @@ -106,12 +139,33 @@ public: &m_endpoint, websocketpp::lib::placeholders::_1 )); + con->set_close_handler(websocketpp::lib::bind( + &connection_metadata::on_close, + metadata_ptr, + &m_endpoint, + websocketpp::lib::placeholders::_1 + )); m_endpoint.connect(con); return new_id; } + void close(int id, websocketpp::close::status::value code, std::string reason) { + websocketpp::lib::error_code ec; + + con_list::iterator metadata_it = m_connection_list.find(id); + if (metadata_it == m_connection_list.end()) { + std::cout << "> No connection found with id " << id << std::endl; + return; + } + + m_endpoint.close(metadata_it->second->get_hdl(), code, reason, ec); + if (ec) { + std::cout << "> Error initiating close: " << ec.message() << std::endl; + } + } + connection_metadata::ptr get_metadata(int id) const { con_list::const_iterator metadata_it = m_connection_list.find(id); if (metadata_it == m_connection_list.end()) { @@ -145,6 +199,7 @@ int main() { std::cout << "\nCommand List:\n" << "connect \n" + << "close [] []\n" << "show \n" << "help: Display this help text\n" << "quit: Exit the program\n" @@ -154,7 +209,19 @@ int main() { if (id != -1) { std::cout << "> Created connection with id " << id << std::endl; } - } else if (input.substr(0,4) == "show") { + } 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); diff --git a/tutorials/app_client_tutorial/step5a.cpp b/tutorials/app_client_tutorial/step5a.cpp deleted file mode 100644 index 3cbffd2c02..0000000000 --- a/tutorials/app_client_tutorial/step5a.cpp +++ /dev/null @@ -1,219 +0,0 @@ -#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 diff --git a/tutorials/app_client_tutorial/step5b.cpp b/tutorials/app_client_tutorial/step5b.cpp deleted file mode 100644 index 7bfacf1682..0000000000 --- a/tutorials/app_client_tutorial/step5b.cpp +++ /dev/null @@ -1,216 +0,0 @@ -#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.set_access_channels(websocketpp::log::alevel::app); - - 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 - - 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) { - m_connection_list[new_id] = connection_metadata(new_id, websocketpp::connection_hdl(), uri); - m_connection_list[new_id].set_error("Connect initialization error: "+ec.message()); - std::cout << "> Connect initialization error: " << ec.message() << std::endl; - } else { - 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