From d6a59f459cf74de0b382bab460d0e2646ab70190 Mon Sep 17 00:00:00 2001 From: Peter Thorson Date: Sat, 5 Nov 2011 11:01:01 -0500 Subject: [PATCH] http parser and logger work --- examples/echo_client/echo_client_handler.hpp | 2 - examples/echo_server/echo.hpp | 1 + src/http/constants.hpp | 8 +- src/http/parser.hpp | 238 +++++++- src/logger/logger.hpp | 147 +++++ src/session_handler_interface.hpp | 49 ++ src/websocket_client.hpp | 286 +++++++-- src/websocket_constants.hpp | 2 - src/websocket_server.hpp | 88 ++- src/websocket_session.hpp | 595 ++++++++++++++----- src/websocketpp.hpp | 2 - test/basic/logging.cpp | 40 ++ websocketpp.xcodeproj/project.pbxproj | 12 + 13 files changed, 1215 insertions(+), 255 deletions(-) create mode 100644 src/logger/logger.hpp create mode 100644 src/session_handler_interface.hpp create mode 100644 test/basic/logging.cpp diff --git a/examples/echo_client/echo_client_handler.hpp b/examples/echo_client/echo_client_handler.hpp index f011c56265..5610e08c31 100644 --- a/examples/echo_client/echo_client_handler.hpp +++ b/examples/echo_client/echo_client_handler.hpp @@ -47,8 +47,6 @@ #include #include -using websocketpp::session_ptr; - namespace websocketecho { class echo_client_handler : public websocketpp::connection_handler { diff --git a/examples/echo_server/echo.hpp b/examples/echo_server/echo.hpp index b3105eea2b..5551f231f5 100644 --- a/examples/echo_server/echo.hpp +++ b/examples/echo_server/echo.hpp @@ -27,6 +27,7 @@ #ifndef ECHO_SERVER_HANDLER_HPP #define ECHO_SERVER_HANDLER_HPP + #include "../../src/websocketpp.hpp" #include "../../src/websocket_connection_handler.hpp" #include diff --git a/src/http/constants.hpp b/src/http/constants.hpp index 2a7b81c748..df49b0238a 100644 --- a/src/http/constants.hpp +++ b/src/http/constants.hpp @@ -23,8 +23,6 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * This Makefile was derived from a similar one included in the libjson project - * It's authors were Jonathan Wallace and Bernhard Fluehmann. */ #ifndef HTTP_CONSTANTS_HPP @@ -36,6 +34,7 @@ namespace http { enum value { CONTINUE = 100, SWITCHING_PROTOCOLS = 101, + OK = 200, CREATED = 201, ACCEPTED = 202, @@ -43,6 +42,7 @@ namespace http { NO_CONTENT = 204, RESET_CONTENT = 205, PARTIAL_CONTENT = 206, + MULTIPLE_CHOICES = 300, MOVED_PERMANENTLY = 301, FOUND = 302, @@ -50,6 +50,7 @@ namespace http { NOT_MODIFIED = 304, USE_PROXY = 305, TEMPORARY_REDIRECT = 307, + BAD_REQUEST = 400, UNAUTHORIZED = 401, PAYMENT_REQUIRED = 402, @@ -68,9 +69,7 @@ namespace http { UNSUPPORTED_MEDIA_TYPE = 415, REQUEST_RANGE_NOT_SATISFIABLE = 416, EXPECTATION_FAILED = 417, - IM_A_TEAPOT = 418, - UPGRADE_REQUIRED = 426, PRECONDITION_REQUIRED = 428, TOO_MANY_REQUESTS = 429, @@ -82,7 +81,6 @@ namespace http { SERVICE_UNAVAILABLE = 503, GATEWAY_TIMEOUT = 504, HTTP_VERSION_NOT_SUPPORTED = 505, - NOT_EXTENDED = 510, NETWORK_AUTHENTICATION_REQUIRED = 511 }; diff --git a/src/http/parser.hpp b/src/http/parser.hpp index 5bc731bf96..4e701b56b4 100644 --- a/src/http/parser.hpp +++ b/src/http/parser.hpp @@ -23,24 +23,240 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * This Makefile was derived from a similar one included in the libjson project - * It's authors were Jonathan Wallace and Bernhard Fluehmann. */ -#ifndef BLANK_RNG_HPP -#define BLANK_RNG_HPP +#ifndef HTTP_PARSER_HPP +#define HTTP_PARSER_HPP -#include +#include +#include + +#include "constants.hpp" namespace websocketpp { - -class blank_rng { -public: - int32_t gen() { - throw "Random Number generation not supported"; +namespace http { +namespace parser { + + namespace state { + enum value { + METHOD, + RESOURCE, + VERSION, + HEADERS + }; } + +typedef std::map header_list; + + +class parser { +public: + // consumes bytes from the stream and returns true if enough bytes have + // been read + bool consume (std::istream& s) { + throw "No Implimented"; + } + + void set_version(const std::string& version) { + // TODO: validation? + m_version = version; + } + + const std::string& version() const { + return m_version; + } + + std::string header(const std::string& key) const { + header_list::const_iterator h = m_headers.find(key); + + if (h == m_headers.end()) { + return ""; + } else { + return h->second; + } + } + + // multiple calls to set header will result in values aggregating. + // use replace_header if you do not want this behavior. + void set_header(const std::string &key,const std::string &val) { + // TODO: prevent use of reserved headers? + if (this->header(key) == "") { + m_headers[key] = val; + } else { + m_headers[key] += ", " + val; + } + } + + void replace_header(const std::string &key,const std::string &val) { + m_headers[key] = val; + } +protected: + bool 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(": ",0); + + if (end != std::string::npos) { + set_header(header.substr(0,end),header.substr(end+2)); + } + } + + return true; + } + + std::string raw_headers() { + std::stringstream raw; + + header_list::iterator it; + for (it = m_headers.begin(); it != m_headers.end(); it++) { + raw << it->first << ": " << it->second << "\r\n"; + } + + return raw.str(); + } + +private: + std::string m_version; + header_list m_headers; +}; + +class request : public parser { +public: + // parse a complete header (ie \r\n\r\n MUST be in the input stream) + bool parse_complete(std::istream& s) { + std::string request; + + // get status line + std::getline(s, request); + + if (request[request.size()-1] == '\r') { + request.erase(request.end()-1); + + std::stringstream ss(request); + 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); + } + + // TODO: validation. Make sure all required fields have been set? + std::string raw() { + std::stringstream raw; + + raw << m_method << " " << m_uri << " " << version() << "\r\n"; + raw << raw_headers() << "\r\n"; + + return raw.str(); + } + + void set_method(const std::string& method) { + // TODO: validation? + m_method = method; + } + + const std::string& method() const { + return m_method; + } + + void set_uri(const std::string& uri) { + // TODO: validation? + m_uri = uri; + } + + const std::string& uri() const { + return m_uri; + } + +private: + std::string m_method; + std::string m_uri; +}; + +class response : public parser { +public: + // parse a complete header (ie \r\n\r\n MUST be in the input stream) + bool parse_complete(std::istream& s) { + std::string response; + + // get status line + std::getline(s, response); + + if (response[response.size()-1] == '\r') { + response.erase(response.end()-1); + + std::stringstream ss(response); + std::string str_val; + int int_val; + + ss >> str_val; + set_version(str_val); + + ss >> int_val; + set_status(status_code::value(int_val),str_val); + } else { + return false; + } + + return parse_headers(s); + } + + // TODO: validation. Make sure all required fields have been set? + std::string raw() { + std::stringstream raw; + + raw << version() << " " << m_status_code << " " << m_status_msg << "\r\n"; + raw << raw_headers() << "\r\n"; + + return raw.str(); + } + + void set_status(status_code::value code) { + // TODO: validation? + m_status_code = code; + m_status_msg = get_string(code); + } + + void set_status(status_code::value code, const std::string& msg) { + // TODO: validation? + m_status_code = code; + m_status_msg = msg; + } + + status_code::value status_code() const { + return m_status_code; + } + + const std::string& status_msg() const { + return m_status_msg; + } +private: + status_code::value m_status_code; + std::string m_status_msg; }; + +} +} } -#endif // BLANK_RNG_HPP +#endif // HTTP_PARSER_HPP diff --git a/src/logger/logger.hpp b/src/logger/logger.hpp new file mode 100644 index 0000000000..90d1d15b01 --- /dev/null +++ b/src/logger/logger.hpp @@ -0,0 +1,147 @@ +/* + * 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 ZS_LOGGER_HPP +#define ZS_LOGGER_HPP + +#include +#include + +namespace websocketpp { +namespace log { + +namespace alog_level { + typedef uint16_t value; + + static const value OFF = 0x0; + + // A single line on connect with connecting ip, websocket version, + // request resource, user agent, and the response code. + static const value CONNECT = 0x1; + // A single line on disconnect with wasClean status and local and remote + // close codes and reasons. + static const value DISCONNECT = 0x2; + // A single line on incoming and outgoing control messages. + static const value CONTROL = 0x4; + // A single line on incoming and outgoing frames with full frame headers + static const value FRAME_HEADER = 0x10; + // Adds payloads to frame logs. Note these can be long! + static const value FRAME_PAYLOAD = 0x20; + // A single line on incoming and outgoing messages with metadata about type, + // length, etc + static const value MESSAGE_HEADER = 0x40; + // Adds payloads to message logs. Note these can be long! + static const value MESSAGE_PAYLOAD = 0x80; + + static const value ALL = 0xFFFF; +} + +namespace elog_level { + typedef uint16_t value; + + static const value OFF = 0x0; + + static const value DEBUG = 0x1; + static const value INFO = 0x2; + static const value WARN = 0x4; + static const value ERROR = 0x10; + static const value FATAL = 0x20; + + static const value ALL = 0xFFFF; +} + +template +class logger { +public: + + + //template + //logger& operator<<(T a); + + template + logger& operator<<(T a) { + if (test_level(m_write_level)) { + oss << a; + } + return *this; + } + + logger& operator<<(logger& (*f)(logger& out)) { + return f(*this); + } + + /* + logger& operator<<(endl) { + std::cout << "fff" << oss.str() << std::endl; + oss.str(""); + + return *this; + }*/ + + bool test_level(level_type l) { + return (m_level & l) != 0; + } + + void set_level(level_type l) { + m_level |= l; + } + + void unset_level(level_type l) { + m_level &= ~l; + } + + logger& print() { + if (test_level(m_write_level)) { + std::cout << "[timestamp] " << oss.str() << std::endl; + oss.str(""); + } + + return *this; + } + + logger& at(level_type l) { + m_write_level = l; + return *this; + } +private: + std::ostringstream oss; + level_type m_write_level; + level_type m_level; +}; + +template +logger& endl(logger& out) +{ + return out.print(); +} + + + +} +} + +#endif // ZS_LOGGER_HPP diff --git a/src/session_handler_interface.hpp b/src/session_handler_interface.hpp new file mode 100644 index 0000000000..236904fdc0 --- /dev/null +++ b/src/session_handler_interface.hpp @@ -0,0 +1,49 @@ +/* + * 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_SESSION_HANDLER_INTERFACE_HPP +#define WEBSOCKETPP_SESSION_HANDLER_INTERFACE_HPP + +#include + +#include +#include + +namespace websocketpp { +namespace session { + + + +class session_handler_interface { +public: + +}; + + +} +} +#endif // WEBSOCKETPP_SESSION_HANDLER_INTERFACE_HPP diff --git a/src/websocket_client.hpp b/src/websocket_client.hpp index 0ef1437178..0d0bd655eb 100644 --- a/src/websocket_client.hpp +++ b/src/websocket_client.hpp @@ -30,18 +30,22 @@ #include #include +#include +#include +namespace po = boost::program_options; #include -namespace websocketpp { - class client; - typedef boost::shared_ptr client_ptr; -} - #include "websocketpp.hpp" -#include "websocket_client_session.hpp" +#include "websocket_session.hpp" #include "websocket_connection_handler.hpp" +#include + +#include "rng/boost_rng.hpp" + +#include "http/parser.hpp" + using boost::asio::ip::tcp; namespace websocketpp { @@ -59,75 +63,231 @@ private: std::string m_msg; }; -class client : public boost::enable_shared_from_this { - public: - static const uint16_t CLIENT_STATE_NULL = 0; - static const uint16_t CLIENT_STATE_INITIALIZED = 1; - static const uint16_t CLIENT_STATE_CONNECTING = 2; - static const uint16_t CLIENT_STATE_CONNECTED = 3; +template +class client : public boost::enable_shared_from_this > { +public: + typedef rng_policy rng_t; + + typedef client endpoint_type; + typedef session session_type; + typedef connection_handler connection_handler_type; + + typedef boost::shared_ptr ptr; + typedef boost::shared_ptr session_ptr; + typedef boost::shared_ptr connection_handler_ptr; + + static const uint16_t CLIENT_STATE_NULL = 0; + static const uint16_t CLIENT_STATE_INITIALIZED = 1; + static const uint16_t CLIENT_STATE_CONNECTING = 2; + static const uint16_t CLIENT_STATE_CONNECTED = 3; - client(boost::asio::io_service& io_service, - connection_handler_ptr defc); + client(boost::asio::io_service& io_service, + connection_handler_ptr defc) + : m_elog_level(LOG_OFF), + m_alog_level(ALOG_OFF), + m_state(CLIENT_STATE_NULL), + m_max_message_size(DEFAULT_MAX_MESSAGE_SIZE), + m_io_service(io_service), + m_resolver(io_service), + m_def_con_handler(defc) {} - // INTERFACE FOR LOCAL APPLICATIONS - - // initializes the session. Methods that affect the opening handshake - // such as add_protocol and set_header must be called after init and - // before connect. - void init(); + // INTERFACE FOR LOCAL APPLICATIONS + + // initializes the session. Methods that affect the opening handshake + // such as add_protocol and set_header must be called after init and + // before connect. + void init() { + // TODO: sanity check whether the session buffer size bound could be reduced + m_client_session = session_ptr( + new session_type( + endpoint_type::shared_from_this(), + m_io_service, + m_def_con_handler, + m_max_message_size*2 + ) + ); + m_state = CLIENT_STATE_INITIALIZED; + } - // starts the connection process. Should be called before - // io_service.run(), connection process will not start until run() has - // been called. - void connect(const std::string& url); + // starts the connection process. Should be called before + // io_service.run(), connection process will not start until run() has + // been called. + void connect(const std::string& u) { + if (m_state != CLIENT_STATE_INITIALIZED) { + throw client_error("connect can only be called after init and before a connection has been established"); + } - // Adds a protocol to the opening handshake. - // Must be called before connect - void add_subprotocol(const std::string& p); + ws_uri uri; - // Sets the value of the given HTTP header to be sent during the - // opening handshake. Must be called before connect - void set_header(const std::string& key,const std::string& val); + if (!uri.parse(u)) { + throw client_error("Invalid WebSocket URI"); + } - void set_origin(const std::string& val); + if (uri.secure) { + throw client_error("wss / secure connections are not supported at this time"); + } + + m_client_session->set_uri(uri); + + std::stringstream port; + port << uri.port; + + tcp::resolver::query query(uri.host,port.str()); + tcp::resolver::iterator iterator = m_resolver.resolve(query); + + boost::asio::async_connect( + m_client_session->socket(), + iterator, + boost::bind( + &endpoint_type::handle_connect, + endpoint_type::shared_from_this(), + boost::asio::placeholders::error + ) + ); + m_state = CLIENT_STATE_CONNECTING; + } + + // Adds a protocol to the opening handshake. + // Must be called before connect + void add_subprotocol(const std::string& p) { + if (m_state != CLIENT_STATE_INITIALIZED) { + throw client_error("add_protocol can only be called after init and before connect"); + } + m_client_session->add_subprotocol(p); + } + + // Sets the value of the given HTTP header to be sent during the + // opening handshake. Must be called before connect + void set_header(const std::string& key,const std::string& val) { + if (m_state != CLIENT_STATE_INITIALIZED) { + throw client_error("set_header can only be called after init and before connect"); + } + m_client_session->set_request_header(key,val); + } + + void set_origin(const std::string& val) { + if (m_state != CLIENT_STATE_INITIALIZED) { + throw client_error("set_origin can only be called after init and before connect"); + } + m_client_session->set_origin(val); + } - void set_max_message_size(uint64_t val); + void set_max_message_size(uint64_t val) { + if (val > frame::limits::PAYLOAD_SIZE_JUMBO) { + std::stringstream err; + err << "Invalid maximum message size: " << val; + + // TODO: Figure out what the ideal error behavior for this method. + // Options: + // Throw exception + // Log error and set value to maximum allowed + // Log error and leave value at whatever it was before + log(err.str(),LOG_WARN); + //throw client_error(err.str()); + } + m_max_message_size = val; + } + + // Test methods determine if a message of the given level should be + // written. elog shows all values above the level set. alog shows only + // the values explicitly set. + bool test_elog_level(uint16_t level) { + return (level >= m_elog_level); + } + void set_elog_level(uint16_t level) { + std::stringstream msg; + msg << "Error logging level changing from " + << m_elog_level << " to " << level; + log(msg.str(),LOG_INFO); - // Test methods determine if a message of the given level should be - // written. elog shows all values above the level set. alog shows only - // the values explicitly set. - bool test_elog_level(uint16_t level); - void set_elog_level(uint16_t level); + m_elog_level = level; + } + + bool test_alog_level(uint16_t level) { + return ((level & m_alog_level) != 0); + } + void set_alog_level(uint16_t level) { + if (test_alog_level(level)) { + return; + } + std::stringstream msg; + msg << "Access logging level " << level << " being set"; + access_log(msg.str(),ALOG_INFO); - bool test_alog_level(uint16_t level); - void set_alog_level(uint16_t level); - void unset_alog_level(uint16_t level); + m_alog_level |= level; + } + void unset_alog_level(uint16_t level) { + if (!test_alog_level(level)) { + return; + } + std::stringstream msg; + msg << "Access logging level " << level << " being unset"; + access_log(msg.str(),ALOG_INFO); + + m_alog_level &= ~level; + } - // INTERFACE FOR SESSIONS + // INTERFACE FOR SESSIONS - // Check if message size is within server's acceptable parameters - bool validate_message_size(uint64_t val); - - // write to the server's logs - void log(std::string msg,uint16_t level = LOG_ERROR); - void access_log(std::string msg,uint16_t level); - private: - // if no errors starts the session's read loop and returns to the - // start_accept phase. - void handle_connect(const boost::system::error_code& error); - - private: - uint16_t m_elog_level; - uint16_t m_alog_level; - - uint16_t m_state; + // Check if message size is within server's acceptable parameters + bool validate_message_size(uint64_t val) { + if (val > m_max_message_size) { + return false; + } + return true; + } + + // write to the server's logs + void log(std::string msg,uint16_t level = LOG_ERROR) { + if (!test_elog_level(level)) { + return; + } + std::cerr << "[Error Log] " + << boost::posix_time::to_iso_extended_string( + boost::posix_time::second_clock::local_time()) + << " " << msg << std::endl; + } + void access_log(std::string msg,uint16_t level) { + if (!test_alog_level(level)) { + return; + } + std::cout << "[Access Log] " + << boost::posix_time::to_iso_extended_string( + boost::posix_time::second_clock::local_time()) + << " " << msg << std::endl; + } +private: + // if no errors starts the session's read loop and returns to the + // start_accept phase. + void handle_connect(const boost::system::error_code& error) { + if (!error) { + std::stringstream err; + err << "Successful Connection "; + log(err.str(),LOG_ERROR); + + m_state = CLIENT_STATE_CONNECTED; + m_client_session->on_connect(); + } else { + std::stringstream err; + err << "An error occurred while establishing a connection: " << error; + + log(err.str(),LOG_ERROR); + throw client_error(err.str()); + } + } + +private: + uint16_t m_elog_level; + uint16_t m_alog_level; + + uint16_t m_state; - std::set m_hosts; - uint64_t m_max_message_size; - boost::asio::io_service& m_io_service; - tcp::resolver m_resolver; - client_session_ptr m_client_session; - connection_handler_ptr m_def_con_handler; + std::set m_hosts; + uint64_t m_max_message_size; + boost::asio::io_service& m_io_service; + tcp::resolver m_resolver; + session_ptr m_client_session; + connection_handler_ptr m_def_con_handler; }; } diff --git a/src/websocket_constants.hpp b/src/websocket_constants.hpp index 4eb69b72e3..8841509d7e 100644 --- a/src/websocket_constants.hpp +++ b/src/websocket_constants.hpp @@ -23,8 +23,6 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * This Makefile was derived from a similar one included in the libjson project - * It's authors were Jonathan Wallace and Bernhard Fluehmann. */ #ifndef WEBSOCKET_CONSTANTS_HPP diff --git a/src/websocket_server.hpp b/src/websocket_server.hpp index 694cf62967..4c4cce3716 100644 --- a/src/websocket_server.hpp +++ b/src/websocket_server.hpp @@ -44,22 +44,25 @@ namespace po = boost::program_options; #include "rng/blank_rng.hpp" +#include "http/parser.hpp" + using boost::asio::ip::tcp; namespace websocketpp { - +// TODO: potential policies: +// - http parser template class server : public boost::enable_shared_from_this< server > { public: typedef rng_policy rng_t; - typedef server server_type; - typedef session session_type; + typedef server endpoint_type; + typedef session session_type; typedef connection_handler connection_handler_type; - typedef boost::shared_ptr ptr; + typedef boost::shared_ptr ptr; typedef boost::shared_ptr session_ptr; typedef boost::shared_ptr connection_handler_ptr; @@ -93,7 +96,7 @@ public: // TODO: sanity check whether the session buffer size bound could be reduced session_ptr new_session( new session_type( - server_type::shared_from_this(), + endpoint_type::shared_from_this(), m_io_service, m_def_con_handler, m_max_message_size*2 @@ -103,8 +106,8 @@ public: m_acceptor.async_accept( new_session->socket(), boost::bind( - &server_type::handle_accept, - server_type::shared_from_this(), + &endpoint_type::handle_accept, + endpoint_type::shared_from_this(), new_session, boost::asio::placeholders::error ) @@ -202,6 +205,75 @@ public: return m_rng; } + // checks a handshake for validity. Returns true if valid and throws a + // handshake_error otherwise + bool validate_handshake(const http::parser::request& handshake) { + std::stringstream err; + std::string h; + + if (handshake.method() != "GET") { + err << "Websocket handshake has invalid method: " + << handshake.method(); + + throw(handshake_error(err.str(),http::status_code::BAD_REQUEST)); + } + + // TODO: allow versions greater than 1.1 + if (handshake.version() != "HTTP/1.1") { + err << "Websocket handshake has invalid HTTP version: " + << handshake.method(); + + throw(handshake_error(err.str(),http::status_code::BAD_REQUEST)); + } + + // verify the presence of required headers + h = handshake.header("Host"); + if (h == "") { + throw(handshake_error("Required Host header is missing",http::status_code::BAD_REQUEST)); + } else if (!this->validate_host(h)) { + err << "Host " << h << " is not one of this server's names."; + throw(handshake_error(err.str(),http::status_code::BAD_REQUEST)); + } + + h = handshake.header("Upgrade"); + if (h == "") { + throw(handshake_error("Required Upgrade header is missing",http::status_code::BAD_REQUEST)); + } else if (!boost::ifind_first(h,"websocket")) { + err << "Upgrade header \"" << h << "\", does not contain required token \"websocket\""; + throw(handshake_error(err.str(),http::status_code::BAD_REQUEST)); + } + + h = handshake.header("Connection"); + if (h == "") { + throw(handshake_error("Required Connection header is missing",http::status_code::BAD_REQUEST)); + } else if (!boost::ifind_first(h,"upgrade")) { + err << "Connection header, \"" << h + << "\", does not contain required token \"upgrade\""; + throw(handshake_error(err.str(),http::status_code::BAD_REQUEST)); + } + + if (handshake.header("Sec-WebSocket-Key") == "") { + throw(handshake_error("Required Sec-WebSocket-Key header is missing",http::status_code::BAD_REQUEST)); + } + + h = handshake.header("Sec-WebSocket-Version"); + if (h == "") { + // TODO: if we want to support draft 00 this line should set version to 0 + // rather than bail + throw(handshake_error("Required Sec-WebSocket-Version header is missing",http::status_code::BAD_REQUEST)); + } else { + int version = atoi(h.c_str()); + + if (version != 7 && version != 8 && version != 13) { + err << "This server doesn't support WebSocket protocol version " + << version; + throw(handshake_error(err.str(),http::status_code::BAD_REQUEST)); + } + } + + return true; + } + // Confirms that the port in the host string matches the port we are listening // on. End user application is responsible for checking the /host/ part. bool validate_host(std::string host) { @@ -251,7 +323,7 @@ private: void handle_accept(session_ptr session,const boost::system::error_code& error) { if (!error) { - session->on_connect(); + session->read_request(); } else { std::stringstream err; err << "Error accepting socket connection: " << error; diff --git a/src/websocket_session.hpp b/src/websocket_session.hpp index f0cb60baee..16967b928d 100644 --- a/src/websocket_session.hpp +++ b/src/websocket_session.hpp @@ -70,6 +70,96 @@ - idle client timeout? API specifiable? - wait for pong? + + + INTERFACES + + CLIENT ENDPOINT + + SERVER ENDPOINT + + CLIENT HANDLER + Valid for OPEN connections + - get_state() // CONNECTING, OPEN, CLOSING, CLOSED + - get_origin() + - get_request_header(const std::string&) + - get_version() + - get_uri() // includes secure, host, port, resource + - get_secure() + + - send(const std::string&) + - send(const std::vector&) + - close(status::code::value,const std::string&) + - ping(const std::string&) + - pong(const std::string&) + + - get_subprotocol() + - **** get extensions **** + - get_request_header(const std::string&) + - get_response_header(const std::string&) + + + Valid for CLOSED connections + - get_local_close_code() + - get_local_close_reason() + - get_remote_close_code() + - get_remote_close_reason() + - dropped_by_me? + - failed_by_me? + - closed_by_me? + + Callbacks that may be implimented + - on_message(const std::string&) + - on_message(const std::vector&) + - on_close + - on_fail? + - on_write_avaliable + + SERVER HANDLER + + Valid for CONNECTING connections + - **** get subprotocols **** + - **** get extensions **** + - set_request_header(const std::string&,const std::string&) + - select_subprotocol(const std::string&) + - select_extension(const std::string&) + + Valid during and after CONNECTING + - get_origin() + - get_request_header(const std::string&) + - get_version() + - get_uri() // includes secure, host, port, resource + - get_secure() + - get_state() // CONNECTING, OPEN, CLOSING, CLOSED + + Valid for OPEN connections + - send(const std::string&) + - send(const std::vector&) + - close(status::code::value,const std::string&) + - ping(const std::string&) + - pong(const std::string&) + + - get_subprotocol() + - **** get extensions **** + - get_response_header(const std::string&) + + Valid for CLOSED connections + - get_local_close_code() + - get_local_close_reason() + - get_remote_close_code() + - get_remote_close_reason() + - dropped_by_me? + - failed_by_me? + - closed_by_me? + + Callbacks that may be implimented + - validate + - on_message(const std::string&) + - on_message(const std::vector&) + - on_close + - on_fail? + - on_write_avaliable + */ #ifndef WEBSOCKET_SESSION_HPP @@ -111,6 +201,8 @@ namespace websocketpp { #include "sha1/sha1.h" #include "utf8_validator/utf8_validator.hpp" +#include "http/parser.hpp" + using boost::asio::ip::tcp; namespace websocketpp { @@ -129,7 +221,7 @@ namespace state { class handshake_error : public std::exception { public: handshake_error(const std::string& msg, - int http_error, + http::status_code::value http_error, const std::string& http_msg = "") : m_msg(msg),m_http_error_code(http_error),m_http_error_msg(http_msg) {} ~handshake_error() throw() {} @@ -138,9 +230,9 @@ public: return m_msg.c_str(); } - std::string m_msg; - int m_http_error_code; - std::string m_http_error_msg; + std::string m_msg; + http::status_code::value m_http_error_code; + std::string m_http_error_msg; }; typedef std::map header_list; @@ -162,7 +254,8 @@ public: boost::asio::io_service& io_service, connection_handler_ptr defc, uint64_t buf_size) - : m_state(state::CONNECTING), + : m_secure(false), + m_state(state::CONNECTING), m_writing(false), m_local_close_code(close::status::NO_STATUS), m_remote_close_code(close::status::NO_STATUS), @@ -178,29 +271,93 @@ public: m_utf8_state(utf8_validator::UTF8_ACCEPT), m_utf8_codepoint(0), m_read_frame(e->get_rng()), - m_write_frame(e->get_rng()) - { - - } + m_write_frame(e->get_rng()) {} + /*** ENDPOINT INTERFACE ***/ tcp::socket& socket() { return m_socket; } + boost::asio::io_service& io_service() { return m_io_service; } - /*** SERVER INTERFACE ***/ - - // This function is called to begin the session loop. This method and all - // that come after it are called as a result of an async event completing. - // if any method in this chain returns before adding a new async event the - // session will end. - // TODO: this needs to be a template specialization or member of the endpoint - void on_connect() { - read_handshake(); + void set_uri(const ws_uri& uri) { + m_uri = uri; } + const ws_uri& get_uri() { + return m_uri; + } + + void set_origin(const std::string& val) { + // TODO: input validation + m_origin = val; + } + + // TODO: should these be one set? + void set_request_header(const std::string& key,const std::string& val) { + // TODO: input validation + m_request.set_header(key,val); + } + + void set_response_header(const std::string& key,const std::string& val) { + // TODO: input validation + m_response.set_header(key,val); + } + + // Adds a subprotocol to the list to propose to the remote endpoint + // TODO: this should not be callable by server handlers + void request_subprotocol(const std::string &val) { + // TODO: input validation + m_requested_subprotocols.push_back(val); + } + + // Adds an extension to the list to propose to the remote endpoint + // TODO: this should not be callable by server handlers + void request_extension(const std::string& val) { + // TODO: input validation + m_requested_extensions.push_back(val); + } + + // Selects a subprotocol from the requested list to use. + // TODO: this should only be callable by server handlers + void select_subprotocol(const std::string& val) { + std::vector::iterator it; + + it = std::find(m_requested_subprotocols.begin(), + m_requested_subprotocols.end(), + val); + + if (val != "" && it == m_requested_subprotocols.end()) { + throw server_error("Attempted to choose a subprotocol not proposed by the client"); + } + + m_subprotocol = val; + } + + // Selects an extension from the requested list to use. + // TODO: this should only be callable by server handlers + void select_extension(const std::string& val) { + if (val == "") { + return; + } + + std::vector::iterator it; + + it = std::find(m_requested_extensions.begin(), + m_requested_extensions.end(), + val); + + if (it == m_requested_extensions.end()) { + throw server_error("Attempted to choose an extension not proposed by the client"); + } + + m_extensions.push_back(val); + } + + /*** SERVER INTERFACE ***/ + // sets the internal connection handler of this connection to new_con. // This is useful if you want to switch handler objects during a connection // Example: a generic lobby handler could validate the handshake negotiate a @@ -231,23 +388,23 @@ public: //throw server_error("Subprotocol is not avaliable before the handshake has completed."); throw "Subprotocol is not avaliable before the handshake has completed"; } - return m_server_subprotocol; + return m_subprotocol; } const std::string& get_resource() const { - return m_resource; + return m_uri.resource; } const std::string& get_origin() const { - return m_client_origin; + return m_origin; } - std::string get_client_header(const std::string& key) const { - return get_header(key,m_client_headers); + std::string get_request_header(const std::string& key) const { + return m_request.header(key); } - std::string get_server_header(const std::string& key) const { - return get_header(key,m_server_headers); + std::string get_response_header(const std::string& key) const { + return m_response.header(key); } const std::vector& get_extensions() const { - return m_server_extensions; + return m_extensions; } unsigned int get_version() const { return m_version; @@ -255,7 +412,7 @@ public: /**** TODO: SERVER SPECIFIC ****/ - + /* void set_header(const std::string &key,const std::string &val) { // TODO: prevent use of reserved headers; m_server_headers[key] = val; @@ -291,7 +448,7 @@ public: } m_server_extensions.push_back(val); - } + }*/ /********/ @@ -353,55 +510,114 @@ public: bool is_server() const { return endpoint_type::is_server; } - - // Opening handshake processors and callbacks. These need to be defined in - // derived classes. - // TODO: endpoint specific - //virtual void handle_write_handshake(const boost::system::error_code& e) = 0; - void handle_write_handshake(const boost::system::error_code& error) { - if (error) { - log_error("Error writing handshake response",error); - drop_tcp(); - return; - } + // These two function series are called to begin the session loop. The first + // method and all that come after it are called as a result of an async + // event completing. if any method in this chain returns before adding a new + // async event the session will end. + + // ****** Read Handshake Thread *********************** + // read_request -> handle_read_request -> + // write_response > handle_write_response -> + // read frame + // **************************************************** + + // Initiates the read of an HTTP request + void read_request() { + m_timer.expires_from_now(boost::posix_time::seconds(5)); - log_open_result(); + m_timer.async_wait( + boost::bind( + &session_type::handle_handshake_expired, + session_type::shared_from_this(), + boost::asio::placeholders::error + ) + ); - if (m_server_http_code != 101) { - std::stringstream err; - err << "Handshake ended with HTTP error: " << m_server_http_code << " " - << (m_server_http_string != "" ? m_server_http_string : lookup_http_error_string(m_server_http_code)); - log(err.str(),LOG_ERROR); - drop_tcp(); - // TODO: tell client that connection failed. - return; - } - - m_state = state::OPEN; - - // stop the handshake timer - m_timer.cancel(); - - if (m_local_interface) { - m_local_interface->on_open(session_type::shared_from_this()); - } - - reset_message(); - this->read_frame(); + boost::asio::async_read_until( + m_socket, + m_buf, + "\r\n\r\n", + boost::bind( + &session_type::handle_read_request, + session_type::shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred + ) + ); } - // TODO: endpoint specific - //virtual void handle_read_handshake(const boost::system::error_code& e,std::size_t bytes_transferred) = 0; - void handle_read_handshake(const boost::system::error_code& e, + // Callback for reading an HTTP request + void handle_read_request(const boost::system::error_code& e, std::size_t bytes_transferred) { - std::ostringstream line; - line << &m_buf; - m_raw_client_handshake += line.str(); + if (e) { + log_error("Error reading HTTP request",e); + drop_tcp(); + return; + } - access_log(m_raw_client_handshake,ALOG_HANDSHAKE); + try { + std::istream request(&m_buf); + + // TODO: use a more generic consume api where we just call read_some + // and have the handshake consume and validate as we go. + // + // For now, because it simplifies things we will use the parse_header + // member function which requires the complete header to be passed in + // initially. ASIO can guarantee us this. + // + // + //m_remote_handshake.consume(response_stream); + if (!m_request.parse_complete(request)) { + // not a valid HTTP request/response + throw handshake_error("Recieved invalid HTTP Request",http::status_code::BAD_REQUEST); + } + + // Log the raw handshake. + access_log(m_request.raw(),ALOG_HANDSHAKE); + + // confirm that this is a valid handshake / response for our endpoint type. + m_endpoint->validate_handshake(m_request); + + // set some connection state from the handshake + // The endpoint validation should have ensured that all of these values + // exist and are acceptable. + std::string h = m_request.header("Sec-WebSocket-Version"); + + m_version = (h == "" ? 0 : atoi(h.c_str())); + + h = (m_version < 13 ? "Sec-WebSocket-Origin" : "Origin"); + + m_origin = m_request.header(h); + + // TODO: extract subprotocols? + // TODO: extract extensions? + + // Check with the local interface to confirm that it wants to accept + // this connection. + if (m_local_interface) { + m_local_interface->validate(session_type::shared_from_this()); + } + + m_response.set_status(http::status_code::SWITCHING_PROTOCOLS); + } catch (const handshake_error& e) { + // TODO: add a hook here for passing the request to the local handler + // in case they can answer it instead of returning an error. + + std::stringstream err; + err << "Caught handshake exception: " << e.what(); + + access_log(e.what(),ALOG_HANDSHAKE); + log(err.str(),LOG_ERROR); + + m_response.set_status(e.m_http_error_code,e.m_http_error_msg); + } - std::vector tokens; + write_response(); + + /* legacy code here until I am sure I wont need any of it */ + + /*std::vector tokens; std::string::size_type start = 0; std::string::size_type end; @@ -432,39 +648,15 @@ public: m_client_headers[h] += ", " + tokens[i].substr(end+2); } } - } + }*/ + + // handshake error checking - try { + /*try { std::stringstream err; std::string h; - // check the method - if (m_client_http_request.substr(0,4) != "GET ") { - err << "Websocket handshake has invalid method: " - << m_client_http_request.substr(0,4); - - throw(handshake_error(err.str(),400)); - } - - // check the HTTP version - // TODO: allow versions greater than 1.1 - end = m_client_http_request.find(" HTTP/1.1",4); - if (end == std::string::npos) { - err << "Websocket handshake has invalid HTTP version"; - throw(handshake_error(err.str(),400)); - } - - m_resource = m_client_http_request.substr(4,end-4); - - // verify the presence of required headers - h = get_client_header("Host"); - if (h == "") { - throw(handshake_error("Required Host header is missing",400)); - } else if (!m_endpoint->validate_host(h)) { - err << "Host " << h << " is not one of this server's names."; - throw(handshake_error(err.str(),400)); - } h = get_client_header("Upgrade"); if (h == "") { @@ -529,17 +721,17 @@ public: m_server_http_code = e.m_http_error_code; m_server_http_string = e.m_http_error_msg; - } - - write_handshake(); + }*/ } -public: //protected: - // TODO: endpoint specific - void write_handshake() { + + // write the response to the client's request. + void write_response() { std::stringstream h; - if (m_server_http_code == 101) { - std::string server_key = get_client_header("Sec-WebSocket-Key"); + m_response.set_version("HTTP/1.1"); + + if (m_response.status_code() == http::status_code::SWITCHING_PROTOCOLS) { + std::string server_key = m_request.header("Sec-WebSocket-Key"); server_key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; SHA1 sha; @@ -556,67 +748,155 @@ public: //protected: } server_key = base64_encode( - reinterpret_cast(message_digest),20); + reinterpret_cast(message_digest),20 + ); // set handshake accept headers - set_header("Sec-WebSocket-Accept",server_key); - set_header("Upgrade","websocket"); - set_header("Connection","Upgrade"); + m_response.replace_header("Sec-WebSocket-Accept",server_key); + m_response.set_header("Upgrade","websocket"); + m_response.set_header("Connection","Upgrade"); } else { log("Error computing handshake sha1 hash.",LOG_ERROR); - m_server_http_code = 500; - m_server_http_string = ""; + m_response.set_status(http::status_code::INTERNAL_SERVER_ERROR); } } - // hardcoded server headers - set_header("Server","WebSocket++/2011-09-25"); - - h << "HTTP/1.1 " << m_server_http_code << " " - << (m_server_http_string != "" ? m_server_http_string : - lookup_http_error_string(m_server_http_code)) - << "\r\n"; - - header_list::iterator it; - for (it = m_server_headers.begin(); it != m_server_headers.end(); it++) { - h << it->first << ": " << it->second << "\r\n"; + if (m_subprotocol != "") { + m_response.replace_header("Sec-WebSocket-Protocol",m_subprotocol); } - h << "\r\n"; + // TODO: return negotiated extensions - m_raw_server_handshake = h.str(); + // hardcoded server headers + // TODO: make this configurable + m_response.replace_header("Server","WebSocket++/2011-10-31"); + + access_log(m_response.raw(),ALOG_HANDSHAKE); // start async write to handle_write_handshake boost::asio::async_write( m_socket, - boost::asio::buffer(m_raw_server_handshake), + boost::asio::buffer(m_response.raw()), boost::bind( - &session_type::handle_write_handshake, + &session_type::handle_write_response, session_type::shared_from_this(), boost::asio::placeholders::error ) ); } - - // TODO: endpoint specific - //virtual void read_handshake() = 0; - void read_handshake() { - m_timer.expires_from_now(boost::posix_time::seconds(5)); + + // + void handle_write_response(const boost::system::error_code& error) { + if (error) { + log_error("Error writing handshake response",error); + drop_tcp(); + return; + } - m_timer.async_wait( + log_open_result(); + + if (m_response.status_code() != http::status_code::SWITCHING_PROTOCOLS) { + std::stringstream err; + err << "Handshake ended with HTTP error: " << m_response.status_code() + << " " << m_response.status_code(); + log(err.str(),LOG_ERROR); + drop_tcp(); + // TODO: tell client that connection failed? + // use on_fail? + return; + } + + m_state = state::OPEN; + + // stop the handshake timer + m_timer.cancel(); + + if (m_local_interface) { + m_local_interface->on_open(session_type::shared_from_this()); + } + + reset_message(); + this->read_frame(); + } + + // ****** Write Handshake Thread ********************** + // write_request -> handle_write_request -> + // read_response > handle_read_response -> + // read frame + // **************************************************** + + void write_request() { + m_request.set_method("GET"); + m_request.set_uri(m_uri.resource); + m_request.set_version("HTTP/1.1"); + + // Set request headers + m_request.set_header("Upgrade","websocket"); + m_request.set_header("Connection","Upgrade"); + m_request.replace_header("Sec-WebSocket-Version","13"); + m_version = 13; + + std::stringstream host; + if (m_uri.port == (m_secure ? 443 : 80)) { + host << m_uri.host; + } else { + host << m_uri.host << ":" << m_uri.port; + } + m_request.replace_header("Host",host.str()); + + if (m_origin != "") { + m_request.replace_header("Origin",m_origin); + } + + std::string client_key; + int32_t raw_key[4]; + + /*boost::random::random_device rng; + boost::random::variate_generator > gen(rng, boost::random::uniform_int_distribution<>(INT32_MIN,INT32_MAX));*/ + + for (int i = 0; i < 4; i++) { + raw_key[i] = m_endpoint->get_rng().gen(); + } + + client_key = base64_encode(reinterpret_cast(raw_key), 16); + + access_log("Client key chosen: "+client_key, ALOG_HANDSHAKE); + + m_request.replace_header("Sec-WebSocket-Key",client_key); + + + + m_request.replace_header("User Agent","WebSocket++/2011-10-31"); + + // start async write to write the request + boost::asio::async_write( + m_socket, + boost::asio::buffer(m_request.raw()), boost::bind( - &session_type::handle_handshake_expired, + &session_type::handle_write_request, session_type::shared_from_this(), boost::asio::placeholders::error ) ); + } + + void handle_write_request(const boost::system::error_code& error) { + if (error) { + log_error("Error writing HTTP request",error); + drop_tcp(); + return; + } + read_response(); + } + + void read_response() { boost::asio::async_read_until( m_socket, m_buf, "\r\n\r\n", boost::bind( - &session_type::handle_read_handshake, + &session_type::handle_read_response, session_type::shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred @@ -624,6 +904,10 @@ public: //protected: ); } + void handle_read_response(const boost::system::error_code& e,std::size_t bytes_transferred) { + + } + void read_frame() { // the initial read in the handshake may have read in the first frame. // handle it (if it exists) before we read anything else. @@ -1053,7 +1337,6 @@ public: //protected: // reset session for a new message void reset_message() { - m_error = false; m_fragmented = false; m_current_message.clear(); @@ -1088,8 +1371,8 @@ public: //protected: msg << "[Connection " << this << "] " << m_socket.remote_endpoint() << " v" << m_version << " " - << (get_client_header("User-Agent") == "" ? "NULL" : get_client_header("User-Agent")) - << " " << m_resource << " " << m_server_http_code; + << (get_request_header("User-Agent") == "" ? "NULL" : get_request_header("User-Agent")) + << " " << m_uri.resource << " " << m_response.status_code(); access_log(msg.str(),ALOG_HANDSHAKE); } @@ -1116,6 +1399,7 @@ public: //protected: return false; } + void send_close(close::status::value status,const std::string& reason) { if (m_state != state::OPEN) { log("Tried to disconnect a session that wasn't open",LOG_WARN); @@ -1187,27 +1471,19 @@ private: } } -protected: - // Immutable state about the current connection from the handshake - // Client handshake - std::string m_raw_client_handshake; - std::string m_client_http_request; - std::string m_resource; - std::string m_client_origin; - header_list m_client_headers; - std::vector m_client_subprotocols; - std::vector m_client_extensions; + http::parser::request m_request; + http::parser::response m_response; + + // some settings about the connection + std::vector m_requested_subprotocols; + std::vector m_requested_extensions; + std::string m_subprotocol; + std::vector m_extensions; + std::string m_origin; unsigned int m_version; - - // Server handshake - std::string m_raw_server_handshake; - std::string m_server_http_request; - header_list m_server_headers; - std::string m_server_subprotocol; - std::vector m_server_extensions; - uint16_t m_server_http_code; - std::string m_server_http_string; - + bool m_secure; + ws_uri m_uri; + // Mutable connection state; uint8_t m_state; bool m_writing; @@ -1241,13 +1517,8 @@ protected: // frame parsers frame::parser m_read_frame; frame::parser m_write_frame; - - // unknown - bool m_error; }; - - } #endif // WEBSOCKET_SESSION_HPP diff --git a/src/websocketpp.hpp b/src/websocketpp.hpp index 2f9c73ffe8..356148b43b 100644 --- a/src/websocketpp.hpp +++ b/src/websocketpp.hpp @@ -23,8 +23,6 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * This Makefile was derived from a similar one included in the libjson project - * It's authors were Jonathan Wallace and Bernhard Fluehmann. */ #ifndef WEBSOCKETPP_HPP diff --git a/test/basic/logging.cpp b/test/basic/logging.cpp new file mode 100644 index 0000000000..4dee510f5a --- /dev/null +++ b/test/basic/logging.cpp @@ -0,0 +1,40 @@ +/* + * 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. + * + */ + +#include "../../src/logger/logger.hpp" + +int main () { + websocketpp::log::logger log; + + log.set_level(websocketpp::log::elog_level::ALL); + log.at(websocketpp::log::elog_level::DEBUG) << "debug: " << 5 << websocketpp::log::endl; + log.at(websocketpp::log::elog_level::INFO) << "info: " << 5 << websocketpp::log::endl; + log.at(websocketpp::log::elog_level::WARN) << "warn: " << 5 << websocketpp::log::endl; + log.at(websocketpp::log::elog_level::ERROR) << "error: " << 5 << websocketpp::log::endl; + log.at(websocketpp::log::elog_level::FATAL) << "fatal: " << 5 << websocketpp::log::endl; + +} \ No newline at end of file diff --git a/websocketpp.xcodeproj/project.pbxproj b/websocketpp.xcodeproj/project.pbxproj index 237792e096..975e9529a9 100644 --- a/websocketpp.xcodeproj/project.pbxproj +++ b/websocketpp.xcodeproj/project.pbxproj @@ -135,6 +135,8 @@ B61387A3145D846400ED9B19 /* boost_rng.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = boost_rng.hpp; path = src/rng/boost_rng.hpp; sourceTree = ""; }; B61387A5145D849E00ED9B19 /* constants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = constants.hpp; path = src/http/constants.hpp; sourceTree = ""; }; B61387A6145D849E00ED9B19 /* parser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = parser.hpp; path = src/http/parser.hpp; sourceTree = ""; }; + B61387B31462AD4900ED9B19 /* session_handler_interface.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = session_handler_interface.hpp; path = src/session_handler_interface.hpp; sourceTree = ""; }; + B61387B61462B35700ED9B19 /* logger.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = logger.hpp; path = src/logger/logger.hpp; sourceTree = ""; }; B6828875143745DA002BA48B /* chat_client_handler.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = chat_client_handler.cpp; path = examples/chat_client/chat_client_handler.cpp; sourceTree = ""; }; B6828876143745DA002BA48B /* chat_client_handler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = chat_client_handler.hpp; path = examples/chat_client/chat_client_handler.hpp; sourceTree = ""; }; B6828877143745DA002BA48B /* chat_client.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = chat_client.cpp; path = examples/chat_client/chat_client.cpp; sourceTree = ""; }; @@ -250,6 +252,14 @@ name = http; sourceTree = ""; }; + B61387B51462B34400ED9B19 /* logger */ = { + isa = PBXGroup; + children = ( + B61387B61462B35700ED9B19 /* logger.hpp */, + ); + name = logger; + sourceTree = ""; + }; B6CF18121437C370009295BE /* echo_client */ = { isa = PBXGroup; children = ( @@ -295,6 +305,7 @@ B6DF1C7F1434ABB70029A1B1 /* src */ = { isa = PBXGroup; children = ( + B61387B51462B34400ED9B19 /* logger */, B61387A4145D847A00ED9B19 /* http */, B6FE8D09145B0F7400B32547 /* rng */, B6FE8D05145AFF5F00B32547 /* websocket_constants.hpp */, @@ -302,6 +313,7 @@ B6DF1C931434AC470029A1B1 /* websocket_client_session.hpp */, B6DF1C941434AC470029A1B1 /* websocket_client.cpp */, B6DF1C951434AC470029A1B1 /* websocket_client.hpp */, + B61387B31462AD4900ED9B19 /* session_handler_interface.hpp */, B6DF1C961434AC470029A1B1 /* websocket_connection_handler.hpp */, B6BE76E9144EF53000716A77 /* websocket_endpoint.hpp */, B6DF1C971434AC470029A1B1 /* websocket_frame.cpp */,