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:
Vinnie Falco
2017-04-25 09:35:22 -07:00
parent fab1015b89
commit fa1f4c7d56
16 changed files with 2255 additions and 515 deletions

View File

@ -5,6 +5,7 @@
API Changes: API Changes:
* Provide websocket::stream accept() overloads * Provide websocket::stream accept() overloads
* Refactor websocket decorators
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View File

@ -112,7 +112,6 @@
<bridgehead renderas="sect3">Options</bridgehead> <bridgehead renderas="sect3">Options</bridgehead>
<simplelist type="vert" columns="1"> <simplelist type="vert" columns="1">
<member><link linkend="beast.ref.websocket__auto_fragment">auto_fragment</link></member> <member><link linkend="beast.ref.websocket__auto_fragment">auto_fragment</link></member>
<member><link linkend="beast.ref.websocket__decorate">decorate</link></member>
<member><link linkend="beast.ref.websocket__keep_alive">keep_alive</link></member> <member><link linkend="beast.ref.websocket__keep_alive">keep_alive</link></member>
<member><link linkend="beast.ref.websocket__message_type">message_type</link></member> <member><link linkend="beast.ref.websocket__message_type">message_type</link></member>
<member><link linkend="beast.ref.websocket__permessage_deflate">permessage_deflate</link></member> <member><link linkend="beast.ref.websocket__permessage_deflate">permessage_deflate</link></member>

View File

@ -38,25 +38,6 @@ public:
using endpoint_type = boost::asio::ip::tcp::endpoint; using endpoint_type = boost::asio::ip::tcp::endpoint;
private: private:
struct identity
{
template<class Body, class Fields>
void
operator()(beast::http::message<
true, Body, Fields>& req) const
{
req.fields.replace("User-Agent", "async_echo_client");
}
template<class Body, class Fields>
void
operator()(beast::http::message<
false, Body, Fields>& resp) const
{
resp.fields.replace("Server", "async_echo_server");
}
};
/** A container of type-erased option setters. /** A container of type-erased option setters.
*/ */
template<class NextLayer> template<class NextLayer>
@ -159,8 +140,6 @@ public:
, acceptor_(ios_) , acceptor_(ios_)
, work_(ios_) , work_(ios_)
{ {
opts_.set_option(
beast::websocket::decorate(identity{}));
thread_.reserve(threads); thread_.reserve(threads);
for(std::size_t i = 0; i < threads; ++i) for(std::size_t i = 0; i < threads; ++i)
thread_.emplace_back( thread_.emplace_back(
@ -282,7 +261,13 @@ private:
void run() void run()
{ {
auto& d = *d_; auto& d = *d_;
d.ws.async_accept(std::move(*this)); d.ws.async_accept_ex(
[](beast::websocket::response_type& res)
{
res.fields.insert(
"Server", "async_echo_server");
},
std::move(*this));
} }
void operator()(error_code ec, std::size_t) void operator()(error_code ec, std::size_t)

View File

@ -38,25 +38,6 @@ public:
using socket_type = boost::asio::ip::tcp::socket; using socket_type = boost::asio::ip::tcp::socket;
private: private:
struct identity
{
template<class Body, class Fields>
void
operator()(beast::http::message<
true, Body, Fields>& req) const
{
req.fields.replace("User-Agent", "sync_echo_client");
}
template<class Body, class Fields>
void
operator()(beast::http::message<
false, Body, Fields>& resp) const
{
resp.fields.replace("Server", "sync_echo_server");
}
};
/** A container of type-erased option setters. /** A container of type-erased option setters.
*/ */
template<class NextLayer> template<class NextLayer>
@ -151,8 +132,6 @@ public:
, sock_(ios_) , sock_(ios_)
, acceptor_(ios_) , acceptor_(ios_)
{ {
opts_.set_option(
beast::websocket::decorate(identity{}));
} }
/** Destructor. /** Destructor.
@ -293,7 +272,13 @@ private:
socket_type> ws{std::move(sock)}; socket_type> ws{std::move(sock)};
opts_.set_options(ws); opts_.set_options(ws);
error_code ec; error_code ec;
ws.accept(ec); ws.accept_ex(
[](beast::websocket::response_type& res)
{
res.fields.insert(
"Server", "sync_echo_server");
},
ec);
if(ec) if(ec)
{ {
fail("accept", ec, id, ep); fail("accept", ec, id, ep);

View File

@ -54,6 +54,7 @@ public:
read_some(MutableBufferSequence const& buffers, read_some(MutableBufferSequence const& buffers,
error_code& ec) error_code& ec)
{ {
ec = boost::asio::error::eof;
return 0; return 0;
} }
@ -66,7 +67,7 @@ public:
async_completion<ReadHandler, async_completion<ReadHandler,
void(error_code, std::size_t)> completion{handler}; void(error_code, std::size_t)> completion{handler};
ios_.post(bind_handler(completion.handler, ios_.post(bind_handler(completion.handler,
error_code{}, 0)); boost::asio::error::eof, 0));
return completion.result.get(); return completion.result.get();
} }

View File

@ -1,166 +0,0 @@
//
// Copyright (c) 2013-2017 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)
//
#ifndef BEAST_WEBSOCKET_DETAIL_DECORATOR_HPP
#define BEAST_WEBSOCKET_DETAIL_DECORATOR_HPP
#include <beast/http/message.hpp>
#include <beast/http/string_body.hpp>
#include <beast/version.hpp>
#include <type_traits>
#include <utility>
namespace beast {
namespace websocket {
namespace detail {
using request_type = http::request_header;
using response_type = http::response_header;
struct abstract_decorator
{
virtual
~abstract_decorator() = default;
virtual
void
operator()(request_type& req) const = 0;
virtual
void
operator()(response_type& res) const = 0;
};
template<class F>
class decorator : public abstract_decorator
{
F f_;
class call_req_possible
{
template<class U, class R = decltype(
std::declval<U const>().operator()(
std::declval<request_type&>()),
std::true_type{})>
static R check(int);
template<class>
static std::false_type check(...);
public:
using type = decltype(check<F>(0));
};
class call_res_possible
{
template<class U, class R = decltype(
std::declval<U const>().operator()(
std::declval<response_type&>()),
std::true_type{})>
static R check(int);
template<class>
static std::false_type check(...);
public:
using type = decltype(check<F>(0));
};
public:
decorator(F&& t)
: f_(std::move(t))
{
}
decorator(F const& t)
: f_(t)
{
}
void
operator()(request_type& req) const override
{
(*this)(req, typename call_req_possible::type{});
}
void
operator()(response_type& res) const override
{
(*this)(res, typename call_res_possible::type{});
}
private:
void
operator()(request_type& req, std::true_type) const
{
f_(req);
}
void
operator()(request_type& req, std::false_type) const
{
req.fields.replace("User-Agent",
std::string{"Beast/"} + BEAST_VERSION_STRING);
}
void
operator()(response_type& res, std::true_type) const
{
f_(res);
}
void
operator()(response_type& res, std::false_type) const
{
res.fields.replace("Server",
std::string{"Beast/"} + BEAST_VERSION_STRING);
}
};
class decorator_type
{
std::shared_ptr<abstract_decorator> p_;
public:
decorator_type() = delete;
decorator_type(decorator_type&&) = default;
decorator_type(decorator_type const&) = default;
decorator_type& operator=(decorator_type&&) = default;
decorator_type& operator=(decorator_type const&) = default;
template<class F, class =
typename std::enable_if<! std::is_same<
typename std::decay<F>::type,
decorator_type>::value>>
decorator_type(F&& f)
: p_(std::make_shared<decorator<F>>(
std::forward<F>(f)))
{
BOOST_ASSERT(p_);
}
void
operator()(request_type& req)
{
(*p_)(req);
BOOST_ASSERT(p_);
}
void
operator()(response_type& res)
{
(*p_)(res);
BOOST_ASSERT(p_);
}
};
struct default_decorator
{
};
} // detail
} // websocket
} // beast
#endif

View File

@ -11,7 +11,6 @@
#include <beast/websocket/error.hpp> #include <beast/websocket/error.hpp>
#include <beast/websocket/option.hpp> #include <beast/websocket/option.hpp>
#include <beast/websocket/rfc6455.hpp> #include <beast/websocket/rfc6455.hpp>
#include <beast/websocket/detail/decorator.hpp>
#include <beast/websocket/detail/frame.hpp> #include <beast/websocket/detail/frame.hpp>
#include <beast/websocket/detail/invokable.hpp> #include <beast/websocket/detail/invokable.hpp>
#include <beast/websocket/detail/mask.hpp> #include <beast/websocket/detail/mask.hpp>
@ -50,7 +49,6 @@ protected:
struct op {}; struct op {};
detail::maskgen maskgen_; // source of mask keys detail::maskgen maskgen_; // source of mask keys
decorator_type d_; // adorns http messages
bool keep_alive_ = false; // close on failed upgrade bool keep_alive_ = false; // close on failed upgrade
std::size_t rd_msg_max_ = std::size_t rd_msg_max_ =
16 * 1024 * 1024; // max message size 16 * 1024 * 1024; // max message size
@ -153,16 +151,12 @@ protected:
// Offer for clients, negotiated result for servers // Offer for clients, negotiated result for servers
pmd_offer pmd_config_; pmd_offer pmd_config_;
stream_base() = default;
stream_base(stream_base&&) = default; stream_base(stream_base&&) = default;
stream_base(stream_base const&) = delete; stream_base(stream_base const&) = delete;
stream_base& operator=(stream_base&&) = default; stream_base& operator=(stream_base&&) = default;
stream_base& operator=(stream_base const&) = delete; stream_base& operator=(stream_base const&) = delete;
stream_base()
: d_(detail::default_decorator{})
{
}
template<class = void> template<class = void>
void void
open(role_type role); open(role_type role);

View File

@ -0,0 +1,32 @@
//
// Copyright (c) 2013-2017 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)
//
#ifndef BEAST_WEBSOCKET_DETAIL_TYPE_TRAITS_HPP
#define BEAST_WEBSOCKET_DETAIL_TYPE_TRAITS_HPP
#include <beast/websocket/rfc6455.hpp>
#include <beast/core/detail/is_call_possible.hpp>
namespace beast {
namespace websocket {
namespace detail {
template<class F>
using is_RequestDecorator =
typename beast::detail::is_call_possible<F,
void(request_type&)>::type;
template<class F>
using is_ResponseDecorator =
typename beast::detail::is_call_possible<F,
void(response_type&)>::type;
} // detail
} // websocket
} // beast
#endif

View File

@ -8,6 +8,7 @@
#ifndef BEAST_WEBSOCKET_IMPL_ACCEPT_IPP #ifndef BEAST_WEBSOCKET_IMPL_ACCEPT_IPP
#define BEAST_WEBSOCKET_IMPL_ACCEPT_IPP #define BEAST_WEBSOCKET_IMPL_ACCEPT_IPP
#include <beast/websocket/detail/type_traits.hpp>
#include <beast/http/message.hpp> #include <beast/http/message.hpp>
#include <beast/http/header_parser.hpp> #include <beast/http/header_parser.hpp>
#include <beast/http/read.hpp> #include <beast/http/read.hpp>
@ -38,23 +39,27 @@ class stream<NextLayer>::response_op
http::response_header res; http::response_header res;
int state = 0; int state = 0;
template<class Fields> template<class Fields, class Decorator>
data(Handler&, stream<NextLayer>& ws_, data(Handler&, stream<NextLayer>& ws_,
http::header<true, Fields> const& req, http::header<true, Fields> const& req,
bool cont_) Decorator const& decorator,
bool cont_)
: cont(cont_) : cont(cont_)
, ws(ws_) , ws(ws_)
, res(ws_.build_response(req)) , res(ws_.build_response(req, decorator))
{ {
} }
template<class Fields, class Buffers> template<class Fields,
class Buffers, class Decorator>
data(Handler&, stream<NextLayer>& ws_, data(Handler&, stream<NextLayer>& ws_,
http::header<true, Fields> const& req, http::header<true, Fields> const& req,
Buffers const& buffers, bool cont_) Buffers const& buffers,
Decorator const& decorator,
bool cont_)
: cont(cont_) : cont(cont_)
, ws(ws_) , ws(ws_)
, res(ws_.build_response(req)) , res(ws_.build_response(req, decorator))
{ {
using boost::asio::buffer_copy; using boost::asio::buffer_copy;
using boost::asio::buffer_size; using boost::asio::buffer_size;
@ -155,26 +160,32 @@ operator()(error_code ec, bool again)
// read and respond to an upgrade request // read and respond to an upgrade request
// //
template<class NextLayer> template<class NextLayer>
template<class Handler> template<class Decorator, class Handler>
class stream<NextLayer>::accept_op class stream<NextLayer>::accept_op
{ {
struct data struct data
{ {
bool cont; bool cont;
stream<NextLayer>& ws; stream<NextLayer>& ws;
Decorator decorator;
http::header_parser<true, http::fields> p; http::header_parser<true, http::fields> p;
int state = 0; int state = 0;
// VFALCO These lines are formatted to work around a codecov defect data(Handler& handler, stream<NextLayer>& ws_,
data(Handler& handler, stream<NextLayer>& ws_) : cont(beast_asio_helpers::is_continuation(handler)) Decorator const& decorator_)
, ws(ws_) {} : cont(beast_asio_helpers::is_continuation(handler))
, ws(ws_)
, decorator(decorator_)
{
}
template<class Buffers> template<class Buffers>
data(Handler& handler, stream<NextLayer>& ws_, data(Handler& handler, stream<NextLayer>& ws_,
Buffers const& buffers) Buffers const& buffers,
: cont(beast_asio_helpers:: Decorator const& decorator_)
is_continuation(handler)) : cont(beast_asio_helpers::is_continuation(handler))
, ws(ws_) , ws(ws_)
, decorator(decorator_)
{ {
using boost::asio::buffer_copy; using boost::asio::buffer_copy;
using boost::asio::buffer_size; using boost::asio::buffer_size;
@ -235,9 +246,9 @@ public:
}; };
template<class NextLayer> template<class NextLayer>
template<class Handler> template<class Decorator, class Handler>
void void
stream<NextLayer>::accept_op<Handler>:: stream<NextLayer>::accept_op<Decorator, Handler>::
operator()(error_code ec, operator()(error_code ec,
std::size_t bytes_used, bool again) std::size_t bytes_used, bool again)
{ {
@ -263,10 +274,19 @@ operator()(error_code ec,
// moved to the stack before releasing // moved to the stack before releasing
// the handler. // the handler.
auto& ws = d.ws; auto& ws = d.ws;
auto m = d.p.release(); auto const req = d.p.release();
auto const decorator = d.decorator;
#if 1
response_op<Handler>{ response_op<Handler>{
d_.release_handler(), d_.release_handler(),
ws, std::move(m), true}; ws, req, decorator, true};
#else
// VFALCO This *should* work but breaks
// coroutine invariants in the unit test.
// Also it calls reset() when it shouldn't.
ws.async_accept_ex(
req, decorator, d_.release_handler());
#endif
return; return;
} }
} }
@ -289,6 +309,23 @@ accept()
throw system_error{ec}; throw system_error{ec};
} }
template<class NextLayer>
template<class ResponseDecorator>
void
stream<NextLayer>::
accept_ex(ResponseDecorator const& decorator)
{
static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met");
static_assert(detail::is_ResponseDecorator<
ResponseDecorator>::value,
"ResponseDecorator requirements not met");
error_code ec;
accept_ex(decorator, ec);
if(ec)
throw system_error{ec};
}
template<class NextLayer> template<class NextLayer>
void void
stream<NextLayer>:: stream<NextLayer>::
@ -297,7 +334,22 @@ accept(error_code& ec)
static_assert(is_SyncStream<next_layer_type>::value, static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met"); "SyncStream requirements not met");
reset(); reset();
do_accept(ec); do_accept(&default_decorate_res, ec);
}
template<class NextLayer>
template<class ResponseDecorator>
void
stream<NextLayer>::
accept_ex(ResponseDecorator const& decorator, error_code& ec)
{
static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met");
static_assert(detail::is_ResponseDecorator<
ResponseDecorator>::value,
"ResponseDecorator requirements not met");
reset();
do_accept(decorator, ec);
} }
template<class NextLayer> template<class NextLayer>
@ -317,6 +369,28 @@ accept(ConstBufferSequence const& buffers)
throw system_error{ec}; throw system_error{ec};
} }
template<class NextLayer>
template<
class ConstBufferSequence, class ResponseDecorator>
void
stream<NextLayer>::
accept_ex(ConstBufferSequence const& buffers,
ResponseDecorator const &decorator)
{
static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met");
static_assert(is_ConstBufferSequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
static_assert(detail::is_ResponseDecorator<
ResponseDecorator>::value,
"ResponseDecorator requirements not met");
error_code ec;
accept_ex(buffers, decorator, ec);
if(ec)
throw system_error{ec};
}
template<class NextLayer> template<class NextLayer>
template<class ConstBufferSequence> template<class ConstBufferSequence>
void void
@ -334,7 +408,32 @@ accept(ConstBufferSequence const& buffers, error_code& ec)
stream_.buffer().commit(buffer_copy( stream_.buffer().commit(buffer_copy(
stream_.buffer().prepare( stream_.buffer().prepare(
buffer_size(buffers)), buffers)); buffer_size(buffers)), buffers));
do_accept(ec); do_accept(&default_decorate_res, ec);
}
template<class NextLayer>
template<
class ConstBufferSequence, class ResponseDecorator>
void
stream<NextLayer>::
accept_ex(ConstBufferSequence const& buffers,
ResponseDecorator const& decorator, error_code& ec)
{
static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met");
static_assert(is_ConstBufferSequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
static_assert(is_ConstBufferSequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
reset();
using boost::asio::buffer_copy;
using boost::asio::buffer_size;
stream_.buffer().commit(buffer_copy(
stream_.buffer().prepare(
buffer_size(buffers)), buffers));
do_accept(decorator, ec);
} }
template<class NextLayer> template<class NextLayer>
@ -351,6 +450,24 @@ accept(http::header<true, Fields> const& req)
throw system_error{ec}; throw system_error{ec};
} }
template<class NextLayer>
template<class Fields, class ResponseDecorator>
void
stream<NextLayer>::
accept_ex(http::header<true, Fields> const& req,
ResponseDecorator const& decorator)
{
static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met");
static_assert(detail::is_ResponseDecorator<
ResponseDecorator>::value,
"ResponseDecorator requirements not met");
error_code ec;
accept_ex(req, decorator, ec);
if(ec)
throw system_error{ec};
}
template<class NextLayer> template<class NextLayer>
template<class Fields> template<class Fields>
void void
@ -361,15 +478,30 @@ accept(http::header<true, Fields> const& req,
static_assert(is_SyncStream<next_layer_type>::value, static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met"); "SyncStream requirements not met");
reset(); reset();
do_accept(req, ec); do_accept(req, &default_decorate_res, ec);
}
template<class NextLayer>
template<class Fields, class ResponseDecorator>
void
stream<NextLayer>::
accept_ex(http::header<true, Fields> const& req,
ResponseDecorator const& decorator, error_code& ec)
{
static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met");
static_assert(detail::is_ResponseDecorator<
ResponseDecorator>::value,
"ResponseDecorator requirements not met");
reset();
do_accept(req, decorator, ec);
} }
template<class NextLayer> template<class NextLayer>
template<class Fields, class ConstBufferSequence> template<class Fields, class ConstBufferSequence>
void void
stream<NextLayer>:: stream<NextLayer>::
accept( accept(http::header<true, Fields> const& req,
http::header<true, Fields> const& req,
ConstBufferSequence const& buffers) ConstBufferSequence const& buffers)
{ {
static_assert(is_SyncStream<next_layer_type>::value, static_assert(is_SyncStream<next_layer_type>::value,
@ -383,14 +515,35 @@ accept(
throw system_error{ec}; throw system_error{ec};
} }
template<class NextLayer>
template<class Fields,
class ConstBufferSequence, class ResponseDecorator>
void
stream<NextLayer>::
accept_ex(http::header<true, Fields> const& req,
ConstBufferSequence const& buffers,
ResponseDecorator const& decorator)
{
static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met");
static_assert(is_ConstBufferSequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
static_assert(detail::is_ResponseDecorator<
ResponseDecorator>::value,
"ResponseDecorator requirements not met");
error_code ec;
accept_ex(req, buffers, decorator, ec);
if(ec)
throw system_error{ec};
}
template<class NextLayer> template<class NextLayer>
template<class Fields, class ConstBufferSequence> template<class Fields, class ConstBufferSequence>
void void
stream<NextLayer>:: stream<NextLayer>::
accept( accept(http::header<true, Fields> const& req,
http::header<true, Fields> const& req, ConstBufferSequence const& buffers, error_code& ec)
ConstBufferSequence const& buffers,
error_code& ec)
{ {
static_assert(is_SyncStream<next_layer_type>::value, static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met"); "SyncStream requirements not met");
@ -403,7 +556,34 @@ accept(
stream_.buffer().commit(buffer_copy( stream_.buffer().commit(buffer_copy(
stream_.buffer().prepare( stream_.buffer().prepare(
buffer_size(buffers)), buffers)); buffer_size(buffers)), buffers));
do_accept(req, ec); do_accept(req, &default_decorate_res, ec);
}
template<class NextLayer>
template<class Fields,
class ConstBufferSequence, class ResponseDecorator>
void
stream<NextLayer>::
accept_ex(http::header<true, Fields> const& req,
ConstBufferSequence const& buffers,
ResponseDecorator const& decorator,
error_code& ec)
{
static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met");
static_assert(is_ConstBufferSequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
static_assert(detail::is_ResponseDecorator<
ResponseDecorator>::value,
"ResponseDecorator requirements not met");
reset();
using boost::asio::buffer_copy;
using boost::asio::buffer_size;
stream_.buffer().commit(buffer_copy(
stream_.buffer().prepare(
buffer_size(buffers)), buffers));
do_accept(req, decorator, ec);
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -420,8 +600,30 @@ async_accept(AcceptHandler&& handler)
beast::async_completion<AcceptHandler, beast::async_completion<AcceptHandler,
void(error_code)> completion{handler}; void(error_code)> completion{handler};
reset(); reset();
accept_op<decltype(completion.handler)>{ accept_op<decltype(&default_decorate_res),
completion.handler, *this}; decltype(completion.handler)>{completion.handler,
*this, &default_decorate_res};
return completion.result.get();
}
template<class NextLayer>
template<class ResponseDecorator, class AcceptHandler>
typename async_completion<AcceptHandler,
void(error_code)>::result_type
stream<NextLayer>::
async_accept_ex(ResponseDecorator const& decorator,
AcceptHandler&& handler)
{
static_assert(is_AsyncStream<next_layer_type>::value,
"AsyncStream requirements requirements not met");
static_assert(detail::is_ResponseDecorator<
ResponseDecorator>::value,
"ResponseDecorator requirements not met");
beast::async_completion<AcceptHandler,
void(error_code)> completion{handler};
reset();
accept_op<ResponseDecorator, decltype(completion.handler)>{
completion.handler, *this, decorator};
return completion.result.get(); return completion.result.get();
} }
@ -441,8 +643,35 @@ async_accept(ConstBufferSequence const& buffers,
beast::async_completion<AcceptHandler, beast::async_completion<AcceptHandler,
void(error_code)> completion{handler}; void(error_code)> completion{handler};
reset(); reset();
accept_op<decltype(completion.handler)>{ accept_op<decltype(&default_decorate_res),
completion.handler, *this, buffers}; decltype(completion.handler)>{completion.handler,
*this, buffers, &default_decorate_res};
return completion.result.get();
}
template<class NextLayer>
template<class ConstBufferSequence,
class ResponseDecorator, class AcceptHandler>
typename async_completion<AcceptHandler,
void(error_code)>::result_type
stream<NextLayer>::
async_accept_ex(ConstBufferSequence const& buffers,
ResponseDecorator const& decorator,
AcceptHandler&& handler)
{
static_assert(is_AsyncStream<next_layer_type>::value,
"AsyncStream requirements requirements not met");
static_assert(is_ConstBufferSequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
static_assert(detail::is_ResponseDecorator<
ResponseDecorator>::value,
"ResponseDecorator requirements not met");
beast::async_completion<AcceptHandler,
void(error_code)> completion{handler};
reset();
accept_op<ResponseDecorator, decltype(completion.handler)>{
completion.handler, *this, buffers, decorator};
return completion.result.get(); return completion.result.get();
} }
@ -461,11 +690,35 @@ async_accept(http::header<true, Fields> const& req,
reset(); reset();
response_op<decltype(completion.handler)>{ response_op<decltype(completion.handler)>{
completion.handler, *this, req, completion.handler, *this, req,
beast_asio_helpers:: &default_decorate_res, beast_asio_helpers::
is_continuation(completion.handler)}; is_continuation(completion.handler)};
return completion.result.get(); return completion.result.get();
} }
template<class NextLayer>
template<class Fields,
class ResponseDecorator, class AcceptHandler>
typename async_completion<AcceptHandler,
void(error_code)>::result_type
stream<NextLayer>::
async_accept_ex(http::header<true, Fields> const& req,
ResponseDecorator const& decorator, AcceptHandler&& handler)
{
static_assert(is_AsyncStream<next_layer_type>::value,
"AsyncStream requirements requirements not met");
static_assert(detail::is_ResponseDecorator<
ResponseDecorator>::value,
"ResponseDecorator requirements not met");
beast::async_completion<AcceptHandler,
void(error_code)> completion{handler};
reset();
response_op<decltype(completion.handler)>{
completion.handler, *this, req, decorator,
beast_asio_helpers::is_continuation(
completion.handler)};
return completion.result.get();
}
template<class NextLayer> template<class NextLayer>
template<class Fields, template<class Fields,
class ConstBufferSequence, class AcceptHandler> class ConstBufferSequence, class AcceptHandler>
@ -486,8 +739,37 @@ async_accept(http::header<true, Fields> const& req,
reset(); reset();
response_op<decltype(completion.handler)>{ response_op<decltype(completion.handler)>{
completion.handler, *this, req, buffers, completion.handler, *this, req, buffers,
beast_asio_helpers:: &default_decorate_res, beast_asio_helpers::
is_continuation(completion.handler)}; is_continuation(completion.handler)};
return completion.result.get();
}
template<class NextLayer>
template<class Fields, class ConstBufferSequence,
class ResponseDecorator, class AcceptHandler>
typename async_completion<AcceptHandler,
void(error_code)>::result_type
stream<NextLayer>::
async_accept_ex(http::header<true, Fields> const& req,
ConstBufferSequence const& buffers,
ResponseDecorator const& decorator,
AcceptHandler&& handler)
{
static_assert(is_AsyncStream<next_layer_type>::value,
"AsyncStream requirements requirements not met");
static_assert(is_ConstBufferSequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
static_assert(detail::is_ResponseDecorator<
ResponseDecorator>::value,
"ResponseDecorator requirements not met");
beast::async_completion<AcceptHandler,
void(error_code)> completion{handler};
reset();
response_op<decltype(completion.handler)>{
completion.handler, *this, req, buffers, decorator,
beast_asio_helpers::is_continuation(
completion.handler)};
return completion.result.get(); return completion.result.get();
} }

View File

@ -8,6 +8,7 @@
#ifndef BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP #ifndef BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP
#define BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP #define BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP
#include <beast/websocket/detail/type_traits.hpp>
#include <beast/http/message.hpp> #include <beast/http/message.hpp>
#include <beast/http/read.hpp> #include <beast/http/read.hpp>
#include <beast/http/streambuf_body.hpp> #include <beast/http/streambuf_body.hpp>
@ -33,18 +34,24 @@ class stream<NextLayer>::handshake_op
{ {
bool cont; bool cont;
stream<NextLayer>& ws; stream<NextLayer>& ws;
response_type* res_p;
std::string key; std::string key;
http::request_header req; request_type req;
http::response<http::streambuf_body> res; response_type res;
int state = 0; int state = 0;
template<class Decorator>
data(Handler& handler, stream<NextLayer>& ws_, data(Handler& handler, stream<NextLayer>& ws_,
boost::string_ref const& host, response_type* res_p_,
boost::string_ref const& resource) boost::string_ref const& host,
boost::string_ref const& resource,
Decorator const& decorator)
: cont(beast_asio_helpers:: : cont(beast_asio_helpers::
is_continuation(handler)) is_continuation(handler))
, ws(ws_) , ws(ws_)
, req(ws.build_request(host, resource, key)) , res_p(res_p_)
, req(ws.build_request(key,
host, resource, decorator))
{ {
ws.reset(); ws.reset();
} }
@ -117,10 +124,14 @@ operator()(error_code ec, bool again)
d.state = 1; d.state = 1;
// VFALCO Do we need the ability to move // VFALCO Do we need the ability to move
// a message on the async_write? // a message on the async_write?
//
pmd_read( pmd_read(
d.ws.pmd_config_, d.req.fields); d.ws.pmd_config_, d.req.fields);
http::async_write(d.ws.stream_, http::async_write(d.ws.stream_,
d.req, std::move(*this)); d.req, std::move(*this));
// TODO We don't need d.req now. Figure
// out a way to make it a parameter instead
// of a state variable to reduce footprint.
return; return;
} }
@ -143,24 +154,94 @@ operator()(error_code ec, bool again)
} }
} }
} }
if(d.res_p)
swap(d.res, *d.res_p);
d_.invoke(ec); d_.invoke(ec);
} }
template<class NextLayer> template<class NextLayer>
template<class HandshakeHandler> template<class HandshakeHandler>
typename async_completion< typename async_completion<HandshakeHandler,
HandshakeHandler, void(error_code)>::result_type void(error_code)>::result_type
stream<NextLayer>:: stream<NextLayer>::
async_handshake(boost::string_ref const& host, async_handshake(boost::string_ref const& host,
boost::string_ref const& resource, HandshakeHandler&& handler) boost::string_ref const& resource,
HandshakeHandler&& handler)
{ {
static_assert(is_AsyncStream<next_layer_type>::value, static_assert(is_AsyncStream<next_layer_type>::value,
"AsyncStream requirements not met"); "AsyncStream requirements not met");
beast::async_completion< beast::async_completion<HandshakeHandler,
HandshakeHandler, void(error_code) void(error_code)> completion{handler};
> completion{handler};
handshake_op<decltype(completion.handler)>{ handshake_op<decltype(completion.handler)>{
completion.handler, *this, host, resource}; completion.handler, *this, nullptr,
host, resource, &default_decorate_req};
return completion.result.get();
}
template<class NextLayer>
template<class HandshakeHandler>
typename async_completion<HandshakeHandler,
void(error_code)>::result_type
stream<NextLayer>::
async_handshake(response_type& res,
boost::string_ref const& host,
boost::string_ref const& resource,
HandshakeHandler&& handler)
{
static_assert(is_AsyncStream<next_layer_type>::value,
"AsyncStream requirements not met");
beast::async_completion<HandshakeHandler,
void(error_code)> completion{handler};
handshake_op<decltype(completion.handler)>{
completion.handler, *this, &res,
host, resource, &default_decorate_req};
return completion.result.get();
}
template<class NextLayer>
template<class RequestDecorator, class HandshakeHandler>
typename async_completion<HandshakeHandler,
void(error_code)>::result_type
stream<NextLayer>::
async_handshake_ex(boost::string_ref const& host,
boost::string_ref const& resource,
RequestDecorator const& decorator,
HandshakeHandler&& handler)
{
static_assert(is_AsyncStream<next_layer_type>::value,
"AsyncStream requirements not met");
static_assert(detail::is_RequestDecorator<
RequestDecorator>::value,
"RequestDecorator requirements not met");
beast::async_completion<HandshakeHandler,
void(error_code)> completion{handler};
handshake_op<decltype(completion.handler)>{
completion.handler, *this, nullptr,
host, resource, decorator};
return completion.result.get();
}
template<class NextLayer>
template<class RequestDecorator, class HandshakeHandler>
typename async_completion<HandshakeHandler,
void(error_code)>::result_type
stream<NextLayer>::
async_handshake_ex(response_type& res,
boost::string_ref const& host,
boost::string_ref const& resource,
RequestDecorator const& decorator,
HandshakeHandler&& handler)
{
static_assert(is_AsyncStream<next_layer_type>::value,
"AsyncStream requirements not met");
static_assert(detail::is_RequestDecorator<
RequestDecorator>::value,
"RequestDecorator requirements not met");
beast::async_completion<HandshakeHandler,
void(error_code)> completion{handler};
handshake_op<decltype(completion.handler)>{
completion.handler, *this, &res,
host, resource, decorator};
return completion.result.get(); return completion.result.get();
} }
@ -173,7 +254,62 @@ handshake(boost::string_ref const& host,
static_assert(is_SyncStream<next_layer_type>::value, static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met"); "SyncStream requirements not met");
error_code ec; error_code ec;
handshake(host, resource, ec); handshake(
host, resource, ec);
if(ec)
throw system_error{ec};
}
template<class NextLayer>
void
stream<NextLayer>::
handshake(response_type& res,
boost::string_ref const& host,
boost::string_ref const& resource)
{
static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met");
error_code ec;
handshake(res, host, resource, ec);
if(ec)
throw system_error{ec};
}
template<class NextLayer>
template<class RequestDecorator>
void
stream<NextLayer>::
handshake_ex(boost::string_ref const& host,
boost::string_ref const& resource,
RequestDecorator const& decorator)
{
static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met");
static_assert(detail::is_RequestDecorator<
RequestDecorator>::value,
"RequestDecorator requirements not met");
error_code ec;
handshake_ex(host, resource, decorator, ec);
if(ec)
throw system_error{ec};
}
template<class NextLayer>
template<class RequestDecorator>
void
stream<NextLayer>::
handshake_ex(response_type& res,
boost::string_ref const& host,
boost::string_ref const& resource,
RequestDecorator const& decorator)
{
static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met");
static_assert(detail::is_RequestDecorator<
RequestDecorator>::value,
"RequestDecorator requirements not met");
error_code ec;
handshake_ex(res, host, resource, decorator, ec);
if(ec) if(ec)
throw system_error{ec}; throw system_error{ec};
} }
@ -186,21 +322,59 @@ handshake(boost::string_ref const& host,
{ {
static_assert(is_SyncStream<next_layer_type>::value, static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met"); "SyncStream requirements not met");
reset(); do_handshake(nullptr,
std::string key; host, resource, &default_decorate_req, ec);
{ }
auto const req =
build_request(host, resource, key); template<class NextLayer>
pmd_read(pmd_config_, req.fields); void
http::write(stream_, req, ec); stream<NextLayer>::
} handshake(response_type& res,
if(ec) boost::string_ref const& host,
return; boost::string_ref const& resource,
http::response<http::streambuf_body> res; error_code& ec)
http::read(next_layer(), stream_.buffer(), res, ec); {
if(ec) static_assert(is_SyncStream<next_layer_type>::value,
return; "SyncStream requirements not met");
do_response(res, key, ec); do_handshake(&res,
host, resource, &default_decorate_req, ec);
}
template<class NextLayer>
template<class RequestDecorator>
void
stream<NextLayer>::
handshake_ex(boost::string_ref const& host,
boost::string_ref const& resource,
RequestDecorator const& decorator,
error_code& ec)
{
static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met");
static_assert(detail::is_RequestDecorator<
RequestDecorator>::value,
"RequestDecorator requirements not met");
do_handshake(nullptr,
host, resource, decorator, ec);
}
template<class NextLayer>
template<class RequestDecorator>
void
stream<NextLayer>::
handshake_ex(response_type& res,
boost::string_ref const& host,
boost::string_ref const& resource,
RequestDecorator const& decorator,
error_code& ec)
{
static_assert(is_SyncStream<next_layer_type>::value,
"SyncStream requirements not met");
static_assert(detail::is_RequestDecorator<
RequestDecorator>::value,
"RequestDecorator requirements not met");
do_handshake(&res,
host, resource, decorator, ec);
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View File

@ -11,6 +11,7 @@
#include <beast/websocket/teardown.hpp> #include <beast/websocket/teardown.hpp>
#include <beast/websocket/detail/hybi13.hpp> #include <beast/websocket/detail/hybi13.hpp>
#include <beast/websocket/detail/pmd_extension.hpp> #include <beast/websocket/detail/pmd_extension.hpp>
#include <beast/version.hpp>
#include <beast/http/read.hpp> #include <beast/http/read.hpp>
#include <beast/http/write.hpp> #include <beast/http/write.hpp>
#include <beast/http/reason.hpp> #include <beast/http/reason.hpp>
@ -83,9 +84,11 @@ reset()
} }
template<class NextLayer> template<class NextLayer>
template<class Decorator>
void void
stream<NextLayer>:: stream<NextLayer>::
do_accept(error_code& ec) do_accept(
Decorator const& decorator, error_code& ec)
{ {
http::header_parser<true, http::fields> p; http::header_parser<true, http::fields> p;
auto const bytes_used = http::read_some( auto const bytes_used = http::read_some(
@ -94,17 +97,17 @@ do_accept(error_code& ec)
return; return;
BOOST_ASSERT(p.got_header()); BOOST_ASSERT(p.got_header());
stream_.buffer().consume(bytes_used); stream_.buffer().consume(bytes_used);
do_accept(p.get(), ec); do_accept(p.get(), decorator, ec);
} }
template<class NextLayer> template<class NextLayer>
template<class Fields> template<class Fields, class Decorator>
void void
stream<NextLayer>:: stream<NextLayer>::
do_accept(http::header<true, Fields> const& req, do_accept(http::header<true, Fields> const& req,
error_code& ec) Decorator const& decorator, error_code& ec)
{ {
auto const res = build_response(req); auto const res = build_response(req, decorator);
http::write(stream_, res, ec); http::write(stream_, res, ec);
if(ec) if(ec)
return; return;
@ -120,12 +123,44 @@ do_accept(http::header<true, Fields> const& req,
} }
template<class NextLayer> template<class NextLayer>
http::request_header template<class RequestDecorator>
void
stream<NextLayer>:: stream<NextLayer>::
build_request(boost::string_ref const& host, do_handshake(response_type* res_p,
boost::string_ref const& resource, std::string& key) boost::string_ref const& host,
boost::string_ref const& resource,
RequestDecorator const& decorator,
error_code& ec)
{ {
http::request_header req; response_type res;
reset();
std::string key;
{
auto const req = build_request(
key, host, resource, decorator);
pmd_read(pmd_config_, req.fields);
http::write(stream_, req, ec);
}
if(ec)
return;
http::read(next_layer(), stream_.buffer(), res, ec);
if(ec)
return;
do_response(res, key, ec);
if(res_p)
swap(res, *res_p);
}
template<class NextLayer>
template<class Decorator>
request_type
stream<NextLayer>::
build_request(std::string& key,
boost::string_ref const& host,
boost::string_ref const& resource,
Decorator const& decorator)
{
request_type req;
req.url = { resource.data(), resource.size() }; req.url = { resource.data(), resource.size() };
req.version = 11; req.version = 11;
req.method = "GET"; req.method = "GET";
@ -150,28 +185,42 @@ build_request(boost::string_ref const& host,
detail::pmd_write( detail::pmd_write(
req.fields, config); req.fields, config);
} }
d_(req); decorator(req);
if(! req.fields.exists("User-Agent"))
req.fields.insert("User-Agent",
std::string("Beast/") + BEAST_VERSION_STRING);
return req; return req;
} }
template<class NextLayer> template<class NextLayer>
http::response_header template<class Decorator>
response_type
stream<NextLayer>:: stream<NextLayer>::
build_response(http::request_header const& req) build_response(request_type const& req,
Decorator const& decorator)
{ {
auto const decorate =
[&decorator](response_type& res)
{
decorator(res);
if(! res.fields.exists("Server"))
res.fields.insert("Server",
std::string("Beast/") +
BEAST_VERSION_STRING);
};
auto err = auto err =
[&](std::string const& text) [&](std::string const& text)
{ {
http::response<http::string_body> res; response_type res;
res.status = 400; res.status = 400;
res.reason = http::reason_string(res.status); res.reason = http::reason_string(res.status);
res.version = req.version; res.version = req.version;
res.body = text; res.body = text;
d_(res);
prepare(res, prepare(res,
(is_keep_alive(req) && keep_alive_) ? (is_keep_alive(req) && keep_alive_) ?
http::connection::keep_alive : http::connection::keep_alive :
http::connection::close); http::connection::close);
decorate(res);
return res; return res;
}; };
if(req.version < 11) if(req.version < 11)
@ -193,20 +242,20 @@ build_response(http::request_header const& req)
return err("Missing Sec-WebSocket-Version"); return err("Missing Sec-WebSocket-Version");
if(version != "13") if(version != "13")
{ {
http::response<http::string_body> res; response_type res;
res.status = 426; res.status = 426;
res.reason = http::reason_string(res.status); res.reason = http::reason_string(res.status);
res.version = req.version; res.version = req.version;
res.fields.insert("Sec-WebSocket-Version", "13"); res.fields.insert("Sec-WebSocket-Version", "13");
d_(res);
prepare(res, prepare(res,
(is_keep_alive(req) && keep_alive_) ? (is_keep_alive(req) && keep_alive_) ?
http::connection::keep_alive : http::connection::keep_alive :
http::connection::close); http::connection::close);
decorate(res);
return res; return res;
} }
} }
http::response_header res; response_type res;
{ {
detail::pmd_offer offer; detail::pmd_offer offer;
detail::pmd_offer unused; detail::pmd_offer unused;
@ -225,8 +274,7 @@ build_response(http::request_header const& req)
res.fields.insert("Sec-WebSocket-Accept", res.fields.insert("Sec-WebSocket-Accept",
detail::make_sec_ws_accept(key)); detail::make_sec_ws_accept(key));
} }
res.fields.replace("Server", "Beast.WebSocket"); decorate(res);
d_(res);
return res; return res;
} }

View File

@ -10,7 +10,6 @@
#include <beast/config.hpp> #include <beast/config.hpp>
#include <beast/websocket/rfc6455.hpp> #include <beast/websocket/rfc6455.hpp>
#include <beast/websocket/detail/decorator.hpp>
#include <beast/core/detail/type_traits.hpp> #include <beast/core/detail/type_traits.hpp>
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
@ -59,56 +58,6 @@ struct auto_fragment
}; };
#endif #endif
/** HTTP decorator option.
The decorator transforms the HTTP requests and responses used
when requesting or responding to the WebSocket Upgrade. This may
be used to set or change header fields. For example to set the
Server or User-Agent fields. The default setting applies no
transformation to the HTTP message.
The context in which the decorator is called depends on the
type of operation performed:
@li For synchronous operations, the implementation will call the
decorator before the operation unblocks.
@li For asynchronous operations, the implementation guarantees
that calls to the decorator will be made from the same implicit
or explicit strand used to call the asynchronous initiation
function.
The default setting is no decorator.
@note Objects of this type are used with
@ref beast::websocket::stream::set_option.
@par Example
Setting the decorator.
@code
struct identity
{
template<bool isRequest, class Body, class Fields>
void
operator()(http::message<isRequest, Body, Fields>& m)
{
if(isRequest)
m.fields.replace("User-Agent", "MyClient");
else
m.fields.replace("Server", "MyServer");
}
};
...
websocket::stream<ip::tcp::socket> ws(ios);
ws.set_option(decorate(identity{}));
@endcode
*/
#if BEAST_DOXYGEN
using decorate = implementation_defined;
#else
using decorate = detail::decorator_type;
#endif
/** Keep-alive option. /** Keep-alive option.
Determines if the connection is closed after a failed upgrade Determines if the connection is closed after a failed upgrade

File diff suppressed because it is too large Load Diff

View File

@ -111,33 +111,6 @@ public:
return false; 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 struct SyncClient
{ {
template<class NextLayer> template<class NextLayer>
@ -173,6 +146,46 @@ public:
ws.accept(req, buffers); 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> template<class NextLayer>
void void
handshake(stream<NextLayer>& ws, handshake(stream<NextLayer>& ws,
@ -182,6 +195,37 @@ public:
ws.handshake(uri, path); 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> template<class NextLayer>
void void
ping(stream<NextLayer>& ws, ping(stream<NextLayer>& ws,
@ -301,14 +345,56 @@ public:
} }
template<class NextLayer, template<class NextLayer,
class Fields, class Buffers> class Decorator>
void 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, http::header<true, Fields> const& req,
Buffers const& buffers, 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> template<class NextLayer>
@ -318,7 +404,51 @@ public:
boost::string_ref const& path) const boost::string_ref const& path) const
{ {
error_code ec; 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) if(ec)
throw system_error{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> template<class Client>
void void
testAccept(Client const& c) testAccept(Client const& c)
@ -460,7 +612,22 @@ public:
"\r\n" "\r\n"
, 20}; , 20};
c.accept(ws); 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 // request in buffers
{ {
@ -476,6 +643,21 @@ public:
"\r\n" "\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 // request in buffers and stream
{ {
stream<test::fail_stream< stream<test::fail_stream<
@ -491,9 +673,25 @@ public:
"Upgrade: websocket\r\n" "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 // request in message
{ {
http::request_header req; request_type req;
req.method = "GET"; req.method = "GET";
req.url = "/"; req.url = "/";
req.version = 11; req.version = 11;
@ -506,9 +704,26 @@ public:
test::string_ostream>> ws{fc, ios_}; test::string_ostream>> ws{fc, ios_};
c.accept(ws, req); 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 // request in message, close frame in buffers
{ {
http::request_header req; request_type req;
req.method = "GET"; req.method = "GET";
req.url = "/"; req.url = "/";
req.version = 11; req.version = 11;
@ -534,9 +749,39 @@ public:
throw; 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 // request in message, close frame in stream
{ {
http::request_header req; request_type req;
req.method = "GET"; req.method = "GET";
req.url = "/"; req.url = "/";
req.version = 11; req.version = 11;
@ -564,7 +809,7 @@ public:
} }
// request in message, close frame in stream and buffers // request in message, close frame in stream and buffers
{ {
http::request_header req; request_type req;
req.method = "GET"; req.method = "GET";
req.url = "/"; req.url = "/";
req.version = 11; req.version = 11;
@ -609,8 +854,10 @@ public:
} }
catch(system_error const& e) catch(system_error const& e)
{ {
if(e.code() != if( e.code() !=
websocket::error::handshake_failed) websocket::error::handshake_failed &&
e.code() !=
boost::asio::error::eof)
throw; 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() void testBadHandshakes()
{ {
auto const check = 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, void testMask(endpoint_type const& ep,
yield_context do_yield) yield_context do_yield)
{ {
@ -1525,6 +1855,7 @@ public:
testOptions(); testOptions();
testAccept(); testAccept();
testHandshake();
testBadHandshakes(); testBadHandshakes();
testBadResponses(); testBadResponses();
@ -1539,7 +1870,6 @@ public:
server.open(any, ec); server.open(any, ec);
BEAST_EXPECTS(! ec, ec.message()); BEAST_EXPECTS(! ec, ec.message());
auto const ep = server.local_endpoint(); auto const ep = server.local_endpoint();
testDecorator(ep);
//testInvokable1(ep); //testInvokable1(ep);
testInvokable2(ep); testInvokable2(ep);
testInvokable3(ep); testInvokable3(ep);

View File

@ -38,25 +38,6 @@ public:
using endpoint_type = boost::asio::ip::tcp::endpoint; using endpoint_type = boost::asio::ip::tcp::endpoint;
private: private:
struct identity
{
template<class Body, class Fields>
void
operator()(beast::http::message<
true, Body, Fields>& req) const
{
req.fields.replace("User-Agent", "async_echo_client");
}
template<class Body, class Fields>
void
operator()(beast::http::message<
false, Body, Fields>& resp) const
{
resp.fields.replace("Server", "async_echo_server");
}
};
/** A container of type-erased option setters. /** A container of type-erased option setters.
*/ */
template<class NextLayer> template<class NextLayer>
@ -159,8 +140,6 @@ public:
, acceptor_(ios_) , acceptor_(ios_)
, work_(ios_) , work_(ios_)
{ {
opts_.set_option(
beast::websocket::decorate(identity{}));
thread_.reserve(threads); thread_.reserve(threads);
for(std::size_t i = 0; i < threads; ++i) for(std::size_t i = 0; i < threads; ++i)
thread_.emplace_back( thread_.emplace_back(
@ -282,7 +261,13 @@ private:
void run() void run()
{ {
auto& d = *d_; auto& d = *d_;
d.ws.async_accept(std::move(*this)); d.ws.async_accept_ex(
[](beast::websocket::response_type& res)
{
res.fields.insert(
"Server", "async_echo_server");
},
std::move(*this));
} }
template<class DynamicBuffer, std::size_t N> template<class DynamicBuffer, std::size_t N>

View File

@ -38,25 +38,6 @@ public:
using socket_type = boost::asio::ip::tcp::socket; using socket_type = boost::asio::ip::tcp::socket;
private: private:
struct identity
{
template<class Body, class Fields>
void
operator()(beast::http::message<
true, Body, Fields>& req) const
{
req.fields.replace("User-Agent", "sync_echo_client");
}
template<class Body, class Fields>
void
operator()(beast::http::message<
false, Body, Fields>& resp) const
{
resp.fields.replace("Server", "sync_echo_server");
}
};
/** A container of type-erased option setters. /** A container of type-erased option setters.
*/ */
template<class NextLayer> template<class NextLayer>
@ -151,8 +132,6 @@ public:
, sock_(ios_) , sock_(ios_)
, acceptor_(ios_) , acceptor_(ios_)
{ {
opts_.set_option(
beast::websocket::decorate(identity{}));
} }
/** Destructor. /** Destructor.
@ -312,7 +291,13 @@ private:
socket_type> ws{std::move(sock)}; socket_type> ws{std::move(sock)};
opts_.set_options(ws); opts_.set_options(ws);
error_code ec; error_code ec;
ws.accept(ec); ws.accept_ex(
[](beast::websocket::response_type& res)
{
res.fields.insert(
"Server", "sync_echo_server");
},
ec);
if(ec) if(ec)
{ {
fail("accept", ec, id, ep); fail("accept", ec, id, ep);