Files
boost_beast/include/boost/beast/websocket/impl/accept.hpp
T
Mohammad Nejati 5f5507f647 Initiating functions no longer perform mutating operations
Using `asio::deferred` as a completion token defers the initiation of
operations and creates a lazy operation which might even be discarded.
We need to move mutating operations to the initiator function objects or
the constructor of operation objects, which are guaranteed to run when
the deferred operation is launched.
2024-08-06 15:42:40 +03:30

699 lines
20 KiB
C++

//
// Copyright (c) 2016-2019 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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_WEBSOCKET_IMPL_ACCEPT_IPP
#define BOOST_BEAST_WEBSOCKET_IMPL_ACCEPT_IPP
#include <boost/beast/websocket/impl/stream_impl.hpp>
#include <boost/beast/websocket/detail/type_traits.hpp>
#include <boost/beast/http/empty_body.hpp>
#include <boost/beast/http/parser.hpp>
#include <boost/beast/http/read.hpp>
#include <boost/beast/http/string_body.hpp>
#include <boost/beast/http/write.hpp>
#include <boost/beast/core/async_base.hpp>
#include <boost/beast/core/buffer_traits.hpp>
#include <boost/beast/core/stream_traits.hpp>
#include <boost/beast/core/detail/buffer.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/assert.hpp>
#include <boost/throw_exception.hpp>
#include <memory>
#include <type_traits>
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 =
[this, &decorator](response_type& res)
{
decorator_opt(res);
decorator(res);
if(! res.count(http::field::server))
res.set(http::field::server,
string_view(BOOST_BEAST_VERSION_STRING));
};
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::static_capacity)
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, to_string_view(acc));
}
this->build_response_pmd(res, req);
decorate(res);
result = {};
return res;
}
//------------------------------------------------------------------------------
/** Respond to an HTTP request
*/
template<class NextLayer, bool deflateSupported>
template<class Handler>
class stream<NextLayer, deflateSupported>::response_op
: public beast::stable_async_base<
Handler, beast::executor_type<stream>>
, public asio::coroutine
{
boost::weak_ptr<impl_type> wp_;
error_code result_; // must come before res_
response_type& res_;
http::response<http::empty_body> res_100_;
bool needs_res_100_{false};
public:
template<
class Handler_,
class Body, class Allocator,
class Decorator>
response_op(
Handler_&& h,
boost::shared_ptr<impl_type> const& sp,
http::request<Body,
http::basic_fields<Allocator>> const& req,
Decorator const& decorator,
bool cont = false)
: stable_async_base<Handler,
beast::executor_type<stream>>(
std::forward<Handler_>(h),
sp->stream().get_executor())
, wp_(sp)
, res_(beast::allocate_stable<response_type>(*this,
sp->build_response(req, decorator, result_)))
{
auto itr = req.find(http::field::expect);
if (itr != req.end() && iequals(itr->value(), "100-continue")) // do
{
res_100_.version(res_.version());
res_100_.set(http::field::server, res_[http::field::server]);
res_100_.result(http::status::continue_);
res_100_.prepare_payload();
needs_res_100_ = true;
}
(*this)({}, 0, cont);
}
void operator()(
error_code ec = {},
std::size_t bytes_transferred = 0,
bool cont = true)
{
boost::ignore_unused(bytes_transferred);
auto sp = wp_.lock();
if(! sp)
{
BOOST_BEAST_ASSIGN_EC(ec, net::error::operation_aborted);
return this->complete(cont, ec);
}
auto& impl = *sp;
BOOST_ASIO_CORO_REENTER(*this)
{
impl.change_status(status::handshake);
impl.update_timer(this->get_executor());
if (needs_res_100_)
{
BOOST_ASIO_CORO_YIELD
{
BOOST_ASIO_HANDLER_LOCATION((__FILE__, __LINE__, "websocket::async_accept"));
http::async_write(
impl.stream(), res_100_, std::move(*this));
}
}
// Send response
BOOST_ASIO_CORO_YIELD
{
BOOST_ASIO_HANDLER_LOCATION((
__FILE__, __LINE__,
"websocket::async_accept"));
http::async_write(
impl.stream(), res_, std::move(*this));
}
if(impl.check_stop_now(ec))
goto upcall;
if(! ec)
{
BOOST_BEAST_ASSIGN_EC(ec, result_);
BOOST_BEAST_ASSIGN_EC(ec, result_);
}
if(! ec)
{
impl.do_pmd_config(res_);
impl.open(role_type::server);
}
upcall:
this->complete(cont, ec);
}
}
};
//------------------------------------------------------------------------------
// read and respond to an upgrade request
//
// Cancellation: the async_accept cancellation can be terminal
// because it will just interrupt the reading of the header.
//
template<class NextLayer, bool deflateSupported>
template<class Handler, class Decorator>
class stream<NextLayer, deflateSupported>::accept_op
: public beast::stable_async_base<
Handler, beast::executor_type<stream>>
, public asio::coroutine
{
boost::weak_ptr<impl_type> wp_;
http::request_parser<http::empty_body>& p_;
Decorator d_;
public:
template<class Handler_, class Buffers>
accept_op(
Handler_&& h,
boost::shared_ptr<impl_type> const& sp,
Decorator const& decorator,
Buffers const& buffers)
: stable_async_base<Handler,
beast::executor_type<stream>>(
std::forward<Handler_>(h),
sp->stream().get_executor())
, wp_(sp)
, p_(beast::allocate_stable<
http::request_parser<http::empty_body>>(*this))
, d_(decorator)
{
auto& impl = *sp;
impl.reset();
error_code ec;
auto const mb =
beast::detail::dynamic_buffer_prepare(
impl.rd_buf, buffer_bytes(buffers),
ec, error::buffer_overflow);
if(! ec)
impl.rd_buf.commit(
net::buffer_copy(*mb, buffers));
(*this)(ec);
}
void operator()(
error_code ec = {},
std::size_t bytes_transferred = 0,
bool cont = true)
{
boost::ignore_unused(bytes_transferred);
auto sp = wp_.lock();
if(! sp)
{
BOOST_BEAST_ASSIGN_EC(ec, net::error::operation_aborted);
return this->complete(cont, ec);
}
auto& impl = *sp;
BOOST_ASIO_CORO_REENTER(*this)
{
impl.change_status(status::handshake);
impl.update_timer(this->get_executor());
// The constructor could have set ec
if(ec)
goto upcall;
BOOST_ASIO_CORO_YIELD
{
BOOST_ASIO_HANDLER_LOCATION((
__FILE__, __LINE__,
"websocket::async_accept"));
http::async_read(impl.stream(),
impl.rd_buf, p_, std::move(*this));
}
if(ec == http::error::end_of_stream)
{
BOOST_BEAST_ASSIGN_EC(ec, error::closed);
}
if(impl.check_stop_now(ec))
goto upcall;
{
// Arguments from our state must be
// moved to the stack before releasing
// the handler.
auto const req = p_.release();
auto const decorator = d_;
response_op<Handler>(
this->release_handler(),
sp, req, decorator, true);
return;
}
upcall:
this->complete(cont, ec);
}
}
};
template<class NextLayer, bool deflateSupported>
struct stream<NextLayer, deflateSupported>::
run_response_op
{
boost::shared_ptr<impl_type> const& self;
using executor_type = typename stream::executor_type;
executor_type
get_executor() const noexcept
{
return self->stream().get_executor();
}
template<
class AcceptHandler,
class Body, class Allocator,
class Decorator>
void
operator()(
AcceptHandler&& h,
http::request<Body,
http::basic_fields<Allocator>> const* m,
Decorator const& d)
{
// If you get an error on the following line it means
// that your handler does not meet the documented type
// requirements for the handler.
static_assert(
beast::detail::is_invocable<AcceptHandler,
void(error_code)>::value,
"AcceptHandler type requirements not met");
response_op<
typename std::decay<AcceptHandler>::type>(
std::forward<AcceptHandler>(h), self, *m, d);
}
};
template<class NextLayer, bool deflateSupported>
struct stream<NextLayer, deflateSupported>::
run_accept_op
{
boost::shared_ptr<impl_type> const& self;
using executor_type = typename stream::executor_type;
executor_type
get_executor() const noexcept
{
return self->stream().get_executor();
}
template<
class AcceptHandler,
class Decorator,
class Buffers>
void
operator()(
AcceptHandler&& h,
Decorator const& d,
Buffers const& b)
{
// If you get an error on the following line it means
// that your handler does not meet the documented type
// requirements for the handler.
static_assert(
beast::detail::is_invocable<AcceptHandler,
void(error_code)>::value,
"AcceptHandler type requirements not met");
accept_op<
typename std::decay<AcceptHandler>::type,
Decorator>(
std::forward<AcceptHandler>(h),
self,
d,
b);
}
};
//------------------------------------------------------------------------------
template<class NextLayer, bool deflateSupported>
template<class Body, class Allocator,
class Decorator>
void
stream<NextLayer, deflateSupported>::
do_accept(
http::request<Body,
http::basic_fields<Allocator>> const& req,
Decorator const& decorator,
error_code& ec)
{
impl_->change_status(status::handshake);
error_code result;
auto const res = impl_->build_response(req, decorator, result);
auto itr = req.find(http::field::expect);
if (itr != req.end() && iequals(itr->value(), "100-continue")) // do
{
http::response<http::empty_body> res_100;
res_100.version(res.version());
res_100.set(http::field::server, res[http::field::server]);
res_100.result(http::status::continue_);
res_100.prepare_payload();
http::write(impl_->stream(), res_100, ec);
if (ec)
return;
}
http::write(impl_->stream(), res, ec);
if(ec)
return;
BOOST_BEAST_ASSIGN_EC(ec, result);
if(ec)
{
// VFALCO TODO Respect keep alive setting, perform
// teardown if Connection: close.
return;
}
impl_->do_pmd_config(res);
impl_->open(role_type::server);
}
template<class NextLayer, bool deflateSupported>
template<class Buffers, class Decorator>
void
stream<NextLayer, deflateSupported>::
do_accept(
Buffers const& buffers,
Decorator const& decorator,
error_code& ec)
{
impl_->reset();
auto const mb =
beast::detail::dynamic_buffer_prepare(
impl_->rd_buf, buffer_bytes(buffers), ec,
error::buffer_overflow);
if(ec)
return;
impl_->rd_buf.commit(net::buffer_copy(*mb, buffers));
http::request_parser<http::empty_body> p;
http::read(next_layer(), impl_->rd_buf, p, ec);
if(ec == http::error::end_of_stream)
{
BOOST_BEAST_ASSIGN_EC(ec, error::closed);
}
if(ec)
return;
do_accept(p.get(), decorator, ec);
}
//------------------------------------------------------------------------------
template<class NextLayer, bool deflateSupported>
void
stream<NextLayer, deflateSupported>::
accept()
{
static_assert(is_sync_stream<next_layer_type>::value,
"SyncStream type requirements not met");
error_code ec;
accept(ec);
if(ec)
BOOST_THROW_EXCEPTION(system_error{ec});
}
template<class NextLayer, bool deflateSupported>
void
stream<NextLayer, deflateSupported>::
accept(error_code& ec)
{
static_assert(is_sync_stream<next_layer_type>::value,
"SyncStream type requirements not met");
do_accept(
net::const_buffer{},
&default_decorate_res, ec);
}
template<class NextLayer, bool deflateSupported>
template<class ConstBufferSequence>
typename std::enable_if<! http::detail::is_header<
ConstBufferSequence>::value>::type
stream<NextLayer, deflateSupported>::
accept(ConstBufferSequence const& buffers)
{
static_assert(is_sync_stream<next_layer_type>::value,
"SyncStream type requirements not met");
static_assert(net::is_const_buffer_sequence<
ConstBufferSequence>::value,
"ConstBufferSequence type requirements not met");
error_code ec;
accept(buffers, ec);
if(ec)
BOOST_THROW_EXCEPTION(system_error{ec});
}
template<class NextLayer, bool deflateSupported>
template<class ConstBufferSequence>
typename std::enable_if<! http::detail::is_header<
ConstBufferSequence>::value>::type
stream<NextLayer, deflateSupported>::
accept(
ConstBufferSequence const& buffers, error_code& ec)
{
static_assert(is_sync_stream<next_layer_type>::value,
"SyncStream type requirements not met");
static_assert(net::is_const_buffer_sequence<
ConstBufferSequence>::value,
"ConstBufferSequence type requirements not met");
do_accept(buffers, &default_decorate_res, ec);
}
template<class NextLayer, bool deflateSupported>
template<class Body, class Allocator>
void
stream<NextLayer, deflateSupported>::
accept(
http::request<Body,
http::basic_fields<Allocator>> const& req)
{
static_assert(is_sync_stream<next_layer_type>::value,
"SyncStream type requirements not met");
error_code ec;
accept(req, ec);
if(ec)
BOOST_THROW_EXCEPTION(system_error{ec});
}
template<class NextLayer, bool deflateSupported>
template<class Body, class Allocator>
void
stream<NextLayer, deflateSupported>::
accept(
http::request<Body,
http::basic_fields<Allocator>> const& req,
error_code& ec)
{
static_assert(is_sync_stream<next_layer_type>::value,
"SyncStream type requirements not met");
impl_->reset();
do_accept(req, &default_decorate_res, ec);
}
//------------------------------------------------------------------------------
template<class NextLayer, bool deflateSupported>
template<
BOOST_BEAST_ASYNC_TPARAM1 AcceptHandler>
BOOST_BEAST_ASYNC_RESULT1(AcceptHandler)
stream<NextLayer, deflateSupported>::
async_accept(
AcceptHandler&& handler,
typename std::enable_if<
! net::is_const_buffer_sequence<
AcceptHandler>::value>::type*
)
{
static_assert(is_async_stream<next_layer_type>::value,
"AsyncStream type requirements not met");
return net::async_initiate<
AcceptHandler,
void(error_code)>(
run_accept_op{impl_},
handler,
&default_decorate_res,
net::const_buffer{});
}
template<class NextLayer, bool deflateSupported>
template<
class ConstBufferSequence,
BOOST_BEAST_ASYNC_TPARAM1 AcceptHandler>
BOOST_BEAST_ASYNC_RESULT1(AcceptHandler)
stream<NextLayer, deflateSupported>::
async_accept(
ConstBufferSequence const& buffers,
AcceptHandler&& handler,
typename std::enable_if<
net::is_const_buffer_sequence<
ConstBufferSequence>::value>::type*,
typename std::enable_if<
! http::detail::is_header<
ConstBufferSequence>::value>::type*
)
{
static_assert(is_async_stream<next_layer_type>::value,
"AsyncStream type requirements not met");
static_assert(net::is_const_buffer_sequence<
ConstBufferSequence>::value,
"ConstBufferSequence type requirements not met");
return net::async_initiate<
AcceptHandler,
void(error_code)>(
run_accept_op{impl_},
handler,
&default_decorate_res,
buffers);
}
template<class NextLayer, bool deflateSupported>
template<
class Body, class Allocator,
BOOST_BEAST_ASYNC_TPARAM1 AcceptHandler>
BOOST_BEAST_ASYNC_RESULT1(AcceptHandler)
stream<NextLayer, deflateSupported>::
async_accept(
http::request<Body, http::basic_fields<Allocator>> const& req,
AcceptHandler&& handler)
{
static_assert(is_async_stream<next_layer_type>::value,
"AsyncStream type requirements not met");
return net::async_initiate<
AcceptHandler,
void(error_code)>(
run_response_op{impl_},
handler,
&req,
&default_decorate_res);
}
} // websocket
} // beast
} // boost
#endif