WebSocket ping, fixes, coverage:

* Improve test coverage
* tests for invokable in composed ops

* Update documentation
* Add License badge to README
* Target Windows 7 SDK and later
* Make role_type private
* Remove extra unused masking functions
* Allow stream reuse / reconnect after failure
* Restructure logic of composed operations
* Allow 0 for read_message_max meaning no limit
* Respect keep alive when building HTTP responses
* Check version in upgrade request
* Response with 426 status on unsupported WebSocket version
* Remove unnecessary Sec-WebSocket-Key in HTTP responses
* Rename to mask_buffer_size

* Remove maybe_throw
* Add ping, async_ping, async_on_pong
* Add ping_op
* Add pong_op
* Fix crash in accept_op
* Fix suspend in close_op
* Fix read_frame_op logic
* Fix crash in read_op
* Fix races in echo sync and async echo servers
This commit is contained in:
Vinnie Falco
2016-05-15 16:22:25 -04:00
parent a570b74038
commit eb7bd6a2f1
40 changed files with 2757 additions and 1365 deletions

View File

@@ -35,6 +35,7 @@ unit-test core-tests :
core/write_streambuf.cpp
core/detail/base64.cpp
core/detail/empty_base_optimization.cpp
core/detail/get_lowest_layer.cpp
core/detail/sha1.cpp
;
@@ -77,6 +78,7 @@ unit-test websocket-tests :
websocket/teardown.cpp
websocket/detail/frame.cpp
websocket/detail/mask.cpp
websocket/detail/stream_base.cpp
websocket/detail/utf8_checker.cpp
;

View File

@@ -29,6 +29,7 @@ add_executable (core-tests
write_streambuf.cpp
detail/base64.cpp
detail/empty_base_optimization.cpp
detail/get_lowest_layer.cpp
detail/sha1.cpp
)

View File

@@ -0,0 +1,88 @@
//
// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Test that header file is self-contained.
#include <beast/core/detail/get_lowest_layer.hpp>
#include <beast/unit_test/suite.hpp>
#include <type_traits>
namespace beast {
namespace detail {
class get_lowest_layer_test
: public beast::unit_test::suite
{
public:
struct F1
{
};
struct F2
{
};
template<class F>
struct F3
{
using next_layer_type =
typename std::remove_reference<F>::type;
using lowest_layer_type = typename
get_lowest_layer<next_layer_type>::type;
};
template<class F>
struct F4
{
using next_layer_type =
typename std::remove_reference<F>::type;
using lowest_layer_type = typename
get_lowest_layer<next_layer_type>::type;
};
void
run()
{
static_assert(! has_lowest_layer<F1>::value, "");
static_assert(! has_lowest_layer<F2>::value, "");
static_assert(has_lowest_layer<F3<F1>>::value, "");
static_assert(has_lowest_layer<F4<F3<F2>>>::value, "");
static_assert(std::is_same<
get_lowest_layer<F1>::type, F1>::value, "");
static_assert(std::is_same<
get_lowest_layer<F2>::type, F2>::value, "");
static_assert(std::is_same<
get_lowest_layer<F3<F1>>::type, F1>::value, "");
static_assert(std::is_same<
get_lowest_layer<F3<F2>>::type, F2>::value, "");
static_assert(std::is_same<
get_lowest_layer<F4<F1>>::type, F1>::value, "");
static_assert(std::is_same<
get_lowest_layer<F4<F2>>::type, F2>::value, "");
static_assert(std::is_same<
get_lowest_layer<F4<F3<F1>>>::type, F1>::value, "");
static_assert(std::is_same<
get_lowest_layer<F4<F3<F2>>>::type, F2>::value, "");
pass();
}
};
BEAST_DEFINE_TESTSUITE(get_lowest_layer,core,beast);
} // detail
} // beast

View File

@@ -89,6 +89,7 @@ public:
void testIterator()
{
using boost::asio::buffer_size;
using boost::asio::const_buffer;
char b[3];
std::array<const_buffer, 3> bs{{
@@ -98,7 +99,12 @@ public:
auto pb = prepare_buffers(2, bs);
std::size_t n = 0;
for(auto it = pb.end(); it != pb.begin(); --it)
{
decltype(pb)::const_iterator it2(std::move(it));
expect(buffer_size(*it2) == 1);
it = std::move(it2);
++n;
}
expect(n == 2);
}

View File

@@ -59,6 +59,7 @@ public:
}
catch(std::exception const&)
{
pass();
}
m.headers.erase("Content-Length");
m.headers.insert("Connection", "keep-alive");
@@ -69,7 +70,12 @@ public:
}
catch(std::exception const&)
{
pass();
}
m.version = 11;
m.headers.erase("Connection");
m.headers.insert("Connection", "close");
expect(! is_keep_alive(m));
}
void run() override

View File

@@ -16,6 +16,7 @@ add_executable (websocket-tests
teardown.cpp
detail/frame.cpp
detail/mask.cpp
detail/stream_base.cpp
detail/utf8_checker.cpp
)

View File

@@ -225,7 +225,6 @@ public:
testCloseCodes();
testFrameHeader();
testBadFrameHeaders();
pass();
}
};

View File

@@ -5,7 +5,9 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Test that header file is self-contained.
#include <beast/websocket/detail/mask.hpp>
#include <beast/unit_test/suite.hpp>
namespace beast {

View File

@@ -0,0 +1,40 @@
//
// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Test that header file is self-contained.
#include <beast/websocket/detail/stream_base.hpp>
#include <beast/unit_test/suite.hpp>
#include <initializer_list>
#include <climits>
namespace beast {
namespace websocket {
namespace detail {
class stream_base_test : public beast::unit_test::suite
{
public:
void testClamp()
{
expect(detail::clamp(
std::numeric_limits<std::uint64_t>::max()) ==
std::numeric_limits<std::size_t>::max());
}
void run() override
{
testClamp();
}
};
BEAST_DEFINE_TESTSUITE(stream_base,websocket,beast);
} // detail
} // websocket
} // beast

File diff suppressed because it is too large Load Diff

View File

@@ -91,14 +91,16 @@ private:
bool log;
int state = 0;
boost::optional<endpoint_type> ep;
websocket::stream<socket_type> ws;
websocket::opcode op;
stream<socket_type> ws;
boost::asio::io_service::strand strand;
opcode op;
beast::streambuf sb;
int id;
data(bool log_, socket_type&& sock_)
: log(log_)
, ws(std::move(sock_))
, strand(ws.get_io_service())
, id([]
{
static int n = 0;
@@ -112,6 +114,7 @@ private:
: log(log_)
, ep(ep_)
, ws(std::move(sock_))
, strand(ws.get_io_service())
, id([]
{
static int n = 0;
@@ -174,8 +177,34 @@ private:
}
}
template<class Streambuf, std::size_t N>
static
bool
match(Streambuf& sb, char const(&s)[N])
{
using boost::asio::buffer;
using boost::asio::buffer_copy;
if(sb.size() < N-1)
return false;
static_string<N-1> t;
t.resize(N-1);
buffer_copy(buffer(t.data(), t.size()),
sb.data());
if(t != s)
return false;
sb.consume(N-1);
return true;
}
void operator()(error_code ec, std::size_t)
{
(*this)(ec);
}
void operator()(error_code ec)
{
using boost::asio::buffer;
using boost::asio::buffer_copy;
auto& d = *d_;
switch(d.state)
{
@@ -191,19 +220,54 @@ private:
d.sb.consume(d.sb.size());
// read message
d.state = 2;
d.ws.async_read(d.op, d.sb, std::move(*this));
d.ws.async_read(d.op, d.sb,
d.strand.wrap(std::move(*this)));
return;
// got message
case 2:
if(ec == websocket::error::closed)
if(ec == error::closed)
return;
if(ec)
return fail(ec, "async_read");
if(match(d.sb, "RAW"))
{
d.state = 1;
boost::asio::async_write(d.ws.next_layer(),
d.sb.data(), d.strand.wrap(std::move(*this)));
return;
}
else if(match(d.sb, "TEXT"))
{
d.state = 1;
d.ws.set_option(message_type{opcode::text});
d.ws.async_write(
d.sb.data(), d.strand.wrap(std::move(*this)));
return;
}
else if(match(d.sb, "PING"))
{
ping_data payload;
d.sb.consume(buffer_copy(
buffer(payload.data(), payload.size()),
d.sb.data()));
d.state = 1;
d.ws.async_ping(payload,
d.strand.wrap(std::move(*this)));
return;
}
else if(match(d.sb, "CLOSE"))
{
d.state = 1;
d.ws.async_close({},
d.strand.wrap(std::move(*this)));
return;
}
// write message
d.state = 1;
d.ws.set_option(websocket::message_type(d.op));
d.ws.async_write(d.sb.data(), std::move(*this));
d.ws.set_option(message_type(d.op));
d.ws.async_write(d.sb.data(),
d.strand.wrap(std::move(*this)));
return;
// connected
@@ -214,7 +278,7 @@ private:
d.ws.async_handshake(
d.ep->address().to_string() + ":" +
std::to_string(d.ep->port()),
"/", std::move(*this));
"/", d.strand.wrap(std::move(*this)));
return;
}
}
@@ -226,7 +290,7 @@ private:
auto& d = *d_;
if(d.log)
{
if(ec != websocket::error::closed)
if(ec != error::closed)
std::cerr << "#" << d_->id << " " <<
what << ": " << ec.message() << std::endl;
}
@@ -256,6 +320,8 @@ private:
{
if(! acceptor_.is_open())
return;
if(ec == boost::asio::error::operation_aborted)
return;
maybe_throw(ec, "accept");
socket_type sock(std::move(sock_));
acceptor_.async_accept(sock_,

View File

@@ -101,15 +101,17 @@ private:
{
int id;
sync_echo_peer& self;
socket_type sock;
boost::asio::io_service::work work;
// Must be destroyed before work otherwise the
// io_service could be destroyed before the socket.
socket_type sock;
lambda(int id_, sync_echo_peer& self_,
socket_type&& sock_)
: id(id_)
, self(self_)
, work(sock_.get_io_service())
, sock(std::move(sock_))
, work(sock.get_io_service())
{
}
@@ -149,10 +151,31 @@ private:
}
};
template<class Streambuf, std::size_t N>
static
bool
match(Streambuf& sb, char const(&s)[N])
{
using boost::asio::buffer;
using boost::asio::buffer_copy;
if(sb.size() < N-1)
return false;
static_string<N-1> t;
t.resize(N-1);
buffer_copy(buffer(t.data(), t.size()),
sb.data());
if(t != s)
return false;
sb.consume(N-1);
return true;
}
void
do_peer(int id, socket_type&& sock)
{
websocket::stream<socket_type> ws(std::move(sock));
using boost::asio::buffer;
using boost::asio::buffer_copy;
stream<socket_type> ws(std::move(sock));
ws.set_option(decorate(identity{}));
ws.set_option(read_message_max(64 * 1024 * 1024));
error_code ec;
@@ -164,17 +187,45 @@ private:
}
for(;;)
{
websocket::opcode op;
opcode op;
beast::streambuf sb;
ws.read(op, sb, ec);
if(ec)
{
auto const s = ec.message();
break;
ws.set_option(websocket::message_type(op));
ws.write(sb.data(), ec);
}
ws.set_option(message_type(op));
if(match(sb, "RAW"))
{
boost::asio::write(
ws.next_layer(), sb.data(), ec);
}
else if(match(sb, "TEXT"))
{
ws.set_option(message_type{opcode::text});
ws.write(sb.data(), ec);
}
else if(match(sb, "PING"))
{
ping_data payload;
sb.consume(buffer_copy(
buffer(payload.data(), payload.size()),
sb.data()));
ws.ping(payload, ec);
}
else if(match(sb, "CLOSE"))
{
ws.close({}, ec);
}
else
{
ws.write(sb.data(), ec);
}
if(ec)
break;
}
if(ec && ec != websocket::error::closed)
if(ec && ec != error::closed)
{
fail(id, ec, "read");
}