Overhaul error handling for failed WebSocket and HTTP connections

This commit is contained in:
Peter Thorson committed 2014-12-12 18:31:21 -05:00
1 parent 99f4a66221
commit ef393f8aef
10 files changed
+306 -57

No files matched your search

+6
View File
@@ -18,6 +18,12 @@ HEAD
than binary.
- Improvement: Overhaul of handshake state machine. Should make it impossible
for exceptions to bubble out of transport methods like `io_service::run`.
- Improvement: Overhaul of handshake error reporting. Fail handler error codes
will be more detailed and precise. Adds new [fail] and [http] logging channels
that log failed websocket connections and successful HTTP connections
respectively. A new aggregate channel package, `alevel::access_core`, allows
enabling connect, disconnect, fail, and http together. Successful HTTP
connections will no longer trigger a fail handler.
- Documentation: Add Sending & Receiving Messages step to chapter one of the
`utility_client` tutorial. Update `utility_client` example to match.
- Cleanup: Removes unused files & STL includes. Adds required STL includes.
+12 -5
View File
@@ -56,10 +56,13 @@ struct debug_custom : public websocketpp::config::debug_asio {
typedef base::endpoint_msg_manager_type endpoint_msg_manager_type;
/// Custom Logging policies
typedef websocketpp::log::syslog<concurrency_type,
/*typedef websocketpp::log::syslog<concurrency_type,
websocketpp::log::elevel> elog_type;
typedef websocketpp::log::syslog<concurrency_type,
websocketpp::log::alevel> alog_type;
*/
typedef base::alog_type alog_type;
typedef base::elog_type elog_type;
typedef base::rng_type rng_type;
@@ -75,6 +78,8 @@ struct debug_custom : public websocketpp::config::debug_asio {
typedef websocketpp::transport::asio::endpoint<transport_config>
transport_type;
static const long timeout_open_handshake = 0;
};
////////////////////////////////////////////////////////////////////////////////
@@ -90,7 +95,7 @@ typedef server::message_ptr message_ptr;
bool validate(server *, websocketpp::connection_hdl) {
//sleep(6);
return false;
return true;
}
void on_http(server* s, websocketpp::connection_hdl hdl) {
@@ -105,8 +110,10 @@ void on_http(server* s, websocketpp::connection_hdl hdl) {
con->set_status(websocketpp::http::status_code::ok);
}
void on_fail(websocketpp::connection_hdl) {
std::cout << "Fail handler" << std::endl;
void on_fail(server* s, websocketpp::connection_hdl hdl) {
server::connection_ptr con = s->get_con_from_hdl(hdl);
std::cout << "Fail handler: " << con->get_ec() << " " << con->get_ec().message() << std::endl;
}
void on_close(websocketpp::connection_hdl) {
@@ -144,7 +151,7 @@ int main() {
echo_server.set_message_handler(bind(&on_message,&echo_server,::_1,::_2));
echo_server.set_http_handler(bind(&on_http,&echo_server,::_1));
echo_server.set_fail_handler(&on_fail);
echo_server.set_fail_handler(bind(&on_fail,&echo_server,::_1));
echo_server.set_close_handler(&on_close);
echo_server.set_validate_handler(bind(&validate,&echo_server,::_1));
@@ -37,8 +37,7 @@ public:
telemetry_server() : m_count(0) {
// set up access channels to only log interesting things
m_endpoint.clear_access_channels(websocketpp::log::alevel::all);
m_endpoint.set_access_channels(websocketpp::log::alevel::connect);
m_endpoint.set_access_channels(websocketpp::log::alevel::disconnect);
m_endpoint.set_access_channels(websocketpp::log::alevel::access_core);
m_endpoint.set_access_channels(websocketpp::log::alevel::app);
// Initialize the Asio transport policy
@@ -183,6 +182,7 @@ int main(int argc, char* argv[]) {
if (argc == 1) {
std::cout << "Usage: telemetry_server [documentroot] [port]" << std::endl;
return 1;
}
if (argc >= 2) {
+97
View File
@@ -167,6 +167,29 @@ void http_func(server* s, websocketpp::connection_hdl hdl) {
con->set_status(websocketpp::http::status_code::ok);
}
void check_on_fail(server* s, websocketpp::lib::error_code ec, bool & called,
websocketpp::connection_hdl hdl)
{
server::connection_ptr con = s->get_con_from_hdl(hdl);
BOOST_CHECK_EQUAL(ec, con->get_ec());
called = true;
}
void on_open_print(server* s, websocketpp::connection_hdl hdl)
{
server::connection_ptr con = s->get_con_from_hdl(hdl);
std::cout << con->get_uri() << std::endl;
}
void fail_on_open(websocketpp::connection_hdl hdl) {
BOOST_CHECK(false);
}
void fail_on_http(websocketpp::connection_hdl hdl) {
BOOST_CHECK(false);
}
BOOST_AUTO_TEST_CASE( connection_extensions ) {
connection_setup env(true);
@@ -270,6 +293,80 @@ BOOST_AUTO_TEST_CASE( set_max_message_size ) {
BOOST_CHECK_EQUAL(run_server_test(s,input), output);
}
BOOST_AUTO_TEST_CASE( websocket_fail_parse_error ) {
std::string input = "asdf\r\n\r\n";
server s;
websocketpp::lib::error_code ec = make_error_code(websocketpp::error::http_parse_error);
bool called = false;
s.set_fail_handler(bind(&check_on_fail,&s,ec,websocketpp::lib::ref(called),::_1));
run_server_test(s,input,false);
BOOST_CHECK(called);
}
BOOST_AUTO_TEST_CASE( websocket_fail_invalid_version ) {
std::string input = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: foo\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example.com\r\n\r\n";
server s;
websocketpp::lib::error_code ec = make_error_code(websocketpp::error::invalid_version);
bool called = false;
s.set_fail_handler(bind(&check_on_fail,&s,ec,websocketpp::lib::ref(called),::_1));
run_server_test(s,input,false);
BOOST_CHECK(called);
}
BOOST_AUTO_TEST_CASE( websocket_fail_unsupported_version ) {
std::string input = "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 12\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example.com\r\n\r\n";
server s;
websocketpp::lib::error_code ec = make_error_code(websocketpp::error::unsupported_version);
bool called = false;
s.set_fail_handler(bind(&check_on_fail,&s,ec,websocketpp::lib::ref(called),::_1));
run_server_test(s,input,false);
BOOST_CHECK(called);
}
/*BOOST_AUTO_TEST_CASE( websocket_fail_invalid_uri ) {
std::string input = "GET http://345.123.123.123/foo HTTP/1.1\r\nHost: www.example.com\r\nConnection: upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nOrigin: http://www.example.com\r\n\r\n";
server s;
websocketpp::lib::error_code ec = make_error_code(websocketpp::error::unsupported_version);
bool called = false;
s.set_fail_handler(bind(&check_on_fail,&s,ec,websocketpp::lib::ref(called),::_1));
s.set_open_handler(bind(&on_open_print,&s,::_1));
std::cout << run_server_test(s,input,true) << std::endl;
BOOST_CHECK(called);
}
BOOST_AUTO_TEST_CASE( websocket_fail_invalid_uri_http ) {
std::string input = "GET http://345.123.123.123/foo HTTP/1.1\r\nHost: www.example.com\r\nOrigin: http://www.example.com\r\n\r\n";
server s;
websocketpp::lib::error_code ec = make_error_code(websocketpp::error::unsupported_version);
bool called = false;
s.set_fail_handler(bind(&check_on_fail,&s,ec,websocketpp::lib::ref(called),::_1));
s.set_open_handler(bind(&on_open_print,&s,::_1));
std::cout << run_server_test(s,input,true) << std::endl;
BOOST_CHECK(called);
}*/
BOOST_AUTO_TEST_CASE( websocket_fail_upgrade_required ) {
std::string input = "GET /foo/bar HTTP/1.1\r\nHost: www.example.com\r\nOrigin: http://www.example.com\r\n\r\n";
server s;
websocketpp::lib::error_code ec = make_error_code(websocketpp::error::upgrade_required);
bool called = false;
s.set_fail_handler(bind(&check_on_fail,&s,ec,websocketpp::lib::ref(called),::_1));
run_server_test(s,input,false);
BOOST_CHECK(called);
}
// TODO: set max message size in client endpoint test case
// TODO: set max message size mid connection test case
// TODO: [maybe] set max message size in open handler
+10 -5
View File
@@ -31,17 +31,22 @@ void echo_func(server* s, websocketpp::connection_hdl hdl, message_ptr msg) {
s->send(hdl, msg->get_payload(), msg->get_opcode());
}
std::string run_server_test(std::string input) {
std::string run_server_test(std::string input, bool log) {
server test_server;
return run_server_test(test_server,input);
return run_server_test(test_server,input,log);
}
std::string run_server_test(server & s, std::string input) {
std::string run_server_test(server & s, std::string input, bool log) {
server::connection_ptr con;
std::stringstream output;
s.clear_access_channels(websocketpp::log::alevel::all);
s.clear_error_channels(websocketpp::log::elevel::all);
if (log) {
s.set_access_channels(websocketpp::log::alevel::all);
s.set_error_channels(websocketpp::log::elevel::all);
} else {
s.clear_access_channels(websocketpp::log::alevel::all);
s.clear_error_channels(websocketpp::log::elevel::all);
}
s.register_ostream(&output);
+3 -2
View File
@@ -30,6 +30,7 @@
// Test Environment:
// server, no TLS, no locks, iostream based transport
#include <websocketpp/config/debug.hpp>
#include <websocketpp/config/core.hpp>
#include <websocketpp/server.hpp>
#include <websocketpp/client.hpp>
@@ -46,5 +47,5 @@ using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;
void echo_func(server* s, websocketpp::connection_hdl hdl, message_ptr msg);
std::string run_server_test(std::string input);
std::string run_server_test(server & s, std::string input);
std::string run_server_test(std::string input, bool log = false);
std::string run_server_test(server & s, std::string input, bool log = false);
+17 -6
View File
@@ -311,6 +311,7 @@ public:
, m_rng(rng)
, m_local_close_code(close::status::abnormal_close)
, m_remote_close_code(close::status::abnormal_close)
, m_is_http(false)
, m_was_clean(false)
{
m_alog.write(log::alevel::devel,"connection constructor");
@@ -1245,21 +1246,21 @@ protected:
void handle_transport_init(lib::error_code const & ec);
/// Set m_processor based on information in m_request. Set m_response
/// status and return false on error.
bool initialize_processor();
/// status and return an error code indicating status.
lib::error_code initialize_processor();
/// Perform WebSocket handshake validation of m_request using m_processor.
/// set m_response and return false on error.
bool process_handshake_request();
/// set m_response and return an error code indicating status.
lib::error_code process_handshake_request();
private:
/// Completes m_response, serializes it, and sends it out on the wire.
void send_http_response();
void send_http_response(lib::error_code const & ec);
/// Sends an opening WebSocket connect request
void send_http_request();
/// Alternate path for send_http_response in error conditions
void send_http_response_error();
void send_http_response_error(lib::error_code const & ec);
/// Process control message
/**
@@ -1356,6 +1357,12 @@ private:
* Includes: error code and message for why it was failed
*/
void log_fail_result();
/// Prints information about HTTP connections
/**
* Includes: TODO
*/
void log_http_result();
/// Prints information about an arbitrary error code on the specified channel
template <typename error_type>
@@ -1499,6 +1506,10 @@ private:
/// Detailed internal error code
lib::error_code m_ec;
/// A flag that gets set once it is determined that the connection is an
/// HTTP connection and not a WebSocket one.
bool m_is_http;
bool m_was_clean;
+27 -1
View File
@@ -124,7 +124,23 @@ enum value {
async_accept_not_listening,
/// The requested operation was canceled
operation_canceled
operation_canceled,
/// Connection rejected
rejected,
/// Upgrade Required. This happens if an HTTP request is made to a
/// WebSocket++ server that doesn't implement an http handler
upgrade_required,
/// Invalid WebSocket protocol version
invalid_version,
/// Unsupported WebSocket protocol version
unsupported_version,
/// HTTP parse error
http_parse_error
}; // enum value
@@ -190,6 +206,16 @@ public:
return "Async Accept not listening";
case error::operation_canceled:
return "Operation canceled";
case error::rejected:
return "Connection rejected";
case error::upgrade_required:
return "Upgrade required";
case error::invalid_version:
return "Invalid version";
case error::unsupported_version:
return "Unsupported version";
case error::http_parse_error:
return "HTTP parse error";
default:
return "Unknown";
}
+121 -36
View File
@@ -784,7 +784,7 @@ void connection<config>::handle_read_handshake(lib::error_code const & ec,
// All HTTP exceptions will result in this request failing and an error
// response being returned. No more bytes will be read in this con.
m_response.set_status(e.m_error_code,e.m_error_msg);
this->send_http_response_error();
this->send_http_response_error(error::make_error_code(error::http_parse_error));
return;
}
@@ -804,8 +804,9 @@ void connection<config>::handle_read_handshake(lib::error_code const & ec,
}
if (m_request.ready()) {
if (!this->initialize_processor()) {
this->send_http_response_error();
lib::error_code processor_ec = this->initialize_processor();
if (processor_ec) {
this->send_http_response_error(processor_ec);
return;
}
@@ -822,7 +823,7 @@ void connection<config>::handle_read_handshake(lib::error_code const & ec,
// TODO: need more bytes
m_alog.write(log::alevel::devel,"short key3 read");
m_response.set_status(http::status_code::internal_server_error);
this->send_http_response_error();
this->send_http_response_error(processor::error::make_error_code(processor::error::short_key3));
return;
}
}
@@ -845,8 +846,8 @@ void connection<config>::handle_read_handshake(lib::error_code const & ec,
m_internal_state = istate::PROCESS_HTTP_REQUEST;
// We have the complete request. Process it.
this->process_handshake_request();
this->send_http_response();
lib::error_code handshake_ec = this->process_handshake_request();
this->send_http_response(handshake_ec);
} else {
// read at least 1 more byte
transport_con_type::async_read_at_least(
@@ -869,7 +870,7 @@ void connection<config>::handle_read_handshake(lib::error_code const & ec,
// sure if the hybi00 key3 bytes need to be read). This method sets the correct
// state and calls send_http_response
template <typename config>
void connection<config>::send_http_response_error() {
void connection<config>::send_http_response_error(lib::error_code const & ec) {
if (m_internal_state != istate::READ_HTTP_REQUEST) {
m_alog.write(log::alevel::devel,
"send_http_response_error called in invalid state");
@@ -879,7 +880,7 @@ void connection<config>::send_http_response_error() {
m_internal_state = istate::PROCESS_HTTP_REQUEST;
this->send_http_response();
this->send_http_response(ec);
}
// All exit paths for this function need to call send_http_response() or submit
@@ -1048,12 +1049,12 @@ void connection<config>::read_frame() {
}
template <typename config>
bool connection<config>::initialize_processor() {
lib::error_code connection<config>::initialize_processor() {
m_alog.write(log::alevel::devel,"initialize_processor");
// if it isn't a websocket handshake nothing to do.
if (!processor::is_websocket_handshake(m_request)) {
return true;
return lib::error_code();
}
int version = processor::get_websocket_version(m_request);
@@ -1061,14 +1062,14 @@ bool connection<config>::initialize_processor() {
if (version < 0) {
m_alog.write(log::alevel::devel, "BAD REQUEST: can't determine version");
m_response.set_status(http::status_code::bad_request);
return false;
return error::make_error_code(error::invalid_version);
}
m_processor = get_processor(version);
// if the processor is not null we are done
if (m_processor) {
return true;
return lib::error_code();
}
// We don't have a processor for this version. Return bad request
@@ -1086,11 +1087,11 @@ bool connection<config>::initialize_processor() {
}
m_response.replace_header("Sec-WebSocket-Version",ss.str());
return false;
return error::make_error_code(error::unsupported_version);
}
template <typename config>
bool connection<config>::process_handshake_request() {
lib::error_code connection<config>::process_handshake_request() {
m_alog.write(log::alevel::devel,"process handshake request");
if (!processor::is_websocket_handshake(m_request)) {
@@ -1106,16 +1107,18 @@ bool connection<config>::process_handshake_request() {
if (!m_uri->get_valid()) {
m_alog.write(log::alevel::devel, "Bad request: failed to parse uri");
m_response.set_status(http::status_code::bad_request);
return false;
return error::make_error_code(error::invalid_uri);
}
if (m_http_handler) {
m_is_http = true;
m_http_handler(m_connection_hdl);
} else {
set_status(http::status_code::upgrade_required);
return error::make_error_code(error::upgrade_required);
}
return true;
return lib::error_code();
}
lib::error_code ec = m_processor->validate_handshake(m_request);
@@ -1125,7 +1128,7 @@ bool connection<config>::process_handshake_request() {
// Not a valid handshake request
m_alog.write(log::alevel::devel, "Bad request " + ec.message());
m_response.set_status(http::status_code::bad_request);
return false;
return ec;
}
// Read extension parameters and set up values necessary for the end user
@@ -1138,7 +1141,7 @@ bool connection<config>::process_handshake_request() {
// a failed connection attempt.
m_alog.write(log::alevel::devel, "Bad request: " + neg_results.first.message());
m_response.set_status(http::status_code::bad_request);
return false;
return neg_results.first;
} else {
// extension negotiation succeeded, set response header accordingly
// we don't send an empty extensions header because it breaks many
@@ -1156,7 +1159,7 @@ bool connection<config>::process_handshake_request() {
if (!m_uri->get_valid()) {
m_alog.write(log::alevel::devel, "Bad request: failed to parse uri");
m_response.set_status(http::status_code::bad_request);
return false;
return error::make_error_code(error::invalid_uri);
}
// extract subprotocols
@@ -1181,7 +1184,7 @@ bool connection<config>::process_handshake_request() {
m_alog.write(log::alevel::devel, s.str());
m_response.set_status(http::status_code::internal_server_error);
return false;
return ec;
}
} else {
// User application has rejected the handshake
@@ -1193,19 +1196,22 @@ bool connection<config>::process_handshake_request() {
if (m_response.get_status_code() == http::status_code::uninitialized) {
m_response.set_status(http::status_code::bad_request);
}
return false;
return error::make_error_code(error::rejected);
}
return true;
return lib::error_code();
}
template <typename config>
void connection<config>::send_http_response() {
void connection<config>::send_http_response(lib::error_code const & ec) {
m_alog.write(log::alevel::devel,"connection send_http_response");
if (m_response.get_status_code() == http::status_code::uninitialized) {
m_response.set_status(http::status_code::internal_server_error);
m_ec = error::make_error_code(error::general);
} else {
m_ec = ec;
}
m_response.set_version("HTTP/1.1");
@@ -1290,12 +1296,13 @@ void connection<config>::handle_send_http_response(lib::error_code const & ec) {
m_handshake_timer.reset();
}
this->log_open_result();
if (m_response.get_status_code() != http::status_code::switching_protocols)
{
if (m_processor) {
// this was a websocket connection that ended in an error
/*if (m_processor || m_ec == error::http_parse_error ||
m_ec == error::invalid_version || m_ec == error::unsupported_version
|| m_ec == error::upgrade_required)
{*/
if (!m_is_http) {
std::stringstream s;
s << "Handshake ended with HTTP error: "
<< m_response.get_status_code();
@@ -1303,11 +1310,22 @@ void connection<config>::handle_send_http_response(lib::error_code const & ec) {
} else {
// if this was not a websocket connection, we have written
// the expected response and the connection can be closed.
}
this->terminate(make_error_code(error::http_connection_ended));
this->log_http_result();
if (m_ec) {
m_alog.write(log::alevel::devel,
"got to writing HTTP results with m_ec set: "+m_ec.message());
}
m_ec = make_error_code(error::http_connection_ended);
}
this->terminate(m_ec);
return;
}
this->log_open_result();
m_internal_state = istate::PROCESS_CONNECTION;
m_state = session::state::open;
@@ -1586,6 +1604,12 @@ void connection<config>::terminate(lib::error_code const & ec) {
if (m_state == session::state::connecting) {
m_state = session::state::closed;
tstat = failed;
// Log fail result here before socket is shut down and we can't get
// the remote address, etc anymore
if (m_ec != error::http_connection_ended) {
log_fail_result();
}
} else if (m_state != session::state::closed) {
m_state = session::state::closed;
tstat = closed;
@@ -1622,10 +1646,11 @@ void connection<config>::handle_terminate(terminate_status tstat,
// clean shutdown
if (tstat == failed) {
if (m_fail_handler) {
m_fail_handler(m_connection_hdl);
if (m_ec != error::http_connection_ended) {
if (m_fail_handler) {
m_fail_handler(m_connection_hdl);
}
}
log_fail_result();
} else if (tstat == closed) {
if (m_close_handler) {
m_close_handler(m_connection_hdl);
@@ -2164,9 +2189,69 @@ void connection<config>::log_close_result()
template <typename config>
void connection<config>::log_fail_result()
{
// TODO: include more information about the connection?
// should this be filed under connect rather than disconnect?
m_alog.write(log::alevel::disconnect,"Failed: "+m_ec.message());
std::stringstream s;
int version = processor::get_websocket_version(m_request);
// Connection Type
s << "WebSocket Connection ";
// Remote endpoint address & WebSocket version
s << transport_con_type::get_remote_endpoint();
if (version < 0) {
s << " -";
} else {
s << " v" << version;
}
// User Agent
std::string ua = m_request.get_header("User-Agent");
if (ua == "") {
s << " \"\" ";
} else {
// check if there are any quotes in the user agent
s << " \"" << utility::string_replace_all(ua,"\"","\\\"") << "\" ";
}
// URI
s << (m_uri ? m_uri->get_resource() : "-");
// HTTP Status code
s << " " << m_response.get_status_code();
// WebSocket++ error code & reason
s << " " << m_ec << " " << m_ec.message();
m_alog.write(log::alevel::fail,s.str());
}
template <typename config>
void connection<config>::log_http_result() {
std::stringstream s;
if (processor::is_websocket_handshake(m_request)) {
m_alog.write(log::alevel::devel,"Call to log_http_result for WebSocket");
return;
}
// Connection Type
s << (m_request.get_header("host") == "" ? "-" : m_request.get_header("host"))
<< " " << transport_con_type::get_remote_endpoint()
<< " \"" << m_request.get_method()
<< " " << (m_uri ? m_uri->get_resource() : "-")
<< " " << m_request.get_version() << "\" " << m_response.get_status_code()
<< " " << m_response.get_body().size();
// User Agent
std::string ua = m_request.get_header("User-Agent");
if (ua == "") {
s << " \"\" ";
} else {
// check if there are any quotes in the user agent
s << " \"" << utility::string_replace_all(ua,"\"","\\\"") << "\" ";
}
m_alog.write(log::alevel::http,s.str());
}
} // namespace websocketpp
+11
View File
@@ -141,6 +141,13 @@ struct alevel {
static level const devel = 0x400;
/// Special channel for application specific logs. Not used by the library.
static level const app = 0x800;
/// Access related to HTTP requests
static level const http = 0x1000;
/// One line for each failed WebSocket connection with details
static level const fail = 0x2000;
/// Aggregate package representing the commonly used core access channels
/// Connect, Disconnect, Fail, and HTTP
static level const access_core = 0x00003003;
/// Special aggregate value representing "all levels"
static level const all = 0xffffffff;
@@ -180,6 +187,10 @@ struct alevel {
return "devel";
case app:
return "application";
case http:
return "http";
case fail:
return "fail";
default:
return "unknown";
}