diff --git a/.gitignore b/.gitignore index 48d6c63c0c..5c1b2fbd4f 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,5 @@ test/basic/tests libwebsocketpp.dylib.0.1.0 websocketpp.xcodeproj/xcuserdata/* -websocketpp.xcodeproj/project.xcworkspace/xcuserdata/* \ No newline at end of file +websocketpp.xcodeproj/project.xcworkspace/xcuserdata/* +policy_based_notes.hpp diff --git a/examples/chat_client/chat_client_handler.cpp b/examples/chat_client/chat_client_handler.cpp index 38f8ff80ee..e16a5f4717 100644 --- a/examples/chat_client/chat_client_handler.cpp +++ b/examples/chat_client/chat_client_handler.cpp @@ -39,7 +39,7 @@ void chat_client_handler::on_open(session_ptr s) { std::cout << "Successfully connected" << std::endl; } -void chat_client_handler::on_close(session_ptr s,uint16_t status,const std::string &reason) { +void chat_client_handler::on_close(session_ptr s) { // not sure if anything needs to happen here either. m_session = client_session_ptr(); diff --git a/examples/chat_client/chat_client_handler.hpp b/examples/chat_client/chat_client_handler.hpp index fb90a6e7aa..0b1f0e0676 100644 --- a/examples/chat_client/chat_client_handler.hpp +++ b/examples/chat_client/chat_client_handler.hpp @@ -63,7 +63,7 @@ public: void on_open(session_ptr s); // connection to chat room closed - void on_close(session_ptr s,uint16_t status,const std::string &reason); + void on_close(session_ptr s); // got a new message from server void on_message(session_ptr s,const std::string &msg); diff --git a/examples/chat_server/Makefile b/examples/chat_server/Makefile index 882386ceee..9906162064 100644 --- a/examples/chat_server/Makefile +++ b/examples/chat_server/Makefile @@ -7,7 +7,7 @@ SHARED ?= "1" ifeq ($(SHARED), 1) LDFLAGS := $(LDFLAGS) -lboost_system -lboost_date_time -lwebsocketpp else - LDFLAGS := $(LDFLAGS) -lboost_system -lboost_date_time -lboost_regex -lboost_random ../../libwebsocketpp.a + LDFLAGS := $(LDFLAGS) -lboost_system -lboost_date_time -lboost_regex -lboost_random -lboost_program_options ../../libwebsocketpp.a endif chat_server: chat_server.o chat.o diff --git a/examples/chat_server/chat.cpp b/examples/chat_server/chat.cpp index d346281f99..9b629b45fb 100644 --- a/examples/chat_server/chat.cpp +++ b/examples/chat_server/chat.cpp @@ -59,7 +59,7 @@ void chat_server_handler::on_open(session_ptr client) { send_to_all(encode_message("server",m_connections[client]+" has joined the chat.")); } -void chat_server_handler::on_close(session_ptr client,uint16_t status,const std::string &reason) { +void chat_server_handler::on_close(session_ptr client) { std::map::iterator it = m_connections.find(client); if (it == m_connections.end()) { diff --git a/examples/chat_server/chat.hpp b/examples/chat_server/chat.hpp index 3eb9b721ce..0ef21c325c 100644 --- a/examples/chat_server/chat.hpp +++ b/examples/chat_server/chat.hpp @@ -59,7 +59,7 @@ public: void on_open(websocketpp::session_ptr client); // someone disconnected from the lobby, remove them - void on_close(websocketpp::session_ptr client,uint16_t status,const std::string &reason); + void on_close(websocketpp::session_ptr client); void on_message(websocketpp::session_ptr client,const std::string &msg); diff --git a/examples/echo_client/Makefile b/examples/echo_client/Makefile index 2be6a3b967..901343976e 100644 --- a/examples/echo_client/Makefile +++ b/examples/echo_client/Makefile @@ -7,7 +7,7 @@ SHARED ?= "1" ifeq ($(SHARED), 1) LDFLAGS := $(LDFLAGS) -lboost_system -lboost_thread -lwebsocketpp else - LDFLAGS := $(LDFLAGS) -lboost_system -lboost_thread -lboost_date_time -lboost_regex -lboost_random ../../libwebsocketpp.a + LDFLAGS := $(LDFLAGS) -lboost_system -lboost_thread -lboost_date_time -lboost_regex -lboost_random -lboost_program_options ../../libwebsocketpp.a endif echo_client: echo_client.o echo_client_handler.o diff --git a/examples/echo_client/echo_client.cpp b/examples/echo_client/echo_client.cpp index e0f05de88e..62174ca986 100644 --- a/examples/echo_client/echo_client.cpp +++ b/examples/echo_client/echo_client.cpp @@ -27,9 +27,10 @@ #include "echo_client_handler.hpp" -#include +#include "../../src/websocketpp.hpp" #include #include +#include #include @@ -53,12 +54,32 @@ int main(int argc, char* argv[]) { websocketpp::client_ptr client(new websocketpp::client(io_service,c)); client->init(); - client->set_header("User Agent","WebSocket++/2011-09-25"); + client->set_header("User Agent","WebSocket++/2011-10-27"); client->connect("ws://localhost:9001/getCaseCount"); - io_service.run(); + std::cout << "case count: " << c->m_case_count << std::endl; + + for (int i = 1; i <= c->m_case_count; i++) { + io_service.reset(); + + client->set_alog_level(websocketpp::ALOG_OFF); + client->set_elog_level(websocketpp::LOG_OFF); + + client->init(); + client->set_header("User Agent","WebSocket++/2011-10-27"); + + + std::stringstream url; + + url << "ws://localhost:9001/runCase?case=" << i << "&agent=\"WebSocket++Snapshot/2011-10-27\""; + + client->connect(url.str()); + + io_service.run(); + } + std::cout << "done" << std::endl; } catch (std::exception& e) { diff --git a/examples/echo_client/echo_client_handler.cpp b/examples/echo_client/echo_client_handler.cpp index ab43350161..b91b4d69f1 100644 --- a/examples/echo_client/echo_client_handler.cpp +++ b/examples/echo_client/echo_client_handler.cpp @@ -28,22 +28,23 @@ #include "echo_client_handler.hpp" #include +#include using websocketecho::echo_client_handler; using websocketpp::client_session_ptr; void echo_client_handler::on_open(session_ptr s) { - std::cout << "Successfully connected: " << s->get_resource() << std::endl; + std::cout << " Successfully connected (handshake complete): " << s->get_resource() << std::endl; } -void echo_client_handler::on_close(session_ptr s,uint16_t status,const std::string &reason) { - std::cout << "client was disconnected" << std::endl; +void echo_client_handler::on_close(session_ptr s) { + std::cout << " client was disconnected (WS state is now CLOSED)" << std::endl; } void echo_client_handler::on_message(session_ptr s,const std::string &msg) { if (s->get_resource() == "/getCaseCount") { - std::cout << "msg |" << msg.substr(1,msg.size()-2) << "|" << std::endl; - m_case_count = atoi(msg.substr(1,msg.size()-2).c_str()); + std::cout << "Detected " << msg << " test cases." << std::endl; + m_case_count = atoi(msg.c_str()); } else { s->send(msg); } diff --git a/examples/echo_client/echo_client_handler.hpp b/examples/echo_client/echo_client_handler.hpp index a2c3c385c3..f011c56265 100644 --- a/examples/echo_client/echo_client_handler.hpp +++ b/examples/echo_client/echo_client_handler.hpp @@ -56,21 +56,18 @@ public: echo_client_handler() : m_case_count(0) {} virtual ~echo_client_handler() {} - // ignored for clients? - void validate(session_ptr s) {} - // connection to chat room complete void on_open(session_ptr s); // connection to chat room closed - void on_close(session_ptr s,uint16_t status,const std::string &reason); + void on_close(session_ptr sn); // got a new message from server void on_message(session_ptr s,const std::string &msg); // ignore messages void on_message(session_ptr s,const std::vector &data); -private: + int m_case_count; }; diff --git a/examples/echo_server/Makefile b/examples/echo_server/Makefile index df12f2a59d..ab1c38d55d 100644 --- a/examples/echo_server/Makefile +++ b/examples/echo_server/Makefile @@ -7,7 +7,7 @@ SHARED ?= "1" ifeq ($(SHARED), 1) LDFLAGS := $(LDFLAGS) -lboost_system -lboost_date_time -lwebsocketpp else - LDFLAGS := $(LDFLAGS) -lboost_system -lboost_date_time -lboost_regex -lboost_random ../../libwebsocketpp.a + LDFLAGS := $(LDFLAGS) -lboost_system -lboost_date_time -lboost_regex -lboost_random -lboost_program_options ../../libwebsocketpp.a endif echo_server: echo_server.o echo.o diff --git a/examples/echo_server/echo.hpp b/examples/echo_server/echo.hpp index 3416f8b0b2..c2c989a6a8 100644 --- a/examples/echo_server/echo.hpp +++ b/examples/echo_server/echo.hpp @@ -48,8 +48,9 @@ public: // an echo server is stateless. // The handler has no need to keep track of connected clients. + void on_fail(session_ptr client) {} void on_open(session_ptr client) {} - void on_close(session_ptr client,uint16_t status,const std::string &reason) {} + void on_close(session_ptr client) {} // both text and binary messages are echoed back to the sending client. void on_message(session_ptr client,const std::string &msg); diff --git a/examples/echo_server/echo_server.cpp b/examples/echo_server/echo_server.cpp index 021c2656de..25ab4b5a50 100644 --- a/examples/echo_server/echo_server.cpp +++ b/examples/echo_server/echo_server.cpp @@ -60,7 +60,14 @@ int main(int argc, char* argv[]) { new websocketpp::server(io_service,endpoint,echo_handler) ); + //server->parse_command_line(argc, argv); + + + // setup server settings + //server->set_alog_level(websocketpp::ALOG_OFF); + //server->set_elog_level(websocketpp::LOG_OFF); + server->add_host(host); server->add_host(full_host); diff --git a/readme.txt b/readme.txt index 8570367bc7..6badd7dca1 100644 --- a/readme.txt +++ b/readme.txt @@ -105,3 +105,195 @@ Acknowledgements - Autobahn test suite - testing by Keith Brisson + + +API spec notes + + + +## Server API ## +websocketpp.hpp + +create a websocketpp::server_ptr initialized to a new websocketpp::server object + +the server constructor will need three things. +- A boost::asio::io_service object to use to manage its async operations +- A boost::asio::ip::tcp::endpoint to listen to for new connections +- An object that impliments the websocketpp::connection_handler interface to provide callback functions (See Handler API) + +After construction the server object will be in the initialization state. At this time you can set up server config options either via calling individual set option commands or by loading them in a batch from a config file. + +The only required option is that at least one host value must be set. Incoming websocket connections must specify a host value that they wish to connect to and if the server object does not have that host value in it's list of canonical hosts it will reject the connection. + +[note about settings that can be changed live?] + +Once the server has been configured the way you want, call the start_accept() method. This will add the first async call to your io_service. If your io_service was already running, the server will start accepting connections immediately. If not you will need to call io_service.run() to start it. + +Once the server has started it will accept new connections. A new session object will be created for each connection accepted. The session will perform the websocket handshake and if it is successful begin reading frames. The session will continue reading frames until an error occurs or a connection close frame is seen. The session will notify the handler that it was initilized with (see Handler API) as necessary. The Session API defines how a handler (or other part of the end application) can interact with the session (to get information about the session, send messages back to the client, etc) + +## Client API ## +include websocketpp.hpp + +create a websocketpp::client_ptr initialized to a new websocketpp::client object + +the client constructor will need: +- A boost::asio::io_service object to use to manage its async operations +- An object that impliments the websocketpp::connection_handler interface to privde callback functions (See Handler API) + +After construction, the client object will be in the initialization state. At this time you can set up client config options either via calling individual set options commands or by loading them in a batch from a config file. + +Opening a new connection: +Per the websocket spec, a client can only have one connection in the connecting state at a time. Client method new_session() will create a new session and return a shared pointer to it. After this point new_session will throw an exception if you attempt to call it again before the most recently created session has either successfully connected or failed to connect. new_session() +- call websocketpp::client::new_session(). This will return a session_ptr + + +## Handler API ## +The handler API defines the interface that a websocketpp session will use to communicate information about the session state and new messages to your application. + +A client or server must be initialized with a default handler that will be used for all sessions. The default handler may pass a session off to another handler as necessary. + +A handler must impliment the following methods: +- validate(session_ptr) +- on_fail(session_ptr) +- on_open(session_ptr) +- on_close(session_ptr) +- on_message(session_ptr,const std::vector &) +- on_message(session_ptr,const std::string &) + +validate will be called after a websocket handshake has been received and before it is accepted. It provides a handler the ability to refuse a connection based on application specific logic (ex: restrict domains or negotiate subprotocols). To reject the connection throw a handshake_error. Validate is never called for client sessions. To refuse a client session (ex: if you do not like the set of extensions/subprotocols the server chose) you can close the connection immediately in the on_open member function. + +on_fail is called whenever a session is terminated or failed before it was successfully established. This happens if there is an error during the handshake process or if the server refused the connection. +on_fail will be the last time a session calls its handler. If your application will need information from `session` after this function you should either save the session_ptr somewhere or copy the data out. + +on_open is called after the websocket session has been successfully established and is in the OPEN state. The session is now avaliable to send messages and will begin reading frames and calling the on_message/on_close/on_error callbacks. A client may reject the connection by closing the session at this point. + +on_close is called whenever an open session is closed for any reason. This can be due to either endpoint requesting a connection close or an error occuring. Information about why the session was closed can be extracted from the session itself. +on_close will be the last time a session calls its handler. If your application will need information from `session` after this function you should either save the session_ptr somewhere or copy the data out. + +on_message (binary version) will be called when a binary message is recieved. Message data is passed as a vector of bytes (unsigned char). data will not be avaliable after this callback ends so the handler must either completely process the message or copy it somewhere else for processing later. + +TODO: Notes about thread safety + + +## Session API ## +The Session API allows a handler to look up information about a session as well as interact with that session (send messages, close the connection, etc) + +Session pointers are returned with every handler callback as well as every call to websocketpp::client::connect. + + +Handler Interface: +- set_handler(connection_handler_ptr) + +Handshake Interface: +For the client these methods are valid after the server's handshake has been received. This is guaranteed to be the case by the time `on_open` is called. +For the server these methods are valid after the client's handshake has been received. This is guaranteed to be the case by the time `validate` is called. + +- const std::string& get_subprotocol() const; +- const std::string& get_resource() const; +- const std::string& get_origin() const; +- std::string get_client_header(const std::string&) const; +- std::string get_server_header(const std::string&) const; +- const std::vector& get_extensions() const; +- unsigned int get_version() const; + +Frame Interface +- void send(const std::string &); +- void send(const std::vector &); +- void ping(const std::string &); +- void pong(const std::string &); + +These methods are valid only for open connections. They will throw an exception if called from any other state. + +WebSocket++ does not queue messages. As such only one send operation can be occuring at once. +TODO: failure behavior. OPTIONS: +- send will throw a `session_busy` exception if busy +- send will return true/false +- a callback could be defined letting the handler know that it is safe to write again. + +Session Interface +- void close(uint16_t status,const std::string &reason); +- bool is_server() const; + + + + +-------------------------------- +screwing around with a policy based refactoring +-------------------------------- + +template +class endpoint : public WebSocketRole, Logger { +public: + endpoint(connection_handler_ptr); + + size_t get_connected_client_count() const; + + void set_endpoint(const tcp::endpoint& endpoint); // asio::bind + +private: + std::list m_connections; + connection_handler_ptr m_handler; + + boost::asio::io_service m_io_service; + tcp::acceptor m_acceptor; +} + +class server_interface { +public: + void add_host(const std::string &host); + void remove_host(const std::string &host); + bool validate_host(const std::string &host) const; + + void set_max_message_size(uint64_t size); + bool validate_message_size(uint64_t size) const; + + void start() { + // start_accept() + // io_service.run() + } +private: + void start_accept(); + void handle_accept(session_ptr session, const boost::system::error_code&) + + std::set m_hosts; + uint64_t m_max_message_size; +} + +class client_interface { +public: + session_ptr connect(const std::string &url); +protected: + +private: + +} + + + +template +class session : public HandshakePolicy { +public: + session(); +private: + +} + +namespace websocketpp { +namespace handshake { + +/* a handshake policy must define: +void on_connect(); +bool is_server() const; + +*/ + +class server { + +} + +class client { + +} + +} // handshake +} // websocketpp \ No newline at end of file diff --git a/src/websocket_client.cpp b/src/websocket_client.cpp index 69bdd1f89f..1e08d9d6bd 100644 --- a/src/websocket_client.cpp +++ b/src/websocket_client.cpp @@ -37,8 +37,8 @@ using boost::asio::ip::tcp; client::client(boost::asio::io_service& io_service, websocketpp::connection_handler_ptr defc) - : m_elog_level(LOG_ALL), - m_alog_level(ALOG_ALL), + : 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), @@ -46,11 +46,13 @@ client::client(boost::asio::io_service& io_service, m_def_con_handler(defc) {} void client::init() { + // TODO: sanity check whether the session buffer size bound could be reduced m_client_session = client_session_ptr( new client_session( shared_from_this(), m_io_service, - m_def_con_handler + m_def_con_handler, + m_max_message_size*2 ) ); m_state = CLIENT_STATE_INITIALIZED; @@ -180,6 +182,12 @@ void client::access_log(std::string msg,uint16_t level) { void client::handle_connect(const boost::system::error_code& error) { if (!error) { + std::stringstream err; + err << "Successful Connection "; + log(err.str(),LOG_ERROR); + + std::cout << boost::posix_time::to_iso_extended_string(boost::posix_time::microsec_clock::local_time()) << " TCP established" << std::endl; + m_state = CLIENT_STATE_CONNECTED; m_client_session->on_connect(); } else { diff --git a/src/websocket_client.hpp b/src/websocket_client.hpp index 89bbe27395..0ef1437178 100644 --- a/src/websocket_client.hpp +++ b/src/websocket_client.hpp @@ -61,31 +61,6 @@ private: class client : public boost::enable_shared_from_this { public: - // System logging levels - /* static const uint16_t LOG_ALL = 0; - static const uint16_t LOG_DEBUG = 1; - static const uint16_t LOG_INFO = 2; - static const uint16_t LOG_WARN = 3; - static const uint16_t LOG_ERROR = 4; - static const uint16_t LOG_FATAL = 5; - static const uint16_t LOG_OFF = 6; - - // Access logging controls - // Individual bits - static const uint16_t ALOG_CONNECT = 0x1; - static const uint16_t ALOG_DISCONNECT = 0x2; - static const uint16_t ALOG_MISC_CONTROL = 0x4; - static const uint16_t ALOG_FRAME = 0x8; - static const uint16_t ALOG_MESSAGE = 0x10; - static const uint16_t ALOG_INFO = 0x20; - static const uint16_t ALOG_HANDSHAKE = 0x40; - // Useful groups - static const uint16_t ALOG_OFF = 0x0; - static const uint16_t ALOG_CONTROL = ALOG_CONNECT - & ALOG_DISCONNECT - & ALOG_MISC_CONTROL; - static const uint16_t ALOG_ALL = 0xFFFF; - */ static const uint16_t CLIENT_STATE_NULL = 0; static const uint16_t CLIENT_STATE_INITIALIZED = 1; static const uint16_t CLIENT_STATE_CONNECTING = 2; diff --git a/src/websocket_client_session.cpp b/src/websocket_client_session.cpp index 456a01e505..8532afdde6 100644 --- a/src/websocket_client_session.cpp +++ b/src/websocket_client_session.cpp @@ -47,8 +47,9 @@ using websocketpp::client_session; client_session::client_session (websocketpp::client_ptr c, boost::asio::io_service& io_service, - websocketpp::connection_handler_ptr defc) - : session(io_service,defc),m_client(c) {} + websocketpp::connection_handler_ptr defc, + uint64_t buf_size) + : session(io_service,defc,buf_size),m_client(c) {} void client_session::on_connect() { // TODO: section 4.1: Figure out if we have another connection to this @@ -123,57 +124,57 @@ void client_session::read_handshake() { void client_session::handle_read_handshake(const boost::system::error_code& e, std::size_t bytes_transferred) { + + if (e) { + log_error("Error reading server handshake",e); + drop_tcp(); + return; + } + // parse server handshake - - - // read handshake and set local state (or pass to write_handshake) - std::ostringstream line; - line << &m_buf; - m_raw_server_handshake += line.str(); - - m_buf << m_raw_server_handshake.substr(bytes_transferred); - - std::stringstream foo; - - foo << "data size: " << m_raw_server_handshake.size() - << " bytes transferred" << bytes_transferred; - - m_client->access_log(foo.str(), ALOG_HANDSHAKE); - m_client->access_log(m_raw_server_handshake,ALOG_HANDSHAKE); - m_client->access_log("SPACER",ALOG_HANDSHAKE); - - std::vector tokens; - std::string::size_type start = 0; + std::istream response_stream(&m_buf); + std::string header; std::string::size_type end; - // Get request and parse headers - end = m_raw_server_handshake.find("\r\n",start); - - while(end != std::string::npos) { - tokens.push_back(m_raw_server_handshake.substr(start, end - start)); - - start = end + 2; - - end = m_raw_server_handshake.find("\r\n",start); + // get status line + std::getline(response_stream, header); + if (header[header.size()-1] == '\r') { + header.erase(header.end()-1); + m_server_http_request = header; + m_raw_server_handshake += header+"\n"; } - for (size_t i = 0; i < tokens.size(); i++) { - if (i == 0) { - m_server_http_request = tokens[i]; + // get headers + while (std::getline(response_stream, header) && header != "\r") { + if (header[header.size()-1] != '\r') { + continue; // ignore malformed header lines? + } else { + header.erase(header.end()-1); } - end = tokens[i].find(": ",0); + end = header.find(": ",0); - if (end != std::string::npos) { - std::string h = tokens[i].substr(0,end); + if (end != std::string::npos) { + std::string h = header.substr(0,end); if (get_server_header(h) == "") { - m_server_headers[h] = tokens[i].substr(end+2); + m_server_headers[h] = header.substr(end+2); } else { - m_server_headers[h] += ", " + tokens[i].substr(end+2); + m_server_headers[h] += ", " + header.substr(end+2); } } + + m_raw_server_handshake += header+"\n"; } + // temporary debugging + if (m_buf.size() > 0) { + std::stringstream foo; + foo << "bytes left over: " << m_buf.size(); + access_log(foo.str(), ALOG_HANDSHAKE); + } + + m_client->access_log(m_raw_server_handshake,ALOG_HANDSHAKE); + // handshake error checking try { std::stringstream err; @@ -202,7 +203,7 @@ void client_session::handle_read_handshake(const boost::system::error_code& e, if (h == "") { throw(handshake_error("Required Upgrade header is missing",400)); } else if (!boost::iequals(h,"websocket")) { - err << "Upgrade header was " << h << " instead of \"websocket\""; + err << "Upgrade header was \"" << h << "\" instead of \"websocket\""; throw(handshake_error(err.str(),400)); } @@ -261,7 +262,7 @@ void client_session::handle_read_handshake(const boost::system::error_code& e, log_open_result(); - m_status = OPEN; + m_state = STATE_OPEN; if (m_local_interface) { m_local_interface->on_open(shared_from_this()); @@ -332,8 +333,8 @@ void client_session::write_handshake() { void client_session::handle_write_handshake(const boost::system::error_code& error) { if (error) { - handle_error("Error writing handshake",error); - // TODO: close behavior + log_error("Error writing handshake",error); + drop_tcp(); return; } diff --git a/src/websocket_client_session.hpp b/src/websocket_client_session.hpp index 510f02cd66..2dd431e272 100644 --- a/src/websocket_client_session.hpp +++ b/src/websocket_client_session.hpp @@ -65,7 +65,8 @@ class client_session : public session { public: client_session (client_ptr c, boost::asio::io_service& io_service, - connection_handler_ptr defc); + connection_handler_ptr defc, + uint64_t buf_size); /*** CLIENT INTERFACE ***/ diff --git a/src/websocket_connection_handler.hpp b/src/websocket_connection_handler.hpp index 5a1778e274..f3d93e43bf 100644 --- a/src/websocket_connection_handler.hpp +++ b/src/websocket_connection_handler.hpp @@ -49,39 +49,62 @@ public: // connection based on application specific logic (ex: restrict domains or // negotiate subprotocols). To reject the connection throw a handshake_error // + // Validate is never called for client sessions. To refuse a client session + // (ex: if you do not like the set of extensions/subprotocols the server + // chose) you can close the connection immediately in the on_open method. + // // handshake_error parameters: // log_message - error message to send to server log // http_error_code - numeric HTTP error code to return to the client // http_error_msg - (optional) string HTTP error code to return to the // client (useful for returning non-standard error codes) - virtual void validate(session_ptr client) = 0; - - + virtual void validate(session_ptr session) {}; - // this will be called once the connected websocket is avaliable for - // writing messages. client may be a new websocket session or an existing - // session that was recently passed to this handler. - virtual void on_open(session_ptr client) = 0; - + // on_open is called after the websocket session has been successfully + // established and is in the OPEN state. The session is now avaliable to + // send messages and will begin reading frames and calling the on_message/ + // on_close/on_error callbacks. A client may reject the connection by + // closing the session at this point. + virtual void on_open(session_ptr session) = 0; - // this will be called when the connected websocket is no longer avaliable - // for writing messages. This occurs under the following conditions: - // - Disconnect message recieved from the remote endpoint - // - Someone (usually this object) calls the disconnect method of session - // - A disconnect acknowledgement is recieved (in case another object - // calls the disconnect method of session - // - The connection handler assigned to this client was set to another - // handler - virtual void on_close(session_ptr client,uint16_t status,const std::string &reason) = 0; + // on_close is called whenever an open session is closed for any reason. + // This can be due to either endpoint requesting a connection close or an + // error occuring. Information about why the session was closed can be + // extracted from the session itself. + // + // on_close will be the last time a session calls its handler. If your + // application will need information from `session` after this function you + // should either save the session_ptr somewhere or copy the data out. + virtual void on_close(session_ptr session) = 0; - // this will be called when a text message is recieved. Text will be - // encoded as UTF-8. - virtual void on_message(session_ptr client,const std::string &msg) = 0; + // on_message (binary version) will be called when a binary message is + // recieved. Message data is passed as a vector of bytes (unsigned char). + // data will not be avaliable after this callback ends so the handler must + // either completely process the message or copy it somewhere else for + // processing later. + virtual void on_message(session_ptr session, + const std::vector &data) = 0; - // this will be called when a binary message is recieved. Argument is a - // vector of the raw bytes in the message body. - virtual void on_message(session_ptr client, - const std::vector &data) = 0; + // on_message (text version). Identical to on_message except the data + // parameter is a string interpreted as UTF-8. WebSocket++ guarantees that + // this string is valid UTF-8. + virtual void on_message(session_ptr session,const std::string &msg) = 0; + + + + // #### optional error cases #### + + // on_fail is called whenever a session is terminated or failed before it + // was successfully established. This happens if there is an error during + // the handshake process or if the server refused the connection. + // + // on_fail will be the last time a session calls its handler. If your + // application will need information from `session` after this function you + // should either save the session_ptr somewhere or copy the data out. + virtual void on_fail(session_ptr session) {}; + + // experimental + virtual void on_ping_timeout(session_ptr session) {} }; diff --git a/src/websocket_endpoint.hpp b/src/websocket_endpoint.hpp new file mode 100644 index 0000000000..4f80bf992f --- /dev/null +++ b/src/websocket_endpoint.hpp @@ -0,0 +1,55 @@ +/* + * 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 WEBSOCKET_ENDPOINT_HPP +#define WEBSOCKET_ENDPOINT_HPP + +#include + +#include +#include + +namespace websocketpp { + class endpoint; + typedef boost::shared_ptr endpoint_ptr; +} + +#include "websocket_session.hpp" + +namespace websocketpp { + +class endpoint : public boost::enable_shared_from_this { +public: + virtual bool is_server() = 0; + // log + // access_log +}; + + + +} +#endif // WEBSOCKET_ENDPOINT_HPP diff --git a/src/websocket_frame.cpp b/src/websocket_frame.cpp index 6be794f501..c630f05007 100644 --- a/src/websocket_frame.cpp +++ b/src/websocket_frame.cpp @@ -41,6 +41,123 @@ using websocketpp::frame; + +uint8_t frame::get_state() const { + return m_state; +} + +uint64_t frame::get_bytes_needed() const { + return m_bytes_needed; +} + +void frame::reset() { + m_state = STATE_BASIC_HEADER; + m_bytes_needed = BASIC_HEADER_LENGTH; + m_degraded = false; + m_payload.empty(); + memset(m_header,0,MAX_HEADER_LENGTH); +} + +// Method invariant: One of the following must always be true even in the case +// of exceptions. +// - m_bytes_needed > 0 +// - m-state = STATE_READY +void frame::consume(std::istream &s) { + try { + switch (m_state) { + case STATE_BASIC_HEADER: + s.read(&m_header[BASIC_HEADER_LENGTH-m_bytes_needed],m_bytes_needed); + + m_bytes_needed -= s.gcount(); + + if (m_bytes_needed == 0) { + process_basic_header(); + + validate_basic_header(); + + if (m_bytes_needed > 0) { + m_state = STATE_EXTENDED_HEADER; + } else { + process_extended_header(); + + if (m_bytes_needed == 0) { + m_state = STATE_READY; + process_payload(); + + } else { + m_state = STATE_PAYLOAD; + } + } + } + break; + case STATE_EXTENDED_HEADER: + s.read(&m_header[get_header_len()-m_bytes_needed],m_bytes_needed); + + m_bytes_needed -= s.gcount(); + + if (m_bytes_needed == 0) { + process_extended_header(); + if (m_bytes_needed == 0) { + m_state = STATE_READY; + process_payload(); + } else { + m_state = STATE_PAYLOAD; + } + } + break; + case STATE_PAYLOAD: + s.read(reinterpret_cast(&m_payload[m_payload.size()-m_bytes_needed]), + m_bytes_needed); + + m_bytes_needed -= s.gcount(); + + if (m_bytes_needed == 0) { + m_state = STATE_READY; + process_payload(); + } + break; + case STATE_RECOVERY: + // Recovery state discards all bytes that are not the first byte + // of a close frame. + do { + s.read(reinterpret_cast(&m_header[0]),1); + + //std::cout << std::hex << int(static_cast(m_header[0])) << " "; + + if (int(static_cast(m_header[0])) == 0x88) { + //(BPB0_FIN && CONNECTION_CLOSE) + m_bytes_needed--; + m_state = STATE_BASIC_HEADER; + break; + } + } while (s.gcount() > 0); + + //std::cout << std::endl; + + break; + default: + break; + } + + /*if (s.gcount() == 0) { + throw frame_error("consume read zero bytes",FERR_FATAL_SESSION_ERROR); + }*/ + } catch (const frame_error& e) { + // After this point all non-close frames must be considered garbage, + // including the current one. Reset it and put the reading frame into + // a recovery state. + if (m_degraded == true) { + throw frame_error("An error occurred while trying to gracefully recover from a less serious frame error.",FERR_FATAL_SESSION_ERROR); + } else { + reset(); + m_state = STATE_RECOVERY; + m_degraded = true; + + throw e; + } + } +} + char* frame::get_header() { return m_header; } @@ -66,8 +183,8 @@ unsigned int frame::get_header_len() const { } char* frame::get_masking_key() { - if (m_extended_header_bytes_needed > 0) { - throw "attempted to get masking_key before reading full header"; + if (m_state != STATE_READY) { + throw frame_error("attempted to get masking_key before reading full header"); } return m_masking_key; } @@ -128,12 +245,12 @@ frame::opcode frame::get_opcode() const { void frame::set_opcode(frame::opcode op) { if (op > 0x0F) { - throw "invalid opcode"; + throw frame_error("invalid opcode",FERR_PROTOCOL_VIOLATION); } if (get_basic_size() > BASIC_PAYLOAD_LIMIT && is_control()) { - throw "control frames can't have large payloads"; + throw frame_error("control frames can't have large payloads",FERR_PROTOCOL_VIOLATION); } m_header[0] &= (0xFF ^ BPB0_OPCODE); // clear op bits @@ -159,16 +276,18 @@ uint8_t frame::get_basic_size() const { } size_t frame::get_payload_size() const { - if (m_extended_header_bytes_needed > 0) { + if (m_state != STATE_READY && m_state != STATE_PAYLOAD) { // problem - throw "attempted to get payload size before reading full header"; + throw frame_error("attempted to get payload size before reading full header"); } return m_payload.size(); } uint16_t frame::get_close_status() const { - if (get_payload_size() >= 2) { + if (get_payload_size() == 0) { + return close::status::NO_STATUS; + } else if (get_payload_size() >= 2) { char val[2]; val[0] = m_payload[0]; @@ -180,12 +299,19 @@ uint16_t frame::get_close_status() const { return code; } else { - return 1005; // defined in spec as "no status recieved" + return close::status::PROTOCOL_ERROR; } } std::string frame::get_close_msg() const { if (get_payload_size() > 2) { + uint32_t state = utf8_validator::UTF8_ACCEPT; + uint32_t codep = 0; + validate_utf8(&state,&codep,2); + if (state != utf8_validator::UTF8_ACCEPT) { + throw frame_error("Invalid UTF-8 Data", + frame::FERR_PAYLOAD_VIOLATION); + } return std::string(m_payload.begin()+2,m_payload.end()); } else { return std::string(); @@ -213,13 +339,13 @@ bool frame::is_control() const { void frame::set_payload_helper(size_t s) { if (s > max_payload_size) { - throw "requested payload is over implimentation defined limit"; + throw frame_error("requested payload is over implimentation defined limit",FERR_MSG_TOO_BIG); } // limits imposed by the websocket spec if (s > BASIC_PAYLOAD_LIMIT && get_opcode() > MAX_FRAME_OPCODE) { - throw "control frames can't have large payloads"; + throw frame_error("control frames can't have large payloads",FERR_PROTOCOL_VIOLATION); } if (s <= BASIC_PAYLOAD_LIMIT) { @@ -235,7 +361,7 @@ void frame::set_payload_helper(size_t s) { m_header[1] = BASIC_PAYLOAD_64BIT_CODE; *reinterpret_cast(&m_header[BASIC_HEADER_LENGTH]) = htonll(s); } else { - throw "payload size limit is 63 bits"; + throw frame_error("payload size limit is 63 bits",FERR_PROTOCOL_VIOLATION); } m_payload.resize(s); @@ -243,12 +369,16 @@ void frame::set_payload_helper(size_t s) { void frame::set_status(uint16_t status,const std::string message) { // check for valid statuses - if (status < 1000 || status > 4999) { - throw server_error("Status codes must be in the range 1000-4999"); + if (close::status::invalid(status)) { + std::stringstream err; + err << "Status code " << status << " is invalid"; + throw frame_error(err.str()); } - if (status == 1005 || status == 1006) { - throw server_error("Status codes 1005 and 1006 are reserved for internal use and cannot be written to a frame."); + if (close::status::reserved(status)) { + std::stringstream err; + err << "Status code " << status << " is reserved"; + throw frame_error(err.str()); } m_payload.resize(2+message.size()); @@ -276,25 +406,22 @@ std::string frame::print_frame() const { f << std::hex << (unsigned short)m_header[i] << " "; } // print message - std::vector::const_iterator it; - for (it = m_payload.begin(); it != m_payload.end(); it++) { - f << *it; + if (m_payload.size() > 50) { + f << "[payload of " << m_payload.size() << " bytes]"; + } else { + std::vector::const_iterator it; + for (it = m_payload.begin(); it != m_payload.end(); it++) { + f << *it; + } } return f.str(); } -unsigned int frame::process_basic_header() { - m_extended_header_bytes_needed = 0; - m_payload.empty(); - - m_extended_header_bytes_needed = get_header_len() - BASIC_HEADER_LENGTH; - - return m_extended_header_bytes_needed; +void frame::process_basic_header() { + m_bytes_needed = get_header_len() - BASIC_HEADER_LENGTH; } void frame::process_extended_header() { - m_extended_header_bytes_needed = 0; - uint8_t s = get_basic_size(); uint64_t payload_size; int mask_index = BASIC_HEADER_LENGTH; @@ -308,22 +435,32 @@ void frame::process_extended_header() { reinterpret_cast(&m_header[BASIC_HEADER_LENGTH]) )); + if (payload_size < s) { + std::stringstream err; + err << "payload length not minimally encoded. Using 16 bit form for payload size: " << payload_size; + m_bytes_needed = payload_size; + throw frame_error(err.str(), + FERR_PROTOCOL_VIOLATION); + } + mask_index += 2; } else if (s == BASIC_PAYLOAD_64BIT_CODE) { - // reinterpret the second eight bytes as a 16 bit integer in + // reinterpret the second eight bytes as a 64 bit integer in // network byte order. Convert to host byte order and store. payload_size = ntohll(*( reinterpret_cast(&m_header[BASIC_HEADER_LENGTH]) )); + if (payload_size <= PAYLOAD_16BIT_LIMIT) { + m_bytes_needed = payload_size; + throw frame_error("payload length not minimally encoded", + FERR_PROTOCOL_VIOLATION); + } + mask_index += 8; } else { // shouldn't be here - throw server_error("invalid get_basic_size in process_extended_header"); - } - - if (payload_size < s) { - throw server_error("payload size error"); + throw frame_error("invalid get_basic_size in process_extended_header"); } if (get_masked() == 0) { @@ -338,9 +475,11 @@ void frame::process_extended_header() { } if (payload_size > max_payload_size) { + // TODO: frame/message size limits throw server_error("got frame with payload greater than maximum frame buffer size."); } m_payload.resize(payload_size); + m_bytes_needed = payload_size; } void frame::process_payload() { @@ -380,55 +519,47 @@ void frame::process_payload2() { } } -bool frame::validate_utf8(uint32_t* state,uint32_t* codep) const { - for (size_t i = 0; i < m_payload.size(); i++) { +void frame::validate_utf8(uint32_t* state,uint32_t* codep, size_t offset) const { + for (size_t i = offset; i < m_payload.size(); i++) { using utf8_validator::decode; - //std::cout << "decoding: " << std::hex << m_payload[i] << std::endl; if (decode(state,codep,m_payload[i]) == utf8_validator::UTF8_REJECT) { - // std::cout << "bad byte" << std::endl; - return false; + throw frame_error("Invalid UTF-8 Data",FERR_PAYLOAD_VIOLATION); } } - - return true; } -bool frame::validate_basic_header() const { +void frame::validate_basic_header() const { // check for control frame size if (get_basic_size() > BASIC_PAYLOAD_LIMIT && is_control()) { - return false; + throw frame_error("Control Frame is too large",FERR_PROTOCOL_VIOLATION); } // check for reserved opcodes if (get_rsv1() || get_rsv2() || get_rsv3()) { - return false; + throw frame_error("Reserved bit used",FERR_PROTOCOL_VIOLATION); } // check for reserved opcodes opcode op = get_opcode(); if (op > 0x02 && op < 0x08) { - return false; + throw frame_error("Reserved opcode used",FERR_PROTOCOL_VIOLATION); } if (op > 0x0A) { - return false; + throw frame_error("Reserved opcode used",FERR_PROTOCOL_VIOLATION); } // check for fragmented control message if (is_control() && !get_fin()) { - return false; + throw frame_error("Fragmented control message",FERR_PROTOCOL_VIOLATION); } - - return true; } void frame::generate_masking_key() { //throw "masking key generation not implimented"; int32_t key = m_gen(); - - std::cout << "genkey: " << key << std::endl; - + //m_masking_key[0] = reinterpret_cast(&key)[0]; //m_masking_key[1] = reinterpret_cast(&key)[1]; //m_masking_key[2] = reinterpret_cast(&key)[2]; diff --git a/src/websocket_frame.hpp b/src/websocket_frame.hpp index 33436306a5..9c719f38b2 100644 --- a/src/websocket_frame.hpp +++ b/src/websocket_frame.hpp @@ -39,6 +39,13 @@ namespace websocketpp { +/* policies to abstract out + + - random number generation + - utf8 validation + + */ + class frame { public: enum opcode_s { @@ -54,6 +61,19 @@ public: static const uint8_t MAX_FRAME_OPCODE = 0x07; + static const uint8_t STATE_BASIC_HEADER = 1; + static const uint8_t STATE_EXTENDED_HEADER = 2; + static const uint8_t STATE_PAYLOAD = 3; + static const uint8_t STATE_READY = 4; + static const uint8_t STATE_RECOVERY = 5; + + static const uint16_t FERR_FATAL_SESSION_ERROR = 0; // force session end + static const uint16_t FERR_SOFT_SESSION_ERROR = 1; // should log and ignore + static const uint16_t FERR_PROTOCOL_VIOLATION = 2; // must end session + static const uint16_t FERR_PAYLOAD_VIOLATION = 3; // should end session + static const uint16_t FERR_INTERNAL_SERVER_ERROR = 4; // cleanly end session + static const uint16_t FERR_MSG_TOO_BIG = 5; + // basic payload byte flags static const uint8_t BPB0_OPCODE = 0x0F; static const uint8_t BPB0_RSV3 = 0x10; @@ -76,11 +96,16 @@ public: // create an empty frame for writing into frame() : m_gen(m_rng, - boost::random::uniform_int_distribution<>(INT32_MIN,INT32_MAX)) { - // not sure if these are necessary with c++ but putting in just in case - memset(m_header,0,MAX_HEADER_LENGTH); + boost::random::uniform_int_distribution<>(INT32_MIN,INT32_MAX)),m_degraded(false) { + reset(); } + uint8_t get_state() const; + uint64_t get_bytes_needed() const; + void reset(); + + void consume(std::istream &s); + // get pointers to underlying buffers char* get_header(); char* get_extended_header(); @@ -112,7 +137,7 @@ public: uint16_t get_close_status() const; std::string get_close_msg() const; - + std::vector &get_payload(); void set_payload(const std::vector source); @@ -126,23 +151,26 @@ public: std::string print_frame() const; // reads basic header, sets and returns m_header_bits_needed - unsigned int process_basic_header(); + void process_basic_header(); void process_extended_header(); void process_payload(); void process_payload2(); // experiment with more efficient masking code. - bool validate_utf8(uint32_t* state,uint32_t* codep) const; - bool validate_basic_header() const; + void validate_utf8(uint32_t* state,uint32_t* codep,size_t offset = 0) const; + void validate_basic_header() const; void generate_masking_key(); void clear_masking_key(); private: + uint8_t m_state; + uint64_t m_bytes_needed; + bool m_degraded; + char m_header[MAX_HEADER_LENGTH]; std::vector m_payload; char m_masking_key[4]; - unsigned int m_extended_header_bytes_needed; boost::random::random_device m_rng; boost::random::variate_generator #include +#include + #include using websocketpp::server; @@ -42,7 +44,34 @@ server::server(boost::asio::io_service& io_service, m_max_message_size(DEFAULT_MAX_MESSAGE_SIZE), m_io_service(io_service), m_acceptor(io_service, endpoint), - m_def_con_handler(defc) {} + m_def_con_handler(defc), + m_desc("websocketpp::server") { + m_desc.add_options() + ("help", "produce help message") + ("host,h",po::value >()->multitoken()->composing(), "hostnames to listen on") + ("port,p",po::value(), "port to listen on") + ; + +} + +void server::parse_command_line(int ac, char* av[]) { + po::store(po::parse_command_line(ac,av, m_desc),m_vm); + po::notify(m_vm); + + if (m_vm.count("help") ) { + std::cout << m_desc << std::endl; + } + + //m_vm["host"].as(); + + const std::vector< std::string > &foo = m_vm["host"].as< std::vector >(); + + for (int i = 0; i < foo.size(); i++) { + std::cout << foo[i] << std::endl; + } + + //std::cout << m_vm["host"].as< std::vector >() << std::endl; +} void server::add_host(std::string host) { m_hosts.insert(host); @@ -138,9 +167,11 @@ void server::access_log(std::string msg,uint16_t level) { } void server::start_accept() { + // TODO: sanity check whether the session buffer size bound could be reduced server_session_ptr new_session(new server_session(shared_from_this(), m_io_service, - m_def_con_handler)); + m_def_con_handler, + m_max_message_size*2)); m_acceptor.async_accept( new_session->socket(), diff --git a/src/websocket_server.hpp b/src/websocket_server.hpp index a1415b63ed..dc9b4f03cb 100644 --- a/src/websocket_server.hpp +++ b/src/websocket_server.hpp @@ -30,6 +30,8 @@ #include #include +#include +namespace po = boost::program_options; #include @@ -60,86 +62,66 @@ private: }; class server : public boost::enable_shared_from_this { - public: - // System logging levels - /*static const uint16_t LOG_ALL = 0; - static const uint16_t LOG_DEBUG = 1; - static const uint16_t LOG_INFO = 2; - static const uint16_t LOG_WARN = 3; - static const uint16_t LOG_ERROR = 4; - static const uint16_t LOG_FATAL = 5; - static const uint16_t LOG_OFF = 6; - - // Access logging controls - // Individual bits - static const uint16_t ALOG_CONNECT = 0x1; - static const uint16_t ALOG_DISCONNECT = 0x2; - static const uint16_t ALOG_MISC_CONTROL = 0x4; - static const uint16_t ALOG_FRAME = 0x8; - static const uint16_t ALOG_MESSAGE = 0x10; - static const uint16_t ALOG_INFO = 0x20; - static const uint16_t ALOG_HANDSHAKE = 0x40; - // Useful groups - static const uint16_t ALOG_OFF = 0x0; - static const uint16_t ALOG_CONTROL = ALOG_CONNECT - & ALOG_DISCONNECT - & ALOG_MISC_CONTROL; - static const uint16_t ALOG_ALL = 0xFFFF; -*/ - server(boost::asio::io_service& io_service, - const tcp::endpoint& endpoint, - connection_handler_ptr defc); - - // creates a new session object and connects the next websocket - // connection to it. - void start_accept(); - - // INTERFACE FOR LOCAL APPLICATIONS +public: + server(boost::asio::io_service& io_service, + const tcp::endpoint& endpoint, + connection_handler_ptr defc); + + // creates a new session object and connects the next websocket + // connection to it. + void start_accept(); + + // INTERFACE FOR LOCAL APPLICATIONS - // Add or remove a host string (host:port) to the list of acceptable - // hosts to accept websocket connections from. Additions/deletions here - // only affect new connections. - void add_host(std::string host); - void remove_host(std::string host); - - void set_max_message_size(uint64_t 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); - void set_elog_level(uint16_t level); - - bool test_alog_level(uint16_t level); - void set_alog_level(uint16_t level); - void unset_alog_level(uint16_t level); + // Add or remove a host string (host:port) to the list of acceptable + // hosts to accept websocket connections from. Additions/deletions here + // only affect new connections. + void add_host(std::string host); + void remove_host(std::string host); + + void set_max_message_size(uint64_t 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); + void set_elog_level(uint16_t level); + + bool test_alog_level(uint16_t level); + void set_alog_level(uint16_t level); + void unset_alog_level(uint16_t level); + + void parse_command_line(int ac, char* av[]); + + // INTERFACE FOR SESSIONS - // INTERFACE FOR SESSIONS + // Check if this server will respond to this host. + bool validate_host(std::string host); + + // 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_accept(server_session_ptr session, + const boost::system::error_code& error); + +private: + uint16_t m_elog_level; + uint16_t m_alog_level; - // Check if this server will respond to this host. - bool validate_host(std::string host); - - // 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_accept(server_session_ptr session, - const boost::system::error_code& error); - - private: - uint16_t m_elog_level; - uint16_t m_alog_level; - - std::set m_hosts; - uint64_t m_max_message_size; - boost::asio::io_service& m_io_service; - tcp::acceptor m_acceptor; - 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::acceptor m_acceptor; + connection_handler_ptr m_def_con_handler; + + po::options_description m_desc; + po::variables_map m_vm; }; } diff --git a/src/websocket_server_session.cpp b/src/websocket_server_session.cpp index 1213fb8eca..d8f0aa6c38 100644 --- a/src/websocket_server_session.cpp +++ b/src/websocket_server_session.cpp @@ -45,8 +45,9 @@ using websocketpp::server_session; server_session::server_session(websocketpp::server_ptr s, boost::asio::io_service& io_service, - websocketpp::connection_handler_ptr defc) - : session(io_service,defc),m_server(s) {} + websocketpp::connection_handler_ptr defc, + uint64_t buf_size) + : session(io_service,defc,buf_size),m_server(s) {} void server_session::on_connect() { read_handshake(); @@ -91,6 +92,16 @@ void server_session::select_extension(const std::string& val) { } void server_session::read_handshake() { + m_timer.expires_from_now(boost::posix_time::seconds(5)); + + m_timer.async_wait( + boost::bind( + &session::handle_handshake_expired, + shared_from_this(), + boost::asio::placeholders::error + ) + ); + boost::asio::async_read_until( m_socket, m_buf, @@ -312,7 +323,8 @@ void server_session::write_handshake() { void server_session::handle_write_handshake(const boost::system::error_code& error) { if (error) { - handle_error("Error writing handshake response",error); + log_error("Error writing handshake response",error); + drop_tcp(); return; } @@ -323,11 +335,15 @@ void server_session::handle_write_handshake(const boost::system::error_code& 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); - // TODO: close behavior + drop_tcp(); + // TODO: tell client that connection failed. return; } - m_status = OPEN; + m_state = STATE_OPEN; + + // stop the handshake timer + m_timer.cancel(); if (m_local_interface) { m_local_interface->on_open(shared_from_this()); diff --git a/src/websocket_server_session.hpp b/src/websocket_server_session.hpp index 0fd93bd8c9..538edaba72 100644 --- a/src/websocket_server_session.hpp +++ b/src/websocket_server_session.hpp @@ -65,7 +65,8 @@ class server_session : public session { public: server_session (server_ptr s, boost::asio::io_service& io_service, - connection_handler_ptr defc); + connection_handler_ptr defc, + uint64_t buf_size); /*** SERVER INTERFACE ***/ @@ -102,6 +103,7 @@ protected: std::size_t bytes_transferred); + private: protected: diff --git a/src/websocket_session.cpp b/src/websocket_session.cpp index 9ebf2a9a8d..a8119b4728 100644 --- a/src/websocket_session.cpp +++ b/src/websocket_session.cpp @@ -44,8 +44,10 @@ using websocketpp::session; session::session (boost::asio::io_service& io_service, - websocketpp::connection_handler_ptr defc) - : m_status(CONNECTING), + websocketpp::connection_handler_ptr defc, + uint64_t buf_size) + : m_state(STATE_CONNECTING), + m_writing(false), m_local_close_code(CLOSE_STATUS_NO_STATUS), m_remote_close_code(CLOSE_STATUS_NO_STATUS), m_was_clean(false), @@ -54,8 +56,8 @@ session::session (boost::asio::io_service& io_service, m_socket(io_service), m_io_service(io_service), m_local_interface(defc), - - + m_timer(io_service,boost::posix_time::seconds(0)), + m_buf(buf_size), // maximum buffered (unconsumed) bytes from network m_utf8_state(utf8_validator::UTF8_ACCEPT), m_utf8_codepoint(0) {} @@ -77,7 +79,7 @@ void session::set_handler(websocketpp::connection_handler_ptr new_con) { } const std::string& session::get_subprotocol() const { - if (m_status == CONNECTING) { + if (m_state == STATE_CONNECTING) { log("Subprotocol is not avaliable before the handshake has completed.",LOG_WARN); throw server_error("Subprotocol is not avaliable before the handshake has completed."); } @@ -120,7 +122,7 @@ unsigned int session::get_version() const { } void session::send(const std::string &msg) { - if (m_status != OPEN) { + if (m_state != STATE_OPEN) { log("Tried to send a message from a session that wasn't open",LOG_WARN); return; } @@ -132,7 +134,7 @@ void session::send(const std::string &msg) { } void session::send(const std::vector &data) { - if (m_status != OPEN) { + if (m_state != STATE_OPEN) { log("Tried to send a message from a session that wasn't open",LOG_WARN); return; } @@ -143,22 +145,37 @@ void session::send(const std::vector &data) { write_frame(); } +// end user interface to close the connection void session::close(uint16_t status,const std::string& msg) { - disconnect(status,msg); - // TODO: close behavior + validate_app_close_status(status); + + send_close(status,msg); } // TODO: clean this up, needs to be broken out into more specific methods -void session::disconnect(uint16_t status,const std::string &message) { - if (m_status != OPEN) { + +// This method initiates a clean disconnect with the given status code and reason +// it logs an error and is ignored if it is called from a state other than OPEN + +// called by process_close when an initiate close method is received. + +void session::send_close(uint16_t status,const std::string &message) { + if (m_state != STATE_OPEN) { log("Tried to disconnect a session that wasn't open",LOG_WARN); return; } - m_status = CLOSING; + m_state = STATE_CLOSING; - m_close_code = status; - m_close_message = message; + m_timer.expires_from_now(boost::posix_time::milliseconds(1000)); + + m_timer.async_wait( + boost::bind( + &session::handle_close_expired, + shared_from_this(), + boost::asio::placeholders::error + ) + ); m_local_close_code = status; m_local_close_msg = message; @@ -166,20 +183,25 @@ void session::disconnect(uint16_t status,const std::string &message) { m_write_frame.set_fin(true); m_write_frame.set_opcode(frame::CONNECTION_CLOSE); + // echo close value unless there is a good reason not to. if (status == CLOSE_STATUS_NO_STATUS) { m_write_frame.set_status(CLOSE_STATUS_NORMAL,""); } else if (status == CLOSE_STATUS_ABNORMAL_CLOSE) { - // unknown internal error, don't set a status? use protocol error? - log("Tried to disconnect with status ABNORMAL_CLOSE",LOG_DEBUG); + // Internal implimentation error. There is no good close code for this. + m_write_frame.set_status(CLOSE_STATUS_POLICY_VIOLATION,message); + } else if (close::status::invalid(status)) { + m_write_frame.set_status(close::status::PROTOCOL_ERROR,"Status code is invalid"); + } else if (close::status::reserved(status)) { + m_write_frame.set_status(close::status::PROTOCOL_ERROR,"Status code is reserved"); } else { m_write_frame.set_status(status,message); } - + write_frame(); } void session::ping(const std::string &msg) { - if (m_status != OPEN) { + if (m_state != STATE_OPEN) { log("Tried to send a ping from a session that wasn't open",LOG_WARN); return; } @@ -191,7 +213,7 @@ void session::ping(const std::string &msg) { } void session::pong(const std::string &msg) { - if (m_status != OPEN) { + if (m_state != STATE_OPEN) { log("Tried to send a pong from a session that wasn't open",LOG_WARN); return; } @@ -203,90 +225,148 @@ void session::pong(const std::string &msg) { } void session::read_frame() { - boost::asio::async_read( - m_socket, - boost::asio::buffer(m_read_frame.get_header(), - frame::BASIC_HEADER_LENGTH), - boost::bind( - &session::handle_frame_header, - shared_from_this(), - boost::asio::placeholders::error - ) - ); + // the initial read in the handshake may have read in the first frame. + // handle it (if it exists) before we read anything else. + handle_read_frame(boost::system::error_code()); } -void session::handle_frame_header(const boost::system::error_code& error) { - if (error) { - handle_error("Error reading basic frame header",error); - // TODO: close behavior - return; - } - log(m_read_frame.print_frame(),LOG_DEBUG); - - uint16_t extended_header_bytes = m_read_frame.process_basic_header(); - - if (!m_read_frame.validate_basic_header()) { - handle_error("Basic header validation failed",boost::system::error_code()); - disconnect(CLOSE_STATUS_PROTOCOL_ERROR,""); - - - // TODO: close behavior +// handle_read_frame reads and processes all socket read commands for the +// session by consuming the read buffer and then starting an async read with +// itself as the callback. The connection is over when this method returns. +void session::handle_read_frame(const boost::system::error_code& error) { + if (m_state != STATE_OPEN && m_state != STATE_CLOSING) { + log("handle_read_frame called in invalid state",LOG_ERROR); return; } - - if (extended_header_bytes == 0) { - m_read_frame.process_extended_header(); - read_payload(); - } else { + + if (error) { + if (error == boost::asio::error::eof) { + // if this is a case where we are expecting eof, return, else log & drop + + log_error("Recieved EOF",error); + //drop_tcp(false); + //m_state = STATE_CLOSED; + } else if (error == boost::asio::error::operation_aborted) { + // some other part of our client called shutdown on our socket. + // This is usually due to a write error. Everything should have + // already been logged and dropped so we just return here + return; + } else { + log_error("Error reading frame",error); + //drop_tcp(false); + m_state = STATE_CLOSED; + } + } + + std::istream s(&m_buf); + + while (m_buf.size() > 0 && m_state != STATE_CLOSED) { + try { + if (m_read_frame.get_bytes_needed() == 0) { + throw frame_error("have bytes that no frame needs",frame::FERR_FATAL_SESSION_ERROR); + } + + // Consume will read bytes from s + // will throw a frame_error on error. + + std::stringstream err; + + err << "consuming. have: " << m_buf.size() << " bytes. Need: " << m_read_frame.get_bytes_needed() << " state: " << (int)m_read_frame.get_state(); + log(err.str(),LOG_DEBUG); + m_read_frame.consume(s); + + err.str(""); + err << "consume complete, " << m_buf.size() << " bytes left, " << m_read_frame.get_bytes_needed() << " still needed, state: " << (int)m_read_frame.get_state(); + log(err.str(),LOG_DEBUG); + + if (m_read_frame.get_state() == frame::STATE_READY) { + // process frame and reset frame state for the next frame. + // will throw a frame_error on error. May set m_state to CLOSED, + // if so no more frames should be processed. + err.str(""); + err << "processing frame " << m_buf.size(); + log(err.str(),LOG_DEBUG); + m_timer.cancel(); + process_frame(); + } + } catch (const frame_error& e) { + std::stringstream err; + err << "Caught frame exception: " << e.what(); + + access_log(e.what(),ALOG_FRAME); + log(err.str(),LOG_ERROR); + + // if the exception happened while processing. + // TODO: this is not elegant, perhaps separate frame read vs process + // exceptions need to be used. + if (m_read_frame.get_state() == frame::STATE_READY) { + m_read_frame.reset(); + } + + // process different types of frame errors + // + if (e.code() == frame::FERR_PROTOCOL_VIOLATION) { + send_close(CLOSE_STATUS_PROTOCOL_ERROR, e.what()); + } else if (e.code() == frame::FERR_PAYLOAD_VIOLATION) { + send_close(CLOSE_STATUS_INVALID_PAYLOAD, e.what()); + } else if (e.code() == frame::FERR_INTERNAL_SERVER_ERROR) { + send_close(CLOSE_STATUS_ABNORMAL_CLOSE, e.what()); + } else if (e.code() == frame::FERR_SOFT_SESSION_ERROR) { + // ignore and continue processing frames + continue; + } else { + // Fatal error, forcibly end connection immediately. + log("Dropping TCP due to unrecoverable exception",LOG_DEBUG); + drop_tcp(true); + } + + break; + } + } + + if (error == boost::asio::error::eof) { + m_state = STATE_CLOSED; + } + + // we have read everything, check if we should read more + + if ((m_state == STATE_OPEN || m_state == STATE_CLOSING) && m_read_frame.get_bytes_needed() > 0) { + std::stringstream msg; + msg << "starting async read for " << m_read_frame.get_bytes_needed() << " bytes."; + + log(msg.str(),LOG_DEBUG); + + // TODO: set a timer here in case we don't want to read forever. + // Ex: when the frame is in a degraded state. + boost::asio::async_read( m_socket, - boost::asio::buffer(m_read_frame.get_extended_header(), - extended_header_bytes), + m_buf, + boost::asio::transfer_at_least(m_read_frame.get_bytes_needed()), boost::bind( - &session::handle_extended_frame_header, + &session::handle_read_frame, shared_from_this(), boost::asio::placeholders::error ) ); + } else if (m_state == STATE_CLOSED) { + log_close_result(); + + if (m_local_interface) { + // TODO: make sure close code/msg are properly set. + m_local_interface->on_close(shared_from_this()); + } + + m_timer.cancel(); + } else { + log("handle_read_frame called in invalid state",LOG_ERROR); } } -void session::handle_extended_frame_header( - const boost::system::error_code& error) { - if (error) { - handle_error("Error reading extended frame header",error); - // TODO: close behavior - return; - } +void session::process_frame () { + log("process_frame",LOG_DEBUG); - // this sets up the buffer we are about to read into. - m_read_frame.process_extended_header(); - - this->read_payload(); -} - -void session::read_payload() { - boost::asio::async_read( - m_socket, - boost::asio::buffer(m_read_frame.get_payload()), - boost::bind( - &session::handle_read_payload, - shared_from_this(), - boost::asio::placeholders::error - ) - ); -} - -void session::handle_read_payload (const boost::system::error_code& error) { - if (error) { - handle_error("Error reading payload data frame header",error); - // TODO: close behavior - return; - } - - m_read_frame.process_payload(); - - if (m_status == OPEN) { + if (m_state == STATE_OPEN) { switch (m_read_frame.get_opcode()) { case frame::CONTINUATION_FRAME: process_continuation(); @@ -298,6 +378,7 @@ void session::handle_read_payload (const boost::system::error_code& error) { process_binary(); break; case frame::CONNECTION_CLOSE: + log("process_close",LOG_DEBUG); process_close(); break; case frame::PING: @@ -307,51 +388,102 @@ void session::handle_read_payload (const boost::system::error_code& error) { process_pong(); break; default: - disconnect(CLOSE_STATUS_PROTOCOL_ERROR,"Invalid Opcode"); - // TODO: close behavior + throw frame_error("Invalid Opcode", + frame::FERR_PROTOCOL_VIOLATION); break; } - } else if (m_status == CLOSING) { + } else if (m_state == STATE_CLOSING) { if (m_read_frame.get_opcode() == frame::CONNECTION_CLOSE) { process_close(); } else { // Ignore all other frames in closing state + log("ignoring this frame",LOG_DEBUG); } } else { // Recieved message before or after connection was opened/closed - // TODO: close behavior - return; + throw frame_error("process_frame called from invalid state"); } - // check if there was an error processing this frame and fail the connection - if (m_error) { - log("Connection has been closed uncleanly",LOG_ERROR); - // TODO: close behavior - return; - } - - if (m_status == CLOSED) { - log_close_result(); - - if (m_local_interface) { - m_local_interface->on_close(shared_from_this(), - m_close_code, - m_close_message); - } - // TODO: close behavior - return; - } - - this->read_frame(); + m_read_frame.reset(); } void session::handle_write_frame (const boost::system::error_code& error) { if (error) { - handle_error("Error writing frame data",error); - // TODO: close behavior + log_error("Error writing frame data",error); + drop_tcp(false); } - //std::cout << "Successfully wrote frame." << std::endl; + access_log("handle_write_frame complete",ALOG_FRAME); + m_writing = false; +} + + +void session::handle_timer_expired (const boost::system::error_code& error) { + if (error) { + if (error == boost::asio::error::operation_aborted) { + log("timer was aborted",LOG_DEBUG); + //drop_tcp(false); + } else { + log("timer ended with error",LOG_DEBUG); + } + return; + } + + log("timer ended without error",LOG_DEBUG); + + +} + +void session::handle_handshake_expired (const boost::system::error_code& error) { + if (error) { + if (error != boost::asio::error::operation_aborted) { + log("Unexpected handshake timer error.",LOG_DEBUG); + drop_tcp(true); + } + return; + } + + log("Handshake timed out",LOG_DEBUG); + drop_tcp(true); +} + +// The error timer is set when we want to give the other endpoint some time to +// do something but don't want to wait forever. There is a special error code +// that represents the timer being canceled by us (because the other endpoint +// responded in time. All other cases should assume that the other endpoint is +// irrepairibly broken and drop the TCP connection. +void session::handle_error_timer_expired (const boost::system::error_code& error) { + if (error) { + if (error == boost::asio::error::operation_aborted) { + log("error timer was aborted",LOG_DEBUG); + //drop_tcp(false); + } else { + log("error timer ended with error",LOG_DEBUG); + drop_tcp(true); + } + return; + } + + log("error timer ended without error",LOG_DEBUG); + drop_tcp(true); +} + +void session::handle_close_expired (const boost::system::error_code& error) { + if (error) { + if (error == boost::asio::error::operation_aborted) { + log("timer was aborted",LOG_DEBUG); + //drop_tcp(false); + } else { + log("Unexpected close timer error.",LOG_DEBUG); + drop_tcp(false); + } + return; + } + + if (m_state != STATE_CLOSED) { + log("close timed out",LOG_DEBUG); + drop_tcp(false); + } } void session::process_ping() { @@ -372,22 +504,17 @@ void session::process_pong() { } void session::process_text() { - if (!m_read_frame.validate_utf8(&m_utf8_state,&m_utf8_codepoint)) { - disconnect(CLOSE_STATUS_INVALID_PAYLOAD,"Invalid UTF8 Data"); - // TODO: close behavior - return; - } - + // this will throw an exception if validation fails at any point + m_read_frame.validate_utf8(&m_utf8_state,&m_utf8_codepoint); + + // otherwise, treat as binary process_binary(); } void session::process_binary() { if (m_fragmented) { - handle_error("Got a new message before the previous was finished.", - boost::system::error_code()); - disconnect(CLOSE_STATUS_PROTOCOL_ERROR,""); - // TODO: close behavior - return; + throw frame_error("Got a new message before the previous was finished.", + frame::FERR_PROTOCOL_VIOLATION); } m_current_opcode = m_read_frame.get_opcode(); @@ -403,19 +530,13 @@ void session::process_binary() { void session::process_continuation() { if (!m_fragmented) { - handle_error("Got a continuation frame without an outstanding message.", - boost::system::error_code()); - disconnect(CLOSE_STATUS_PROTOCOL_ERROR,""); - // TODO: close behavior - return; + throw frame_error("Got a continuation frame without an outstanding message.", + frame::FERR_PROTOCOL_VIOLATION); } if (m_current_opcode == frame::TEXT_FRAME) { - if (!m_read_frame.validate_utf8(&m_utf8_state,&m_utf8_codepoint)) { - disconnect(CLOSE_STATUS_INVALID_PAYLOAD,"Invalid UTF8 Data"); - // TODO: close behavior - return; - } + // this will throw an exception if validation fails at any point + m_read_frame.validate_utf8(&m_utf8_state,&m_utf8_codepoint); } extract_payload(); @@ -428,27 +549,31 @@ void session::process_continuation() { } void session::process_close() { - uint16_t status = m_read_frame.get_close_status(); - std::string message = m_read_frame.get_close_msg(); - - m_remote_close_code = status; - m_remote_close_msg = message; + m_remote_close_code = m_read_frame.get_close_status(); + m_remote_close_msg = m_read_frame.get_close_msg(); - - if (m_status == OPEN) { + if (m_state == STATE_OPEN) { + log("process_close sending ack",LOG_DEBUG); // This is the case where the remote initiated the close. m_closed_by_me = false; - // TODO: close behavior - disconnect(status,message); - } else if (m_status == CLOSING) { + // send acknowledgement + + // check if the remote close code + if (m_remote_close_code >= close::status::RSV_START) { + + } + + send_close(m_remote_close_code,m_remote_close_msg); + } else if (m_state == STATE_CLOSING) { + log("process_close got ack",LOG_DEBUG); // this is an ack of our close message m_closed_by_me = true; } else { - throw "fixme"; + throw frame_error("process_closed called from wrong state"); } m_was_clean = true; - m_status = CLOSED; + m_state = STATE_CLOSED; } void session::deliver_message() { @@ -457,6 +582,7 @@ void session::deliver_message() { } if (m_current_opcode == frame::BINARY_FRAME) { + //log("Dispatching Binary Message",LOG_DEBUG); if (m_fragmented) { m_local_interface->on_message(shared_from_this(),m_current_message); } else { @@ -467,10 +593,12 @@ void session::deliver_message() { std::string msg; // make sure the finished frame is valid utf8 + // the streaming validator checks for bad codepoints as it goes. It + // doesn't know where the end of the message is though, so we need to + // check here to make sure the final message ends on a valid codepoint. if (m_utf8_state != utf8_validator::UTF8_ACCEPT) { - disconnect(CLOSE_STATUS_INVALID_PAYLOAD,"Invalid UTF8 Data"); - // TODO: close behavior - return; + throw frame_error("Invalid UTF-8 Data", + frame::FERR_PAYLOAD_VIOLATION); } if (m_fragmented) { @@ -482,12 +610,13 @@ void session::deliver_message() { ); } + //log("Dispatching Text Message",LOG_DEBUG); m_local_interface->on_message(shared_from_this(),msg); } else { // Not sure if this should be a fatal error or not std::stringstream err; err << "Attempted to deliver a message of unsupported opcode " << m_current_opcode; - log(err.str(),LOG_ERROR); + throw frame_error(err.str(),frame::FERR_SOFT_SESSION_ERROR); } } @@ -518,7 +647,9 @@ void session::write_frame() { ); log("Write Frame: "+m_write_frame.print_frame(),LOG_DEBUG); - + + m_writing = true; + boost::asio::async_write( m_socket, data, @@ -564,17 +695,44 @@ void session::log_open_result() { access_log(msg.str(),ALOG_HANDSHAKE); } -void session::handle_error(std::string msg, - const boost::system::error_code& error) { - std::stringstream e; +// this is called when an async asio call encounters an error +void session::log_error(std::string msg,const boost::system::error_code& e) { + std::stringstream err; - e << "[Connection " << this << "] " << msg << " (" << error << ")"; + err << "[Connection " << this << "] " << msg << " (" << e << ")"; - log(e.str(),LOG_ERROR); - - if (m_local_interface) { - m_local_interface->on_close(shared_from_this(),1006,e.str()); + log(err.str(),LOG_ERROR); +} + +// validates status codes that the end application is allowed to use +bool session::validate_app_close_status(uint16_t status) { + if (status == CLOSE_STATUS_NORMAL) { + return true; } - m_error = true; + if (status >= 4000 && status < 5000) { + return true; + } + + return false; +} + +void session::drop_tcp(bool dropped_by_me) { + m_timer.cancel(); + try { + if (m_socket.is_open()) { + m_socket.shutdown(tcp::socket::shutdown_both); + m_socket.close(); + } + } catch (boost::system::system_error& e) { + if (e.code() == boost::asio::error::not_connected) { + // this means the socket was disconnected by the other side before + // we had a chance to. Ignore and continue. + } else { + throw e; + } + } + m_dropped_by_me = dropped_by_me; + m_state = STATE_CLOSED; + } diff --git a/src/websocket_session.hpp b/src/websocket_session.hpp index c6a4847d6a..d1c7833d8c 100644 --- a/src/websocket_session.hpp +++ b/src/websocket_session.hpp @@ -25,6 +25,53 @@ * */ +/* + + Exit path mapping + + In every path: + - If it is safe to close cleanly, close cleanly + - Write to the access log on clean close + - Write to the error log on unclean close and clean closes with a server error. + - If session state is open and a local client is connected, send on_close msg + + - make sure the following bits are properly set: + + - If we initiated the close by sending the first close frame or by dropping the TCP connection, set closed_by_me. If the other endpoint sent the first close method or we got an EOF while reading clear closed_by_me + - If we initiated the TCP connection drop set dropped_by_me. If we got EOF while reading clear dropped_by_me + - If we sent and received a close frame or we received and sent an acknowledgement close frame set was_clean to true. + + - If we are the server we should drop TCP immediately + - If we are the client we should drop TCP immediately except in the case where we just recieved an acknowledgement close frame. In this case wait a certain period of time for the server EOF. + + Questions: + - if the client rejects + + Paths: (+ indicates path has been checked and implimented) + Server Handshake Paths + - Accept connection, read handshake, handshake is valid, write handshake, no errors. This is the correct path and leads to the frame reading paths + - Accept connection, connection is not in state open after a time out (due to no bytes being read or no CRLFCRLF being read). This needs a time out after which we drop TCP. + - Accept connection, read handshake, handshake is invalid. write HTTP error. drop TCP + - Accept connection, read handshake, handshake is valid, write handshake returns EOF. This means client rejected something about our response. We should drop and notify our client. (note alternative client handshake reject method is to accept the handshake then immediately send a close message with the non-acceptance reason) + - Accept connection, read handshake, handshake is valid, write handshake returns another error. We should drop and notify our client. + Client Handshake Paths + - + Server Frame Reading Paths + - async read returns EOF. Close our own socket and notify our local interface. + - async read returns another error + + + + + Timeouts: + - handshake timeout + - wait for close frame after error + - (client) wait for server to drop tcp after close handshake + - idle client timeout? API specifiable? + - wait for pong? + + */ + #ifndef WEBSOCKET_SESSION_HPP #define WEBSOCKET_SESSION_HPP @@ -73,14 +120,10 @@ class session : public boost::enable_shared_from_this { public: friend class handshake_error; - enum ws_status { - CONNECTING, - OPEN, - CLOSING, - CLOSED - }; - - typedef enum ws_status status_code; + static const uint8_t STATE_CONNECTING = 0; + static const uint8_t STATE_OPEN = 1; + static const uint8_t STATE_CLOSING = 2; + static const uint8_t STATE_CLOSED = 3; static const uint16_t CLOSE_STATUS_NORMAL = 1000; static const uint16_t CLOSE_STATUS_GOING_AWAY = 1001; @@ -92,9 +135,11 @@ public: static const uint16_t CLOSE_STATUS_POLICY_VIOLATION = 1008; static const uint16_t CLOSE_STATUS_MESSAGE_TOO_BIG = 1009; static const uint16_t CLOSE_STATUS_EXTENSION_REQUIRE = 1010; + static const uint16_t CLOSE_STATUS_MAXIMUM = 1011; session (boost::asio::io_service& io_service, - connection_handler_ptr defc); + connection_handler_ptr defc, + uint64_t buf_size); tcp::socket& socket(); boost::asio::io_service& io_service(); @@ -141,7 +186,6 @@ public: // initiate a connection close void close(uint16_t status,const std::string &reason); - void disconnect(uint16_t status,const std::string& reason); // temp virtual bool is_server() const = 0; @@ -150,22 +194,24 @@ public: virtual void handle_write_handshake(const boost::system::error_code& e) = 0; virtual void handle_read_handshake(const boost::system::error_code& e, std::size_t bytes_transferred) = 0; -protected: +public: //protected: virtual void write_handshake() = 0; virtual void read_handshake() = 0; - // start async read for a websocket frame (2 bytes) to handle_frame_header void read_frame(); - void handle_frame_header(const boost::system::error_code& error); - void handle_extended_frame_header(const boost::system::error_code& error); - void read_payload(); - void handle_read_payload (const boost::system::error_code& error); + void handle_read_frame (const boost::system::error_code& error); // write m_write_frame out to the socket. void write_frame(); void handle_write_frame (const boost::system::error_code& error); + void handle_timer_expired(const boost::system::error_code& error); + void handle_handshake_expired(const boost::system::error_code& error); + void handle_close_expired(const boost::system::error_code& error); + void handle_error_timer_expired (const boost::system::error_code& error); + // helper functions for processing each opcode + void process_frame(); void process_ping(); void process_pong(); void process_text(); @@ -190,9 +236,12 @@ protected: void log_close_result(); void log_open_result(); - - // prints a diagnostic message and disconnects the local interface - void handle_error(std::string msg,const boost::system::error_code& error); + void log_error(std::string msg,const boost::system::error_code& e); + + // misc helpers + bool validate_app_close_status(uint16_t status); + void send_close(uint16_t status,const std::string& reason); + void drop_tcp(bool dropped_by_me = true); private: std::string get_header(const std::string& key, const header_list& list) const; @@ -219,26 +268,26 @@ protected: std::string m_server_http_string; // Mutable connection state; - status_code m_status; - uint16_t m_close_code; - std::string m_close_message; + uint8_t m_state; + bool m_writing; // Close state - uint16_t m_local_close_code; - std::string m_local_close_msg; - uint16_t m_remote_close_code; - std::string m_remote_close_msg; - bool m_was_clean; - bool m_closed_by_me; - bool m_dropped_by_me; + uint16_t m_local_close_code; + std::string m_local_close_msg; + uint16_t m_remote_close_code; + std::string m_remote_close_msg; + bool m_was_clean; + bool m_closed_by_me; + bool m_dropped_by_me; // Connection Resources tcp::socket m_socket; boost::asio::io_service& m_io_service; connection_handler_ptr m_local_interface; + boost::asio::deadline_timer m_timer; // Buffers - boost::asio::streambuf m_buf; + boost::asio::streambuf m_buf; // current message state uint32_t m_utf8_state; @@ -248,11 +297,11 @@ protected: frame::opcode m_current_opcode; // current frame state - frame m_read_frame; + frame m_read_frame; // unorganized - frame m_write_frame; - bool m_error; + frame m_write_frame; + bool m_error; }; // Exception classes @@ -277,13 +326,3 @@ public: } #endif // WEBSOCKET_SESSION_HPP - - - -// better debug printing system -// set acceptible origin and host headers -// case sensitive header values? e.g. websocket - - -// double check bugs in autobahn (sending wrong localhost:9000 header) not -// checking masking in the 9.x tests diff --git a/src/websocketpp.hpp b/src/websocketpp.hpp index 0b8322ec7b..a40855df35 100644 --- a/src/websocketpp.hpp +++ b/src/websocketpp.hpp @@ -59,7 +59,42 @@ namespace websocketpp { static const uint16_t ALOG_CONTROL = ALOG_CONNECT & ALOG_DISCONNECT & ALOG_MISC_CONTROL; - static const uint16_t ALOG_ALL = 0xFFFF; + static const uint16_t ALOG_ALL = 0xFFFF; + + + namespace close { + namespace status { + enum value { + INVALID_END = 999, + NORMAL = 1000, + GOING_AWAY = 1001, + PROTOCOL_ERROR = 1002, + UNSUPPORTED_DATA = 1003, + RSV_ADHOC_1 = 1004, + NO_STATUS = 1005, + ABNORMAL_CLOSE = 1006, + INVALID_PAYLOAD = 1007, + POLICY_VIOLATION = 1008, + MESSAGE_TOO_BIG = 1009, + EXTENSION_REQUIRE = 1010, + RSV_START = 1011, + RSV_END = 2999, + INVALID_START = 5000 + }; + + inline bool reserved(uint16_t s) { + return ((s >= RSV_START && s <= RSV_END) || + s == RSV_ADHOC_1); + } + + inline bool invalid(uint16_t s) { + return ((s <= INVALID_END || s >= INVALID_START) || + s == NO_STATUS || + s == ABNORMAL_CLOSE); + } + } + } + } #include "websocket_session.hpp" diff --git a/websocketpp.xcodeproj/project.pbxproj b/websocketpp.xcodeproj/project.pbxproj index c44ea01195..3bfb7e15dc 100644 --- a/websocketpp.xcodeproj/project.pbxproj +++ b/websocketpp.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ B682888B14374623002BA48B /* libboost_system.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = B682888A14374623002BA48B /* libboost_system.dylib */; }; B682888D1437464A002BA48B /* libboost_random.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = B682888C1437464A002BA48B /* libboost_random.dylib */; }; B682888F14374689002BA48B /* libboost_thread.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = B682888E14374689002BA48B /* libboost_thread.dylib */; }; + B6BE76EA144EF53000716A77 /* websocket_endpoint.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B6BE76E9144EF53000716A77 /* websocket_endpoint.hpp */; }; + B6BE76EB144EF53000716A77 /* websocket_endpoint.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B6BE76E9144EF53000716A77 /* websocket_endpoint.hpp */; }; B6CF18281437C3B1009295BE /* echo_client.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B6CF18131437C370009295BE /* echo_client.cpp */; }; B6CF18291437C3B1009295BE /* echo_client_handler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B6CF18141437C370009295BE /* echo_client_handler.cpp */; }; B6CF182A1437C3BD009295BE /* libwebsocketpp.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = B6DF1C721434A8280029A1B1 /* libwebsocketpp.dylib */; }; @@ -67,6 +69,7 @@ B6DF1CDE1435EDF00029A1B1 /* libwebsocketpp.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = B6DF1C721434A8280029A1B1 /* libwebsocketpp.dylib */; }; B6DF1CE21435F1860029A1B1 /* libboost_system.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = B6DF1CE11435F1860029A1B1 /* libboost_system.dylib */; }; B6DF1CE41435F8250029A1B1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6DF1CE31435F8250029A1B1 /* Foundation.framework */; }; + B6FE8CEC145A0F1900B32547 /* libboost_program_options.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = B6FE8CEB145A0F1900B32547 /* libboost_program_options.dylib */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -117,6 +120,13 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + B6138760145AD09700ED9B19 /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; name = Makefile; path = examples/echo_server/Makefile; sourceTree = ""; }; + B6138762145AD0A500ED9B19 /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; name = Makefile; path = examples/echo_client/Makefile; sourceTree = ""; }; + B6138763145AD1F700ED9B19 /* chat_client.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = chat_client.html; path = examples/chat_server/chat_client.html; sourceTree = ""; }; + B6138764145AD1F700ED9B19 /* chat_server.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = chat_server.cpp; path = examples/chat_server/chat_server.cpp; sourceTree = ""; }; + B6138765145AD1F700ED9B19 /* chat.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = chat.cpp; path = examples/chat_server/chat.cpp; sourceTree = ""; }; + B6138766145AD1F700ED9B19 /* chat.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = chat.hpp; path = examples/chat_server/chat.hpp; sourceTree = ""; }; + B6138767145AD1F700ED9B19 /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; name = Makefile; path = examples/chat_server/Makefile; 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 = ""; }; @@ -124,6 +134,7 @@ B682888A14374623002BA48B /* libboost_system.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libboost_system.dylib; path = usr/local/lib/libboost_system.dylib; sourceTree = SDKROOT; }; B682888C1437464A002BA48B /* libboost_random.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libboost_random.dylib; path = usr/local/lib/libboost_random.dylib; sourceTree = SDKROOT; }; B682888E14374689002BA48B /* libboost_thread.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libboost_thread.dylib; path = usr/local/lib/libboost_thread.dylib; sourceTree = SDKROOT; }; + B6BE76E9144EF53000716A77 /* websocket_endpoint.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = websocket_endpoint.hpp; path = src/websocket_endpoint.hpp; sourceTree = ""; }; B6CF18131437C370009295BE /* echo_client.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = echo_client.cpp; sourceTree = ""; }; B6CF18141437C370009295BE /* echo_client_handler.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = echo_client_handler.cpp; sourceTree = ""; }; B6CF18151437C370009295BE /* echo_client_handler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = echo_client_handler.hpp; sourceTree = ""; }; @@ -163,6 +174,8 @@ B6DF1CD11435ED910029A1B1 /* echo_server */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = echo_server; sourceTree = BUILT_PRODUCTS_DIR; }; B6DF1CE11435F1860029A1B1 /* libboost_system.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libboost_system.dylib; path = usr/local/lib/libboost_system.dylib; sourceTree = SDKROOT; }; B6DF1CE31435F8250029A1B1 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + B6FE8CE2144DE17F00B32547 /* readme.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = readme.txt; sourceTree = ""; }; + B6FE8CEB145A0F1900B32547 /* libboost_program_options.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libboost_program_options.dylib; path = usr/local/lib/libboost_program_options.dylib; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -196,6 +209,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B6FE8CEC145A0F1900B32547 /* libboost_program_options.dylib in Frameworks */, B682888D1437464A002BA48B /* libboost_random.dylib in Frameworks */, B6DF1CC11434AF6A0029A1B1 /* libboost_date_time.dylib in Frameworks */, B6DF1CC21434AF6A0029A1B1 /* libboost_regex.dylib in Frameworks */, @@ -231,11 +245,13 @@ B6DF1C451434A5940029A1B1 = { isa = PBXGroup; children = ( + B6FE8CEB145A0F1900B32547 /* libboost_program_options.dylib */, B6CF182B1437C3CA009295BE /* libboost_system.dylib */, B682888E14374689002BA48B /* libboost_thread.dylib */, B682888C1437464A002BA48B /* libboost_random.dylib */, B682888A14374623002BA48B /* libboost_system.dylib */, B6DF1CE31435F8250029A1B1 /* Foundation.framework */, + B6FE8CE4144DE18900B32547 /* documentation */, B6DF1CC61435ED380029A1B1 /* examples */, B6DF1CC51435ECE40029A1B1 /* libraries */, B6DF1C7F1434ABB70029A1B1 /* src */, @@ -263,6 +279,7 @@ B6DF1C941434AC470029A1B1 /* websocket_client.cpp */, B6DF1C951434AC470029A1B1 /* websocket_client.hpp */, B6DF1C961434AC470029A1B1 /* websocket_connection_handler.hpp */, + B6BE76E9144EF53000716A77 /* websocket_endpoint.hpp */, B6DF1C971434AC470029A1B1 /* websocket_frame.cpp */, B6DF1C981434AC470029A1B1 /* websocket_frame.hpp */, B6DF1C991434AC470029A1B1 /* websocket_server_session.cpp */, @@ -338,6 +355,7 @@ B6DF1CCA1435ED760029A1B1 /* echo_server.cpp */, B6DF1CCB1435ED760029A1B1 /* echo.cpp */, B6DF1CCC1435ED760029A1B1 /* echo.hpp */, + B6138760145AD09700ED9B19 /* Makefile */, ); name = echo_server; sourceTree = ""; @@ -348,6 +366,7 @@ B6828875143745DA002BA48B /* chat_client_handler.cpp */, B6828876143745DA002BA48B /* chat_client_handler.hpp */, B6828877143745DA002BA48B /* chat_client.cpp */, + B6138762145AD0A500ED9B19 /* Makefile */, ); name = chat_client; sourceTree = ""; @@ -355,10 +374,23 @@ B6DF1CC91435ED460029A1B1 /* chat_server */ = { isa = PBXGroup; children = ( + B6138763145AD1F700ED9B19 /* chat_client.html */, + B6138764145AD1F700ED9B19 /* chat_server.cpp */, + B6138765145AD1F700ED9B19 /* chat.cpp */, + B6138766145AD1F700ED9B19 /* chat.hpp */, + B6138767145AD1F700ED9B19 /* Makefile */, ); name = chat_server; sourceTree = ""; }; + B6FE8CE4144DE18900B32547 /* documentation */ = { + isa = PBXGroup; + children = ( + B6FE8CE2144DE17F00B32547 /* readme.txt */, + ); + name = documentation; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -378,6 +410,7 @@ B6DF1CB01434AC470029A1B1 /* websocket_server_session.hpp in Headers */, B6DF1CB41434AC470029A1B1 /* websocket_server.hpp in Headers */, B6DF1CB81434AC470029A1B1 /* websocket_session.hpp in Headers */, + B6BE76EA144EF53000716A77 /* websocket_endpoint.hpp in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -397,6 +430,7 @@ B6DF1CB11434AC470029A1B1 /* websocket_server_session.hpp in Headers */, B6DF1CB51434AC470029A1B1 /* websocket_server.hpp in Headers */, B6DF1CB91434AC470029A1B1 /* websocket_session.hpp in Headers */, + B6BE76EB144EF53000716A77 /* websocket_endpoint.hpp in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/windows/.gitignore b/windows/vcpp2008/.gitignore similarity index 100% rename from windows/.gitignore rename to windows/vcpp2008/.gitignore diff --git a/windows/examples/chatclient.vcproj b/windows/vcpp2008/examples/chatclient.vcproj similarity index 88% rename from windows/examples/chatclient.vcproj rename to windows/vcpp2008/examples/chatclient.vcproj index 4919d9f806..929efff01e 100644 --- a/windows/examples/chatclient.vcproj +++ b/windows/vcpp2008/examples/chatclient.vcproj @@ -41,7 +41,7 @@ @@ -191,7 +191,7 @@ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" > diff --git a/windows/examples/chatserver.vcproj b/windows/vcpp2008/examples/chatserver.vcproj similarity index 89% rename from windows/examples/chatserver.vcproj rename to windows/vcpp2008/examples/chatserver.vcproj index a0080e75a8..d16f5eb89c 100644 --- a/windows/examples/chatserver.vcproj +++ b/windows/vcpp2008/examples/chatserver.vcproj @@ -41,7 +41,7 @@ @@ -191,7 +191,7 @@ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" > diff --git a/windows/examples/echoserver.vcproj b/windows/vcpp2008/examples/echoserver.vcproj similarity index 89% rename from windows/examples/echoserver.vcproj rename to windows/vcpp2008/examples/echoserver.vcproj index f0d6c88bea..f2fc495064 100644 --- a/windows/examples/echoserver.vcproj +++ b/windows/vcpp2008/examples/echoserver.vcproj @@ -41,7 +41,7 @@ @@ -192,7 +192,7 @@ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" > diff --git a/windows/stdint.h b/windows/vcpp2008/stdint.h similarity index 100% rename from windows/stdint.h rename to windows/vcpp2008/stdint.h diff --git a/windows/websocketpp.sln b/windows/vcpp2008/websocketpp.sln similarity index 100% rename from windows/websocketpp.sln rename to windows/vcpp2008/websocketpp.sln diff --git a/windows/websocketpp.vcproj b/windows/vcpp2008/websocketpp.vcproj similarity index 77% rename from windows/websocketpp.vcproj rename to windows/vcpp2008/websocketpp.vcproj index deea893c45..eb63530692 100644 --- a/windows/websocketpp.vcproj +++ b/windows/vcpp2008/websocketpp.vcproj @@ -41,7 +41,7 @@ @@ -194,7 +194,7 @@ Name="sha" > @@ -205,46 +205,46 @@ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" > @@ -252,7 +252,7 @@ Name="sha" > @@ -260,7 +260,7 @@ Name="utf8_validator" > diff --git a/windows/vcpp2010/.gitignore b/windows/vcpp2010/.gitignore new file mode 100644 index 0000000000..62b99499b0 --- /dev/null +++ b/windows/vcpp2010/.gitignore @@ -0,0 +1,17 @@ +# files to be ignored: +# http://stackoverflow.com/questions/2538149/what-should-be-contained-in-a-global-subversion-ignore-pattern-for-visual-studio +# http://stackoverflow.com/questions/3922660/which-visual-c-file-types-should-be-committed-to-version-control +# http://msdn.microsoft.com/en-us/library/3awe4781%28v=VS.100%29.aspx + +*.user +*.ncb +*.suo +*.sdf +*.opensdf +Debug +Release +examples/*.user +examples/*.ncb +examples/Debug +examples/Release +ipch/* diff --git a/windows/vcpp2010/examples/chatclient.vcxproj b/windows/vcpp2010/examples/chatclient.vcxproj new file mode 100644 index 0000000000..5a07f036a5 --- /dev/null +++ b/windows/vcpp2010/examples/chatclient.vcxproj @@ -0,0 +1,108 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {116BFEDA-AF8E-4B3F-8508-ACC5EE89F905} + chatclient + Win32Proj + + + + Application + Unicode + true + + + Application + Unicode + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + + + + Disabled + $(BOOSTROOT);..;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;WIN32_LEAN_AND_MEAN;NOCOMM;_WIN32_WINNT=0x0600;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + + + Level3 + ProgramDatabase + + + $(BOOSTROOT)\stage\lib;%(AdditionalLibraryDirectories) + true + Console + MachineX86 + + + + + MaxSpeed + true + $(BOOSTROOT);..;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;WIN32_LEAN_AND_MEAN;NOCOMM;_WIN32_WINNT=0x0600;%(PreprocessorDefinitions) + MultiThreaded + false + true + StreamingSIMDExtensions2 + + + Level3 + + + + + $(BOOSTROOT)\stage\lib;%(AdditionalLibraryDirectories) + false + Console + true + true + MachineX86 + + + + + + + + + + + + {1c0fd04e-5aca-4031-b3d1-320a5360c9d0} + false + + + + + + \ No newline at end of file diff --git a/windows/vcpp2010/examples/chatclient.vcxproj.filters b/windows/vcpp2010/examples/chatclient.vcxproj.filters new file mode 100644 index 0000000000..34e66c830b --- /dev/null +++ b/windows/vcpp2010/examples/chatclient.vcxproj.filters @@ -0,0 +1,26 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/windows/vcpp2010/examples/chatserver.vcxproj b/windows/vcpp2010/examples/chatserver.vcxproj new file mode 100644 index 0000000000..bfdec866f8 --- /dev/null +++ b/windows/vcpp2010/examples/chatserver.vcxproj @@ -0,0 +1,108 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {2AFECE48-86DE-47D0-9263-DC0D203AA62D} + chatserver + Win32Proj + + + + Application + Unicode + true + + + Application + Unicode + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + + + + Disabled + $(BOOSTROOT);..;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;WIN32_LEAN_AND_MEAN;NOCOMM;_WIN32_WINNT=0x0600;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + + + Level3 + ProgramDatabase + + + $(BOOSTROOT)\stage\lib;%(AdditionalLibraryDirectories) + true + Console + MachineX86 + + + + + MaxSpeed + true + $(BOOSTROOT);..;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;WIN32_LEAN_AND_MEAN;NOCOMM;_WIN32_WINNT=0x0600;%(PreprocessorDefinitions) + MultiThreaded + false + true + StreamingSIMDExtensions2 + + + Level3 + + + + + $(BOOSTROOT)\stage\lib;%(AdditionalLibraryDirectories) + false + Console + true + true + MachineX86 + + + + + + + + + + + + {1c0fd04e-5aca-4031-b3d1-320a5360c9d0} + false + + + + + + \ No newline at end of file diff --git a/windows/vcpp2010/examples/chatserver.vcxproj.filters b/windows/vcpp2010/examples/chatserver.vcxproj.filters new file mode 100644 index 0000000000..e2ea4bd9dd --- /dev/null +++ b/windows/vcpp2010/examples/chatserver.vcxproj.filters @@ -0,0 +1,26 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/windows/vcpp2010/examples/echoclient.vcxproj b/windows/vcpp2010/examples/echoclient.vcxproj new file mode 100644 index 0000000000..281a304a64 --- /dev/null +++ b/windows/vcpp2010/examples/echoclient.vcxproj @@ -0,0 +1,99 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {D7C36001-002F-4B8E-B4C0-A04E2F9522D0} + Win32Proj + echoclient + + + + Application + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + false + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;WIN32_LEAN_AND_MEAN;NOCOMM;_WIN32_WINNT=0x0600;%(PreprocessorDefinitions) + $(BOOSTROOT);..;%(AdditionalIncludeDirectories) + MultiThreadedDebug + + + Console + true + $(BOOSTROOT)\stage\lib;%(AdditionalLibraryDirectories) + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;WIN32_LEAN_AND_MEAN;NOCOMM;_WIN32_WINNT=0x0600;%(PreprocessorDefinitions) + $(BOOSTROOT);..;%(AdditionalIncludeDirectories) + MultiThreaded + false + StreamingSIMDExtensions2 + + + Console + true + true + true + $(BOOSTROOT)\stage\lib;%(AdditionalLibraryDirectories) + + + + + + + + + + + + {1c0fd04e-5aca-4031-b3d1-320a5360c9d0} + + + + + + \ No newline at end of file diff --git a/windows/vcpp2010/examples/echoclient.vcxproj.filters b/windows/vcpp2010/examples/echoclient.vcxproj.filters new file mode 100644 index 0000000000..765ace9c46 --- /dev/null +++ b/windows/vcpp2010/examples/echoclient.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/windows/vcpp2010/examples/echoserver.vcxproj b/windows/vcpp2010/examples/echoserver.vcxproj new file mode 100644 index 0000000000..eeace56e03 --- /dev/null +++ b/windows/vcpp2010/examples/echoserver.vcxproj @@ -0,0 +1,109 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {B569A272-D7D3-404B-B5FB-9187C0EB9F48} + examples + Win32Proj + + + + Application + Unicode + true + + + Application + Unicode + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + false + + + + Disabled + $(BOOSTROOT);..;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;WIN32_LEAN_AND_MEAN;NOCOMM;_WIN32_WINNT=0x0600;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + StreamingSIMDExtensions2 + + + Level3 + ProgramDatabase + + + $(BOOSTROOT)\stage\lib;%(AdditionalLibraryDirectories) + true + Console + MachineX86 + + + + + MaxSpeed + true + $(BOOSTROOT);..;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;WIN32_LEAN_AND_MEAN;NOCOMM;_WIN32_WINNT=0x0600;%(PreprocessorDefinitions) + MultiThreaded + false + true + StreamingSIMDExtensions2 + + + Level3 + + + + + $(BOOSTROOT)\stage\lib;%(AdditionalLibraryDirectories) + false + Console + true + true + MachineX86 + + + + + + + + + + + + {1c0fd04e-5aca-4031-b3d1-320a5360c9d0} + false + + + + + + \ No newline at end of file diff --git a/windows/vcpp2010/examples/echoserver.vcxproj.filters b/windows/vcpp2010/examples/echoserver.vcxproj.filters new file mode 100644 index 0000000000..a62d813e87 --- /dev/null +++ b/windows/vcpp2010/examples/echoserver.vcxproj.filters @@ -0,0 +1,26 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/windows/vcpp2010/readme.txt b/windows/vcpp2010/readme.txt new file mode 100644 index 0000000000..d32437e126 --- /dev/null +++ b/windows/vcpp2010/readme.txt @@ -0,0 +1,35 @@ +Build Boost +=========== + +Prerequisites: Visual Sutdio C ++ 2010 Express (or higher) + +Download Boost from http://www.boost.org/ + +Unzip boost_1_47_0.zip to C:\boost_1_47_0 + +Open a "Visual Studio Command Prompt (2010)" (not a regular cmd.exe!). + +cd C:\boost_1_47_0 + +bootstrap + +.\b2 runtime-link=static + +Now set a system environment variable: + +BOOSTROOT = C:\boost_1_47_0 + + +Background: + + - http://www.boost.org/doc/libs/1_47_0/more/getting_started/windows.html + - http://www.boost.org/doc/libs/1_47_0/more/getting_started/windows.html#library-naming + - http://stackoverflow.com/questions/2035287/static-runtime-library-linking-for-visual-c-express-2008 + + +Build websocket++ +================= + +Open websocketpp.sln in VS. + +Build Solution (F7). diff --git a/windows/vcpp2010/websocketpp.sln b/windows/vcpp2010/websocketpp.sln new file mode 100644 index 0000000000..4139952bfa --- /dev/null +++ b/windows/vcpp2010/websocketpp.sln @@ -0,0 +1,47 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual C++ Express 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "websocketpp", "websocketpp.vcxproj", "{1C0FD04E-5ACA-4031-B3D1-320A5360C9D0}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "echoserver", "examples\echoserver.vcxproj", "{B569A272-D7D3-404B-B5FB-9187C0EB9F48}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "chatserver", "examples\chatserver.vcxproj", "{2AFECE48-86DE-47D0-9263-DC0D203AA62D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "chatclient", "examples\chatclient.vcxproj", "{116BFEDA-AF8E-4B3F-8508-ACC5EE89F905}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "echoclient", "examples\echoclient.vcxproj", "{D7C36001-002F-4B8E-B4C0-A04E2F9522D0}" + ProjectSection(ProjectDependencies) = postProject + {1C0FD04E-5ACA-4031-B3D1-320A5360C9D0} = {1C0FD04E-5ACA-4031-B3D1-320A5360C9D0} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1C0FD04E-5ACA-4031-B3D1-320A5360C9D0}.Debug|Win32.ActiveCfg = Debug|Win32 + {1C0FD04E-5ACA-4031-B3D1-320A5360C9D0}.Debug|Win32.Build.0 = Debug|Win32 + {1C0FD04E-5ACA-4031-B3D1-320A5360C9D0}.Release|Win32.ActiveCfg = Release|Win32 + {1C0FD04E-5ACA-4031-B3D1-320A5360C9D0}.Release|Win32.Build.0 = Release|Win32 + {B569A272-D7D3-404B-B5FB-9187C0EB9F48}.Debug|Win32.ActiveCfg = Debug|Win32 + {B569A272-D7D3-404B-B5FB-9187C0EB9F48}.Debug|Win32.Build.0 = Debug|Win32 + {B569A272-D7D3-404B-B5FB-9187C0EB9F48}.Release|Win32.ActiveCfg = Release|Win32 + {B569A272-D7D3-404B-B5FB-9187C0EB9F48}.Release|Win32.Build.0 = Release|Win32 + {2AFECE48-86DE-47D0-9263-DC0D203AA62D}.Debug|Win32.ActiveCfg = Debug|Win32 + {2AFECE48-86DE-47D0-9263-DC0D203AA62D}.Debug|Win32.Build.0 = Debug|Win32 + {2AFECE48-86DE-47D0-9263-DC0D203AA62D}.Release|Win32.ActiveCfg = Release|Win32 + {2AFECE48-86DE-47D0-9263-DC0D203AA62D}.Release|Win32.Build.0 = Release|Win32 + {116BFEDA-AF8E-4B3F-8508-ACC5EE89F905}.Debug|Win32.ActiveCfg = Debug|Win32 + {116BFEDA-AF8E-4B3F-8508-ACC5EE89F905}.Debug|Win32.Build.0 = Debug|Win32 + {116BFEDA-AF8E-4B3F-8508-ACC5EE89F905}.Release|Win32.ActiveCfg = Release|Win32 + {116BFEDA-AF8E-4B3F-8508-ACC5EE89F905}.Release|Win32.Build.0 = Release|Win32 + {D7C36001-002F-4B8E-B4C0-A04E2F9522D0}.Debug|Win32.ActiveCfg = Debug|Win32 + {D7C36001-002F-4B8E-B4C0-A04E2F9522D0}.Debug|Win32.Build.0 = Debug|Win32 + {D7C36001-002F-4B8E-B4C0-A04E2F9522D0}.Release|Win32.ActiveCfg = Release|Win32 + {D7C36001-002F-4B8E-B4C0-A04E2F9522D0}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/windows/vcpp2010/websocketpp.vcxproj b/windows/vcpp2010/websocketpp.vcxproj new file mode 100644 index 0000000000..0ba1ca8384 --- /dev/null +++ b/windows/vcpp2010/websocketpp.vcxproj @@ -0,0 +1,105 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {1C0FD04E-5ACA-4031-B3D1-320A5360C9D0} + websocketpp + Win32Proj + + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + + + + Disabled + $(BOOSTROOT);.;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;WIN32_LEAN_AND_MEAN;NOCOMM;_WIN32_WINNT=0x0600;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreaded + StreamingSIMDExtensions2 + + + Level3 + EditAndContinue + + + + + MaxSpeed + true + Speed + $(BOOSTROOT);.;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;WIN32_LEAN_AND_MEAN;NOCOMM;_WIN32_WINNT=0x0600;%(PreprocessorDefinitions) + MultiThreaded + false + true + StreamingSIMDExtensions2 + + + Level3 + ProgramDatabase + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/vcpp2010/websocketpp.vcxproj.filters b/windows/vcpp2010/websocketpp.vcxproj.filters new file mode 100644 index 0000000000..ace00626f3 --- /dev/null +++ b/windows/vcpp2010/websocketpp.vcxproj.filters @@ -0,0 +1,95 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {79820f1e-61d9-46bc-9eb2-a52c20f62fa4} + + + {b6f4bad9-f45a-495f-93f2-f4b1605b9ac4} + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {d7e1c798-4217-4c1e-a897-90bfc59d83ad} + + + {84b75a09-d8c9-4327-806d-c4f54698e4c5} + + + {c849b58d-df43-4574-ae85-92ac66333899} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files\base64 + + + Source Files\sha + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files\base64 + + + Header Files\sha + + + Header Files\utf8_validator + + +