diff --git a/examples/echo_client/Makefile b/examples/echo_client/Makefile index 901343976e..72d22e338e 100644 --- a/examples/echo_client/Makefile +++ b/examples/echo_client/Makefile @@ -10,7 +10,7 @@ else 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 +echo_client: echo_client.o $(CXX) $(CFLAGS) $^ -o $@ $(LDFLAGS) %.o: %.cpp diff --git a/examples/echo_client/echo_client.cpp b/examples/echo_client/echo_client.cpp index 27ac63a8bb..3881127f6b 100644 --- a/examples/echo_client/echo_client.cpp +++ b/examples/echo_client/echo_client.cpp @@ -58,8 +58,8 @@ public: if (connection->get_resource() == "/getCaseCount") { - std::cout << "Detected " << msg.get_payload() << " test cases." << std::endl; - m_case_count = atoi(msg.get_payload().c_str()); + std::cout << "Detected " << msg->get_payload() << " test cases." << std::endl; + m_case_count = atoi(msg->get_payload().c_str()); } else { connection->send(msg->get_payload(),(msg->get_opcode() == websocketpp::frame::opcode::BINARY)); } @@ -69,7 +69,7 @@ public: } void http(connection_ptr connection) { - connection->set_body("HTTP Response!!"); + //connection->set_body("HTTP Response!!"); } void on_fail(connection_ptr connection) { @@ -109,9 +109,9 @@ int main(int argc, char* argv[]) { client->connect("ws://localhost:9001/getCaseCount"); io_service.run();*/ - std::cout << "case count: " << c->m_case_count << std::endl; + std::cout << "case count: " << boost::dynamic_pointer_cast(handler)->m_case_count << std::endl; - for (int i = 1; i <= c->m_case_count; i++) { + for (int i = 1; i <= boost::dynamic_pointer_cast(handler)->m_case_count; i++) { std::stringstream url; url << "ws://localhost:9001/runCase?case=" << i << "&agent=\"WebSocket++Snapshot/2011-10-27\""; diff --git a/src/http/parser.hpp b/src/http/parser.hpp index cc452ab92d..1fb26de2a4 100644 --- a/src/http/parser.hpp +++ b/src/http/parser.hpp @@ -263,11 +263,11 @@ public: m_body = value; } - status_code::value status_code() const { + status_code::value get_status_code() const { return m_status_code; } - const std::string& status_msg() const { + const std::string& get_status_msg() const { return m_status_msg; } private: diff --git a/src/roles/client.hpp b/src/roles/client.hpp index 603796303a..e116b7e301 100644 --- a/src/roles/client.hpp +++ b/src/roles/client.hpp @@ -34,6 +34,8 @@ #include #include #include +#include +#include #include @@ -51,60 +53,75 @@ public: public: typedef connection type; typedef endpoint endpoint_type; + + // client connections are friends with their respective client endpoint + friend class client; + + // Valid always + int get_version() const { + return m_version; + } + /*std::string get_request_header(const std::string& key) const { + return m_request.header(key); + }*/ + std::string get_origin() const { + return m_origin; + } + + // Information about the requested URI + // valid only after URIs are loaded + // TODO: check m_uri for NULLness + bool get_secure() const { + return m_uri->get_secure(); + } + std::string get_host() const { + return m_uri->get_host(); + } + std::string get_resource() const { + return m_uri->get_resource(); + } + uint16_t get_port() const { + return m_uri->get_port(); + } protected: - connection(endpoint& e) : m_endpoint(e) {} + connection(endpoint& e) + : m_endpoint(e), + m_connection(static_cast< connection_type& >(*this)), + // TODO: version shouldn't be hardcoded + m_version(17) {} + void set_uri(uri_ptr u) { + m_uri = u; + } + void async_init() { write_request(); } - void write_request() { - // async write to handle_write - } - - void handle_write_request(const boost::system::error_code& error) { - if (error) { - m_endpoint.elog().at(log::elevel::ERROR) << "Error writing WebSocket request. code: " << error << log::endl; - m_connection.terminate(false); - return; - - } - - read_request(); - } - - void read_request() { - boost::asio::async_read_until( - m_connection.get_socket(), - m_connection.buffer(), - "\r\n\r\n", - boost::bind( - &type::handle_read_request, - m_connection.shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred - ) - ); - } - - // - void handle_read_request(const boost::system::error_code& error, - std::size_t bytes_transferred) - { - if (error) { - m_endpoint.elog().at(log::elevel::ERROR) << "Error reading HTTP request. code: " << error << log::endl; - m_connection.terminate(false); - return; - } - - // read - - - // start session loop - } + int32_t rand() { + return m_endpoint.rand(); + } + + void write_request(); + void handle_write_request(const boost::system::error_code& error); + void read_response(); + void handle_read_response(const boost::system::error_code& error, + std::size_t bytes_transferred); private: endpoint& m_endpoint; connection_type& m_connection; + + int m_version; + uri_ptr m_uri; + std::string m_origin; + std::vector m_requested_subprotocols; + std::vector m_requested_extensions; + std::string m_subprotocol; + std::vector m_extensions; + + std::string m_handshake_key; + http::parser::request m_request; + http::parser::response m_response; }; // types @@ -134,36 +151,11 @@ public: : m_state(UNINITIALIZED), m_endpoint(static_cast< endpoint_type& >(*this)), m_io_service(m), - m_resolver(m) {} + m_resolver(m), + m_gen(m_rng,boost::random::uniform_int_distribution<>(INT32_MIN, + INT32_MAX)) {} - connection_ptr connect(const std::string& u) { - // TODO: will throw, should we catch and wrap? - uri location(u); - - if (location.get_secure() && !m_endpoint.is_secure()) { - // TODO: what kind of exception does client throw? - throw ""; - } - - tcp::resolver::query query(location.get_host(),location.get_port_str()); - tcp::resolver::iterator iterator = m_resolver.resolve(query); - - connection_ptr con = m_endpoint.create_connection(); - - boost::asio::async_connect( - con->get_raw_socket(), - iterator, - boost::bind( - &endpoint_type::handle_connect, - endpoint_type::shared_from_this(), - con, - boost::asio::placeholders::error - ) - ); - m_state = CONNECTING; - - return con; - } + connection_ptr connect(const std::string& u); // TODO: add a `perpetual` option void run() { @@ -178,6 +170,9 @@ protected: bool is_server() { return false; } + int32_t rand() { + return m_gen(); + } private: enum state { UNINITIALIZED = 0, @@ -186,26 +181,251 @@ private: CONNECTED = 3 }; - void handle_connect(connection_ptr con, const boost::system::error_code& error) { - if (!error) { - m_endpoint.alog().at(log::alevel::CONNECT) << "Successful connection" << log::endl; - - m_state = CONNECTED; - con->start(); - } else { - m_endpoint.elog().at(log::elevel::ERROR) << "An error occurred while establishing a connection: " << error << log::endl; - - // TODO: fix - throw "client error"; - } - } + void handle_connect(connection_ptr con, + const boost::system::error_code& error); state m_state; endpoint_type& m_endpoint; boost::asio::io_service& m_io_service; tcp::resolver m_resolver; + + boost::random::random_device m_rng; + boost::random::variate_generator< + boost::random::random_device&, + boost::random::uniform_int_distribution<> + > m_gen; }; +// client implimentation + +template +typename endpoint_traits::connection_ptr +client::connect(const std::string& u) { + // TODO: will throw, should we catch and wrap? + uri_ptr location(new uri(u)); + + if (location->get_secure() && !m_endpoint.is_secure()) { + // TODO: what kind of exception does client throw? + throw ""; + } + + tcp::resolver::query query(location->get_host(),location->get_port_str()); + tcp::resolver::iterator iterator = m_resolver.resolve(query); + + connection_ptr con = m_endpoint.create_connection(); + con->set_uri(location); + + boost::asio::async_connect( + con->get_raw_socket(), + iterator, + boost::bind( + &endpoint_type::handle_connect, + this, // shared from this? + con, + boost::asio::placeholders::error + ) + ); + m_state = CONNECTING; + + return con; +} + +template +void client::handle_connect(connection_ptr con, + const boost::system::error_code& error) +{ + if (!error) { + m_endpoint.alog().at(log::alevel::CONNECT) + << "Successful connection" << log::endl; + + m_state = CONNECTED; + con->start(); + } else { + m_endpoint.elog().at(log::elevel::ERROR) + << "An error occurred while establishing a connection: " + << error << log::endl; + + // TODO: fix + throw "client error"; + } +} + +// client connection implimentation + +template +template +void client::connection::write_request() { + // async write to handle_write + + m_request.set_method("GET"); + m_request.set_uri(m_uri->get_resource()); + m_request.set_version("HTTP/1.1"); + + m_request.add_header("Upgrade","websocket"); + m_request.add_header("Connection","Upgrade"); + m_request.replace_header("Sec-WebSocket-Version","13"); + + m_request.replace_header("Host",m_uri->get_host()); + + if (m_origin != "") { + m_request.replace_header("Origin",m_origin); + } + + // Generate client key + int32_t raw_key[4]; + + for (int i = 0; i < 4; i++) { + raw_key[i] = this->rand(); + } + + m_handshake_key = base64_encode(reinterpret_cast(raw_key), 16); + + m_request.replace_header("Sec-WebSocket-Key",m_handshake_key); + m_request.replace_header("User Agent","WebSocket++/2011-12-06"); + + std::string raw = m_request.raw(); + + m_endpoint.alog().at(log::alevel::DEBUG_HANDSHAKE) << raw << log::endl; + + boost::asio::async_write( + m_connection.get_socket(), + boost::asio::buffer(raw), + boost::bind( + &type::handle_write_request, + m_connection.shared_from_this(), + boost::asio::placeholders::error + ) + ); +} + +template +template +void client::connection::handle_write_request( + const boost::system::error_code& error) +{ + if (error) { + m_endpoint.elog().at(log::elevel::ERROR) << "Error writing WebSocket request. code: " << error << log::endl; + m_connection.terminate(false); + return; + + } + + read_response(); +} + +template +template +void client::connection::read_response() { + boost::asio::async_read_until( + m_connection.get_socket(), + m_connection.buffer(), + "\r\n\r\n", + boost::bind( + &type::handle_read_response, + m_connection.shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred + ) + ); +} + +template +template +void client::connection::handle_read_response ( + const boost::system::error_code& error, std::size_t bytes_transferred) +{ + if (error) { + m_endpoint.elog().at(log::elevel::ERROR) << "Error reading HTTP request. code: " << error << log::endl; + m_connection.terminate(false); + return; + } + + try { + std::istream request(&m_connection.buffer()); + + if (!m_response.parse_complete(request)) { + // not a valid HTTP response + // TODO: this should be a client error + throw http::exception("Could not parse server response.",http::status_code::BAD_REQUEST); + } + + m_endpoint.alog().at(log::alevel::DEBUG_HANDSHAKE) << m_response.raw() << log::endl; + + // error checking + if (m_response.get_status_code() != http::status_code::SWITCHING_PROTOCOLS) { + throw http::exception("Server failed to upgrade connection.", + m_response.get_status_code(), + m_response.get_status_msg()); + } + + std::string h = m_response.header("Upgrade"); + if (!boost::ifind_first(h,"websocket")) { + throw http::exception("Token `websocket` missing from Upgrade header.", + m_response.get_status_code(), + m_response.get_status_msg()); + } + + h = m_response.header("Connection"); + if (!boost::ifind_first(h,"upgrade")) { + throw http::exception("Token `upgrade` missing from Connection header.", + m_response.get_status_code(), + m_response.get_status_msg()); + } + + h = m_response.header("Sec-WebSocket-Accept"); + if (h == "") { + throw http::exception("Required Sec-WebSocket-Accept header is missing.", + m_response.get_status_code(), + m_response.get_status_msg()); + } else { + std::string server_key = m_handshake_key; + server_key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + SHA1 sha; + uint32_t message_digest[5]; + + sha.Reset(); + sha << server_key.c_str(); + + if (!sha.Result(message_digest)) { + m_endpoint.elog().at(log::elevel::ERROR) << "Error computing handshake sha1 hash." << log::endl; + // TODO: close behavior + return; + } + + // convert sha1 hash bytes to network byte order because this sha1 + // library works on ints rather than bytes + for (int i = 0; i < 5; i++) { + message_digest[i] = htonl(message_digest[i]); + } + + server_key = base64_encode( + reinterpret_cast(message_digest),20); + if (server_key != h) { + m_endpoint.elog().at(log::elevel::ERROR) << "Server returned incorrect handshake key." << log::endl; + // TODO: close behavior + return; + } + } + + log_open_result(); + + m_connection.m_state = session::state::OPEN; + + m_endpoint.get_handler()->on_open(m_connection.shared_from_this()); + + m_connection.handle_read_frame(boost::system::error_code()); + } catch (const http::exception& e) { + m_endpoint.elog().at(log::elevel::ERROR) + << "Error processing server handshake. Server HTTP response: " + << e.m_error_msg << "(" << e.m_error_code + << ") Local error: " << e.what() << log::endl; + return; + } + + + // start session loop +} } // namespace role } // namespace websocketpp diff --git a/src/roles/server.hpp b/src/roles/server.hpp index b1121a2fac..f41c1dfa3e 100644 --- a/src/roles/server.hpp +++ b/src/roles/server.hpp @@ -59,7 +59,7 @@ public: typedef endpoint endpoint_type; // Valid always - unsigned int get_version() const { + int get_version() const { return m_version; } std::string get_request_header(const std::string& key) const { @@ -102,262 +102,26 @@ public: const std::vector& get_extensions() const { return m_requested_extensions; } - void select_subprotocol(const std::string& value) { - std::vector::iterator it; - - it = std::find(m_requested_subprotocols.begin(), - m_requested_subprotocols.end(), - value); - - if (value != "" && it == m_requested_subprotocols.end()) { - throw std::invalid_argument("Attempted to choose a subprotocol not proposed by the client"); - } - - m_subprotocol = value; - } - void select_extension(const std::string& value) { - if (value == "") { - return; - } - - std::vector::iterator it; - - it = std::find(m_requested_extensions.begin(), - m_requested_extensions.end(), - value); - - if (it == m_requested_extensions.end()) { - throw std::invalid_argument("Attempted to choose an extension not proposed by the client"); - } - - m_extensions.push_back(value); - } + void select_subprotocol(const std::string& value); + void select_extension(const std::string& value); // Valid if get_version() returns -1 (ie this is an http connection) - void set_body(const std::string& value) { - if (m_connection.m_version != -1) { - // TODO: throw exception - throw std::invalid_argument("set_body called from invalid state"); - } - - m_response.set_body(value); - } + void set_body(const std::string& value); protected: - //connection(server& e) : m_endpoint(e) {} connection(endpoint& e) - : m_endpoint(e), - m_connection(static_cast< connection_type& >(*this)), - m_version(-1), - m_uri() {} + : m_endpoint(e), + m_connection(static_cast< connection_type& >(*this)), + m_version(-1), + m_uri() {} // initializes the websocket connection - void async_init() { - boost::asio::async_read_until( - m_connection.get_socket(), - m_connection.buffer(), - "\r\n\r\n", - boost::bind( - &type::handle_read_request, - m_connection.shared_from_this(), // shared from this? - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred - ) - ); - } - + void async_init(); void handle_read_request(const boost::system::error_code& error, - std::size_t bytes_transferred) - { - if (error) { - // log error - m_endpoint.elog().at(log::elevel::ERROR) << "Error reading HTTP request. code: " << error << log::endl; - m_connection.terminate(false); - return; - } - - try { - std::istream request(&m_connection.buffer()); - - if (!m_request.parse_complete(request)) { - // not a valid HTTP request/response - throw http::exception("Recieved invalid HTTP Request",http::status_code::BAD_REQUEST); - } - - m_endpoint.alog().at(log::alevel::DEBUG_HANDSHAKE) << m_request.raw() << log::endl; - - std::string h = m_request.header("Upgrade"); - if (boost::ifind_first(h,"websocket")) { - h = m_request.header("Sec-WebSocket-Version"); - if (h == "") { - // websocket upgrade is present but version is not. - // assume hybi00 - m_version = 0; - } else { - m_version = atoi(h.c_str()); - if (m_version == 0) { - throw(http::exception("Unable to determine connection version",http::status_code::BAD_REQUEST)); - } - } - - // create a websocket processor - if (m_version == 0) { - //m_response.add_header("Sec-WebSocket-Version","13, 8, 7"); - - char foo[9]; - foo[8] = 0; - - request.get(foo,9); - - if (request.gcount() != 8) { - throw http::exception("Missing Key3",http::status_code::BAD_REQUEST); - } - m_request.add_header("Sec-WebSocket-Key3",std::string(foo)); - - //throw(http::exception("Unsupported WebSocket version",http::status_code::BAD_REQUEST)); - m_connection.m_processor = processor::ptr(new processor::hybi_legacy(m_connection)); - } else if (m_version == 7 || - m_version == 8 || - m_version == 13) { - m_connection.m_processor = processor::ptr(new processor::hybi(m_connection)); - } else { - m_response.add_header("Sec-WebSocket-Version","13, 8, 7"); - - throw(http::exception("Unsupported WebSocket version",http::status_code::BAD_REQUEST)); - } - - m_connection.m_processor->validate_handshake(m_request); - m_origin = m_connection.m_processor->get_origin(m_request); - m_uri = m_connection.m_processor->get_uri(m_request); - - m_endpoint.get_handler()->validate(m_connection.shared_from_this()); - - m_response.set_status(http::status_code::SWITCHING_PROTOCOLS); - } else { - // continue as HTTP? - m_endpoint.get_handler()->http(m_connection.shared_from_this()); - - // should there be a more encapsulated http processor here? - m_origin = m_request.header("Origin"); - - // Set URI - std::string h = m_request.header("Host"); - - size_t found = h.find(":"); - if (found == std::string::npos) { - // TODO: this makes the assumption that WS and HTTP - // default ports are the same. - m_uri.reset(new uri(m_endpoint.is_secure(),h,m_request.uri())); - } else { - m_uri.reset(new uri(m_endpoint.is_secure(), - h.substr(0,found), - h.substr(found+1), - m_request.uri())); - } - - m_response.set_status(http::status_code::OK); - } - } catch (const http::exception& e) { - m_endpoint.elog().at(log::elevel::ERROR) << e.what() << log::endl; - m_response.set_status(e.m_error_code,e.m_error_msg); - m_response.set_body(e.m_body); - } catch (const uri_exception& e) { - // there was some error building the uri - m_endpoint.elog().at(log::elevel::ERROR) << e.what() << log::endl; - m_response.set_status(http::status_code::BAD_REQUEST); - } - - write_response(); - } - - void write_response() { - m_response.set_version("HTTP/1.1"); - - if (m_response.status_code() == http::status_code::SWITCHING_PROTOCOLS) { - // websocket response - m_connection.m_processor->handshake_response(m_request,m_response); - - if (m_subprotocol != "") { - m_response.replace_header("Sec-WebSocket-Protocol",m_subprotocol); - } - - // TODO: return negotiated extensions - } else { - // HTTP response - } - - m_response.replace_header("Server","WebSocket++/2011-11-18"); - - std::string raw = m_response.raw(); - - // Hack for legacy HyBi - // TODO - if (m_version == 0) { - //raw += boost::dynamic_pointer_cast(m_connection.m_processor)->get_key3(); - raw += boost::dynamic_pointer_cast >(m_connection.m_processor)->get_key3(); - - - //raw += m_connection.m_processor->get_key3(); - } - - m_endpoint.alog().at(log::alevel::DEBUG_HANDSHAKE) << raw << log::endl; - - boost::asio::async_write( - m_connection.get_socket(), - boost::asio::buffer(raw), - boost::bind( - &type::handle_write_response, - m_connection.shared_from_this(), - boost::asio::placeholders::error - ) - ); - } - - void handle_write_response(const boost::system::error_code& error) { - // TODO: handshake timer - - if (error) { - m_endpoint.elog().at(log::elevel::ERROR) << "Network error writing handshake respons. code: " << error << log::endl; - - m_connection.terminate(false); - return; - } - - log_open_result(); - - if (m_response.status_code() != http::status_code::SWITCHING_PROTOCOLS) { - if (m_version == -1) { - // if this was not a websocket connection, we have written - // the expected response and the connection can be closed. - } else { - // this was a websocket connection that ended in an error - m_endpoint.elog().at(log::elevel::ERROR) - << "Handshake ended with HTTP error: " - << m_response.status_code() << " " - << m_response.status_msg() << log::endl; - } - m_connection.terminate(true); - return; - } - - m_connection.m_state = session::state::OPEN; - - m_endpoint.get_handler()->on_open(m_connection.shared_from_this()); - - m_connection.handle_read_frame(boost::system::error_code()); - } - - void log_open_result() { - std::stringstream version; - version << "v" << m_version << " "; - - m_endpoint.alog().at(log::alevel::CONNECT) << (m_version == -1 ? "HTTP" : "WebSocket") << " Connection " - << m_connection.get_raw_socket().remote_endpoint() << " " - << (m_version == -1 ? "" : version.str()) - << (get_request_header("User-Agent") == "" ? "NULL" : get_request_header("User-Agent")) - << " " << m_uri->get_resource() << " " << m_response.status_code() - << log::endl; - } + std::size_t bytes_transferred); + void write_response(); + void handle_write_response(const boost::system::error_code& error); + void log_open_result(); private: endpoint& m_endpoint; connection_type& m_connection; @@ -375,10 +139,6 @@ public: blank_rng m_rng; }; - //class handler_interface; - - //typedef boost::shared_ptr role_handler_ptr; - // types typedef server type; typedef endpoint endpoint_type; @@ -386,8 +146,8 @@ public: typedef typename endpoint_traits::connection_ptr connection_ptr; typedef typename endpoint_traits::handler_ptr handler_ptr; - // handler interface callback class - class handler_interface { + // handler interface callback base class + class handler_interface { public: virtual void validate(connection_ptr connection) {}; virtual void on_open(connection_ptr connection) {}; @@ -405,54 +165,22 @@ public: : m_ws_endpoint(static_cast< endpoint_type& >(*this)), m_io_service(m), m_endpoint(), - m_acceptor(m) - {} + m_acceptor(m) {} - void listen(unsigned short port) { - m_endpoint.port(port); - m_acceptor.open(m_endpoint.protocol()); - m_acceptor.set_option(boost::asio::socket_base::reuse_address(true)); - m_acceptor.bind(m_endpoint); - m_acceptor.listen(); - - this->start_accept(); - - m_ws_endpoint.alog().at(log::alevel::DEVEL) << "role::server listening on port " << port << log::endl; - - m_io_service.run(); - } + void listen(uint16_t port); protected: bool is_server() { return true; } private: // start_accept creates a new connection and begins an async_accept on it - void start_accept() { - connection_ptr con = m_ws_endpoint.create_connection(); - - m_acceptor.async_accept( - con->get_raw_socket(), - boost::bind( - &type::handle_accept, - this, - con, - boost::asio::placeholders::error - ) - ); - } + void start_accept(); // handle_accept will begin the connection's read/write loop and then reset // the server to accept a new connection. Errors returned by async_accept // are logged and ingored. - void handle_accept(connection_ptr con, const boost::system::error_code& error) { - if (error) { - m_ws_endpoint.elog().at(log::elevel::ERROR) << "async_accept returned error: " << error << log::endl; - } else { - con->start(); - } - - this->start_accept(); - } + void handle_accept(connection_ptr con, + const boost::system::error_code& error); endpoint_type& m_ws_endpoint; boost::asio::io_service& m_io_service; @@ -460,7 +188,324 @@ private: boost::asio::ip::tcp::acceptor m_acceptor; }; - +// server Implimentation +template +void server::listen(uint16_t port) { + m_endpoint.port(port); + m_acceptor.open(m_endpoint.protocol()); + m_acceptor.set_option(boost::asio::socket_base::reuse_address(true)); + m_acceptor.bind(m_endpoint); + m_acceptor.listen(); + + this->start_accept(); + + m_ws_endpoint.alog().at(log::alevel::DEVEL) << "role::server listening on port " << port << log::endl; + + m_io_service.run(); +} + +template +void server::start_accept() { + connection_ptr con = m_ws_endpoint.create_connection(); + + m_acceptor.async_accept( + con->get_raw_socket(), + boost::bind( + &type::handle_accept, + this, + con, + boost::asio::placeholders::error + ) + ); +} + +// handle_accept will begin the connection's read/write loop and then reset +// the server to accept a new connection. Errors returned by async_accept +// are logged and ingored. +template +void server::handle_accept(connection_ptr con, + const boost::system::error_code& error) +{ + if (error) { + m_ws_endpoint.elog().at(log::elevel::ERROR) << "async_accept returned error: " << error << log::endl; + } else { + con->start(); + } + + this->start_accept(); +} + +// server::connection Implimentation + +template +template +void server::connection::select_subprotocol( + const std::string& value) +{ + std::vector::iterator it; + + it = std::find(m_requested_subprotocols.begin(), + m_requested_subprotocols.end(), + value); + + if (value != "" && it == m_requested_subprotocols.end()) { + throw std::invalid_argument("Attempted to choose a subprotocol not proposed by the client"); + } + + m_subprotocol = value; +} + +template +template +void server::connection::select_extension( + const std::string& value) +{ + if (value == "") { + return; + } + + std::vector::iterator it; + + it = std::find(m_requested_extensions.begin(), + m_requested_extensions.end(), + value); + + if (it == m_requested_extensions.end()) { + throw std::invalid_argument("Attempted to choose an extension not proposed by the client"); + } + + m_extensions.push_back(value); +} + +// Valid if get_version() returns -1 (ie this is an http connection) +template +template +void server::connection::set_body( + const std::string& value) +{ + if (m_connection.m_version != -1) { + // TODO: throw exception + throw std::invalid_argument("set_body called from invalid state"); + } + + m_response.set_body(value); +} + + +template +template +void server::connection::async_init() { + boost::asio::async_read_until( + m_connection.get_socket(), + m_connection.buffer(), + "\r\n\r\n", + boost::bind( + &type::handle_read_request, + m_connection.shared_from_this(), // shared from this? + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred + ) + ); +} + +template +template +void server::connection::handle_read_request( + const boost::system::error_code& error, std::size_t bytes_transferred) +{ + if (error) { + // log error + m_endpoint.elog().at(log::elevel::ERROR) << "Error reading HTTP request. code: " << error << log::endl; + m_connection.terminate(false); + return; + } + + try { + std::istream request(&m_connection.buffer()); + + if (!m_request.parse_complete(request)) { + // not a valid HTTP request/response + throw http::exception("Recieved invalid HTTP Request",http::status_code::BAD_REQUEST); + } + + m_endpoint.alog().at(log::alevel::DEBUG_HANDSHAKE) << m_request.raw() << log::endl; + + std::string h = m_request.header("Upgrade"); + if (boost::ifind_first(h,"websocket")) { + h = m_request.header("Sec-WebSocket-Version"); + if (h == "") { + // websocket upgrade is present but version is not. + // assume hybi00 + m_version = 0; + } else { + m_version = atoi(h.c_str()); + if (m_version == 0) { + throw(http::exception("Unable to determine connection version",http::status_code::BAD_REQUEST)); + } + } + + // create a websocket processor + if (m_version == 0) { + //m_response.add_header("Sec-WebSocket-Version","13, 8, 7"); + + char foo[9]; + foo[8] = 0; + + request.get(foo,9); + + if (request.gcount() != 8) { + throw http::exception("Missing Key3",http::status_code::BAD_REQUEST); + } + m_request.add_header("Sec-WebSocket-Key3",std::string(foo)); + + //throw(http::exception("Unsupported WebSocket version",http::status_code::BAD_REQUEST)); + m_connection.m_processor = processor::ptr(new processor::hybi_legacy(m_connection)); + } else if (m_version == 7 || + m_version == 8 || + m_version == 13) { + m_connection.m_processor = processor::ptr(new processor::hybi(m_connection)); + } else { + m_response.add_header("Sec-WebSocket-Version","13, 8, 7"); + + throw(http::exception("Unsupported WebSocket version",http::status_code::BAD_REQUEST)); + } + + m_connection.m_processor->validate_handshake(m_request); + m_origin = m_connection.m_processor->get_origin(m_request); + m_uri = m_connection.m_processor->get_uri(m_request); + + m_endpoint.get_handler()->validate(m_connection.shared_from_this()); + + m_response.set_status(http::status_code::SWITCHING_PROTOCOLS); + } else { + // continue as HTTP? + m_endpoint.get_handler()->http(m_connection.shared_from_this()); + + // should there be a more encapsulated http processor here? + m_origin = m_request.header("Origin"); + + // Set URI + std::string h = m_request.header("Host"); + + size_t found = h.find(":"); + if (found == std::string::npos) { + // TODO: this makes the assumption that WS and HTTP + // default ports are the same. + m_uri.reset(new uri(m_endpoint.is_secure(),h,m_request.uri())); + } else { + m_uri.reset(new uri(m_endpoint.is_secure(), + h.substr(0,found), + h.substr(found+1), + m_request.uri())); + } + + m_response.set_status(http::status_code::OK); + } + } catch (const http::exception& e) { + m_endpoint.elog().at(log::elevel::ERROR) << e.what() << log::endl; + m_response.set_status(e.m_error_code,e.m_error_msg); + m_response.set_body(e.m_body); + } catch (const uri_exception& e) { + // there was some error building the uri + m_endpoint.elog().at(log::elevel::ERROR) << e.what() << log::endl; + m_response.set_status(http::status_code::BAD_REQUEST); + } + + write_response(); +} + +template +template +void server::connection::write_response() { + m_response.set_version("HTTP/1.1"); + + if (m_response.status_code() == http::status_code::SWITCHING_PROTOCOLS) { + // websocket response + m_connection.m_processor->handshake_response(m_request,m_response); + + if (m_subprotocol != "") { + m_response.replace_header("Sec-WebSocket-Protocol",m_subprotocol); + } + + // TODO: return negotiated extensions + } else { + // TODO: HTTP response + } + + m_response.replace_header("Server","WebSocket++/2011-11-18"); + + std::string raw = m_response.raw(); + + // Hack for legacy HyBi + if (m_version == 0) { + raw += boost::dynamic_pointer_cast >(m_connection.m_processor)->get_key3(); + } + + m_endpoint.alog().at(log::alevel::DEBUG_HANDSHAKE) << raw << log::endl; + + boost::asio::async_write( + m_connection.get_socket(), + boost::asio::buffer(raw), + boost::bind( + &type::handle_write_response, + m_connection.shared_from_this(), + boost::asio::placeholders::error + ) + ); +} + +template +template +void server::connection::handle_write_response( + const boost::system::error_code& error) +{ + // TODO: handshake timer + + if (error) { + m_endpoint.elog().at(log::elevel::ERROR) << "Network error writing handshake respons. code: " << error << log::endl; + + m_connection.terminate(false); + return; + } + + log_open_result(); + + if (m_response.status_code() != http::status_code::SWITCHING_PROTOCOLS) { + if (m_version == -1) { + // if this was not a websocket connection, we have written + // the expected response and the connection can be closed. + } else { + // this was a websocket connection that ended in an error + m_endpoint.elog().at(log::elevel::ERROR) + << "Handshake ended with HTTP error: " + << m_response.status_code() << " " + << m_response.status_msg() << log::endl; + } + m_connection.terminate(true); + return; + } + + m_connection.m_state = session::state::OPEN; + + m_endpoint.get_handler()->on_open(m_connection.shared_from_this()); + + m_connection.handle_read_frame(boost::system::error_code()); +} + +template +template +void server::connection::log_open_result() { + std::stringstream version; + version << "v" << m_version << " "; + + m_endpoint.alog().at(log::alevel::CONNECT) << (m_version == -1 ? "HTTP" : "WebSocket") << " Connection " + << m_connection.get_raw_socket().remote_endpoint() << " " + << (m_version == -1 ? "" : version.str()) + << (get_request_header("User-Agent") == "" ? "NULL" : get_request_header("User-Agent")) + << " " << m_uri->get_resource() << " " << m_response.status_code() + << log::endl; +} + } // namespace role } // namespace websocketpp diff --git a/websocketpp.xcodeproj/project.pbxproj b/websocketpp.xcodeproj/project.pbxproj index fc081ecdcf..585053c4b8 100644 --- a/websocketpp.xcodeproj/project.pbxproj +++ b/websocketpp.xcodeproj/project.pbxproj @@ -25,6 +25,9 @@ B66388651487FE6C00DDAE13 /* data.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B663885F1487FE6C00DDAE13 /* data.hpp */; }; B66388661487FE6C00DDAE13 /* control.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B66388601487FE6C00DDAE13 /* control.hpp */; }; B66388671487FE6C00DDAE13 /* control.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B66388601487FE6C00DDAE13 /* control.hpp */; }; + B663887B148EDBF700DDAE13 /* libboost_date_time.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = B6DF1CBE1434AF6A0029A1B1 /* libboost_date_time.dylib */; }; + B663887C148EDBF700DDAE13 /* libboost_random.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = B682888C1437464A002BA48B /* libboost_random.dylib */; }; + B663887D148EDBF700DDAE13 /* libboost_regex.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = B6DF1CBF1434AF6A0029A1B1 /* libboost_regex.dylib */; }; B6727429148517180029CF3E /* uri.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B6727428148517180029CF3E /* uri.cpp */; }; B672742A148517180029CF3E /* uri.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B6727428148517180029CF3E /* uri.cpp */; }; B672742D148531250029CF3E /* libboost_regex.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = B6DF1CBF1434AF6A0029A1B1 /* libboost_regex.dylib */; }; @@ -34,7 +37,6 @@ B682888B14374623002BA48B /* libboost_system.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = B682888A14374623002BA48B /* libboost_system.dylib */; }; B682888F14374689002BA48B /* libboost_thread.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = B682888E14374689002BA48B /* libboost_thread.dylib */; }; 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 */; }; B6CF182C1437C3CA009295BE /* libboost_system.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = B6CF182B1437C3CA009295BE /* libboost_system.dylib */; }; B6DF1C7A1434AB740029A1B1 /* network_utilities.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B6DF1C791434AB740029A1B1 /* network_utilities.cpp */; }; @@ -260,6 +262,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B663887B148EDBF700DDAE13 /* libboost_date_time.dylib in Frameworks */, + B663887C148EDBF700DDAE13 /* libboost_random.dylib in Frameworks */, + B663887D148EDBF700DDAE13 /* libboost_regex.dylib in Frameworks */, B6CF182C1437C3CA009295BE /* libboost_system.dylib in Frameworks */, B6CF182A1437C3BD009295BE /* libwebsocketpp.dylib in Frameworks */, ); @@ -811,7 +816,6 @@ buildActionMask = 2147483647; files = ( B6CF18281437C3B1009295BE /* echo_client.cpp in Sources */, - B6CF18291437C3B1009295BE /* echo_client_handler.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };