Files
boost_beast/include/beast/http/impl/write.ipp
T
Vinnie Falco 114175cd1f Implement asio dealloc-before-invoke guarantee:
fix #215

This change guarantees that temporary memory allocated
through the asio hooks by the Beast implementation is
deallocated before invoking the final handler when performing
composed operations.

The change is accomplished by replacing std::shared_ptr with
a thread-safe custom container handler_ptr to manage composed
operation state. The container tracks other instances which
manage the same object and resets them in a safe way before
invoking the final handler.

handler_ptr is provided as a public interface so that users of
this library can utilize the same idiom to write their own
composed operations.
2017-01-07 16:07:01 -05:00

778 lines
20 KiB
C++

//
// Copyright (c) 2013-2016 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_HTTP_IMPL_WRITE_IPP
#define BEAST_HTTP_IMPL_WRITE_IPP
#include <beast/http/concepts.hpp>
#include <beast/http/resume_context.hpp>
#include <beast/http/chunk_encode.hpp>
#include <beast/core/buffer_cat.hpp>
#include <beast/core/bind_handler.hpp>
#include <beast/core/buffer_concepts.hpp>
#include <beast/core/handler_alloc.hpp>
#include <beast/core/handler_ptr.hpp>
#include <beast/core/stream_concepts.hpp>
#include <beast/core/streambuf.hpp>
#include <beast/core/write_dynabuf.hpp>
#include <beast/core/detail/sync_ostream.hpp>
#include <boost/asio/write.hpp>
#include <boost/logic/tribool.hpp>
#include <condition_variable>
#include <mutex>
#include <ostream>
#include <sstream>
#include <type_traits>
namespace beast {
namespace http {
namespace detail {
template<class DynamicBuffer, class Fields>
void
write_start_line(DynamicBuffer& dynabuf,
header<true, Fields> const& msg)
{
BOOST_ASSERT(msg.version == 10 || msg.version == 11);
write(dynabuf, msg.method);
write(dynabuf, " ");
write(dynabuf, msg.url);
switch(msg.version)
{
case 10:
write(dynabuf, " HTTP/1.0\r\n");
break;
case 11:
write(dynabuf, " HTTP/1.1\r\n");
break;
}
}
template<class DynamicBuffer, class Fields>
void
write_start_line(DynamicBuffer& dynabuf,
header<false, Fields> const& msg)
{
BOOST_ASSERT(msg.version == 10 || msg.version == 11);
switch(msg.version)
{
case 10:
write(dynabuf, "HTTP/1.0 ");
break;
case 11:
write(dynabuf, "HTTP/1.1 ");
break;
}
write(dynabuf, msg.status);
write(dynabuf, " ");
write(dynabuf, msg.reason);
write(dynabuf, "\r\n");
}
template<class DynamicBuffer, class FieldSequence>
void
write_fields(DynamicBuffer& dynabuf, FieldSequence const& fields)
{
static_assert(is_DynamicBuffer<DynamicBuffer>::value,
"DynamicBuffer requirements not met");
//static_assert(is_FieldSequence<FieldSequence>::value,
// "FieldSequence requirements not met");
for(auto const& field : fields)
{
write(dynabuf, field.name());
write(dynabuf, ": ");
write(dynabuf, field.value());
write(dynabuf, "\r\n");
}
}
} // detail
//------------------------------------------------------------------------------
namespace detail {
template<class Stream, class Handler>
class write_streambuf_op
{
struct data
{
bool cont;
Stream& s;
streambuf sb;
int state = 0;
data(Handler& handler, Stream& s_,
streambuf&& sb_)
: cont(boost_asio_handler_cont_helpers::
is_continuation(handler))
, s(s_)
, sb(std::move(sb_))
{
}
};
handler_ptr<data, Handler> d_;
public:
write_streambuf_op(write_streambuf_op&&) = default;
write_streambuf_op(write_streambuf_op const&) = default;
template<class DeducedHandler, class... Args>
write_streambuf_op(DeducedHandler&& h, Stream& s,
Args&&... args)
: d_(make_handler_ptr<data, Handler>(
std::forward<DeducedHandler>(h),
s, std::forward<Args>(args)...))
{
(*this)(error_code{}, 0, false);
}
void
operator()(error_code ec,
std::size_t bytes_transferred, bool again = true);
friend
void* asio_handler_allocate(
std::size_t size, write_streambuf_op* op)
{
return boost_asio_handler_alloc_helpers::
allocate(size, op->d_.handler());
}
friend
void asio_handler_deallocate(
void* p, std::size_t size, write_streambuf_op* op)
{
return boost_asio_handler_alloc_helpers::
deallocate(p, size, op->d_.handler());
}
friend
bool asio_handler_is_continuation(write_streambuf_op* op)
{
return op->d_->cont;
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, write_streambuf_op* op)
{
return boost_asio_handler_invoke_helpers::
invoke(f, op->d_.handler());
}
};
template<class Stream, class Handler>
void
write_streambuf_op<Stream, Handler>::
operator()(error_code ec, std::size_t, bool again)
{
auto& d = *d_;
d.cont = d.cont || again;
while(! ec && d.state != 99)
{
switch(d.state)
{
case 0:
{
d.state = 99;
boost::asio::async_write(d.s,
d.sb.data(), std::move(*this));
return;
}
}
}
d_.invoke(ec);
}
} // detail
template<class SyncWriteStream,
bool isRequest, class Fields>
void
write(SyncWriteStream& stream,
header<isRequest, Fields> const& msg)
{
static_assert(is_SyncWriteStream<SyncWriteStream>::value,
"SyncWriteStream requirements not met");
error_code ec;
write(stream, msg, ec);
if(ec)
throw system_error{ec};
}
template<class SyncWriteStream,
bool isRequest, class Fields>
void
write(SyncWriteStream& stream,
header<isRequest, Fields> const& msg,
error_code& ec)
{
static_assert(is_SyncWriteStream<SyncWriteStream>::value,
"SyncWriteStream requirements not met");
streambuf sb;
detail::write_start_line(sb, msg);
detail::write_fields(sb, msg.fields);
beast::write(sb, "\r\n");
boost::asio::write(stream, sb.data(), ec);
}
template<class AsyncWriteStream,
bool isRequest, class Fields,
class WriteHandler>
typename async_completion<
WriteHandler, void(error_code)>::result_type
async_write(AsyncWriteStream& stream,
header<isRequest, Fields> const& msg,
WriteHandler&& handler)
{
static_assert(is_AsyncWriteStream<AsyncWriteStream>::value,
"AsyncWriteStream requirements not met");
beast::async_completion<WriteHandler,
void(error_code)> completion(handler);
streambuf sb;
detail::write_start_line(sb, msg);
detail::write_fields(sb, msg.fields);
beast::write(sb, "\r\n");
detail::write_streambuf_op<AsyncWriteStream,
decltype(completion.handler)>{
completion.handler, stream, std::move(sb)};
return completion.result.get();
}
//------------------------------------------------------------------------------
namespace detail {
template<bool isRequest, class Body, class Fields>
struct write_preparation
{
message<isRequest, Body, Fields> const& msg;
typename Body::writer w;
streambuf sb;
bool chunked;
bool close;
explicit
write_preparation(
message<isRequest, Body, Fields> const& msg_)
: msg(msg_)
, w(msg)
, chunked(token_list{
msg.fields["Transfer-Encoding"]}.exists("chunked"))
, close(token_list{
msg.fields["Connection"]}.exists("close") ||
(msg.version < 11 && ! msg.fields.exists(
"Content-Length")))
{
}
void
init(error_code& ec)
{
w.init(ec);
if(ec)
return;
write_start_line(sb, msg);
write_fields(sb, msg.fields);
beast::write(sb, "\r\n");
}
};
template<class Stream, class Handler,
bool isRequest, class Body, class Fields>
class write_op
{
struct data
{
bool cont;
Stream& s;
// VFALCO How do we use handler_alloc in write_preparation?
write_preparation<
isRequest, Body, Fields> wp;
resume_context resume;
resume_context copy;
int state = 0;
data(Handler& handler, Stream& s_,
message<isRequest, Body, Fields> const& m_)
: cont(boost_asio_handler_cont_helpers::
is_continuation(handler))
, s(s_)
, wp(m_)
{
}
};
class writef0_lambda
{
write_op& self_;
public:
explicit
writef0_lambda(write_op& self)
: self_(self)
{
}
template<class ConstBufferSequence>
void operator()(ConstBufferSequence const& buffers) const
{
auto& d = *self_.d_;
// write header and body
if(d.wp.chunked)
boost::asio::async_write(d.s,
buffer_cat(d.wp.sb.data(),
chunk_encode(false, buffers)),
std::move(self_));
else
boost::asio::async_write(d.s,
buffer_cat(d.wp.sb.data(),
buffers), std::move(self_));
}
};
class writef_lambda
{
write_op& self_;
public:
explicit
writef_lambda(write_op& self)
: self_(self)
{
}
template<class ConstBufferSequence>
void operator()(ConstBufferSequence const& buffers) const
{
auto& d = *self_.d_;
// write body
if(d.wp.chunked)
boost::asio::async_write(d.s,
chunk_encode(false, buffers),
std::move(self_));
else
boost::asio::async_write(d.s,
buffers, std::move(self_));
}
};
handler_ptr<data, Handler> d_;
public:
write_op(write_op&&) = default;
write_op(write_op const&) = default;
template<class DeducedHandler, class... Args>
write_op(DeducedHandler&& h, Stream& s, Args&&... args)
: d_(make_handler_ptr<data, Handler>(
std::forward<DeducedHandler>(h), s,
std::forward<Args>(args)...))
{
auto& d = *d_;
auto sp = d_;
d.resume = {
[sp]() mutable
{
write_op self{std::move(sp)};
self.d_->cont = false;
auto& ios = self.d_->s.get_io_service();
ios.dispatch(bind_handler(std::move(self),
error_code{}, 0, false));
}};
d.copy = d.resume;
(*this)(error_code{}, 0, false);
}
explicit
write_op(handler_ptr<data, Handler> d)
: d_(std::move(d))
{
}
void
operator()(error_code ec,
std::size_t bytes_transferred, bool again = true);
friend
void* asio_handler_allocate(
std::size_t size, write_op* op)
{
return boost_asio_handler_alloc_helpers::
allocate(size, op->d_.handler());
}
friend
void asio_handler_deallocate(
void* p, std::size_t size, write_op* op)
{
return boost_asio_handler_alloc_helpers::
deallocate(p, size, op->d_.handler());
}
friend
bool asio_handler_is_continuation(write_op* op)
{
return op->d_->cont;
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, write_op* op)
{
return boost_asio_handler_invoke_helpers::
invoke(f, op->d_.handler());
}
};
template<class Stream, class Handler,
bool isRequest, class Body, class Fields>
void
write_op<Stream, Handler, isRequest, Body, Fields>::
operator()(error_code ec, std::size_t, bool again)
{
auto& d = *d_;
d.cont = d.cont || again;
while(! ec && d.state != 99)
{
switch(d.state)
{
case 0:
{
d.wp.init(ec);
if(ec)
{
// call handler
d.state = 99;
d.s.get_io_service().post(bind_handler(
std::move(*this), ec, 0, false));
return;
}
d.state = 1;
break;
}
case 1:
{
boost::tribool const result = d.wp.w.write(
std::move(d.copy), ec, writef0_lambda{*this});
if(ec)
{
// call handler
d.state = 99;
d.s.get_io_service().post(bind_handler(
std::move(*this), ec, false));
return;
}
if(boost::indeterminate(result))
{
// suspend
d.copy = d.resume;
return;
}
if(result)
d.state = d.wp.chunked ? 4 : 5;
else
d.state = 2;
return;
}
// sent header and body
case 2:
d.wp.sb.consume(d.wp.sb.size());
d.state = 3;
break;
case 3:
{
boost::tribool result = d.wp.w.write(
std::move(d.copy), ec, writef_lambda{*this});
if(ec)
{
// call handler
d.state = 99;
break;
}
if(boost::indeterminate(result))
{
// suspend
d.copy = d.resume;
return;
}
if(result)
d.state = d.wp.chunked ? 4 : 5;
else
d.state = 2;
return;
}
case 4:
// VFALCO Unfortunately the current interface to the
// Writer concept prevents us from coalescing the
// final body chunk with the final chunk delimiter.
//
// write final chunk
d.state = 5;
boost::asio::async_write(d.s,
chunk_encode_final(), std::move(*this));
return;
case 5:
if(d.wp.close)
{
// VFALCO TODO Decide on an error code
ec = boost::asio::error::eof;
}
d.state = 99;
break;
}
}
d.copy = {};
d.resume = {};
d_.invoke(ec);
}
template<class SyncWriteStream, class DynamicBuffer>
class writef0_lambda
{
DynamicBuffer const& sb_;
SyncWriteStream& stream_;
bool chunked_;
error_code& ec_;
public:
writef0_lambda(SyncWriteStream& stream,
DynamicBuffer const& sb, bool chunked, error_code& ec)
: sb_(sb)
, stream_(stream)
, chunked_(chunked)
, ec_(ec)
{
}
template<class ConstBufferSequence>
void operator()(ConstBufferSequence const& buffers) const
{
// write header and body
if(chunked_)
boost::asio::write(stream_, buffer_cat(
sb_.data(), chunk_encode(false, buffers)), ec_);
else
boost::asio::write(stream_, buffer_cat(
sb_.data(), buffers), ec_);
}
};
template<class SyncWriteStream>
class writef_lambda
{
SyncWriteStream& stream_;
bool chunked_;
error_code& ec_;
public:
writef_lambda(SyncWriteStream& stream,
bool chunked, error_code& ec)
: stream_(stream)
, chunked_(chunked)
, ec_(ec)
{
}
template<class ConstBufferSequence>
void operator()(ConstBufferSequence const& buffers) const
{
// write body
if(chunked_)
boost::asio::write(stream_,
chunk_encode(false, buffers), ec_);
else
boost::asio::write(stream_, buffers, ec_);
}
};
} // detail
template<class SyncWriteStream,
bool isRequest, class Body, class Fields>
void
write(SyncWriteStream& stream,
message<isRequest, Body, Fields> const& msg)
{
static_assert(is_SyncWriteStream<SyncWriteStream>::value,
"SyncWriteStream requirements not met");
static_assert(is_Body<Body>::value,
"Body requirements not met");
static_assert(has_writer<Body>::value,
"Body has no writer");
static_assert(is_Writer<typename Body::writer,
message<isRequest, Body, Fields>>::value,
"Writer requirements not met");
error_code ec;
write(stream, msg, ec);
if(ec)
throw system_error{ec};
}
template<class SyncWriteStream,
bool isRequest, class Body, class Fields>
void
write(SyncWriteStream& stream,
message<isRequest, Body, Fields> const& msg,
error_code& ec)
{
static_assert(is_SyncWriteStream<SyncWriteStream>::value,
"SyncWriteStream requirements not met");
static_assert(is_Body<Body>::value,
"Body requirements not met");
static_assert(has_writer<Body>::value,
"Body has no writer");
static_assert(is_Writer<typename Body::writer,
message<isRequest, Body, Fields>>::value,
"Writer requirements not met");
detail::write_preparation<isRequest, Body, Fields> wp(msg);
wp.init(ec);
if(ec)
return;
std::mutex m;
std::condition_variable cv;
bool ready = false;
resume_context resume{
[&]
{
std::lock_guard<std::mutex> lock(m);
ready = true;
cv.notify_one();
}};
auto copy = resume;
boost::tribool result =
wp.w.write(std::move(copy), ec,
detail::writef0_lambda<SyncWriteStream,
decltype(wp.sb)>{stream,
wp.sb, wp.chunked, ec});
if(ec)
return;
if(boost::indeterminate(result))
{
copy = resume;
{
std::unique_lock<std::mutex> lock(m);
cv.wait(lock, [&]{ return ready; });
ready = false;
}
boost::asio::write(stream, wp.sb.data(), ec);
if(ec)
return;
result = false;
}
wp.sb.consume(wp.sb.size());
if(! result)
{
detail::writef_lambda<SyncWriteStream> wf{
stream, wp.chunked, ec};
for(;;)
{
result = wp.w.write(std::move(copy), ec, wf);
if(ec)
return;
if(result)
break;
if(! result)
continue;
copy = resume;
std::unique_lock<std::mutex> lock(m);
cv.wait(lock, [&]{ return ready; });
ready = false;
}
}
if(wp.chunked)
{
// VFALCO Unfortunately the current interface to the
// Writer concept prevents us from using coalescing the
// final body chunk with the final chunk delimiter.
//
// write final chunk
boost::asio::write(stream, chunk_encode_final(), ec);
if(ec)
return;
}
if(wp.close)
{
// VFALCO TODO Decide on an error code
ec = boost::asio::error::eof;
}
}
template<class AsyncWriteStream,
bool isRequest, class Body, class Fields,
class WriteHandler>
typename async_completion<
WriteHandler, void(error_code)>::result_type
async_write(AsyncWriteStream& stream,
message<isRequest, Body, Fields> const& msg,
WriteHandler&& handler)
{
static_assert(is_AsyncWriteStream<AsyncWriteStream>::value,
"AsyncWriteStream requirements not met");
static_assert(is_Body<Body>::value,
"Body requirements not met");
static_assert(has_writer<Body>::value,
"Body has no writer");
static_assert(is_Writer<typename Body::writer,
message<isRequest, Body, Fields>>::value,
"Writer requirements not met");
beast::async_completion<WriteHandler,
void(error_code)> completion(handler);
detail::write_op<AsyncWriteStream, decltype(completion.handler),
isRequest, Body, Fields>{completion.handler, stream, msg};
return completion.result.get();
}
//------------------------------------------------------------------------------
template<bool isRequest, class Fields>
std::ostream&
operator<<(std::ostream& os,
header<isRequest, Fields> const& msg)
{
beast::detail::sync_ostream oss{os};
error_code ec;
write(oss, msg, ec);
if(ec)
throw system_error{ec};
return os;
}
template<bool isRequest, class Body, class Fields>
std::ostream&
operator<<(std::ostream& os,
message<isRequest, Body, Fields> const& msg)
{
static_assert(is_Body<Body>::value,
"Body requirements not met");
static_assert(has_writer<Body>::value,
"Body has no writer");
static_assert(is_Writer<typename Body::writer,
message<isRequest, Body, Fields>>::value,
"Writer requirements not met");
beast::detail::sync_ostream oss{os};
error_code ec;
write(oss, msg, ec);
if(ec && ec != boost::asio::error::eof)
throw system_error{ec};
return os;
}
} // http
} // beast
#endif