forked from boostorg/beast
Refactor websocket decorators (API Change):
fix #80, #212, fix #303, fix #314, fix #317 websocket::stream now provides the following families of functions for performing handshakes: When operating in the server role: * stream::accept * stream::accept_ex * stream::async_accept * stream::async_accept_ex When operating in the client role: * stream::handshake * stream::handshake_ex * stream::async_handshake * stream::async_handshake_ex Member functions ending with "_ex" allow an additional RequestDecorator parameter (for the accept family of functions) or ResponseDecorator parameter (for the handshake family of functions). The decorator is called to optionally modify the contents of the HTTP request or HTTP response object generated by the implementation, before the message is sent. This permits callers to set the User-Agent or Server fields, add or modify HTTP fields related to subprotocols, or perform any required transformation of the HTTP message for application-specific needs. The handshake() family of functions now have an additional set of overloads accepting a parameter of type response_type&, allowing the caller to receive the HTTP Response to the Upgrade handshake. This permits inspection of the response to handle things like subprotocols, authentication, or other application-specific needs. The new implementation does not require any state to be stored in the stream object. Therefore, websocket::stream objects are now smaller in size. The overload of set_option for setting a decorator on the stream is removed. The only way to set decorators now is with a suitable overload of accept or handshake.
This commit is contained in:
+388
-58
@@ -111,33 +111,6 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
struct test_decorator
|
||||
{
|
||||
int& what;
|
||||
|
||||
test_decorator(test_decorator const&) = default;
|
||||
|
||||
test_decorator(int& what_)
|
||||
: what(what_)
|
||||
{
|
||||
what = 0;
|
||||
}
|
||||
|
||||
template<class Fields>
|
||||
void
|
||||
operator()(http::header<true, Fields>&) const
|
||||
{
|
||||
what |= 1;
|
||||
}
|
||||
|
||||
template<class Fields>
|
||||
void
|
||||
operator()(http::header<false, Fields>&) const
|
||||
{
|
||||
what |= 2;
|
||||
}
|
||||
};
|
||||
|
||||
struct SyncClient
|
||||
{
|
||||
template<class NextLayer>
|
||||
@@ -173,6 +146,46 @@ public:
|
||||
ws.accept(req, buffers);
|
||||
}
|
||||
|
||||
template<class NextLayer, class Decorator>
|
||||
void
|
||||
accept_ex(stream<NextLayer>& ws,
|
||||
Decorator const& d) const
|
||||
{
|
||||
ws.accept_ex(d);
|
||||
}
|
||||
|
||||
template<class NextLayer,
|
||||
class Buffers, class Decorator>
|
||||
void
|
||||
accept_ex(stream<NextLayer>& ws,
|
||||
Buffers const& buffers,
|
||||
Decorator const& d) const
|
||||
{
|
||||
ws.accept_ex(buffers, d);
|
||||
}
|
||||
|
||||
template<class NextLayer,
|
||||
class Fields, class Decorator>
|
||||
void
|
||||
accept_ex(stream<NextLayer>& ws,
|
||||
http::header<true, Fields> const& req,
|
||||
Decorator const& d) const
|
||||
{
|
||||
ws.accept_ex(req, d);
|
||||
}
|
||||
|
||||
template<class NextLayer,
|
||||
class Fields, class Buffers,
|
||||
class Decorator>
|
||||
void
|
||||
accept_ex(stream<NextLayer>& ws,
|
||||
http::header<true, Fields> const& req,
|
||||
Buffers const& buffers,
|
||||
Decorator const& d) const
|
||||
{
|
||||
ws.accept_ex(req, buffers, d);
|
||||
}
|
||||
|
||||
template<class NextLayer>
|
||||
void
|
||||
handshake(stream<NextLayer>& ws,
|
||||
@@ -182,6 +195,37 @@ public:
|
||||
ws.handshake(uri, path);
|
||||
}
|
||||
|
||||
template<class NextLayer>
|
||||
void
|
||||
handshake(stream<NextLayer>& ws,
|
||||
response_type& res,
|
||||
boost::string_ref const& uri,
|
||||
boost::string_ref const& path) const
|
||||
{
|
||||
ws.handshake(res, uri, path);
|
||||
}
|
||||
|
||||
template<class NextLayer, class Decorator>
|
||||
void
|
||||
handshake_ex(stream<NextLayer>& ws,
|
||||
boost::string_ref const& uri,
|
||||
boost::string_ref const& path,
|
||||
Decorator const& d) const
|
||||
{
|
||||
ws.handshake_ex(uri, path, d);
|
||||
}
|
||||
|
||||
template<class NextLayer, class Decorator>
|
||||
void
|
||||
handshake_ex(stream<NextLayer>& ws,
|
||||
response_type& res,
|
||||
boost::string_ref const& uri,
|
||||
boost::string_ref const& path,
|
||||
Decorator const& d) const
|
||||
{
|
||||
ws.handshake_ex(res, uri, path, d);
|
||||
}
|
||||
|
||||
template<class NextLayer>
|
||||
void
|
||||
ping(stream<NextLayer>& ws,
|
||||
@@ -301,14 +345,56 @@ public:
|
||||
}
|
||||
|
||||
template<class NextLayer,
|
||||
class Fields, class Buffers>
|
||||
class Decorator>
|
||||
void
|
||||
accept(stream<NextLayer>& ws,
|
||||
accept_ex(stream<NextLayer>& ws,
|
||||
Decorator const& d) const
|
||||
{
|
||||
error_code ec;
|
||||
ws.async_accept_ex(d, yield_[ec]);
|
||||
if(ec)
|
||||
throw system_error{ec};
|
||||
}
|
||||
|
||||
template<class NextLayer,
|
||||
class Buffers, class Decorator>
|
||||
void
|
||||
accept_ex(stream<NextLayer>& ws,
|
||||
Buffers const& buffers,
|
||||
Decorator const& d) const
|
||||
{
|
||||
error_code ec;
|
||||
ws.async_accept_ex(buffers, d, yield_[ec]);
|
||||
if(ec)
|
||||
throw system_error{ec};
|
||||
}
|
||||
|
||||
template<class NextLayer,
|
||||
class Fields, class Decorator>
|
||||
void
|
||||
accept_ex(stream<NextLayer>& ws,
|
||||
http::header<true, Fields> const& req,
|
||||
Decorator const& d) const
|
||||
{
|
||||
error_code ec;
|
||||
ws.async_accept_ex(req, d, yield_[ec]);
|
||||
if(ec)
|
||||
throw system_error{ec};
|
||||
}
|
||||
|
||||
template<class NextLayer, class Fields,
|
||||
class Buffers, class Decorator>
|
||||
void
|
||||
accept_ex(stream<NextLayer>& ws,
|
||||
http::header<true, Fields> const& req,
|
||||
Buffers const& buffers,
|
||||
error_code& ec) const
|
||||
Decorator const& d) const
|
||||
{
|
||||
ws.async_accept(req, buffers, yield_[ec]);
|
||||
error_code ec;
|
||||
ws.async_accept_ex(
|
||||
req, buffers, d, yield_[ec]);
|
||||
if(ec)
|
||||
throw system_error{ec};
|
||||
}
|
||||
|
||||
template<class NextLayer>
|
||||
@@ -318,7 +404,51 @@ public:
|
||||
boost::string_ref const& path) const
|
||||
{
|
||||
error_code ec;
|
||||
ws.async_handshake(uri, path, yield_[ec]);
|
||||
ws.async_handshake(
|
||||
uri, path, yield_[ec]);
|
||||
if(ec)
|
||||
throw system_error{ec};
|
||||
}
|
||||
|
||||
template<class NextLayer>
|
||||
void
|
||||
handshake(stream<NextLayer>& ws,
|
||||
response_type& res,
|
||||
boost::string_ref const& uri,
|
||||
boost::string_ref const& path) const
|
||||
{
|
||||
error_code ec;
|
||||
ws.async_handshake(
|
||||
res, uri, path, yield_[ec]);
|
||||
if(ec)
|
||||
throw system_error{ec};
|
||||
}
|
||||
|
||||
template<class NextLayer, class Decorator>
|
||||
void
|
||||
handshake_ex(stream<NextLayer>& ws,
|
||||
boost::string_ref const& uri,
|
||||
boost::string_ref const& path,
|
||||
Decorator const &d) const
|
||||
{
|
||||
error_code ec;
|
||||
ws.async_handshake_ex(
|
||||
uri, path, d, yield_[ec]);
|
||||
if(ec)
|
||||
throw system_error{ec};
|
||||
}
|
||||
|
||||
template<class NextLayer, class Decorator>
|
||||
void
|
||||
handshake_ex(stream<NextLayer>& ws,
|
||||
response_type& res,
|
||||
boost::string_ref const& uri,
|
||||
boost::string_ref const& path,
|
||||
Decorator const &d) const
|
||||
{
|
||||
error_code ec;
|
||||
ws.async_handshake_ex(
|
||||
res, uri, path, d, yield_[ec]);
|
||||
if(ec)
|
||||
throw system_error{ec};
|
||||
}
|
||||
@@ -436,6 +566,28 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
class res_decorator
|
||||
{
|
||||
bool& b_;
|
||||
|
||||
public:
|
||||
res_decorator(res_decorator const&) = default;
|
||||
|
||||
explicit
|
||||
res_decorator(bool& b)
|
||||
: b_(b)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(response_type&) const
|
||||
{
|
||||
b_ = true;
|
||||
}
|
||||
};
|
||||
|
||||
template<class Client>
|
||||
void
|
||||
testAccept(Client const& c)
|
||||
@@ -460,7 +612,22 @@ public:
|
||||
"\r\n"
|
||||
, 20};
|
||||
c.accept(ws);
|
||||
//log << ws.next_layer().str << std::endl;
|
||||
// VFALCO validate contents of ws.next_layer().str?
|
||||
}
|
||||
{
|
||||
stream<test::fail_stream<
|
||||
test::string_iostream>> ws{fc, ios_,
|
||||
"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"
|
||||
, 20};
|
||||
bool called = false;
|
||||
c.accept_ex(ws, res_decorator{called});
|
||||
BEAST_EXPECT(called);
|
||||
}
|
||||
// request in buffers
|
||||
{
|
||||
@@ -476,6 +643,21 @@ public:
|
||||
"\r\n"
|
||||
));
|
||||
}
|
||||
{
|
||||
stream<test::fail_stream<
|
||||
test::string_ostream>> ws{fc, ios_};
|
||||
bool called = false;
|
||||
c.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
|
||||
{
|
||||
stream<test::fail_stream<
|
||||
@@ -491,9 +673,25 @@ public:
|
||||
"Upgrade: websocket\r\n"
|
||||
));
|
||||
}
|
||||
{
|
||||
stream<test::fail_stream<
|
||||
test::string_iostream>> ws{fc, ios_,
|
||||
"Connection: upgrade\r\n"
|
||||
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
|
||||
"Sec-WebSocket-Version: 13\r\n"
|
||||
"\r\n"
|
||||
, 16};
|
||||
bool called = false;
|
||||
c.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
|
||||
{
|
||||
http::request_header req;
|
||||
request_type req;
|
||||
req.method = "GET";
|
||||
req.url = "/";
|
||||
req.version = 11;
|
||||
@@ -506,9 +704,26 @@ public:
|
||||
test::string_ostream>> ws{fc, ios_};
|
||||
c.accept(ws, req);
|
||||
}
|
||||
{
|
||||
request_type req;
|
||||
req.method = "GET";
|
||||
req.url = "/";
|
||||
req.version = 11;
|
||||
req.fields.insert("Host", "localhost");
|
||||
req.fields.insert("Upgrade", "websocket");
|
||||
req.fields.insert("Connection", "upgrade");
|
||||
req.fields.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==");
|
||||
req.fields.insert("Sec-WebSocket-Version", "13");
|
||||
stream<test::fail_stream<
|
||||
test::string_ostream>> ws{fc, ios_};
|
||||
bool called = false;
|
||||
c.accept_ex(ws, req,
|
||||
res_decorator{called});
|
||||
BEAST_EXPECT(called);
|
||||
}
|
||||
// request in message, close frame in buffers
|
||||
{
|
||||
http::request_header req;
|
||||
request_type req;
|
||||
req.method = "GET";
|
||||
req.url = "/";
|
||||
req.version = 11;
|
||||
@@ -534,9 +749,39 @@ public:
|
||||
throw;
|
||||
}
|
||||
}
|
||||
{
|
||||
request_type req;
|
||||
req.method = "GET";
|
||||
req.url = "/";
|
||||
req.version = 11;
|
||||
req.fields.insert("Host", "localhost");
|
||||
req.fields.insert("Upgrade", "websocket");
|
||||
req.fields.insert("Connection", "upgrade");
|
||||
req.fields.insert("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==");
|
||||
req.fields.insert("Sec-WebSocket-Version", "13");
|
||||
stream<test::fail_stream<
|
||||
test::string_ostream>> ws{fc, ios_};
|
||||
bool called = false;
|
||||
c.accept_ex(ws, req,
|
||||
cbuf(0x88, 0x82, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x17),
|
||||
res_decorator{called});
|
||||
BEAST_EXPECT(called);
|
||||
try
|
||||
{
|
||||
opcode op;
|
||||
streambuf sb;
|
||||
c.read(ws, op, sb);
|
||||
fail("success", __FILE__, __LINE__);
|
||||
}
|
||||
catch(system_error const& e)
|
||||
{
|
||||
if(e.code() != websocket::error::closed)
|
||||
throw;
|
||||
}
|
||||
}
|
||||
// request in message, close frame in stream
|
||||
{
|
||||
http::request_header req;
|
||||
request_type req;
|
||||
req.method = "GET";
|
||||
req.url = "/";
|
||||
req.version = 11;
|
||||
@@ -564,7 +809,7 @@ public:
|
||||
}
|
||||
// request in message, close frame in stream and buffers
|
||||
{
|
||||
http::request_header req;
|
||||
request_type req;
|
||||
req.method = "GET";
|
||||
req.url = "/";
|
||||
req.version = 11;
|
||||
@@ -609,8 +854,10 @@ public:
|
||||
}
|
||||
catch(system_error const& e)
|
||||
{
|
||||
if(e.code() !=
|
||||
websocket::error::handshake_failed)
|
||||
if( e.code() !=
|
||||
websocket::error::handshake_failed &&
|
||||
e.code() !=
|
||||
boost::asio::error::eof)
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -635,6 +882,107 @@ public:
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
class req_decorator
|
||||
{
|
||||
bool& b_;
|
||||
|
||||
public:
|
||||
req_decorator(req_decorator const&) = default;
|
||||
|
||||
explicit
|
||||
req_decorator(bool& b)
|
||||
: b_(b)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(request_type&) const
|
||||
{
|
||||
b_ = true;
|
||||
}
|
||||
};
|
||||
|
||||
template<class Client>
|
||||
void
|
||||
testHandshake(endpoint_type const& ep, Client const& c)
|
||||
{
|
||||
static std::size_t constexpr limit = 200;
|
||||
std::size_t n;
|
||||
for(n = 0; n < limit; ++n)
|
||||
{
|
||||
test::fail_counter fc{n};
|
||||
try
|
||||
{
|
||||
// handshake
|
||||
{
|
||||
stream<test::fail_stream<
|
||||
boost::asio::ip::tcp::socket>> ws{fc, ios_};
|
||||
ws.next_layer().next_layer().connect(ep);
|
||||
c.handshake(ws, "localhost", "/");
|
||||
}
|
||||
// handshake, response
|
||||
{
|
||||
stream<test::fail_stream<
|
||||
boost::asio::ip::tcp::socket>> ws{fc, ios_};
|
||||
ws.next_layer().next_layer().connect(ep);
|
||||
response_type res;
|
||||
c.handshake(ws, res, "localhost", "/");
|
||||
// VFALCO validate res?
|
||||
}
|
||||
// handshake_ex
|
||||
{
|
||||
stream<test::fail_stream<
|
||||
boost::asio::ip::tcp::socket>> ws{fc, ios_};
|
||||
ws.next_layer().next_layer().connect(ep);
|
||||
bool called = false;
|
||||
c.handshake_ex(ws, "localhost", "/",
|
||||
req_decorator{called});
|
||||
BEAST_EXPECT(called);
|
||||
}
|
||||
// handshake_ex, response
|
||||
{
|
||||
stream<test::fail_stream<
|
||||
boost::asio::ip::tcp::socket>> ws{fc, ios_};
|
||||
ws.next_layer().next_layer().connect(ep);
|
||||
bool called = false;
|
||||
response_type res;
|
||||
c.handshake_ex(ws, res, "localhost", "/",
|
||||
req_decorator{called});
|
||||
// VFALCO validate res?
|
||||
BEAST_EXPECT(called);
|
||||
}
|
||||
}
|
||||
catch(system_error const&)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
BEAST_EXPECT(n < limit);
|
||||
}
|
||||
|
||||
void
|
||||
testHandshake()
|
||||
{
|
||||
error_code ec;
|
||||
::websocket::async_echo_server server{nullptr, 1};
|
||||
auto const any = endpoint_type{
|
||||
address_type::from_string("127.0.0.1"), 0};
|
||||
server.open(any, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
auto const ep = server.local_endpoint();
|
||||
testHandshake(ep, SyncClient{});
|
||||
yield_to(
|
||||
[&](yield_context yield)
|
||||
{
|
||||
testHandshake(ep, AsyncClient{yield});
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void testBadHandshakes()
|
||||
{
|
||||
auto const check =
|
||||
@@ -823,24 +1171,6 @@ public:
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
testDecorator(endpoint_type const& ep)
|
||||
{
|
||||
error_code ec;
|
||||
socket_type sock{ios_};
|
||||
sock.connect(ep, ec);
|
||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||
return;
|
||||
stream<socket_type&> ws{sock};
|
||||
int what;
|
||||
ws.set_option(decorate(test_decorator{what}));
|
||||
BEAST_EXPECT(what == 0);
|
||||
ws.handshake("localhost", "/", ec);
|
||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||
return;
|
||||
BEAST_EXPECT(what == 1);
|
||||
}
|
||||
|
||||
void testMask(endpoint_type const& ep,
|
||||
yield_context do_yield)
|
||||
{
|
||||
@@ -1525,6 +1855,7 @@ public:
|
||||
|
||||
testOptions();
|
||||
testAccept();
|
||||
testHandshake();
|
||||
testBadHandshakes();
|
||||
testBadResponses();
|
||||
|
||||
@@ -1539,7 +1870,6 @@ public:
|
||||
server.open(any, ec);
|
||||
BEAST_EXPECTS(! ec, ec.message());
|
||||
auto const ep = server.local_endpoint();
|
||||
testDecorator(ep);
|
||||
//testInvokable1(ep);
|
||||
testInvokable2(ep);
|
||||
testInvokable3(ep);
|
||||
|
||||
Reference in New Issue
Block a user