From 3bab403c1b303c96e94ade24d718a701bd884c7d Mon Sep 17 00:00:00 2001 From: Peter Thorson Date: Sat, 27 Jul 2013 11:24:24 -0500 Subject: [PATCH] Refactor asio_transport and add full docs and exception free varient methods --- changelog.md | 3 + test/transport/integration.cpp | 2 +- websocketpp/transport/asio/base.hpp | 7 +- websocketpp/transport/asio/endpoint.hpp | 367 +++++++++++++++++++----- 4 files changed, 303 insertions(+), 76 deletions(-) diff --git a/changelog.md b/changelog.md index 3e8e36a9be..f7cc10eb3f 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,7 @@ HEAD +- Refactors `asio_transport` endpoint and adds full documentation and exception + free varients of all methods. +- Removes `asio_transport` endpoint method cancel(). Use `stop_listen()` instead - Wrap internal `io_service` `run_one()` method - Suppress error when trying to shut down a connection that was already closed diff --git a/test/transport/integration.cpp b/test/transport/integration.cpp index 5afb558726..1c12c87339 100644 --- a/test/transport/integration.cpp +++ b/test/transport/integration.cpp @@ -213,7 +213,7 @@ bool on_ping(websocketpp::connection_hdl, std::string payload) { } void cancel_on_open(server * s, websocketpp::connection_hdl hdl) { - s->cancel(); + s->stop_listening(); } void stop_on_close(server * s, websocketpp::connection_hdl hdl) { diff --git a/websocketpp/transport/asio/base.hpp b/websocketpp/transport/asio/base.hpp index 2401dff662..a868ad9a58 100644 --- a/websocketpp/transport/asio/base.hpp +++ b/websocketpp/transport/asio/base.hpp @@ -65,7 +65,10 @@ enum value { proxy_failed, /// Invalid Proxy URI - proxy_invalid + proxy_invalid, + + /// Invalid host or service + invalid_host_service }; /// Asio transport error category @@ -87,6 +90,8 @@ public: return "Proxy connection failed"; case error::proxy_invalid: return "Invalid proxy URI"; + case error::invalid_host_service: + return "Invalid host or service"; default: return "Unknown"; } diff --git a/websocketpp/transport/asio/endpoint.hpp b/websocketpp/transport/asio/endpoint.hpp index 59fd6530c0..44ffd6c485 100644 --- a/websocketpp/transport/asio/endpoint.hpp +++ b/websocketpp/transport/asio/endpoint.hpp @@ -142,21 +142,27 @@ public: return *this; } #endif + /// Return whether or not the endpoint produces secure connections. + bool is_secure() const { + return socket_type::is_secure(); + } - /// initialize asio transport with external io_service + /// initialize asio transport with external io_service (exception free) /** - * Initialize the ASIO transport policy for this endpoint using the + * Initialize the ASIO transport policy for this endpoint using the provided * io_service object. asio_init must be called exactly once on any endpoint * that uses transport::asio before it can be used. * - * Calling init_asio shifts the internal state from UNINITIALIZED to READY + * @param ptr A pointer to the io_service to use for asio events + * @param ec Set to indicate what error occurred, if any. */ - void init_asio(io_service_ptr ptr) { + void init_asio(io_service_ptr ptr, lib::error_code & ec) { if (m_state != UNINITIALIZED) { - // TODO: throw invalid state m_elog->write(log::elevel::library, "asio::init_asio called from the wrong state"); - throw; + using websocketpp::error::make_error_code; + ec = make_error_code(websocketpp::error::invalid_state); + return; } m_alog->write(log::alevel::devel,"asio::init_asio"); @@ -165,10 +171,44 @@ public: m_external_io_service = true; m_acceptor.reset(new boost::asio::ip::tcp::acceptor(*m_io_service)); m_state = READY; + ec = lib::error_code(); + } + + /// initialize asio transport with external io_service + /** + * Initialize the ASIO transport policy for this endpoint using the provided + * io_service object. asio_init must be called exactly once on any endpoint + * that uses transport::asio before it can be used. + * + * @param ptr A pointer to the io_service to use for asio events + */ + void init_asio(io_service_ptr ptr) { + lib::error_code ec; + init_asio(ptr,ec); + if (ec) { + throw ec; + } + } + + /// Initialize asio transport with internal io_service (exception free) + /** + * This method of initialization will allocate and use an internally managed + * io_service. + * + * @see init_asio(io_service_ptr ptr) + * + * @param ec Set to indicate what error occurred, if any. + */ + void init_asio(lib::error_code & ec) { + init_asio(new boost::asio::io_service(),ec); + m_external_io_service = false; } /// Initialize asio transport with internal io_service /** + * This method of initialization will allocate and use an internally managed + * io_service. + * * @see init_asio(io_service_ptr ptr) */ void init_asio() { @@ -176,6 +216,21 @@ public: m_external_io_service = false; } + /// Retrieve a reference to the endpoint's io_service + /** + * The io_service may be an internal or external one. This may be used to + * call methods of the io_service that are not explicitly wrapped by the + * endpoint. + * + * This method is only valid after the endpoint has been initialized with + * `init_asio`. No error will be returned if it isn't. + * + * @return A reference to the endpoint's io_service + */ + boost::asio::io_service & get_io_service() { + return *m_io_service; + } + /// Sets the tcp init handler /** * The tcp init handler is called after the tcp connection has been @@ -183,65 +238,219 @@ public: * * @see WebSocket++ handler documentation for more information about * handlers. + * + * @param h The handler to call on tcp init. */ void set_tcp_init_handler(tcp_init_handler h) { m_tcp_init_handler = h; } - // listen manually - void listen(const boost::asio::ip::tcp::endpoint& e) { + /// Set up endpoint for listening manually (exception free) + /** + * Bind the internal acceptor using the specified settings. The endpoint + * must have been initialized by calling init_asio before listening. + * + * @param ep An endpoint to read settings from + * @param ec Set to indicate what error occurred, if any. + */ + void listen(boost::asio::ip::tcp::endpoint const & ep, lib::error_code & ec) + { if (m_state != READY) { - // TODO m_elog->write(log::elevel::library, "asio::listen called from the wrong state"); - throw; + using websocketpp::error::make_error_code; + ec = make_error_code(websocketpp::error::invalid_state); + return; } m_alog->write(log::alevel::devel,"asio::listen"); - m_acceptor->open(e.protocol()); + m_acceptor->open(ep.protocol()); m_acceptor->set_option(boost::asio::socket_base::reuse_address(true)); - m_acceptor->bind(e); + m_acceptor->bind(ep); m_acceptor->listen(); m_state = LISTENING; - - m_alog->write(log::alevel::devel,"mark"); + ec = lib::error_code(); } - void cancel() { - if (m_state != LISTENING) { - // TODO - throw; + /// Set up endpoint for listening manually + /** + * Bind the internal acceptor using the settings specified by the endpoint e + * + * @param ep An endpoint to read settings from + */ + void listen(boost::asio::ip::tcp::endpoint const & ep) { + lib::error_code ec; + listen(ep,ec); + if (ec) { + throw ec; } - - // TODO: figure out if this is a good way to stop listening. - m_acceptor->cancel(); - m_acceptor->close(); } - // Accept the next connection attempt via m_acceptor and assign it to con. - // callback is called - void async_accept(transport_con_ptr tcon, accept_handler callback) { - if (m_state != LISTENING) { - // TODO: throw invalid state + /// Set up endpoint for listening with protocol and port (exception free) + /** + * Bind the internal acceptor using the given internet protocol and port. + * The endpoint must have been initialized by calling init_asio before + * listening. + * + * Common options include: + * - IPv6 with mapped IPv4 for dual stack hosts boost::asio::ip::tcp::v6() + * - IPv4 only: boost::asio::ip::tcp::v4() + * + * @param internet_protocol The internet protocol to use. + * @param port The port to listen on. + * @param ec Set to indicate what error occurred, if any. + */ + template + void listen(InternetProtocol const & internet_protocol, uint16_t port, + lib::error_code & ec) + { + boost::asio::ip::tcp::endpoint ep(internet_protocol, port); + listen(ep,ec); + } + + /// Set up endpoint for listening with protocol and port + /** + * Bind the internal acceptor using the given internet protocol and port. + * The endpoint must have been initialized by calling init_asio before + * listening. + * + * Common options include: + * - IPv6 with mapped IPv4 for dual stack hosts boost::asio::ip::tcp::v6() + * - IPv4 only: boost::asio::ip::tcp::v4() + * + * @param internet_protocol The internet protocol to use. + * @param port The port to listen on. + */ + template + void listen(InternetProtocol const & internet_protocol, uint16_t port) + { + boost::asio::ip::tcp::endpoint ep(internet_protocol, port); + listen(ep); + } + + /// Set up endpoint for listening on a port (exception free) + /** + * Bind the internal acceptor using the given port. The IPv6 protocol with + * mapped IPv4 for dual stack hosts will be used. If you need IPv4 only use + * the overload that allows specifying the protocol explicitly. + * + * The endpoint must have been initialized by calling init_asio before + * listening. + * + * @param port The port to listen on. + * @param ec Set to indicate what error occurred, if any. + */ + void listen(uint16_t port, lib::error_code & ec) { + listen(boost::asio::ip::tcp::v6(), port, ec); + } + + /// Set up endpoint for listening on a port + /** + * Bind the internal acceptor using the given port. The IPv6 protocol with + * mapped IPv4 for dual stack hosts will be used. If you need IPv4 only use + * the overload that allows specifying the protocol explicitly. + * + * The endpoint must have been initialized by calling init_asio before + * listening. + * + * @param port The port to listen on. + * @param ec Set to indicate what error occurred, if any. + */ + void listen(uint16_t port) { + listen(boost::asio::ip::tcp::v6(), port); + } + + /// Set up endpoint for listening on a host and service (exception free) + /** + * Bind the internal acceptor using the given host and service. More details + * about what host and service can be are available in the boost asio + * documentation for ip::basic_resolver_query::basic_resolver_query's + * constructors. + * + * The endpoint must have been initialized by calling init_asio before + * listening. + * + * @param host A string identifying a location. May be a descriptive name or + * a numeric address string. + * @param service A string identifying the requested service. This may be a + * descriptive name or a numeric string corresponding to a port number. + * @param ec Set to indicate what error occurred, if any. + */ + void listen(std::string const & host, std::string const & service, + lib::error_code & ec) + { + using boost::asio::ip::tcp; + tcp::resolver r(*m_io_service); + tcp::resolver::query query(host, service); + tcp::resolver::iterator endpoint_iterator = r.resolve(query); + tcp::resolver::iterator end; + if (endpoint_iterator == end) { m_elog->write(log::elevel::library, - "asio::async_accept called from the wrong state"); - throw; + "asio::listen could not resolve the supplied host or service"); + ec = make_error_code(error::invalid_host_service); + return; + } + listen(*endpoint_iterator,ec); + } + + /// Set up endpoint for listening on a host and service + /** + * Bind the internal acceptor using the given host and service. More details + * about what host and service can be are available in the boost asio + * documentation for ip::basic_resolver_query::basic_resolver_query's + * constructors. + * + * The endpoint must have been initialized by calling init_asio before + * listening. + * + * @param host A string identifying a location. May be a descriptive name or + * a numeric address string. + * @param service A string identifying the requested service. This may be a + * descriptive name or a numeric string corresponding to a port number. + * @param ec Set to indicate what error occurred, if any. + */ + void listen(std::string const & host, std::string const & service) + { + lib::error_code ec; + listen(host,service,ec); + if (ec) { + throw ec; + } + } + + /// Stop listening (exception free) + /** + * Stop listening and accepting new connections. This will not end any + * existing connections. + * + * @param ec A status code indicating an error, if any. + */ + void stop_listening(lib::error_code & ec) { + if (m_state != LISTENING) { + m_elog->write(log::elevel::library, + "asio::listen called from the wrong state"); + using websocketpp::error::make_error_code; + ec = make_error_code(websocketpp::error::invalid_state); + return; } - m_alog->write(log::alevel::devel, "asio::async_accept"); + m_acceptor->close(); + m_state = READY; + ec = lib::error_code(); + } - // TEMP - m_acceptor->async_accept( - tcon->get_raw_socket(), - lib::bind( - &type::handle_accept, - this, - tcon->get_handle(), - callback, - lib::placeholders::_1 - ) - ); + /// Stop listening + /** + * Stop listening and accepting new connections. This will not end any + * existing connections. + */ + void stop_listening() { + lib::error_code ec; + stop_listening(ec); + if (ec) { + throw ec; + } } /// wraps the run method of the internal io_service object @@ -279,28 +488,6 @@ public: return m_io_service->stopped(); } - // convenience methods - template - void listen(const InternetProtocol &internet_protocol, uint16_t port) { - boost::asio::ip::tcp::endpoint e(internet_protocol, port); - listen(e); - } - - void listen(uint16_t port) { - listen(boost::asio::ip::tcp::v6(), port); - } - - void listen(const std::string &host, const std::string &service) { - boost::asio::ip::tcp::resolver resolver(*m_io_service); - boost::asio::ip::tcp::resolver::query query(host, service); - boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); - boost::asio::ip::tcp::resolver::iterator end; - if (endpoint_iterator == end) { - throw std::invalid_argument("Can't resolve host/service to listen"); - } - listen(*endpoint_iterator); - } - /// Call back a function after a period of time. /** * Sets a timer that calls back a function after the specified period of @@ -309,9 +496,7 @@ public: * A timer that expired will return no error. * * @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. */ @@ -342,13 +527,11 @@ public: * after it has expired. * * @param t Pointer to the timer in question - * * @param callback The function to call back - * - * @param ec The status code + * @param ec A status code indicating an error, if any. */ - void handle_timer(timer_ptr t, timer_handler callback, const - boost::system::error_code& ec) + void handle_timer(timer_ptr t, timer_handler callback, + boost::system::error_code const & ec) { if (ec) { if (ec == boost::asio::error::operation_aborted) { @@ -364,12 +547,48 @@ public: } } - boost::asio::io_service& get_io_service() { - return *m_io_service; + /// Accept the next connection attempt and assign it to con (exception free) + /** + * @param tcon The connection to accept into. + * @param callback The function to call when the operation is complete. + * @param ec A status code indicating an error, if any. + */ + void async_accept(transport_con_ptr tcon, accept_handler callback, + lib::error_code & ec) + { + if (m_state != LISTENING) { + m_elog->write(log::elevel::library, + "asio::async_accept called from the wrong state"); + using websocketpp::error::make_error_code; + ec = make_error_code(websocketpp::error::invalid_state); + return; + } + + m_alog->write(log::alevel::devel, "asio::async_accept"); + + m_acceptor->async_accept( + tcon->get_raw_socket(), + lib::bind( + &type::handle_accept, + this, + tcon->get_handle(), + callback, + lib::placeholders::_1 + ) + ); } - bool is_secure() const { - return socket_type::is_secure(); + /// Accept the next connection attempt and assign it to con. + /** + * @param tcon The connection to accept into. + * @param callback The function to call when the operation is complete. + */ + void async_accept(transport_con_ptr tcon, accept_handler callback) { + lib::error_code ec; + async_accept(tcon,callback,ec); + if (ec) { + throw ec; + } } protected: /// Initialize logging