/* * Copyright (c) 2011, 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_ENDPOINT_HPP #define WEBSOCKETPP_ENDPOINT_HPP #include "connection.hpp" #include "sockets/autotls.hpp" // should this be here? #include "logger/logger.hpp" #include #include #include #include #include #include namespace websocketpp { /// endpoint_base provides core functionality that needs to be constructed /// before endpoint policy classes are constructed. class endpoint_base { protected: /// Start the run method of the endpoint's io_service object. void run_internal() { for (;;) { try { m_io_service.run(); break; } catch (const std::exception & e) { throw e; } } } boost::asio::io_service m_io_service; }; /// Describes a configurable WebSocket endpoint. /** * The endpoint class template provides a configurable WebSocket endpoint * capable that manages WebSocket connection lifecycles. endpoint is a host * class to a series of enriched policy classes that together provide the public * interface for a specific type of WebSocket endpoint. * * @par Thread Safety * @e Distinct @e objects: Safe.@n * @e Shared @e objects: Will be safe when complete. */ template < template class role, template class socket = socket::autotls, template class logger = log::logger> class endpoint : public endpoint_base, public role< endpoint >, public socket< endpoint > { public: /// Type of the traits class that stores endpoint related types. typedef endpoint_traits< endpoint > traits; /// The type of the endpoint itself. typedef typename traits::type type; /// The type of a shared pointer to the endpoint. typedef typename traits::ptr ptr; /// The type of the role policy. typedef typename traits::role_type role_type; /// The type of the socket policy. typedef typename traits::socket_type socket_type; /// The type of the access logger based on the logger policy. typedef typename traits::alogger_type alogger_type; typedef typename traits::alogger_ptr alogger_ptr; /// The type of the error logger based on the logger policy. typedef typename traits::elogger_type elogger_type; typedef typename traits::elogger_ptr elogger_ptr; /// The type of the connection that this endpoint creates. typedef typename traits::connection_type connection_type; /// A shared pointer to the type of connection that this endpoint creates. typedef typename traits::connection_ptr connection_ptr; /// Interface (ABC) that handlers for this type of endpoint must impliment /// role policy and socket policy both may add methods to this interface typedef typename traits::handler handler; /// A shared pointer to the base class that all handlers for this endpoint /// must derive from. typedef typename traits::handler_ptr handler_ptr; // Friend is used here to allow the CRTP base classes to access member // functions in the derived endpoint. This is done to limit the use of // public methods in endpoint and its CRTP bases to only those methods // intended for end-application use. #ifdef _WEBSOCKETPP_CPP11_FRIEND_ // Highly simplified and preferred C++11 version: friend role_type; friend socket_type; friend connection_type; #else friend class role< endpoint >; friend class socket< endpoint >; friend class connection::template connection,socket< type >::template connection>; #endif /// Construct an endpoint. /** * This constructor creates an endpoint and registers the default connection * handler. * * @param handler A shared_ptr to the handler to use as the default handler * when creating new connections. */ explicit endpoint(handler_ptr handler) : role_type(endpoint_base::m_io_service) , socket_type(endpoint_base::m_io_service) , m_handler(handler) , m_read_threshold(DEFAULT_READ_THRESHOLD) , m_silent_close(DEFAULT_SILENT_CLOSE) , m_state(IDLE) , m_alog(new alogger_type()) , m_elog(new elogger_type()) , m_pool(new message::pool(1000)) , m_pool_control(new message::pool(SIZE_MAX)) { m_pool->set_callback(boost::bind(&type::on_new_message,this)); } /// Destroy an endpoint ~endpoint() { m_alog->at(log::alevel::DEVEL) << "Endpoint destructor called" << log::endl; // Tell the memory pool we don't want to be notified about newly freed // messages any more (because we wont be here) m_pool->set_callback(NULL); // Detach any connections that are still alive at this point boost::lock_guard lock(m_lock); typename std::set::iterator it; while (!m_connections.empty()) { remove_connection(*m_connections.begin()); } m_alog->at(log::alevel::DEVEL) << "Endpoint destructor done" << log::endl; } // copy/assignment constructors require C++11 // boost::noncopyable is being used in the meantime. // endpoint(endpoint const&) = delete; // endpoint& operator=(endpoint const&) = delete /// Returns a reference to the endpoint's access logger. /** * Visibility: public * State: Any * Concurrency: Callable from anywhere * * @return A reference to the endpoint's access logger. See @ref logger * for more details about WebSocket++ logging policy classes. * * @par Example * To print a message to the access log of endpoint e at access level DEVEL: * @code * e.alog().at(log::alevel::DEVEL) << "message" << log::endl; * @endcode */ alogger_type& alog() { return *m_alog; } alogger_ptr alog_ptr() { return m_alog; } /// Returns a reference to the endpoint's error logger. /** * @returns A reference to the endpoint's error logger. See @ref logger * for more details about WebSocket++ logging policy classes. * * @par Example * To print a message to the error log of endpoint e at access level DEVEL: * @code * e.elog().at(log::elevel::DEVEL) << "message" << log::endl; * @endcode */ elogger_type& elog() { return *m_elog; } elogger_ptr elog_ptr() { return m_elog; } /// Get default handler /** * Visibility: public * State: valid always * Concurrency: callable from anywhere * * @return A pointer to the default handler */ handler_ptr get_handler() const { boost::lock_guard lock(m_lock); return m_handler; } /// Sets the default handler to be used for future connections /** * Does not affect existing connections. * * @param new_handler A shared pointer to the new default handler. Must not * be NULL. */ void set_handler(handler_ptr new_handler) { boost::lock_guard lock(m_lock); if (!new_handler) { elog().at(log::elevel::FATAL) << "Tried to switch to a NULL handler." << log::endl; throw websocketpp::exception("TODO: handlers can't be null"); } m_handler = new_handler; } /// Set endpoint read threshold /** * Sets the default read threshold value that will be passed to new connections. * Changing this value will only affect new connections, not existing ones. The read * threshold represents the largest block of payload bytes that will be processed in * a single async read. Lower values may experience better callback latency at the * expense of additional ASIO context switching overhead. This value also affects the * maximum number of bytes to be buffered before performing utf8 and other streaming * validation. * * Visibility: public * State: valid always * Concurrency: callable from anywhere * * @param val Size of the threshold in bytes */ void set_read_threshold(size_t val) { boost::lock_guard lock(m_lock); m_read_threshold = val; } /// Get endpoint read threshold /** * Returns the endpoint read threshold. See set_read_threshold for more information * about the read threshold. * * Visibility: public * State: valid always * Concurrency: callable from anywhere * * @return Size of the threshold in bytes * @see set_read_threshold() */ size_t get_read_threshold() const { boost::lock_guard lock(m_lock); return m_read_threshold; } /// Set connection silent close setting /** * Silent close suppresses the return of detailed connection close information during * the closing handshake. This information is critically useful for debugging but may * be undesirable for security reasons for some production environments. Close reasons * could be used to by an attacker to confirm that the implementation is out of * resources or be used to identify the WebSocket library in use. * * Visibility: public * State: valid always * Concurrency: callable from anywhere * * @param val New silent close value */ void set_silent_close(bool val) { boost::lock_guard lock(m_lock); m_silent_close = val; } /// Get connection silent close setting /** * Visibility: public * State: valid always * Concurrency: callable from anywhere * * @return Current silent close value * @see set_silent_close() */ bool get_silent_close() const { boost::lock_guard lock(m_lock); return m_silent_close; } /// Cleanly closes all websocket connections /** * Sends a close signal to every connection with the specified code and * reason. The default code is 1001/Going Away and the default reason is * blank. * * @param code The WebSocket close code to send to remote clients as the * reason that the connection is being closed. * @param reason The WebSocket close reason to send to remote clients as the * text reason that the connection is being closed. Must be valid UTF-8. */ void close_all(close::status::value code = close::status::GOING_AWAY, const std::string& reason = "") { boost::lock_guard lock(m_lock); m_alog->at(log::alevel::ENDPOINT) << "Endpoint received signal to close all connections cleanly with code " << code << " and reason " << reason << log::endl; // TODO: is there a more elegant way to do this? In some code paths // close can call terminate immediately which removes the connection // from m_connections, invalidating the iterator. typename std::set::iterator it; for (it = m_connections.begin(); it != m_connections.end();) { const connection_ptr con = *it++; con->close(code,reason); } } /// Stop the endpoint's ASIO loop /** * Signals the endpoint to call the io_service stop member function. If * clean is true the endpoint will be put into an intermediate state where * it signals all connections to close cleanly and only calls stop once that * process is complete. Otherwise stop is called immediately and all * io_service operations will be aborted. * * If clean is true stop will use code and reason for the close code and * close reason when it closes open connections. The default code is * 1001/Going Away and the default reason is blank. * * Visibility: public * State: Valid from RUNNING only * Concurrency: Callable from anywhere * * @param clean Whether or not to wait until all connections have been * cleanly closed to stop io_service operations. * @param code The WebSocket close code to send to remote clients as the * reason that the connection is being closed. * @param reason The WebSocket close reason to send to remote clients as the * text reason that the connection is being closed. Must be valid UTF-8. */ void stop(bool clean = true, close::status::value code = close::status::GOING_AWAY, const std::string& reason = "") { boost::lock_guard lock(m_lock); if (clean) { m_alog->at(log::alevel::ENDPOINT) << "Endpoint is stopping cleanly" << log::endl; m_state = STOPPING; close_all(code,reason); } else { m_alog->at(log::alevel::ENDPOINT) << "Endpoint is stopping immediately" << log::endl; endpoint_base::m_io_service.stop(); m_state = STOPPED; } } protected: /// Creates and returns a new connection /** * This function creates a new connection of the type and passes it a * reference to this as well as a shared pointer to the default connection * handler. The newly created connection is added to the endpoint's * management list. The endpoint will retain this pointer until * remove_connection is called to remove it. * * If the endpoint is in a state where it is trying to stop or has already * stopped an empty shared pointer is returned. * * Visibility: protected * State: Always valid, behavior differs based on state * Concurrency: Callable from anywhere * * @return A shared pointer to the newly created connection or an empty * shared pointer if one could not be created. */ connection_ptr create_connection() { boost::lock_guard lock(m_lock); if (m_state == STOPPING || m_state == STOPPED) { return connection_ptr(); } connection_ptr new_connection(new connection_type(*this,m_handler)); m_connections.insert(new_connection); m_alog->at(log::alevel::DEVEL) << "Connection created: count is now: " << m_connections.size() << log::endl; return new_connection; } /// Removes a connection from the list managed by this endpoint. /** * This function erases a connection from the list managed by the endpoint. * After this function returns, endpoint all async events related to this * connection should be canceled and neither ASIO nor this endpoint should * have a pointer to this connection. Unless the end user retains a copy of * the shared pointer the connection will be freed and any state it * contained (close code status, etc) will be lost. * * Visibility: protected * State: Always valid, behavior differs based on state * Concurrency: Callable from anywhere * * @param con A shared pointer to a connection created by this endpoint. */ void remove_connection(connection_ptr con) { boost::lock_guard lock(m_lock); // TODO: is this safe to use? // Detaching signals to the connection that the endpoint is no longer aware of it // and it is no longer safe to assume the endpoint exists. con->detach(); m_connections.erase(con); m_alog->at(log::alevel::DEVEL) << "Connection removed: count is now: " << m_connections.size() << log::endl; if (m_state == STOPPING && m_connections.empty()) { // If we are in the process of stopping and have reached zero // connections stop the io_service. m_alog->at(log::alevel::ENDPOINT) << "Endpoint has reached zero connections in STOPPING state. Stopping io_service now." << log::endl; stop(false); } } /// Gets a shared pointer to a read/write data message. // TODO: thread safety message::data::ptr get_data_message() { return m_pool->get(); } /// Gets a shared pointer to a read/write control message. // TODO: thread safety message::data::ptr get_control_message() { return m_pool_control->get(); } /// Asks the endpoint to restart this connection's handle_read_frame loop /// when there are avaliable data messages. void wait(connection_ptr con) { boost::lock_guard lock(m_lock); m_read_waiting.push(con); m_alog->at(log::alevel::DEVEL) << "connection " << con << " is waiting. " << m_read_waiting.size() << log::endl; } /// Message pool callback indicating that there is a free data message /// avaliable. Causes one waiting connection to get restarted. void on_new_message() { boost::lock_guard lock(m_lock); if (!m_read_waiting.empty()) { connection_ptr next = m_read_waiting.front(); m_alog->at(log::alevel::DEVEL) << "Waking connection " << next << ". " << m_read_waiting.size()-1 << log::endl; (*next).handle_read_frame(boost::system::error_code()); m_read_waiting.pop(); } } private: enum state { IDLE = 0, RUNNING = 1, STOPPING = 2, STOPPED = 3 }; // default settings to pass to connections handler_ptr m_handler; size_t m_read_threshold; bool m_silent_close; // other stuff state m_state; std::set m_connections; alogger_ptr m_alog; elogger_ptr m_elog; // resource pools for read/write message buffers message::pool::ptr m_pool; message::pool::ptr m_pool_control; std::queue m_read_waiting; // concurrency support mutable boost::recursive_mutex m_lock; }; /// traits class that allows looking up relevant endpoint types by the fully /// defined endpoint type. template < template class role, template class socket, template class logger> struct endpoint_traits< endpoint > { /// The type of the endpoint itself. typedef endpoint type; typedef boost::shared_ptr ptr; /// The type of the role policy. typedef role< type > role_type; /// The type of the socket policy. typedef socket< type > socket_type; /// The type of the access logger based on the logger policy. typedef logger alogger_type; typedef boost::shared_ptr alogger_ptr; /// The type of the error logger based on the logger policy. typedef logger elogger_type; typedef boost::shared_ptr elogger_ptr; /// The type of the connection that this endpoint creates. typedef connection::template connection, socket< type >::template connection> connection_type; /// A shared pointer to the type of connection that this endpoint creates. typedef boost::shared_ptr connection_ptr; class handler; /// A shared pointer to the base class that all handlers for this endpoint /// must derive from. typedef boost::shared_ptr handler_ptr; /// Interface (ABC) that handlers for this type of endpoint may impliment. /// role policy and socket policy both may add methods to this interface class handler : public role_type::handler_interface, public socket_type::handler_interface { public: // convenience typedefs for use in end application handlers. // TODO: figure out how to not duplicate the definition of connection_ptr typedef boost::shared_ptr ptr; typedef typename connection_type::ptr connection_ptr; typedef typename message::data::ptr message_ptr; virtual ~handler() {} /// on_load is the first callback called for a handler after a new /// connection has been transferred to it mid flight. /** * @param connection A shared pointer to the connection that was transferred * @param old_handler A shared pointer to the previous handler */ virtual void on_load(connection_ptr con, handler_ptr old_handler) {} /// on_unload is the last callback called for a handler before control /// of a connection is handed over to a new handler mid flight. /** * @param connection A shared pointer to the connection being transferred * @param old_handler A shared pointer to the new handler */ virtual void on_unload(connection_ptr con, handler_ptr new_handler) {} }; }; } // namespace websocketpp #endif // WEBSOCKETPP_ENDPOINT_HPP