forked from boostorg/beast
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.
418 lines
12 KiB
C++
418 lines
12 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_HANDSHAKE_HPP
|
|
#define BOOST_BEAST_WEBSOCKET_IMPL_HANDSHAKE_HPP
|
|
|
|
#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/message.hpp>
|
|
#include <boost/beast/http/read.hpp>
|
|
#include <boost/beast/http/write.hpp>
|
|
#include <boost/beast/core/async_base.hpp>
|
|
#include <boost/beast/core/flat_buffer.hpp>
|
|
#include <boost/beast/core/stream_traits.hpp>
|
|
#include <boost/asio/coroutine.hpp>
|
|
#include <boost/assert.hpp>
|
|
#include <boost/throw_exception.hpp>
|
|
#include <memory>
|
|
|
|
namespace boost {
|
|
namespace beast {
|
|
namespace websocket {
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// send the upgrade request and process the response
|
|
//
|
|
template<class NextLayer, bool deflateSupported>
|
|
template<class Handler>
|
|
class stream<NextLayer, deflateSupported>::handshake_op
|
|
: public beast::stable_async_base<Handler,
|
|
beast::executor_type<stream>>
|
|
, public asio::coroutine
|
|
{
|
|
struct data
|
|
{
|
|
// VFALCO This really should be two separate
|
|
// composed operations, to save on memory
|
|
request_type req;
|
|
http::response_parser<
|
|
typename response_type::body_type> p;
|
|
flat_buffer fb;
|
|
bool overflow = false; // could be a member of the op
|
|
|
|
explicit
|
|
data(request_type&& req_)
|
|
: req(std::move(req_))
|
|
{
|
|
}
|
|
};
|
|
|
|
boost::weak_ptr<impl_type> wp_;
|
|
detail::sec_ws_key_type key_;
|
|
response_type* res_p_;
|
|
data& d_;
|
|
|
|
public:
|
|
template<class Handler_>
|
|
handshake_op(
|
|
Handler_&& h,
|
|
boost::shared_ptr<impl_type> const& sp,
|
|
request_type&& req,
|
|
detail::sec_ws_key_type key,
|
|
response_type* res_p)
|
|
: stable_async_base<Handler,
|
|
beast::executor_type<stream>>(
|
|
std::forward<Handler_>(h),
|
|
sp->stream().get_executor())
|
|
, wp_(sp)
|
|
, key_(key)
|
|
, res_p_(res_p)
|
|
, d_(beast::allocate_stable<data>(
|
|
*this, std::move(req)))
|
|
{
|
|
sp->reset(); // VFALCO I don't like this
|
|
if(res_p)
|
|
res_p->result(http::status::internal_server_error);
|
|
(*this)({}, 0, false);
|
|
}
|
|
|
|
void
|
|
operator()(
|
|
error_code ec = {},
|
|
std::size_t bytes_used = 0,
|
|
bool cont = true)
|
|
{
|
|
boost::ignore_unused(bytes_used);
|
|
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());
|
|
|
|
// write HTTP request
|
|
impl.do_pmd_config(d_.req);
|
|
BOOST_ASIO_CORO_YIELD
|
|
{
|
|
BOOST_ASIO_HANDLER_LOCATION((
|
|
__FILE__, __LINE__,
|
|
"websocket::async_handshake"));
|
|
|
|
http::async_write(impl.stream(),
|
|
d_.req, std::move(*this));
|
|
}
|
|
if(impl.check_stop_now(ec))
|
|
goto upcall;
|
|
|
|
// read HTTP response
|
|
BOOST_ASIO_CORO_YIELD
|
|
{
|
|
BOOST_ASIO_HANDLER_LOCATION((
|
|
__FILE__, __LINE__,
|
|
"websocket::async_handshake"));
|
|
|
|
http::async_read(impl.stream(),
|
|
impl.rd_buf, d_.p,
|
|
std::move(*this));
|
|
}
|
|
if(ec == http::error::buffer_overflow)
|
|
{
|
|
// If the response overflows the internal
|
|
// read buffer, switch to a dynamically
|
|
// allocated flat buffer.
|
|
|
|
d_.fb.commit(net::buffer_copy(
|
|
d_.fb.prepare(impl.rd_buf.size()),
|
|
impl.rd_buf.data()));
|
|
impl.rd_buf.clear();
|
|
|
|
BOOST_ASIO_CORO_YIELD
|
|
{
|
|
BOOST_ASIO_HANDLER_LOCATION((
|
|
__FILE__, __LINE__,
|
|
"websocket::async_handshake"));
|
|
|
|
http::async_read(impl.stream(),
|
|
d_.fb, d_.p, std::move(*this));
|
|
}
|
|
|
|
if(! ec)
|
|
{
|
|
// Copy any leftovers back into the read
|
|
// buffer, since this represents websocket
|
|
// frame data.
|
|
|
|
if(d_.fb.size() <= impl.rd_buf.capacity())
|
|
{
|
|
impl.rd_buf.commit(net::buffer_copy(
|
|
impl.rd_buf.prepare(d_.fb.size()),
|
|
d_.fb.data()));
|
|
}
|
|
else
|
|
{
|
|
BOOST_BEAST_ASSIGN_EC(ec, http::error::buffer_overflow);
|
|
}
|
|
}
|
|
|
|
// Do this before the upcall
|
|
d_.fb.clear();
|
|
}
|
|
if(impl.check_stop_now(ec))
|
|
goto upcall;
|
|
|
|
// success
|
|
impl.reset_idle();
|
|
impl.on_response(d_.p.get(), key_, ec);
|
|
if(res_p_)
|
|
swap(d_.p.get(), *res_p_);
|
|
|
|
upcall:
|
|
this->complete(cont ,ec);
|
|
}
|
|
}
|
|
};
|
|
|
|
template<class NextLayer, bool deflateSupported>
|
|
struct stream<NextLayer, deflateSupported>::
|
|
run_handshake_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 HandshakeHandler>
|
|
void operator()(
|
|
HandshakeHandler&& h,
|
|
request_type&& req,
|
|
detail::sec_ws_key_type key,
|
|
response_type* res_p)
|
|
{
|
|
// 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<HandshakeHandler,
|
|
void(error_code)>::value,
|
|
"HandshakeHandler type requirements not met");
|
|
|
|
handshake_op<
|
|
typename std::decay<HandshakeHandler>::type>(
|
|
std::forward<HandshakeHandler>(h),
|
|
self, std::move(req), key, res_p);
|
|
}
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
template<class NextLayer, bool deflateSupported>
|
|
template<class RequestDecorator>
|
|
void
|
|
stream<NextLayer, deflateSupported>::
|
|
do_handshake(
|
|
response_type* res_p,
|
|
string_view host,
|
|
string_view target,
|
|
RequestDecorator const& decorator,
|
|
error_code& ec)
|
|
{
|
|
if(res_p)
|
|
res_p->result(http::status::internal_server_error);
|
|
|
|
auto& impl = *impl_;
|
|
impl.change_status(status::handshake);
|
|
impl.reset();
|
|
detail::sec_ws_key_type key;
|
|
{
|
|
auto const req = impl.build_request(
|
|
key, host, target, decorator);
|
|
impl.do_pmd_config(req);
|
|
http::write(impl.stream(), req, ec);
|
|
}
|
|
if(impl.check_stop_now(ec))
|
|
return;
|
|
http::response_parser<
|
|
typename response_type::body_type> p;
|
|
http::read(next_layer(), impl.rd_buf, p, ec);
|
|
if(ec == http::error::buffer_overflow)
|
|
{
|
|
// If the response overflows the internal
|
|
// read buffer, switch to a dynamically
|
|
// allocated flat buffer.
|
|
|
|
flat_buffer fb;
|
|
fb.commit(net::buffer_copy(
|
|
fb.prepare(impl.rd_buf.size()),
|
|
impl.rd_buf.data()));
|
|
impl.rd_buf.clear();
|
|
|
|
http::read(next_layer(), fb, p, ec);;
|
|
|
|
if(! ec)
|
|
{
|
|
// Copy any leftovers back into the read
|
|
// buffer, since this represents websocket
|
|
// frame data.
|
|
|
|
if(fb.size() <= impl.rd_buf.capacity())
|
|
{
|
|
impl.rd_buf.commit(net::buffer_copy(
|
|
impl.rd_buf.prepare(fb.size()),
|
|
fb.data()));
|
|
}
|
|
else
|
|
{
|
|
BOOST_BEAST_ASSIGN_EC(ec, http::error::buffer_overflow);
|
|
}
|
|
}
|
|
}
|
|
if(impl.check_stop_now(ec))
|
|
return;
|
|
|
|
if (res_p)
|
|
{
|
|
// If res_p is not null, move parser's response into it.
|
|
*res_p = p.release();
|
|
}
|
|
else
|
|
{
|
|
// Otherwise point res_p at the response in the parser.
|
|
res_p = &p.get();
|
|
}
|
|
|
|
impl.on_response(*res_p, key, ec);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
template<class NextLayer, bool deflateSupported>
|
|
template<BOOST_BEAST_ASYNC_TPARAM1 HandshakeHandler>
|
|
BOOST_BEAST_ASYNC_RESULT1(HandshakeHandler)
|
|
stream<NextLayer, deflateSupported>::
|
|
async_handshake(
|
|
string_view host,
|
|
string_view target,
|
|
HandshakeHandler&& handler)
|
|
{
|
|
static_assert(is_async_stream<next_layer_type>::value,
|
|
"AsyncStream type requirements not met");
|
|
detail::sec_ws_key_type key;
|
|
auto req = impl_->build_request(
|
|
key, host, target, &default_decorate_req);
|
|
return net::async_initiate<
|
|
HandshakeHandler,
|
|
void(error_code)>(
|
|
run_handshake_op{impl_},
|
|
handler,
|
|
std::move(req),
|
|
key,
|
|
nullptr);
|
|
}
|
|
|
|
template<class NextLayer, bool deflateSupported>
|
|
template<BOOST_BEAST_ASYNC_TPARAM1 HandshakeHandler>
|
|
BOOST_BEAST_ASYNC_RESULT1(HandshakeHandler)
|
|
stream<NextLayer, deflateSupported>::
|
|
async_handshake(
|
|
response_type& res,
|
|
string_view host,
|
|
string_view target,
|
|
HandshakeHandler&& handler)
|
|
{
|
|
static_assert(is_async_stream<next_layer_type>::value,
|
|
"AsyncStream type requirements not met");
|
|
detail::sec_ws_key_type key;
|
|
auto req = impl_->build_request(
|
|
key, host, target, &default_decorate_req);
|
|
return net::async_initiate<
|
|
HandshakeHandler,
|
|
void(error_code)>(
|
|
run_handshake_op{impl_},
|
|
handler,
|
|
std::move(req),
|
|
key,
|
|
&res);
|
|
}
|
|
|
|
template<class NextLayer, bool deflateSupported>
|
|
void
|
|
stream<NextLayer, deflateSupported>::
|
|
handshake(string_view host,
|
|
string_view target)
|
|
{
|
|
static_assert(is_sync_stream<next_layer_type>::value,
|
|
"SyncStream type requirements not met");
|
|
error_code ec;
|
|
handshake(
|
|
host, target, ec);
|
|
if(ec)
|
|
BOOST_THROW_EXCEPTION(system_error{ec});
|
|
}
|
|
|
|
template<class NextLayer, bool deflateSupported>
|
|
void
|
|
stream<NextLayer, deflateSupported>::
|
|
handshake(response_type& res,
|
|
string_view host,
|
|
string_view target)
|
|
{
|
|
static_assert(is_sync_stream<next_layer_type>::value,
|
|
"SyncStream type requirements not met");
|
|
error_code ec;
|
|
handshake(res, host, target, ec);
|
|
if(ec)
|
|
BOOST_THROW_EXCEPTION(system_error{ec});
|
|
}
|
|
|
|
template<class NextLayer, bool deflateSupported>
|
|
void
|
|
stream<NextLayer, deflateSupported>::
|
|
handshake(string_view host,
|
|
string_view target, error_code& ec)
|
|
{
|
|
static_assert(is_sync_stream<next_layer_type>::value,
|
|
"SyncStream type requirements not met");
|
|
do_handshake(nullptr,
|
|
host, target, &default_decorate_req, ec);
|
|
}
|
|
|
|
template<class NextLayer, bool deflateSupported>
|
|
void
|
|
stream<NextLayer, deflateSupported>::
|
|
handshake(response_type& res,
|
|
string_view host,
|
|
string_view target,
|
|
error_code& ec)
|
|
{
|
|
static_assert(is_sync_stream<next_layer_type>::value,
|
|
"SyncStream type requirements not met");
|
|
do_handshake(&res,
|
|
host, target, &default_decorate_req, ec);
|
|
}
|
|
|
|
} // websocket
|
|
} // beast
|
|
} // boost
|
|
|
|
#endif
|