Merge pull request #322 from zaphoyd/max-message-size

Adds the ability to specify a max message size
This commit is contained in:
Peter Thorson
2014-02-10 20:55:02 -06:00
16 changed files with 266 additions and 52 deletions

View File

@@ -13,6 +13,7 @@ HEAD
the main thread.
- Feature: Adds the ability to specify whether or not to use the `SO_REUSEADDR` TCP socket
option. The default for this value has been changed from `true` to `false`.
- Feature: Adds the ability to specify a maximum message size.
- Improvement: Open, close, and pong timeouts can be disabled entirely by setting their
duration to 0.
- Improvement: Numerous performance improvements. Including: tuned default

View File

@@ -192,6 +192,33 @@ BOOST_AUTO_TEST_CASE( basic_client_websocket ) {
BOOST_CHECK_EQUAL(ref, output.str());
}
BOOST_AUTO_TEST_CASE( set_max_message_size ) {
std::string input = "GET / 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\n\r\n";
// After the handshake, add a single frame with a message that is too long.
char frame0[10] = {char(0x82), char(0x83), 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01};
input.append(frame0, 10);
std::string output = "HTTP/1.1 101 Switching Protocols\r\nConnection: upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\nServer: foo\r\nUpgrade: websocket\r\n\r\n";
// After the handshake, add a single frame with a close message with message too big
// error code.
char frame1[4] = {char(0x88), 0x19, 0x03, char(0xf1)};
output.append(frame1, 4);
output.append("A message was too large");
server s;
s.set_user_agent("");
s.set_validate_handler(bind(&validate_set_ua,&s,::_1));
s.set_max_message_size(2);
BOOST_CHECK_EQUAL(run_server_test(s,input), output);
}
// 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
/*
BOOST_AUTO_TEST_CASE( user_reject_origin ) {

View File

@@ -45,6 +45,8 @@ struct stub_config {
<websocketpp::message_buffer::alloc::con_msg_manager> message_type;
typedef websocketpp::message_buffer::alloc::con_msg_manager<message_type>
con_msg_manager_type;
static const size_t max_message_size = 16000000;
};
struct processor_setup {

View File

@@ -50,6 +50,8 @@ struct stub_config {
typedef websocketpp::random::none::int_generator<uint32_t> rng_type;
static const size_t max_message_size = 16000000;
/// Extension related config
static const bool enable_extensions = false;

View File

@@ -50,6 +50,8 @@ struct stub_config {
typedef websocketpp::random::none::int_generator<uint32_t> rng_type;
static const size_t max_message_size = 16000000;
/// Extension related config
static const bool enable_extensions = false;

View File

@@ -60,6 +60,7 @@ struct stub_config {
typedef websocketpp::extensions::permessage_deflate::disabled
<permessage_deflate_config> permessage_deflate_type;
static const size_t max_message_size = 16000000;
static const bool enable_extensions = false;
};
@@ -81,6 +82,7 @@ struct stub_config_ext {
typedef websocketpp::extensions::permessage_deflate::enabled
<permessage_deflate_config> permessage_deflate_type;
static const size_t max_message_size = 16000000;
static const bool enable_extensions = true;
};
@@ -489,6 +491,36 @@ BOOST_AUTO_TEST_CASE( prepare_data_frame ) {
}
BOOST_AUTO_TEST_CASE( single_frame_message_too_large ) {
processor_setup env(true);
env.p.set_max_message_size(3);
uint8_t frame0[10] = {0x82, 0x84, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01};
// read message that is one byte too large
BOOST_CHECK_EQUAL( env.p.consume(frame0,10,env.ec), 6 );
BOOST_CHECK_EQUAL( env.ec, websocketpp::processor::error::message_too_big );
}
BOOST_AUTO_TEST_CASE( multiple_frame_message_too_large ) {
processor_setup env(true);
env.p.set_max_message_size(4);
uint8_t frame0[8] = {0x02, 0x82, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01};
uint8_t frame1[9] = {0x80, 0x83, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01};
// read first message frame with size under the limit
BOOST_CHECK_EQUAL( env.p.consume(frame0,8,env.ec), 8 );
BOOST_CHECK( !env.ec );
// read second message frame that puts the size over the limit
BOOST_CHECK_EQUAL( env.p.consume(frame1,9,env.ec), 6 );
BOOST_CHECK_EQUAL( env.ec, websocketpp::processor::error::message_too_big );
}
BOOST_AUTO_TEST_CASE( client_handshake_request ) {
processor_setup env(false);

View File

@@ -132,4 +132,4 @@ BOOST_AUTO_TEST_CASE( version_non_numeric ) {
r.consume(handshake.c_str(),handshake.size());
BOOST_CHECK(websocketpp::processor::get_websocket_version(r) == -1);
}
}

View File

@@ -215,6 +215,18 @@ struct core {
*/
static const bool silent_close = false;
/// Default maximum message size
/**
* Default value for the processor's maximum message size. Maximum message size
* determines the point at which the library will fail a connection with the
* message_too_big protocol error.
*
* The default is 32MB
*
* @since 0.4.0-alpha1
*/
static const size_t max_message_size = 32000000;
/// Global flag for enabling/disabling extensions
static const bool enable_extensions = true;

View File

@@ -216,6 +216,18 @@ struct core_client {
*/
static const bool silent_close = false;
/// Default maximum message size
/**
* Default value for the processor's maximum message size. Maximum message size
* determines the point at which the library will fail a connection with the
* message_too_big protocol error.
*
* The default is 32MB
*
* @since 0.4.0-alpha1
*/
static const size_t max_message_size = 32000000;
/// Global flag for enabling/disabling extensions
static const bool enable_extensions = true;

View File

@@ -216,6 +216,18 @@ struct debug_core {
*/
static const bool silent_close = false;
/// Default maximum message size
/**
* Default value for the processor's maximum message size. Maximum message size
* determines the point at which the library will fail a connection with the
* message_too_big protocol error.
*
* The default is 32MB
*
* @since 0.4.0-alpha1
*/
static const size_t max_message_size = 32000000;
/// Global flag for enabling/disabling extensions
static const bool enable_extensions = true;

View File

@@ -297,6 +297,7 @@ public:
, m_open_handshake_timeout_dur(config::timeout_open_handshake)
, m_close_handshake_timeout_dur(config::timeout_close_handshake)
, m_pong_timeout_dur(config::timeout_pong)
, m_max_message_size(config::max_message_size)
, m_state(session::state::connecting)
, m_internal_state(session::internal_state::USER_INIT)
, m_msg_manager(new con_msg_manager_type())
@@ -456,9 +457,9 @@ public:
m_message_handler = h;
}
/////////////////////////
// Connection timeouts //
/////////////////////////
//////////////////////////////////////////
// Connection timeouts and other limits //
//////////////////////////////////////////
/// Set open handshake timeout
/**
@@ -529,6 +530,38 @@ public:
m_pong_timeout_dur = dur;
}
/// Get maximum message size
/**
* Get maximum message size. Maximum message size determines the point at which the
* connection will fail a connection with the message_too_big protocol error.
*
* The default is set by the endpoint that creates the connection.
*
* @since 0.4.0-alpha1
*/
size_t get_max_message_size() const {
return m_max_message_size;
}
/// Set maximum message size
/**
* Set maximum message size. Maximum message size determines the point at which the
* connection will fail a connection with the message_too_big protocol error. This
* value may be changed during the connection.
*
* The default is set by the endpoint that creates the connection.
*
* @since 0.4.0-alpha1
*
* @param new_value The value to set as the maximum message size.
*/
void set_max_message_size(size_t new_value) {
m_max_message_size = new_value;
if (m_processor) {
m_processor->set_max_message_size(new_value);
}
}
//////////////////////////////////
// Uncategorized public methods //
//////////////////////////////////
@@ -1345,6 +1378,7 @@ private:
long m_open_handshake_timeout_dur;
long m_close_handshake_timeout_dur;
long m_pong_timeout_dur;
size_t m_max_message_size;
/// External connection state
/**

View File

@@ -94,6 +94,7 @@ public:
, m_open_handshake_timeout_dur(config::timeout_open_handshake)
, m_close_handshake_timeout_dur(config::timeout_close_handshake)
, m_pong_timeout_dur(config::timeout_pong)
, m_max_message_size(config::max_message_size)
, m_is_server(p_is_server)
{
m_alog.set_channels(config::alog_level);
@@ -272,9 +273,9 @@ public:
m_message_handler = h;
}
/////////////////////////
// Connection timeouts //
/////////////////////////
//////////////////////////////////////////
// Connection timeouts and other limits //
//////////////////////////////////////////
/// Set open handshake timeout
/**
@@ -348,6 +349,36 @@ public:
m_pong_timeout_dur = dur;
}
/// Get default maximum message size
/**
* Get the default maximum message size that will be used for new connections created
* by this endpoint. The maximum message size determines the point at which the
* connection will fail a connection with the message_too_big protocol error.
*
* The default is set by the max_message_size value from the template config
*
* @since 0.4.0-alpha1
*/
size_t get_max_message_size() const {
return m_max_message_size;
}
/// Set default maximum message size
/**
* Set the default maximum message size that will be used for new connections created
* by this endpoint. Maximum message size determines the point at which the connection
* will fail a connection with the message_too_big protocol error.
*
* The default is set by the max_message_size value from the template config
*
* @since 0.4.0-alpha1
*
* @param new_value The value to set as the maximum message size.
*/
void set_max_message_size(size_t new_value) {
m_max_message_size = new_value;
}
/*************************************/
/* Connection pass through functions */
/*************************************/
@@ -534,6 +565,7 @@ private:
long m_open_handshake_timeout_dur;
long m_close_handshake_timeout_dur;
long m_pong_timeout_dur;
size_t m_max_message_size;
rng_type m_rng;

View File

@@ -932,7 +932,7 @@ void connection<config>::handle_read_frame(lib::error_code const & ec,
return;
} else {
lib::error_code close_ec;
this->close(processor::error::to_ws(ec),ec.message(),close_ec);
this->close(processor::error::to_ws(consume_ec),consume_ec.message(),close_ec);
if (close_ec) {
m_elog.write(log::elevel::fatal,
@@ -1945,49 +1945,49 @@ template <typename config>
typename connection<config>::processor_ptr
connection<config>::get_processor(int version) const {
// TODO: allow disabling certain versions
processor_ptr p;
switch (version) {
case 0:
return processor_ptr(
new processor::hybi00<config>(
transport_con_type::is_secure(),
m_is_server,
m_msg_manager
)
);
p.reset(new processor::hybi00<config>(
transport_con_type::is_secure(),
m_is_server,
m_msg_manager
));
break;
case 7:
return processor_ptr(
new processor::hybi07<config>(
transport_con_type::is_secure(),
m_is_server,
m_msg_manager,
m_rng
)
);
p.reset(new processor::hybi07<config>(
transport_con_type::is_secure(),
m_is_server,
m_msg_manager,
m_rng
));
break;
case 8:
return processor_ptr(
new processor::hybi08<config>(
transport_con_type::is_secure(),
m_is_server,
m_msg_manager,
m_rng
)
);
p.reset(new processor::hybi08<config>(
transport_con_type::is_secure(),
m_is_server,
m_msg_manager,
m_rng
));
break;
case 13:
return processor_ptr(
new processor::hybi13<config>(
transport_con_type::is_secure(),
m_is_server,
m_msg_manager,
m_rng
)
);
p.reset(new processor::hybi13<config>(
transport_con_type::is_secure(),
m_is_server,
m_msg_manager,
m_rng
));
break;
default:
return processor_ptr();
return p;
}
// Settings not configured by the constructor
p->set_max_message_size(m_max_message_size);
return p;
}
template <typename config>

View File

@@ -74,6 +74,9 @@ endpoint<connection,config>::create_connection() {
if (m_pong_timeout_dur == config::timeout_pong) {
con->set_pong_timeout(m_pong_timeout_dur);
}
if (m_max_message_size != config::max_message_size) {
con->set_max_message_size(m_max_message_size);
}
lib::error_code ec;

View File

@@ -369,11 +369,25 @@ public:
m_current_msg = &m_control_msg;
} else {
if (!m_data_msg.msg_ptr) {
if (m_bytes_needed > base::m_max_message_size) {
ec = make_error_code(error::message_too_big);
break;
}
m_data_msg = msg_metadata(
m_msg_manager->get_message(op,m_bytes_needed),
frame::get_masking_key(m_basic_header,m_extended_header)
);
} else {
// Fetch the underlying payload buffer from the data message we
// are writing into.
std::string & out = m_data_msg.msg_ptr->get_raw_payload();
if (out.size() + m_bytes_needed > base::m_max_message_size) {
ec = make_error_code(error::message_too_big);
break;
}
// Each frame starts a new masking key. All other state
// remains between frames.
m_data_msg.prepared_key = prepare_masking_key(
@@ -382,7 +396,8 @@ public:
m_extended_header
)
);
// TODO: reserve space in the existing message for the new bytes
out.reserve(out.size() + m_bytes_needed);
}
m_current_msg = &m_data_msg;
}

View File

@@ -161,13 +161,43 @@ public:
explicit processor(bool secure, bool p_is_server)
: m_secure(secure)
, m_server(p_is_server) {}
, m_server(p_is_server)
, m_max_message_size(config::max_message_size)
{}
virtual ~processor() {}
/// Get the protocol version of this processor
virtual int get_version() const = 0;
/// Get maximum message size
/**
* Get maximum message size. Maximum message size determines the point at which the
* processor will fail a connection with the message_too_big protocol error.
*
* The default is retrieved from the max_message_size value from the template config
*
* @since 0.4.0-alpha1
*/
size_t get_max_message_size() const {
return m_max_message_size;
}
/// Set maximum message size
/**
* Set maximum message size. Maximum message size determines the point at which the
* processor will fail a connection with the message_too_big protocol error.
*
* The default is retrieved from the max_message_size value from the template config
*
* @since 0.4.0-alpha1
*
* @param new_value The value to set as the maximum message size.
*/
void set_max_message_size(size_t new_value) {
m_max_message_size = new_value;
}
/// Returns whether or not the permessage_compress extension is implemented
/**
* Compile time flag that indicates whether this processor has implemented
@@ -196,8 +226,7 @@ public:
* @return A status code, 0 on success, non-zero for specific sorts of
* failure
*/
virtual lib::error_code validate_handshake(request_type const & request)
const = 0;
virtual lib::error_code validate_handshake(request_type const & request) const = 0;
/// Calculate the appropriate response for this websocket request
/**
@@ -236,8 +265,7 @@ public:
virtual std::string get_raw(response_type const & request) const = 0;
/// Return the value of the header containing the CORS origin.
virtual std::string const & get_origin(request_type const & request)
const = 0;
virtual std::string const & get_origin(request_type const & request) const = 0;
/// Extracts requested subprotocols from a handshake request
/**
@@ -310,8 +338,7 @@ public:
* Performs validation, masking, compression, etc. will return an error if
* there was an error, otherwise msg will be ready to be written
*/
virtual lib::error_code prepare_data_frame(message_ptr in, message_ptr out)
= 0;
virtual lib::error_code prepare_data_frame(message_ptr in, message_ptr out) = 0;
/// Prepare a ping frame
/**
@@ -324,8 +351,8 @@ public:
*
* @return Status code, zero on success, non-zero on failure
*/
virtual lib::error_code prepare_ping(std::string const & in,
message_ptr out) const = 0;
virtual lib::error_code prepare_ping(std::string const & in, message_ptr out) const
= 0;
/// Prepare a pong frame
/**
@@ -338,8 +365,8 @@ public:
*
* @return Status code, zero on success, non-zero on failure
*/
virtual lib::error_code prepare_pong(std::string const & in,
message_ptr out) const = 0;
virtual lib::error_code prepare_pong(std::string const & in, message_ptr out) const
= 0;
/// Prepare a close frame
/**
@@ -361,6 +388,7 @@ public:
protected:
bool const m_secure;
bool const m_server;
size_t m_max_message_size;
};
} // namespace processor