websocket test coverage

This commit is contained in:
Vinnie Falco
2019-02-17 05:23:40 -08:00
parent e831e8b7ee
commit 085fb66b26
6 changed files with 1152 additions and 459 deletions

View File

@ -189,13 +189,7 @@ struct impl_base<true>
build_response_pmd(
http::response<http::string_body>& res,
http::request<Body,
http::basic_fields<Allocator>> const& req)
{
pmd_offer offer;
pmd_offer unused;
pmd_read(offer, req);
pmd_negotiate(res, unused, offer, pmd_opts_);
}
http::basic_fields<Allocator>> const& req);
void
on_response_pmd(
@ -399,9 +393,7 @@ struct impl_base<false>
build_response_pmd(
http::response<http::string_body>&,
http::request<Body,
http::basic_fields<Allocator>> const&)
{
}
http::basic_fields<Allocator>> const&);
void
on_response_pmd(

View File

@ -10,7 +10,6 @@
#ifndef BOOST_BEAST_WEBSOCKET_IMPL_ACCEPT_IPP
#define BOOST_BEAST_WEBSOCKET_IMPL_ACCEPT_IPP
#include <boost/beast/core/buffer_size.hpp>
#include <boost/beast/websocket/impl/stream_impl.hpp>
#include <boost/beast/websocket/detail/type_traits.hpp>
#include <boost/beast/http/empty_body.hpp>
@ -19,10 +18,11 @@
#include <boost/beast/http/string_body.hpp>
#include <boost/beast/http/write.hpp>
#include <boost/beast/core/async_op_base.hpp>
#include <boost/beast/core/buffers_prefix.hpp>
#include <boost/beast/core/buffer_size.hpp>
#include <boost/beast/core/stream_traits.hpp>
#include <boost/beast/core/detail/buffer.hpp>
#include <boost/beast/core/detail/type_traits.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/post.hpp>
#include <boost/assert.hpp>
@ -34,6 +34,134 @@ namespace boost {
namespace beast {
namespace websocket {
//------------------------------------------------------------------------------
namespace detail {
template<class Body, class Allocator>
void
impl_base<true>::
build_response_pmd(
http::response<http::string_body>& res,
http::request<Body,
http::basic_fields<Allocator>> const& req)
{
pmd_offer offer;
pmd_offer unused;
pmd_read(offer, req);
pmd_negotiate(res, unused, offer, pmd_opts_);
}
template<class Body, class Allocator>
void
impl_base<false>::
build_response_pmd(
http::response<http::string_body>&,
http::request<Body,
http::basic_fields<Allocator>> const&)
{
}
} // detail
template<class NextLayer, bool deflateSupported>
template<class Body, class Allocator, class Decorator>
response_type
stream<NextLayer, deflateSupported>::impl_type::
build_response(
http::request<Body,
http::basic_fields<Allocator>> const& req,
Decorator const& decorator,
error_code& result)
{
auto const decorate =
[&decorator](response_type& res)
{
decorator(res);
if(! res.count(http::field::server))
{
BOOST_STATIC_ASSERT(sizeof(BOOST_BEAST_VERSION_STRING) < 20);
static_string<20> s(BOOST_BEAST_VERSION_STRING);
res.set(http::field::server, s);
}
};
auto err =
[&](error e)
{
result = e;
response_type res;
res.version(req.version());
res.result(http::status::bad_request);
res.body() = result.message();
res.prepare_payload();
decorate(res);
return res;
};
if(req.version() != 11)
return err(error::bad_http_version);
if(req.method() != http::verb::get)
return err(error::bad_method);
if(! req.count(http::field::host))
return err(error::no_host);
{
auto const it = req.find(http::field::connection);
if(it == req.end())
return err(error::no_connection);
if(! http::token_list{it->value()}.exists("upgrade"))
return err(error::no_connection_upgrade);
}
{
auto const it = req.find(http::field::upgrade);
if(it == req.end())
return err(error::no_upgrade);
if(! http::token_list{it->value()}.exists("websocket"))
return err(error::no_upgrade_websocket);
}
string_view key;
{
auto const it = req.find(http::field::sec_websocket_key);
if(it == req.end())
return err(error::no_sec_key);
key = it->value();
if(key.size() > detail::sec_ws_key_type::max_size_n)
return err(error::bad_sec_key);
}
{
auto const it = req.find(http::field::sec_websocket_version);
if(it == req.end())
return err(error::no_sec_version);
if(it->value() != "13")
{
response_type res;
res.result(http::status::upgrade_required);
res.version(req.version());
res.set(http::field::sec_websocket_version, "13");
result = error::bad_sec_version;
res.body() = result.message();
res.prepare_payload();
decorate(res);
return res;
}
}
response_type res;
res.result(http::status::switching_protocols);
res.version(req.version());
res.set(http::field::upgrade, "websocket");
res.set(http::field::connection, "upgrade");
{
detail::sec_ws_accept_type acc;
detail::make_sec_ws_accept(acc, key);
res.set(http::field::sec_websocket_accept, acc);
}
this->build_response_pmd(res, req);
decorate(res);
result = {};
return res;
}
//------------------------------------------------------------------------------
/** Respond to an HTTP request
*/
template<class NextLayer, bool deflateSupported>

View File

@ -10,7 +10,14 @@
#ifndef BOOST_BEAST_WEBSOCKET_IMPL_STREAM_IMPL_HPP
#define BOOST_BEAST_WEBSOCKET_IMPL_STREAM_IMPL_HPP
#include <boost/beast/version.hpp>
#include <boost/beast/websocket/rfc6455.hpp>
#include <boost/beast/websocket/detail/frame.hpp>
#include <boost/beast/websocket/detail/hybi13.hpp>
#include <boost/beast/websocket/detail/mask.hpp>
#include <boost/beast/websocket/detail/pmd_extension.hpp>
#include <boost/beast/websocket/detail/prng.hpp>
#include <boost/beast/websocket/detail/soft_mutex.hpp>
#include <boost/beast/websocket/detail/utf8_checker.hpp>
#include <boost/beast/http/read.hpp>
#include <boost/beast/http/write.hpp>
#include <boost/beast/http/rfc7230.hpp>
@ -23,14 +30,6 @@
#include <boost/beast/core/stream_traits.hpp>
#include <boost/beast/core/detail/clamp.hpp>
#include <boost/beast/core/detail/type_traits.hpp>
#include <boost/beast/websocket/rfc6455.hpp>
#include <boost/beast/websocket/detail/frame.hpp>
#include <boost/beast/websocket/detail/hybi13.hpp>
#include <boost/beast/websocket/detail/mask.hpp>
#include <boost/beast/websocket/detail/pmd_extension.hpp>
#include <boost/beast/websocket/detail/prng.hpp>
#include <boost/beast/websocket/detail/soft_mutex.hpp>
#include <boost/beast/websocket/detail/utf8_checker.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/steady_timer.hpp>
@ -588,104 +587,6 @@ on_response(
this->open(role_type::client);
}
//--------------------------------------------------------------------------
template<class NextLayer, bool deflateSupported>
template<class Body, class Allocator, class Decorator>
response_type
stream<NextLayer, deflateSupported>::impl_type::
build_response(
http::request<Body,
http::basic_fields<Allocator>> const& req,
Decorator const& decorator,
error_code& result)
{
auto const decorate =
[&decorator](response_type& res)
{
decorator(res);
if(! res.count(http::field::server))
{
BOOST_STATIC_ASSERT(sizeof(BOOST_BEAST_VERSION_STRING) < 20);
static_string<20> s(BOOST_BEAST_VERSION_STRING);
res.set(http::field::server, s);
}
};
auto err =
[&](error e)
{
result = e;
response_type res;
res.version(req.version());
res.result(http::status::bad_request);
res.body() = result.message();
res.prepare_payload();
decorate(res);
return res;
};
if(req.version() != 11)
return err(error::bad_http_version);
if(req.method() != http::verb::get)
return err(error::bad_method);
if(! req.count(http::field::host))
return err(error::no_host);
{
auto const it = req.find(http::field::connection);
if(it == req.end())
return err(error::no_connection);
if(! http::token_list{it->value()}.exists("upgrade"))
return err(error::no_connection_upgrade);
}
{
auto const it = req.find(http::field::upgrade);
if(it == req.end())
return err(error::no_upgrade);
if(! http::token_list{it->value()}.exists("websocket"))
return err(error::no_upgrade_websocket);
}
string_view key;
{
auto const it = req.find(http::field::sec_websocket_key);
if(it == req.end())
return err(error::no_sec_key);
key = it->value();
if(key.size() > detail::sec_ws_key_type::max_size_n)
return err(error::bad_sec_key);
}
{
auto const it = req.find(http::field::sec_websocket_version);
if(it == req.end())
return err(error::no_sec_version);
if(it->value() != "13")
{
response_type res;
res.result(http::status::upgrade_required);
res.version(req.version());
res.set(http::field::sec_websocket_version, "13");
result = error::bad_sec_version;
res.body() = result.message();
res.prepare_payload();
decorate(res);
return res;
}
}
response_type res;
res.result(http::status::switching_protocols);
res.version(req.version());
res.set(http::field::upgrade, "websocket");
res.set(http::field::connection, "upgrade");
{
detail::sec_ws_accept_type acc;
detail::make_sec_ws_accept(acc, key);
res.set(http::field::sec_websocket_accept, acc);
}
this->build_response_pmd(res, req);
decorate(res);
result = {};
return res;
}
//------------------------------------------------------------------------------
// Attempt to read a complete frame header.

View File

@ -3570,10 +3570,10 @@ seed_prng(std::seed_seq& ss)
} // beast
} // boost
#include <boost/beast/websocket/impl/stream_impl.hpp> // must be first
#include <boost/beast/websocket/impl/accept.hpp>
#include <boost/beast/websocket/impl/close.hpp>
#include <boost/beast/websocket/impl/handshake.hpp>
#include <boost/beast/websocket/impl/stream_impl.hpp>
#include <boost/beast/websocket/impl/ping.hpp>
#include <boost/beast/websocket/impl/read.hpp>
#include <boost/beast/websocket/impl/stream.hpp>

View File

@ -10,7 +10,9 @@
// Test that header file is self-contained.
#include <boost/beast/websocket/stream.hpp>
#include <boost/beast/_experimental/test/stream.hpp>
#include <boost/beast/_experimental/test/tcp.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
#include "test.hpp"
#include <boost/asio/io_context.hpp>
@ -20,13 +22,9 @@ namespace boost {
namespace beast {
namespace websocket {
class accept_test : public websocket_test_suite
class accept_test : public unit_test::suite //: public websocket_test_suite
{
public:
template<class Wrap>
void
doTestAccept(Wrap const& w)
{
class res_decorator
{
bool& b_;
@ -47,6 +45,252 @@ public:
}
};
template<std::size_t N>
static
net::const_buffer
sbuf(const char (&s)[N])
{
return net::const_buffer(&s[0], N-1);
}
static
void
fail_loop(
std::function<void(stream<test::stream>&)> f,
std::chrono::steady_clock::duration amount =
std::chrono::seconds(5))
{
using clock_type = std::chrono::steady_clock;
auto const expires_at =
clock_type::now() + amount;
net::io_context ioc;
for(std::size_t n = 0;;++n)
{
test::fail_count fc(n);
try
{
stream<test::stream> ws(ioc, fc);
auto tr = connect(ws.next_layer());
f(ws);
break;
}
catch(system_error const& se)
{
if(! BEAST_EXPECTS(
se.code() == test::error::test_failure,
se.code().message()))
throw;
if(! BEAST_EXPECTS(
clock_type::now() < expires_at,
"a test timeout occurred"))
break;
}
}
}
template<class Api>
void
testMatrix(Api api)
{
net::io_context ioc;
// request in stream
fail_loop([&](stream<test::stream>& ws)
{
ws.next_layer().append(
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n");
ws.next_layer().read_size(20);
api.accept(ws);
});
// request in stream, decorator
fail_loop([&](stream<test::stream>& ws)
{
ws.next_layer().append(
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n");
ws.next_layer().read_size(20);
bool called = false;
api.accept_ex(ws, res_decorator{called});
BEAST_EXPECT(called);
});
// request in buffers
fail_loop([&](stream<test::stream>& ws)
{
api.accept(ws, sbuf(
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n"
));
});
// request in buffers, decorator
fail_loop([&](stream<test::stream>& ws)
{
bool called = false;
api.accept_ex(ws, sbuf(
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n"),
res_decorator{called});
BEAST_EXPECT(called);
});
// request in buffers and stream
fail_loop([&](stream<test::stream>& ws)
{
ws.next_layer().append(
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n");
ws.next_layer().read_size(16);
api.accept(ws, sbuf(
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
));
// VFALCO validate contents of ws.next_layer().str?
});
// request in buffers and stream, decorator
fail_loop([&](stream<test::stream>& ws)
{
ws.next_layer().append(
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n");
ws.next_layer().read_size(16);
bool called = false;
api.accept_ex(ws, sbuf(
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"),
res_decorator{called});
BEAST_EXPECT(called);
});
// request in message
{
request_type req;
req.method(http::verb::get);
req.target("/");
req.version(11);
req.insert(http::field::host, "localhost");
req.insert(http::field::upgrade, "websocket");
req.insert(http::field::connection, "upgrade");
req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ==");
req.insert(http::field::sec_websocket_version, "13");
fail_loop([&](stream<test::stream>& ws)
{
api.accept(ws, req);
});
}
// request in message, decorator
{
request_type req;
req.method(http::verb::get);
req.target("/");
req.version(11);
req.insert(http::field::host, "localhost");
req.insert(http::field::upgrade, "websocket");
req.insert(http::field::connection, "upgrade");
req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ==");
req.insert(http::field::sec_websocket_version, "13");
fail_loop([&](stream<test::stream>& ws)
{
bool called = false;
api.accept_ex(ws, req,
res_decorator{called});
BEAST_EXPECT(called);
});
}
// request in message, close frame in stream
{
request_type req;
req.method(http::verb::get);
req.target("/");
req.version(11);
req.insert(http::field::host, "localhost");
req.insert(http::field::upgrade, "websocket");
req.insert(http::field::connection, "upgrade");
req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ==");
req.insert(http::field::sec_websocket_version, "13");
fail_loop([&](stream<test::stream>& ws)
{
ws.next_layer().append("\x88\x82\xff\xff\xff\xff\xfc\x17");
api.accept(ws, req);
try
{
static_buffer<1> b;
api.read(ws, b);
fail("success", __FILE__, __LINE__);
}
catch(system_error const& e)
{
if(e.code() != websocket::error::closed)
throw;
}
});
}
// failed handshake (missing Sec-WebSocket-Key)
fail_loop([&](stream<test::stream>& ws)
{
ws.next_layer().append(
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
"Connection: upgrade\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n");
ws.next_layer().read_size(20);
try
{
api.accept(ws);
BEAST_FAIL();
}
catch(system_error const& e)
{
if( e.code() != websocket::error::no_sec_key &&
e.code() != net::error::eof)
throw;
}
});
}
template<class Api>
void
testOversized(Api const& api)
{
net::io_context ioc;
auto const big = []
{
std::string s;
@ -55,26 +299,8 @@ public:
}();
// request in stream
doStreamLoop([&](test::stream& ts)
{
stream<test::stream&> ws{ts};
auto tr = connect(ws.next_layer());
ts.append(
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n");
ts.read_size(20);
w.accept(ws);
// VFALCO validate contents of ws.next_layer().str?
});
// request in stream, oversized
{
stream<test::stream> ws{ioc_,
stream<test::stream> ws{ioc,
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
@ -86,8 +312,8 @@ public:
auto tr = connect(ws.next_layer());
try
{
w.accept(ws);
fail("", __FILE__, __LINE__);
api.accept(ws);
BEAST_FAIL();
}
catch(system_error const& se)
{
@ -99,27 +325,8 @@ public:
}
// request in stream, decorator
doStreamLoop([&](test::stream& ts)
{
stream<test::stream&> ws{ts};
auto tr = connect(ws.next_layer());
ts.append(
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n");
ts.read_size(20);
bool called = false;
w.accept_ex(ws, res_decorator{called});
BEAST_EXPECT(called);
});
// request in stream, decorator, oversized
{
stream<test::stream> ws{ioc_,
stream<test::stream> ws{ioc,
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
@ -132,8 +339,8 @@ public:
try
{
bool called = false;
w.accept_ex(ws, res_decorator{called});
fail("", __FILE__, __LINE__);
api.accept_ex(ws, res_decorator{called});
BEAST_FAIL();
}
catch(system_error const& se)
{
@ -145,28 +352,12 @@ public:
}
// request in buffers
doStreamLoop([&](test::stream& ts)
{
stream<test::stream&> ws{ts};
auto tr = connect(ws.next_layer());
w.accept(ws, sbuf(
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n"
));
});
// request in buffers, oversize
{
stream<test::stream> ws{ioc_};
stream<test::stream> ws{ioc};
auto tr = connect(ws.next_layer());
try
{
w.accept(ws, net::buffer(
api.accept(ws, net::buffer(
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
@ -176,7 +367,7 @@ public:
+ big +
"\r\n"
));
fail("", __FILE__, __LINE__);
BEAST_FAIL();
}
catch(system_error const& se)
{
@ -187,31 +378,13 @@ public:
}
// request in buffers, decorator
doStreamLoop([&](test::stream& ts)
{
stream<test::stream&> ws{ts};
auto tr = connect(ws.next_layer());
bool called = false;
w.accept_ex(ws, sbuf(
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n"),
res_decorator{called});
BEAST_EXPECT(called);
});
// request in buffers, decorator, oversized
{
stream<test::stream> ws{ioc_};
stream<test::stream> ws{ioc};
auto tr = connect(ws.next_layer());
try
{
bool called = false;
w.accept_ex(ws, net::buffer(
api.accept_ex(ws, net::buffer(
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
@ -221,7 +394,7 @@ public:
+ big +
"\r\n"),
res_decorator{called});
fail("", __FILE__, __LINE__);
BEAST_FAIL();
}
catch(system_error const& se)
{
@ -232,27 +405,8 @@ public:
}
// request in buffers and stream
doStreamLoop([&](test::stream& ts)
{
stream<test::stream&> ws{ts};
auto tr = connect(ws.next_layer());
ts.append(
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n");
ts.read_size(16);
w.accept(ws, sbuf(
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
));
// VFALCO validate contents of ws.next_layer().str?
});
// request in buffers and stream, oversized
{
stream<test::stream> ws{ioc_,
stream<test::stream> ws{ioc,
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
@ -261,12 +415,12 @@ public:
auto tr = connect(ws.next_layer());
try
{
w.accept(ws, sbuf(
api.accept(ws, websocket_test_suite::sbuf(
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
));
fail("", __FILE__, __LINE__);
BEAST_FAIL();
}
catch(system_error const& se)
{
@ -277,28 +431,8 @@ public:
}
// request in buffers and stream, decorator
doStreamLoop([&](test::stream& ts)
{
stream<test::stream&> ws{ts};
auto tr = connect(ws.next_layer());
ts.append(
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n");
ts.read_size(16);
bool called = false;
w.accept_ex(ws, sbuf(
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"),
res_decorator{called});
BEAST_EXPECT(called);
});
// request in buffers and stream, decorator, oversize
{
stream<test::stream> ws{ioc_,
stream<test::stream> ws{ioc,
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
@ -308,12 +442,12 @@ public:
try
{
bool called = false;
w.accept_ex(ws, sbuf(
api.accept_ex(ws, websocket_test_suite::sbuf(
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"),
res_decorator{called});
fail("", __FILE__, __LINE__);
BEAST_FAIL();
}
catch(system_error const& se)
{
@ -322,137 +456,15 @@ public:
se.code().message());
}
}
// request in message
doStreamLoop([&](test::stream& ts)
{
stream<test::stream&> ws{ts};
auto tr = connect(ws.next_layer());
request_type req;
req.method(http::verb::get);
req.target("/");
req.version(11);
req.insert(http::field::host, "localhost");
req.insert(http::field::upgrade, "websocket");
req.insert(http::field::connection, "upgrade");
req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ==");
req.insert(http::field::sec_websocket_version, "13");
w.accept(ws, req);
});
// request in message, decorator
doStreamLoop([&](test::stream& ts)
{
stream<test::stream&> ws{ts};
auto tr = connect(ws.next_layer());
request_type req;
req.method(http::verb::get);
req.target("/");
req.version(11);
req.insert(http::field::host, "localhost");
req.insert(http::field::upgrade, "websocket");
req.insert(http::field::connection, "upgrade");
req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ==");
req.insert(http::field::sec_websocket_version, "13");
bool called = false;
w.accept_ex(ws, req,
res_decorator{called});
BEAST_EXPECT(called);
});
// request in message, close frame in stream
doStreamLoop([&](test::stream& ts)
{
stream<test::stream&> ws{ts};
auto tr = connect(ws.next_layer());
request_type req;
req.method(http::verb::get);
req.target("/");
req.version(11);
req.insert(http::field::host, "localhost");
req.insert(http::field::upgrade, "websocket");
req.insert(http::field::connection, "upgrade");
req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ==");
req.insert(http::field::sec_websocket_version, "13");
ts.append("\x88\x82\xff\xff\xff\xff\xfc\x17");
w.accept(ws, req);
try
{
static_buffer<1> b;
w.read(ws, b);
fail("success", __FILE__, __LINE__);
}
catch(system_error const& e)
{
if(e.code() != websocket::error::closed)
throw;
}
});
// failed handshake (missing Sec-WebSocket-Key)
doStreamLoop([&](test::stream& ts)
{
stream<test::stream&> ws{ts};
auto tr = connect(ws.next_layer());
ts.append(
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
"Connection: upgrade\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n");
ts.read_size(20);
try
{
w.accept(ws);
fail("success", __FILE__, __LINE__);
}
catch(system_error const& e)
{
if( e.code() !=
websocket::error::no_sec_key &&
e.code() !=
net::error::eof)
throw;
}
});
// Closed by client
{
stream<test::stream> ws{ioc_};
auto tr = connect(ws.next_layer());
tr.close();
try
{
w.accept(ws);
fail("success", __FILE__, __LINE__);
}
catch(system_error const& e)
{
if(! BEAST_EXPECTS(
e.code() == error::closed,
e.code().message()))
throw;
}
}
}
void
testAccept()
testInvalidInputs()
{
doTestAccept(SyncClient{});
yield_to([&](yield_context yield)
{
doTestAccept(AsyncClient{yield});
});
//
// Bad requests
//
net::io_context ioc;
auto const check =
[&](error_code const& ev, std::string const& s)
[&](error_code ev, string_view s)
{
for(int i = 0; i < 3; ++i)
{
@ -470,14 +482,14 @@ public:
n = s.size() - 1;
break;
}
stream<test::stream> ws{ioc_};
stream<test::stream> ws(ioc);
auto tr = connect(ws.next_layer());
ws.next_layer().append(
s.substr(n, s.size() - n));
tr.close();
try
{
ws.accept(
net::buffer(s.data(), n));
ws.accept(net::buffer(s.data(), n));
BEAST_EXPECTS(! ev, ev.message());
}
catch(system_error const& se)
@ -497,6 +509,7 @@ public:
"Sec-WebSocket-Version: 13\r\n"
"\r\n"
);
// bad method
check(error::bad_method,
"POST / HTTP/1.1\r\n"
@ -507,6 +520,7 @@ public:
"Sec-WebSocket-Version: 13\r\n"
"\r\n"
);
// no Host
check(error::no_host,
"GET / HTTP/1.1\r\n"
@ -516,6 +530,7 @@ public:
"Sec-WebSocket-Version: 13\r\n"
"\r\n"
);
// no Connection
check(error::no_connection,
"GET / HTTP/1.1\r\n"
@ -525,6 +540,7 @@ public:
"Sec-WebSocket-Version: 13\r\n"
"\r\n"
);
// no Connection upgrade
check(error::no_connection_upgrade,
"GET / HTTP/1.1\r\n"
@ -535,6 +551,7 @@ public:
"Sec-WebSocket-Version: 13\r\n"
"\r\n"
);
// no Upgrade
check(error::no_upgrade,
"GET / HTTP/1.1\r\n"
@ -544,6 +561,7 @@ public:
"Sec-WebSocket-Version: 13\r\n"
"\r\n"
);
// no Upgrade websocket
check(error::no_upgrade_websocket,
"GET / HTTP/1.1\r\n"
@ -554,6 +572,7 @@ public:
"Sec-WebSocket-Version: 13\r\n"
"\r\n"
);
// no Sec-WebSocket-Key
check(error::no_sec_key,
"GET / HTTP/1.1\r\n"
@ -563,6 +582,7 @@ public:
"Sec-WebSocket-Version: 13\r\n"
"\r\n"
);
// bad Sec-WebSocket-Key
check(error::bad_sec_key,
"GET / HTTP/1.1\r\n"
@ -573,6 +593,7 @@ public:
"Sec-WebSocket-Version: 13\r\n"
"\r\n"
);
// no Sec-WebSocket-Version
check(error::no_sec_version,
"GET / HTTP/1.1\r\n"
@ -582,6 +603,7 @@ public:
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"\r\n"
);
// bad Sec-WebSocket-Version
check(error::bad_sec_version,
"GET / HTTP/1.1\r\n"
@ -592,6 +614,7 @@ public:
"Sec-WebSocket-Version: 1\r\n"
"\r\n"
);
// bad Sec-WebSocket-Version
check(error::bad_sec_version,
"GET / HTTP/1.1\r\n"
@ -602,6 +625,7 @@ public:
"Sec-WebSocket-Version: 12\r\n"
"\r\n"
);
// valid request
check({},
"GET / HTTP/1.1\r\n"
@ -615,13 +639,53 @@ public:
}
void
testTimeout()
testEndOfStream()
{
net::io_context ioc;
{
stream<test::stream> ws(ioc);
auto tr = connect(ws.next_layer());
tr.close();
try
{
test_sync_api api;
api.accept(ws, net::const_buffer{});
BEAST_FAIL();
}
catch(system_error const& se)
{
BEAST_EXPECTS(
se.code() == error::closed,
se.code().message());
}
}
{
stream<test::stream> ws(ioc);
auto tr = connect(ws.next_layer());
tr.close();
try
{
test_async_api api;
api.accept(ws, net::const_buffer{});
BEAST_FAIL();
}
catch(system_error const& se)
{
BEAST_EXPECTS(
se.code() == error::closed,
se.code().message());
}
}
}
void
testAsync()
{
using tcp = net::ip::tcp;
net::io_context ioc;
// success
// success, no timeout
{
stream<tcp::socket> ws1(ioc);
@ -700,47 +764,49 @@ public:
ws1.async_accept(test::fail_handler(beast::error::timeout));
test::run_for(ioc, std::chrono::seconds(1));
}
// abandoned operation
{
{
stream<tcp::socket> ws1(ioc);
ws1.async_accept(test::fail_handler(
net::error::operation_aborted));
}
test::run(ioc);
}
void
testMoveOnly()
{
net::io_context ioc;
stream<test::stream> ws{ioc};
ws.async_accept(move_only_handler{});
{
stream<tcp::socket> ws1(ioc);
string_view s =
"GET / HTTP/1.1\r\n"
"Host: localhost\r\n"
"Upgrade: websocket\r\n"
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n";
error_code ec;
http::request_parser<http::empty_body> p;
p.put(net::const_buffer(s.data(), s.size()), ec);
ws1.async_accept(p.get(), test::fail_handler(
net::error::operation_aborted));
}
struct copyable_handler
{
template<class... Args>
void
operator()(Args&&...) const
{
test::run(ioc);
}
};
void
testAsioHandlerInvoke()
{
// make sure things compile, also can set a
// breakpoint in asio_handler_invoke to make sure
// it is instantiated.
net::io_context ioc;
net::strand<
net::io_context::executor_type> s(
ioc.get_executor());
stream<test::stream> ws{ioc};
ws.async_accept(net::bind_executor(
s, copyable_handler{}));
}
void
run() override
{
testAccept();
testTimeout();
testMoveOnly();
testAsioHandlerInvoke();
testMatrix(test_sync_api{});
testMatrix(test_async_api{});
testOversized(test_sync_api{});
testOversized(test_async_api{});
testInvalidInputs();
testEndOfStream();
testAsync();
}
};

View File

@ -1013,6 +1013,612 @@ public:
};
};
struct test_sync_api
{
template<class NextLayer, bool deflateSupported>
void
accept(
stream<NextLayer, deflateSupported>& ws) const
{
ws.accept();
}
template<
class NextLayer, bool deflateSupported,
class Buffers>
typename std::enable_if<
! http::detail::is_header<Buffers>::value>::type
accept(stream<NextLayer, deflateSupported>& ws,
Buffers const& buffers) const
{
ws.accept(buffers);
}
template<class NextLayer, bool deflateSupported>
void
accept(
stream<NextLayer, deflateSupported>& ws,
http::request<http::empty_body> const& req) const
{
ws.accept(req);
}
template<
class NextLayer, bool deflateSupported,
class Decorator>
void
accept_ex(
stream<NextLayer, deflateSupported>& ws,
Decorator const& d) const
{
ws.accept_ex(d);
}
template<
class NextLayer, bool deflateSupported,
class Buffers, class Decorator>
typename std::enable_if<
! http::detail::is_header<Buffers>::value>::type
accept_ex(
stream<NextLayer, deflateSupported>& ws,
Buffers const& buffers,
Decorator const& d) const
{
ws.accept_ex(buffers, d);
}
template<
class NextLayer, bool deflateSupported,
class Decorator>
void
accept_ex(
stream<NextLayer, deflateSupported>& ws,
http::request<http::empty_body> const& req,
Decorator const& d) const
{
ws.accept_ex(req, d);
}
template<
class NextLayer, bool deflateSupported,
class Buffers, class Decorator>
void
accept_ex(
stream<NextLayer, deflateSupported>& ws,
http::request<http::empty_body> const& req,
Buffers const& buffers,
Decorator const& d) const
{
ws.accept_ex(req, buffers, d);
}
template<class NextLayer, bool deflateSupported>
void
handshake(
stream<NextLayer, deflateSupported>& ws,
response_type& res,
string_view uri,
string_view path) const
{
ws.handshake(res, uri, path);
}
template<
class NextLayer, bool deflateSupported,
class Decorator>
void
handshake_ex(
stream<NextLayer, deflateSupported>& ws,
string_view uri,
string_view path,
Decorator const& d) const
{
ws.handshake_ex(uri, path, d);
}
template<
class NextLayer, bool deflateSupported,
class Decorator>
void
handshake_ex(
stream<NextLayer, deflateSupported>& ws,
response_type& res,
string_view uri,
string_view path,
Decorator const& d) const
{
ws.handshake_ex(res, uri, path, d);
}
template<class NextLayer, bool deflateSupported>
void
ping(stream<NextLayer, deflateSupported>& ws,
ping_data const& payload) const
{
ws.ping(payload);
}
template<class NextLayer, bool deflateSupported>
void
pong(stream<NextLayer, deflateSupported>& ws,
ping_data const& payload) const
{
ws.pong(payload);
}
template<class NextLayer, bool deflateSupported>
void
close(stream<NextLayer, deflateSupported>& ws,
close_reason const& cr) const
{
ws.close(cr);
}
template<
class NextLayer, bool deflateSupported,
class DynamicBuffer>
std::size_t
read(stream<NextLayer, deflateSupported>& ws,
DynamicBuffer& buffer) const
{
return ws.read(buffer);
}
template<
class NextLayer, bool deflateSupported,
class DynamicBuffer>
std::size_t
read_some(
stream<NextLayer, deflateSupported>& ws,
std::size_t limit,
DynamicBuffer& buffer) const
{
return ws.read_some(buffer, limit);
}
template<
class NextLayer, bool deflateSupported,
class MutableBufferSequence>
std::size_t
read_some(
stream<NextLayer, deflateSupported>& ws,
MutableBufferSequence const& buffers) const
{
return ws.read_some(buffers);
}
template<
class NextLayer, bool deflateSupported,
class ConstBufferSequence>
std::size_t
write(
stream<NextLayer, deflateSupported>& ws,
ConstBufferSequence const& buffers) const
{
return ws.write(buffers);
}
template<
class NextLayer, bool deflateSupported,
class ConstBufferSequence>
std::size_t
write_some(
stream<NextLayer, deflateSupported>& ws,
bool fin,
ConstBufferSequence const& buffers) const
{
return ws.write_some(fin, buffers);
}
template<
class NextLayer, bool deflateSupported,
class ConstBufferSequence>
std::size_t
write_raw(
stream<NextLayer, deflateSupported>& ws,
ConstBufferSequence const& buffers) const
{
return net::write(
ws.next_layer(), buffers);
}
};
//--------------------------------------------------------------------------
class test_async_api
{
struct handler
{
error_code& ec_;
std::size_t* n_ = 0;
bool pass_ = false;
explicit
handler(error_code& ec)
: ec_(ec)
{
}
explicit
handler(error_code& ec, std::size_t& n)
: ec_(ec)
, n_(&n)
{
*n_ = 0;
}
handler(handler&& other)
: ec_(other.ec_)
, pass_(boost::exchange(other.pass_, true))
{
}
~handler()
{
BEAST_EXPECT(pass_);
}
void
operator()(error_code ec)
{
BEAST_EXPECT(! pass_);
pass_ = true;
ec_ = ec;
}
void
operator()(error_code ec, std::size_t n)
{
BEAST_EXPECT(! pass_);
pass_ = true;
ec_ = ec;
if(n_)
*n_ = n;
}
};
public:
template<class NextLayer, bool deflateSupported>
void
accept(
stream<NextLayer, deflateSupported>& ws) const
{
error_code ec;
ws.async_accept(handler(ec));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
}
template<
class NextLayer, bool deflateSupported,
class Buffers>
typename std::enable_if<
! http::detail::is_header<Buffers>::value>::type
accept(
stream<NextLayer, deflateSupported>& ws,
Buffers const& buffers) const
{
error_code ec;
ws.async_accept(buffers, handler(ec));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
}
template<class NextLayer, bool deflateSupported>
void
accept(
stream<NextLayer, deflateSupported>& ws,
http::request<http::empty_body> const& req) const
{
error_code ec;
ws.async_accept(req, handler(ec));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
}
template<
class NextLayer, bool deflateSupported,
class Decorator>
void
accept_ex(
stream<NextLayer, deflateSupported>& ws,
Decorator const& d) const
{
error_code ec;
ws.async_accept_ex(d, handler(ec));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
}
template<
class NextLayer, bool deflateSupported,
class Buffers, class Decorator>
typename std::enable_if<
! http::detail::is_header<Buffers>::value>::type
accept_ex(
stream<NextLayer, deflateSupported>& ws,
Buffers const& buffers,
Decorator const& d) const
{
error_code ec;
ws.async_accept_ex(buffers, d, handler(ec));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
}
template<
class NextLayer, bool deflateSupported,
class Decorator>
void
accept_ex(
stream<NextLayer, deflateSupported>& ws,
http::request<http::empty_body> const& req,
Decorator const& d) const
{
error_code ec;
ws.async_accept_ex(req, d, handler(ec));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
}
template<
class NextLayer, bool deflateSupported,
class Buffers, class Decorator>
void
accept_ex(
stream<NextLayer, deflateSupported>& ws,
http::request<http::empty_body> const& req,
Buffers const& buffers,
Decorator const& d) const
{
error_code ec;
ws.async_accept_ex(
req, buffers, d, handler(ec));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
}
template<
class NextLayer, bool deflateSupported>
void
handshake(
stream<NextLayer, deflateSupported>& ws,
string_view uri,
string_view path) const
{
error_code ec;
ws.async_handshake(
uri, path, handler(ec));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
}
template<class NextLayer, bool deflateSupported>
void
handshake(
stream<NextLayer, deflateSupported>& ws,
response_type& res,
string_view uri,
string_view path) const
{
error_code ec;
ws.async_handshake(
res, uri, path, handler(ec));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
}
template<
class NextLayer, bool deflateSupported,
class Decorator>
void
handshake_ex(
stream<NextLayer, deflateSupported>& ws,
string_view uri,
string_view path,
Decorator const &d) const
{
error_code ec;
ws.async_handshake_ex(
uri, path, d, handler(ec));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
}
template<
class NextLayer, bool deflateSupported,
class Decorator>
void
handshake_ex(
stream<NextLayer, deflateSupported>& ws,
response_type& res,
string_view uri,
string_view path,
Decorator const &d) const
{
error_code ec;
ws.async_handshake_ex(
res, uri, path, d, handler(ec));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
}
template<class NextLayer, bool deflateSupported>
void
ping(
stream<NextLayer, deflateSupported>& ws,
ping_data const& payload) const
{
error_code ec;
ws.async_ping(payload, handler(ec));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
}
template<class NextLayer, bool deflateSupported>
void
pong(
stream<NextLayer, deflateSupported>& ws,
ping_data const& payload) const
{
error_code ec;
ws.async_pong(payload, handler(ec));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
}
template<class NextLayer, bool deflateSupported>
void
close(
stream<NextLayer, deflateSupported>& ws,
close_reason const& cr) const
{
error_code ec;
ws.async_close(cr, handler(ec));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
}
template<
class NextLayer, bool deflateSupported,
class DynamicBuffer>
std::size_t
read(
stream<NextLayer, deflateSupported>& ws,
DynamicBuffer& buffer) const
{
error_code ec;
std::size_t n;
ws.async_read(buffer, handler(ec, n));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
return n;
}
template<
class NextLayer, bool deflateSupported,
class DynamicBuffer>
std::size_t
read_some(
stream<NextLayer, deflateSupported>& ws,
std::size_t limit,
DynamicBuffer& buffer) const
{
error_code ec;
std::size_t n;
ws.async_read_some(buffer, limit, handler(ec, n));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
return n;
}
template<
class NextLayer, bool deflateSupported,
class MutableBufferSequence>
std::size_t
read_some(
stream<NextLayer, deflateSupported>& ws,
MutableBufferSequence const& buffers) const
{
error_code ec;
std::size_t n;
ws.async_read_some(buffers, handler(ec, n));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
return n;
}
template<
class NextLayer, bool deflateSupported,
class ConstBufferSequence>
std::size_t
write(
stream<NextLayer, deflateSupported>& ws,
ConstBufferSequence const& buffers) const
{
error_code ec;
std::size_t n;
ws.async_write(buffers, handler(ec, n));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
return n;
}
template<
class NextLayer, bool deflateSupported,
class ConstBufferSequence>
std::size_t
write_some(
stream<NextLayer, deflateSupported>& ws,
bool fin,
ConstBufferSequence const& buffers) const
{
error_code ec;
std::size_t n;
ws.async_write_some(fin, buffers, handler(ec, n));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
return n;
}
template<
class NextLayer, bool deflateSupported,
class ConstBufferSequence>
std::size_t
write_raw(
stream<NextLayer, deflateSupported>& ws,
ConstBufferSequence const& buffers) const
{
error_code ec;
std::size_t n;
net::async_write(ws.next_layer(),
buffers, handler(ec, n));
ws.get_executor().context().run();
ws.get_executor().context().restart();
if(ec)
throw system_error{ec};
return n;
}
};
} // websocket
} // beast
} // boost