Files
boost_beast/include/beast/http/impl/write.ipp
T
Vinnie Falco ff5e659545 Refactor http::header contents (API Change):
fix #124

The http::header data members "method", "url", and "reason"
are changed from data members, to pairs of get and set
functions which forward the call to the Fields type used
to instantiate the template.

Previously, these data members were implemented using
std::string. With this change, the implementation of the
Fields type used to instantiate the template is now in
control of the representation of those values. This permits
custom memory allocation strategies including uniform use of
the Allocator type already provided to beast::http::basic_fields.
2017-05-07 13:40:04 -07:00

715 lines
18 KiB
C++

//
// 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_HTTP_IMPL_WRITE_IPP
#define BEAST_HTTP_IMPL_WRITE_IPP
#include <beast/http/concepts.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_helpers.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 <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.target());
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)
{
auto const name = field.name();
BOOST_ASSERT(! name.empty());
if(name[0] == ':')
continue;
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(beast_asio_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_(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 beast_asio_helpers::
allocate(size, op->d_.handler());
}
friend
void asio_handler_deallocate(
void* p, std::size_t size, write_streambuf_op* op)
{
return beast_asio_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 beast_asio_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;
int state = 0;
data(Handler& handler, Stream& s_,
message<isRequest, Body, Fields> const& m_)
: cont(beast_asio_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_(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_op* op)
{
return beast_asio_helpers::
allocate(size, op->d_.handler());
}
friend
void asio_handler_deallocate(
void* p, std::size_t size, write_op* op)
{
return beast_asio_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 beast_asio_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:
{
auto const result =
d.wp.w.write(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(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:
{
auto const result =
d.wp.w.write(ec,
writef_lambda{*this});
if(ec)
{
// call handler
d.state = 99;
break;
}
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_.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;
auto result = wp.w.write(
ec, detail::writef0_lambda<
SyncWriteStream, decltype(wp.sb)>{
stream, wp.sb, wp.chunked, ec});
if(ec)
return;
wp.sb.consume(wp.sb.size());
if(! result)
{
detail::writef_lambda<SyncWriteStream> wf{
stream, wp.chunked, ec};
for(;;)
{
result = wp.w.write(ec, wf);
if(ec)
return;
if(result)
break;
}
}
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