Refactor HTTP serialization (API Change):

A new class `serializer` is introduced to permit incremental
serialization of HTTP messages. Existing free functions are
re-implemented in terms of this new class.

* The BodyReader concept is refined to support a greater variety
of strategies for providing buffers representing the body to
the serialization algorithms.

* Added buffer_body, a new model of Body which allows the caller
to provide a series of owned buffers using their own serialization
loop.

* Added empty_body, a model of Body which is for serialization only,
to represent HTTP messages with no content body.

* Removed overloads of write and async_write which send only
the HTTP header.

* Removed public interfaces for performing low-level chunk encoding.
This commit is contained in:
Vinnie Falco
2017-07-20 08:12:17 -07:00
parent b9bdb1bbbc
commit 3664329ea7
44 changed files with 2540 additions and 1104 deletions
+2 -1
View File
@@ -11,8 +11,9 @@
#include <beast/config.hpp>
#include <beast/http/basic_parser.hpp>
#include <beast/http/chunk_encode.hpp>
#include <beast/http/buffer_body.hpp>
#include <beast/http/dynamic_body.hpp>
#include <beast/http/empty_body.hpp>
#include <beast/http/error.hpp>
#include <beast/http/fields.hpp>
#include <beast/http/header_parser.hpp>
+126
View File
@@ -0,0 +1,126 @@
//
// 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_BUFFER_BODY_HPP
#define BEAST_HTTP_BUFFER_BODY_HPP
#include <beast/http/concepts.hpp>
#include <beast/http/error.hpp>
#include <beast/http/message.hpp>
#include <boost/optional.hpp>
#include <type_traits>
#include <utility>
namespace beast {
namespace http {
/** A serializable body represented by caller provided buffers.
This body type permits incremental message sending of caller
provided buffers using a @ref serializer.
@par Example
@code
template<class SyncWriteStream>
void send(SyncWriteStream& stream)
{
...
}
@endcode
@tparam isDeferred A `bool` which, when set to `true`,
indicates to the serialization implementation that it should
send the entire HTTP Header before attempting to acquire
buffers representing the body.
@tparam ConstBufferSequence The type of buffer sequence
stored in the body by the caller.
*/
template<
bool isDeferred,
class ConstBufferSequence>
struct buffer_body
{
static_assert(is_const_buffer_sequence<ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
/** The type of the body member when used in a message.
When engaged, the first element of the pair represents
the current buffer sequence to be written.
The second element of the pair indicates whether or not
additional buffers will be available. A value of `false`
indicates the end of the message body.
If the buffer in the value is disengaged, and the second
element of the pair is `true`, @ref serializer operations
will return @ref http::error::need_more. This signals the
calling code that a new buffer should be placed into the
body, or that the caller should indicate that no more
buffers will be available.
*/
using value_type =
std::pair<boost::optional<ConstBufferSequence>, bool>;
#if BEAST_DOXYGEN
/// The algorithm used when serializing this body
using writer = implementation_defined;
#else
class writer
{
bool toggle_ = false;
value_type const& body_;
public:
using is_deferred =
std::integral_constant<bool, isDeferred>;
using const_buffers_type = ConstBufferSequence;
template<bool isRequest, class Fields>
explicit
writer(message<isRequest, buffer_body,
Fields> const& msg)
: body_(msg.body)
{
}
void
init(error_code&)
{
}
boost::optional<std::pair<ConstBufferSequence, bool>>
get(error_code& ec)
{
if(toggle_)
{
if(body_.second)
{
toggle_ = false;
ec = error::need_more;
}
return boost::none;
}
if(body_.first)
{
toggle_ = true;
return {{*body_.first, body_.second}};
}
if(body_.second)
ec = error::need_more;
return boost::none;
}
};
#endif
};
} // http
} // beast
#endif
-75
View File
@@ -1,75 +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_HTTP_CHUNK_ENCODE_HPP
#define BEAST_HTTP_CHUNK_ENCODE_HPP
#include <beast/config.hpp>
#include <beast/core/buffer_cat.hpp>
#include <beast/http/detail/chunk_encode.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/assert.hpp>
#include <algorithm>
#include <array>
#include <cstddef>
#include <iterator>
#include <type_traits>
namespace beast {
namespace http {
/** Returns a chunk-encoded ConstBufferSequence.
This returns a buffer sequence representing the
first chunk of a chunked transfer coded body.
@param fin `true` if this is the last chunk.
@param buffers The input buffer sequence.
@return A chunk-encoded ConstBufferSequence representing the input.
@see <a href=https://tools.ietf.org/html/rfc7230#section-4.1.3>rfc7230 section 4.1.3</a>
*/
template<class ConstBufferSequence>
#if BEAST_DOXYGEN
implementation_defined
#else
beast::buffers_view<
detail::chunk_encode_delim,
ConstBufferSequence,
boost::asio::const_buffers_1>
#endif
chunk_encode(bool fin, ConstBufferSequence const& buffers)
{
using boost::asio::buffer_size;
return buffer_cat(
detail::chunk_encode_delim{buffer_size(buffers)},
buffers,
fin ? boost::asio::const_buffers_1{"\r\n0\r\n\r\n", 7}
: boost::asio::const_buffers_1{"\r\n", 2});
}
/** Returns a chunked encoding final chunk.
@see <a href=https://tools.ietf.org/html/rfc7230#section-4.1.3>rfc7230 section 4.1.3</a>
*/
inline
#if BEAST_DOXYGEN
implementation_defined
#else
boost::asio::const_buffers_1
#endif
chunk_encode_final()
{
return boost::asio::const_buffers_1{"0\r\n\r\n", 5};
}
} // http
} // beast
#endif
+34 -44
View File
@@ -10,6 +10,7 @@
#include <beast/config.hpp>
#include <beast/core/error.hpp>
#include <beast/core/string_view.hpp>
#include <beast/core/type_traits.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/optional.hpp>
@@ -19,8 +20,18 @@
namespace beast {
namespace http {
template<bool, class, class>
struct message;
namespace detail {
struct fields_model
{
string_view method() const;
string_view reason() const;
string_view target() const;
};
struct write_function
{
template<class ConstBufferSequence>
@@ -50,43 +61,6 @@ struct has_content_length<T, beast::detail::void_t<decltype(
"Writer::content_length requirements not met");
};
template<class T, class M>
class is_Writer
{
template<class U, class R = decltype(
std::declval<U>().init(std::declval<error_code&>()),
std::true_type{})>
static R check1(int);
template<class>
static std::false_type check1(...);
using type1 = decltype(check1<T>(0));
// VFALCO This is unfortunate, we have to provide the template
// argument type because this is not a deduced context?
//
template<class U, class R =
std::is_convertible<decltype(
std::declval<U>().template write<detail::write_function>(
std::declval<error_code&>(),
std::declval<detail::write_function>()))
, bool>>
static R check2(int);
template<class>
static std::false_type check2(...);
using type2 = decltype(check2<T>(0));
public:
static_assert(std::is_same<
typename M::body_type::writer, T>::value,
"Mismatched writer and message");
using type = std::integral_constant<bool,
std::is_nothrow_constructible<T, M const&>::value
&& type1::value
&& type2::value
>;
};
} // detail
/// Determine if `T` meets the requirements of @b Body.
@@ -173,18 +147,34 @@ struct is_Reader<T, M, beast::detail::void_t<decltype(
};
#endif
/** Determine if `T` meets the requirements of @b Writer for `M`.
/** Determine if a @b Body type has a writer which meets requirements.
@tparam T The type to test.
@tparam M The message type to test with, which must be of
type `message`.
@tparam T The body type to test.
*/
template<class T, class M>
#if BEAST_DOXYGEN
template<class T>
struct is_Writer : std::integral_constant<bool, ...> {};
#else
using is_Writer = typename detail::is_Writer<T, M>::type;
template<class T, class = void>
struct is_Writer : std::false_type {};
template<class T>
struct is_Writer<T, beast::detail::void_t<
typename T::writer,
typename T::writer::const_buffers_type,
decltype(
std::declval<typename T::writer&>().init(std::declval<error_code&>()),
std::declval<boost::optional<std::pair<
typename T::writer::const_buffers_type, bool>>&>() =
std::declval<typename T::writer>().get(std::declval<error_code&>()),
(void)0)>> : std::integral_constant<bool,
is_const_buffer_sequence<
typename T::writer::const_buffers_type>::value &&
std::is_constructible<typename T::writer,
message<true, T, detail::fields_model> const& >::value
>
{
};
#endif
} // http
+65 -19
View File
@@ -17,20 +17,22 @@ namespace beast {
namespace http {
namespace detail {
class chunk_encode_delim
/** A buffer sequence containing a chunk-encoding header
*/
class chunk_header
{
boost::asio::const_buffer cb_;
// Storage for the longest hex string we might need, plus delimiters.
std::array<char, 2 * sizeof(std::size_t) + 2> buf_;
// Storage for the longest hex string we might need
char buf_[2 * sizeof(std::size_t)];
template<class = void>
void
copy(chunk_encode_delim const& other);
copy(chunk_header const& other);
template<class = void>
void
setup(std::size_t n);
prepare_impl(std::size_t n);
template<class OutIter>
static
@@ -55,15 +57,27 @@ public:
using const_iterator = value_type const*;
chunk_encode_delim(chunk_encode_delim const& other)
/** Constructor (default)
Default-constructed chunk headers are in an
undefined state.
*/
chunk_header() = default;
/// Copy constructor
chunk_header(chunk_header const& other)
{
copy(other);
}
/** Construct a chunk header
@param n The number of octets in this chunk.
*/
explicit
chunk_encode_delim(std::size_t n)
chunk_header(std::size_t n)
{
setup(n);
prepare_impl(n);
}
const_iterator
@@ -77,31 +91,63 @@ public:
{
return begin() + 1;
}
void
prepare(std::size_t n)
{
prepare_impl(n);
}
};
template<class>
void
chunk_encode_delim::
copy(chunk_encode_delim const& other)
chunk_header::
copy(chunk_header const& other)
{
using boost::asio::buffer_copy;
auto const n =
boost::asio::buffer_size(other.cb_);
buf_ = other.buf_;
cb_ = boost::asio::const_buffer(
&buf_[buf_.size() - n], n);
auto const mb = boost::asio::mutable_buffers_1(
&buf_[sizeof(buf_) - n], n);
cb_ = *mb.begin();
buffer_copy(mb,
boost::asio::const_buffers_1(other.cb_));
}
template<class>
void
chunk_encode_delim::
setup(std::size_t n)
chunk_header::
prepare_impl(std::size_t n)
{
buf_[buf_.size() - 2] = '\r';
buf_[buf_.size() - 1] = '\n';
auto it = to_hex(buf_.end() - 2, n);
auto const end = &buf_[sizeof(buf_)];
auto it = to_hex(end, n);
cb_ = boost::asio::const_buffer{&*it,
static_cast<std::size_t>(
std::distance(it, buf_.end()))};
std::distance(it, end))};
}
/// Returns a buffer sequence holding a CRLF for chunk encoding
inline
boost::asio::const_buffers_1
chunk_crlf()
{
return {"\r\n", 2};
}
/// Returns a buffer sequence holding a CRLF then final chunk
inline
boost::asio::const_buffers_1
chunk_crlf_final()
{
return {"\r\n0\r\n\r\n", 7};
}
/// Returns a buffer sequence holding a final chunk header
inline
boost::asio::const_buffers_1
chunk_final()
{
return {"0\r\n", 3};
}
} // detail
+39
View File
@@ -0,0 +1,39 @@
//
// 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_DETAIL_TYPE_TRAITS_HPP
#define BEAST_HTTP_DETAIL_TYPE_TRAITS_HPP
#include <beast/core/detail/type_traits.hpp>
namespace beast {
namespace http {
template<bool isRequest, class Fields>
struct header;
namespace detail {
template<class T>
class is_header_impl
{
template<bool b, class F>
static std::true_type check(
header<b, F> const*);
static std::false_type check(...);
public:
using type = decltype(check((T*)0));
};
template<class T>
using is_header = typename is_header_impl<T>::type;
} // detail
} // http
} // beast
#endif
+24 -14
View File
@@ -12,24 +12,26 @@
#include <beast/core/error.hpp>
#include <beast/core/multi_buffer.hpp>
#include <beast/http/message.hpp>
#include <boost/optional.hpp>
#include <utility>
namespace beast {
namespace http {
/** A message body represented by a @b DynamicBuffer
/** An HTTP message body represented by a @b DynamicBuffer.
Meets the requirements of @b Body.
*/
template<class DynamicBuffer>
struct basic_dynamic_body
{
/// The type of the `message::body` member
/// The type of the body member when used in a message.
using value_type = DynamicBuffer;
#if BEAST_DOXYGEN
private:
#endif
/// The algorithm used when parsing this body.
using reader = implementation_defined;
#else
class reader
{
value_type& body_;
@@ -75,40 +77,48 @@ private:
{
}
};
#endif
#if BEAST_DOXYGEN
/// The algorithm used when serializing this body.
using writer = implementation_defined;
#else
class writer
{
DynamicBuffer const& body_;
public:
using is_deferred = std::false_type;
using const_buffers_type =
typename DynamicBuffer::const_buffers_type;
template<bool isRequest, class Fields>
explicit
writer(message<
isRequest, basic_dynamic_body, Fields> const& m) noexcept
isRequest, basic_dynamic_body, Fields> const& m)
: body_(m.body)
{
}
void
init(error_code& ec) noexcept
init(error_code&)
{
beast::detail::ignore_unused(ec);
}
std::uint64_t
content_length() const noexcept
content_length() const
{
return body_.size();
}
template<class WriteFunction>
bool
write(error_code&, WriteFunction&& wf) noexcept
boost::optional<std::pair<const_buffers_type, bool>>
get(error_code& ec)
{
wf(body_.data());
return true;
return {{body_.data(), false}};
}
};
#endif
};
/** A dynamic message body represented by a @ref multi_buffer
+72
View File
@@ -0,0 +1,72 @@
//
// 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_EMPTY_BODY_HPP
#define BEAST_HTTP_EMPTY_BODY_HPP
#include <beast/config.hpp>
#include <beast/http/message.hpp>
#include <boost/optional.hpp>
namespace beast {
namespace http {
/** An empty message body
Meets the requirements of @b Body.
@note This body type may only be written, not read.
*/
struct empty_body
{
/// The type of the body member when used in a message.
struct value_type
{
};
#if BEAST_DOXYGEN
/// The algorithm used when serializing this body.
using writer = implementation_defined;
#else
struct writer
{
using is_deferred = std::false_type;
using const_buffers_type =
boost::asio::null_buffers;
template<bool isRequest, class Fields>
explicit
writer(message<
isRequest, empty_body, Fields> const&)
{
}
void
init(error_code&)
{
}
std::uint64_t
content_length() const
{
return 0;
}
boost::optional<std::pair<const_buffers_type, bool>>
get(error_code& ec)
{
return boost::none;
}
};
#endif
};
} // http
} // beast
#endif
+11
View File
@@ -33,6 +33,17 @@ enum class error
*/
partial_message,
/** Additional buffers are required.
This error is generated during serialization of HTTP
messages using the @ref buffer_body representation.
It indicates to the caller that an additional buffer
sequence should be placed into the body, or that the
caller should indicate that there are no more bytes
remaining in the body to serialize.
*/
need_more,
/** Buffer maximum exceeded.
This error is returned when reading HTTP content
+1
View File
@@ -41,6 +41,7 @@ public:
default:
case error::end_of_stream: return "end of stream";
case error::partial_message: return "partial message";
case error::need_more: return "need more";
case error::buffer_overflow: return "buffer overflow";
case error::bad_line_ending: return "bad line ending";
case error::bad_method: return "bad method";
+1 -2
View File
@@ -163,8 +163,7 @@ prepare(message<isRequest, Body, Fields>& msg,
"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,
static_assert(is_Writer<Body>::value,
"Writer requirements not met");
detail::prepare_info pi;
detail::prepare_content_length(pi, msg,
File diff suppressed because it is too large Load Diff
+25 -13
View File
@@ -13,25 +13,27 @@
#include <beast/http/message.hpp>
#include <beast/core/detail/type_traits.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/optional.hpp>
#include <memory>
#include <string>
#include <utility>
namespace beast {
namespace http {
/** A Body represented by a std::string.
/** An HTTP message body represented by a `std::string`.
Meets the requirements of @b Body.
*/
struct string_body
{
/// The type of the `message::body` member
/// The type of the body member when used in a message.
using value_type = std::string;
#if BEAST_DOXYGEN
private:
#endif
/// The algorithm used when parsing this body.
using reader = implementation_defined;
#else
class reader
{
value_type& body_;
@@ -88,40 +90,50 @@ private:
body_.resize(len_);
}
};
#endif
#if BEAST_DOXYGEN
/// The algorithm used when serializing this body.
using writer = implementation_defined;
#else
class writer
{
value_type const& body_;
public:
using is_deferred = std::false_type;
using const_buffers_type =
boost::asio::const_buffers_1;
template<bool isRequest, class Fields>
explicit
writer(message<
isRequest, string_body, Fields> const& msg) noexcept
isRequest, string_body, Fields> const& msg)
: body_(msg.body)
{
}
void
init(error_code& ec) noexcept
init(error_code& ec)
{
beast::detail::ignore_unused(ec);
}
std::uint64_t
content_length() const noexcept
content_length() const
{
return body_.size();
}
template<class WriteFunction>
bool
write(error_code&, WriteFunction&& wf) noexcept
boost::optional<std::pair<const_buffers_type, bool>>
get(error_code& ec)
{
wf(boost::asio::buffer(body_));
return true;
return {{const_buffers_type{
body_.data(), body_.size()}, false}};
}
};
#endif
};
} // http
+380 -83
View File
@@ -9,127 +9,424 @@
#define BEAST_HTTP_WRITE_HPP
#include <beast/config.hpp>
#include <beast/core/buffer_cat.hpp>
#include <beast/core/consuming_buffers.hpp>
#include <beast/core/multi_buffer.hpp>
#include <beast/http/message.hpp>
#include <beast/http/detail/chunk_encode.hpp>
#include <beast/core/error.hpp>
#include <beast/core/async_result.hpp>
#include <beast/core/string_view.hpp>
#include <boost/variant.hpp>
#include <limits>
#include <memory>
#include <ostream>
#include <type_traits>
#include <utility>
namespace beast {
namespace http {
/** Write a HTTP/1 header to a stream.
/** A chunk decorator which does nothing.
This function is used to synchronously write a header to
a stream. The call will block until one of the following
conditions is true:
When selected as a chunk decorator, objects of this type
affect the output of messages specifying chunked
transfer encodings as follows:
@li The entire header is written.
@li chunk headers will have empty chunk extensions, and
@li An error occurs.
@li final chunks will have an empty set of trailers.
This operation is implemented in terms of one or more calls
to the stream's `write_some` function.
Regardless of the semantic meaning of the header (for example,
specifying "Content-Length: 0" and "Connection: close"),
this function will not return `boost::asio::error::eof`.
@param stream The stream to which the data is to be written.
The type must support the @b SyncWriteStream concept.
@param msg The header to write.
@throws system_error Thrown on failure.
@see @ref serializer
*/
template<class SyncWriteStream,
bool isRequest, class Fields>
void
write(SyncWriteStream& stream,
header<isRequest, Fields> const& msg);
struct empty_decorator
{
template<class ConstBufferSequence>
string_view
operator()(ConstBufferSequence const&) const
{
return {"\r\n"};
}
/** Write a HTTP/1 header to a stream.
string_view
operator()(boost::asio::null_buffers) const
{
return {};
}
};
This function is used to synchronously write a header to
a stream. The call will block until one of the following
conditions is true:
/** Provides stream-oriented HTTP message serialization functionality.
@li The entire header is written.
Objects of this type may be used to perform synchronous or
asynchronous serialization of an HTTP message on a stream.
Unlike functions such as @ref write or @ref async_write,
the stream operations provided here guarantee that bounded
work will be performed. This is accomplished by making one
or more calls to the underlying stream's `write_some` or
`async_write_some` member functions. In order to fully
serialize the message, multiple calls are required.
@li An error occurs.
The ability to incrementally serialize a message, peforming
bounded work at each iteration is useful in many scenarios,
such as:
This operation is implemented in terms of one or more calls
to the stream's `write_some` function.
@li Setting consistent, per-call timeouts
Regardless of the semantic meaning of the header (for example,
specifying "Content-Length: 0" and "Connection: close"),
this function will not return `boost::asio::error::eof`.
@li Efficiently relaying body content from another stream
@param stream The stream to which the data is to be written.
The type must support the @b SyncWriteStream concept.
@li Performing application-layer flow control
@param msg The header to write.
To use this class, construct an instance with the message
to be sent. To make it easier to declare the type, the
helper function @ref make_write_stream is provided:
@param ec Set to the error, if any occurred.
The implementation will automatically perform chunk encoding
if the contents of the message indicate that chunk encoding
is required. If the semantics of the message indicate that
the connection should be closed after the message is sent,
the error delivered from stream operations will be
`boost::asio::error::eof`.
@code
template<class Stream>
void send(Stream& stream, request<string_body> const& msg)
{
serializer<true, string_body, fields> w{msg};
do
{
w.write_some();
}
while(! w.is_done());
}
@endcode
Upon construction, an optional chunk decorator may be
specified. This decorator is a function object called with
each buffer sequence of the body when the chunked transfer
encoding is indicate in the message header. The decorator
will be called with an empty buffer sequence (actually
the type `boost::asio::null_buffers`) to indicate the
final chunk. The decorator may return a string which forms
the chunk extension for chunks, and the field trailers
for the final chunk.
In C++11 the decorator must be declared as a class or
struct with a templated operator() thusly:
@code
// The implementation guarantees that operator()
// will be called only after the view returned by
// any previous calls to operator() are no longer
// needed. The decorator instance is intended to
// manage the lifetime of the storage for all returned
// views.
//
struct decorator
{
// Returns the chunk-extension for each chunk.
// The buffer returned must include a trailing "\r\n",
// and the leading semicolon (";") if one or more
// chunk extensions are specified.
//
template<class ConstBufferSequence>
string_view
operator()(ConstBufferSequence const&) const;
// Returns a set of field trailers for the final chunk.
// Each field should be formatted according to rfc7230
// including the trailing "\r\n" for each field. If
// no trailers are indicated, an empty string is returned.
//
string_view
operator()(boost::asio::null_buffers) const;
};
@endcode
@par Thread Safety
@e Distinct @e objects: Safe.@n
@e Shared @e objects: Unsafe.
@tparam isRequest `true` if the message is a request.
@tparam Body The body type of the message.
@tparam Fields The type of fields in the message.
@tparam Decorator The type of chunk decorator to use.
@tparam Allocator The type of allocator to use.
@see @ref make_write_stream
*/
template<class SyncWriteStream,
bool isRequest, class Fields>
void
write(SyncWriteStream& stream,
header<isRequest, Fields> const& msg,
error_code& ec);
template<
bool isRequest, class Body, class Fields,
class Decorator = empty_decorator,
class Allocator = std::allocator<char>
>
class serializer
{
template<class Stream, class Handler>
class async_op;
/** Write a HTTP/1 header asynchronously to a stream.
enum
{
do_init = 0,
do_header_only = 10,
do_header = 20,
do_body = 40,
do_init_c = 50,
do_header_only_c = 60,
do_header_c = 70,
do_body_c = 90,
do_final_c = 100,
This function is used to asynchronously write a header to
a stream. The function call always returns immediately. The
asynchronous operation will continue until one of the following
conditions is true:
do_complete = 110
};
@li The entire header is written.
void split(bool, std::true_type) {}
void split(bool v, std::false_type) { split_ = v; }
@li An error occurs.
using buffer_type =
basic_multi_buffer<Allocator>;
This operation is implemented in terms of one or more calls to
the stream's `async_write_some` functions, and is known as a
<em>composed operation</em>. The program must ensure that the
stream performs no other write operations until this operation
completes.
using is_deferred =
typename Body::writer::is_deferred;
Regardless of the semantic meaning of the header (for example,
specifying "Content-Length: 0" and "Connection: close"),
this function will not return `boost::asio::error::eof`.
using cb0_t = consuming_buffers<buffers_view<
typename buffer_type::const_buffers_type, // header
typename Body::writer::const_buffers_type>>;// body
@param stream The stream to which the data is to be written.
The type must support the @b AsyncWriteStream concept.
using cb1_t = consuming_buffers<
typename Body::writer::const_buffers_type>; // body
@param msg The header to write. The object must remain valid
at least until the completion handler is called; ownership is
not transferred.
using ch0_t = consuming_buffers<buffers_view<
typename buffer_type::const_buffers_type, // header
detail::chunk_header, // chunk-header
boost::asio::const_buffers_1, // chunk-ext+\r\n
typename Body::writer::const_buffers_type, // body
boost::asio::const_buffers_1>>; // crlf
using ch1_t = consuming_buffers<buffers_view<
detail::chunk_header, // chunk-header
boost::asio::const_buffers_1, // chunk-ext+\r\n
typename Body::writer::const_buffers_type, // body
boost::asio::const_buffers_1>>; // crlf
@param handler The handler to be called when the operation
completes. Copies will be made of the handler as required.
The equivalent function signature of the handler must be:
@code void handler(
error_code const& error // result of operation
); @endcode
Regardless of whether the asynchronous operation completes
immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a
manner equivalent to using `boost::asio::io_service::post`.
*/
template<class AsyncWriteStream,
bool isRequest, class Fields,
class WriteHandler>
using ch2_t = consuming_buffers<buffers_view<
boost::asio::const_buffers_1, // chunk-final
boost::asio::const_buffers_1, // trailers
boost::asio::const_buffers_1>>; // crlf
message<isRequest, Body, Fields> const& m_;
Decorator d_;
std::size_t limit_ =
(std::numeric_limits<std::size_t>::max)();
boost::optional<typename Body::writer> wr_;
buffer_type b_;
boost::variant<boost::blank,
cb0_t, cb1_t, ch0_t, ch1_t, ch2_t> v_;
int s_;
bool split_ = is_deferred::value;
bool header_done_ = false;
bool chunked_;
bool close_;
bool more_;
public:
/** Constructor
@param msg The message to serialize. The message object
must remain valid for the lifetime of the write stream.
@param decorator An optional decorator to use.
@param alloc An optional allocator to use.
*/
explicit
serializer(message<isRequest, Body, Fields> const& msg,
Decorator const& decorator = Decorator{},
Allocator const& alloc = Allocator{});
/// Returns the maximum number of bytes that will be written in each operation
std::size_t
limit() const
{
return limit_;
}
/** Returns `true` if we will pause after writing the header.
*/
bool
split() const
{
return split_;
}
/** Set whether the header and body are written separately.
When the split feature is enabled, the implementation will
write only the octets corresponding to the serialized header
first. If the header has already been written, this function
will have no effect on output. This function should be called
before any writes take place, otherwise the behavior is
undefined.
*/
void
split(bool v)
{
split(v, is_deferred{});
}
/** Set the maximum number of bytes that will be written in each operation.
By default, there is no limit on the size of writes.
@param n The byte limit. This must be greater than zero.
*/
void
limit(std::size_t n)
{
limit_ = n;
}
/** Return `true` if serialization of the header is complete.
This function indicates whether or not all octets
corresponding to the serialized representation of the
header have been successfully delivered to the stream.
*/
bool
is_header_done() const
{
return header_done_;
}
/** Return `true` if serialization is complete
The operation is complete when all octets corresponding
to the serialized representation of the message have been
successfully delivered to the stream.
*/
bool
is_done() const
{
return s_ == do_complete;
}
/** Write some serialized message data to the stream.
This function is used to write serialized message data to the
stream. The function call will block until one of the following
conditions is true:
@li One or more bytes have been transferred.
@li An error occurs on the stream.
In order to completely serialize a message, this function
should be called until @ref is_done returns `true`. If the
semantics of the message indicate that the connection should
be closed after the message is sent, the error delivered from
this call will be `boost::asio::error::eof`.
@param stream The stream to write to. This type must
satisfy the requirements of @b SyncWriteStream.
@throws system_error Thrown on failure.
*/
template<class SyncWriteStream>
void
write_some(SyncWriteStream& stream);
/** Write some serialized message data to the stream.
This function is used to write serialized message data to the
stream. The function call will block until one of the following
conditions is true:
@li One or more bytes have been transferred.
@li An error occurs on the stream.
In order to completely serialize a message, this function
should be called until @ref is_done returns `true`. If the
semantics of the message indicate that the connection should
be closed after the message is sent, the error delivered from
this call will be `boost::asio::error::eof`.
@param stream The stream to write to. This type must
satisfy the requirements of @b SyncWriteStream.
@param ec Set to indicate what error occurred, if any.
*/
template<class SyncWriteStream>
void
write_some(SyncWriteStream& stream, error_code &ec);
/** Start an asynchronous write of some serialized message data.
This function is used to asynchronously write serialized
message data to the stream. The function call always returns
immediately. The asynchronous operation will continue until
one of the following conditions is true:
@li One or more bytes have been transferred.
@li An error occurs on the stream.
In order to completely serialize a message, this function
should be called until @ref is_done returns `true`. If the
semantics of the message indicate that the connection should
be closed after the message is sent, the error delivered from
this call will be `boost::asio::error::eof`.
@param stream The stream to write to. This type must
satisfy the requirements of @b SyncWriteStream.
@param handler The handler to be called when the request
completes. Copies will be made of the handler as required. The
equivalent function signature of the handler must be:
@code void handler(
error_code const& ec // Result of operation
); @endcode
Regardless of whether the asynchronous operation completes
immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a
manner equivalent to using `boost::asio::io_service::post`.
*/
template<class AsyncWriteStream, class WriteHandler>
#if BEAST_DOXYGEN
void_or_deduced
#else
async_return_type<
WriteHandler, void(error_code)>
async_return_type<WriteHandler, void(error_code)>
#endif
async_write(AsyncWriteStream& stream,
header<isRequest, Fields> const& msg,
async_write_some(AsyncWriteStream& stream,
WriteHandler&& handler);
};
/** Return a stateful object to serialize an HTTP message.
This convenience function makes it easier to declare
the variable for a given message.
*/
template<
bool isRequest, class Body, class Fields,
class Decorator = empty_decorator,
class Allocator = std::allocator<char>>
inline
serializer<isRequest, Body, Fields,
typename std::decay<Decorator>::type,
typename std::decay<Allocator>::type>
make_write_stream(message<isRequest, Body, Fields> const& m,
Decorator const& decorator = Decorator{},
Allocator const& allocator = Allocator{})
{
return serializer<isRequest, Body, Fields,
typename std::decay<Decorator>::type,
typename std::decay<Allocator>::type>{
m, decorator, allocator};
}
//------------------------------------------------------------------------------
+19 -11
View File
@@ -9,6 +9,7 @@
#define BEAST_WEBSOCKET_IMPL_ACCEPT_IPP
#include <beast/websocket/detail/type_traits.hpp>
#include <beast/http/empty_body.hpp>
#include <beast/http/message.hpp>
#include <beast/http/header_parser.hpp>
#include <beast/http/read.hpp>
@@ -39,7 +40,8 @@ class stream<NextLayer>::response_op
{
bool cont;
stream<NextLayer>& ws;
http::header<false, http::fields> res;
http::message<false,
http::empty_body, http::fields> res;
int state = 0;
template<class Fields, class Decorator>
@@ -188,8 +190,8 @@ class stream<NextLayer>::accept_op
template<class Buffers>
data(Handler& handler, stream<NextLayer>& ws_,
Buffers const& buffers,
Decorator const& decorator_)
Buffers const& buffers,
Decorator const& decorator_)
: ws(ws_)
, decorator(decorator_)
{
@@ -365,7 +367,8 @@ accept_ex(ResponseDecorator const& decorator, error_code& ec)
template<class NextLayer>
template<class ConstBufferSequence>
void
typename std::enable_if<! http::detail::is_header<
ConstBufferSequence>::value>::type
stream<NextLayer>::
accept(ConstBufferSequence const& buffers)
{
@@ -383,7 +386,8 @@ accept(ConstBufferSequence const& buffers)
template<class NextLayer>
template<
class ConstBufferSequence, class ResponseDecorator>
void
typename std::enable_if<! http::detail::is_header<
ConstBufferSequence>::value>::type
stream<NextLayer>::
accept_ex(ConstBufferSequence const& buffers,
ResponseDecorator const &decorator)
@@ -404,7 +408,8 @@ accept_ex(ConstBufferSequence const& buffers,
template<class NextLayer>
template<class ConstBufferSequence>
void
typename std::enable_if<! http::detail::is_header<
ConstBufferSequence>::value>::type
stream<NextLayer>::
accept(ConstBufferSequence const& buffers, error_code& ec)
{
@@ -425,7 +430,8 @@ accept(ConstBufferSequence const& buffers, error_code& ec)
template<class NextLayer>
template<
class ConstBufferSequence, class ResponseDecorator>
void
typename std::enable_if<! http::detail::is_header<
ConstBufferSequence>::value>::type
stream<NextLayer>::
accept_ex(ConstBufferSequence const& buffers,
ResponseDecorator const& decorator, error_code& ec)
@@ -641,8 +647,9 @@ async_accept_ex(ResponseDecorator const& decorator,
template<class NextLayer>
template<class ConstBufferSequence, class AcceptHandler>
async_return_type<
AcceptHandler, void(error_code)>
typename std::enable_if<
! http::detail::is_header<ConstBufferSequence>::value,
async_return_type<AcceptHandler, void(error_code)>>::type
stream<NextLayer>::
async_accept(ConstBufferSequence const& buffers,
AcceptHandler&& handler)
@@ -664,8 +671,9 @@ async_accept(ConstBufferSequence const& buffers,
template<class NextLayer>
template<class ConstBufferSequence,
class ResponseDecorator, class AcceptHandler>
async_return_type<
AcceptHandler, void(error_code)>
typename std::enable_if<
! http::detail::is_header<ConstBufferSequence>::value,
async_return_type<AcceptHandler, void(error_code)>>::type
stream<NextLayer>::
async_accept_ex(ConstBufferSequence const& buffers,
ResponseDecorator const& decorator,
+3 -1
View File
@@ -9,6 +9,7 @@
#define BEAST_WEBSOCKET_IMPL_HANDSHAKE_IPP
#include <beast/websocket/detail/type_traits.hpp>
#include <beast/http/empty_body.hpp>
#include <beast/http/message.hpp>
#include <beast/http/read.hpp>
#include <beast/http/write.hpp>
@@ -38,7 +39,8 @@ class stream<NextLayer>::handshake_op
stream<NextLayer>& ws;
response_type* res_p;
detail::sec_ws_key_type key;
request_type req;
http::message<true,
http::empty_body, http::fields> req;
response_type res;
int state = 0;
+2 -2
View File
@@ -196,10 +196,10 @@ build_request(detail::sec_ws_key_type& key,
}
template<class NextLayer>
template<class Decorator>
template<class Fields, class Decorator>
response_type
stream<NextLayer>::
build_response(request_type const& req,
build_response(http::header<true, Fields> const& req,
Decorator const& decorator)
{
auto const decorate =
+33 -9
View File
@@ -12,8 +12,10 @@
#include <beast/websocket/option.hpp>
#include <beast/websocket/detail/hybi13.hpp>
#include <beast/websocket/detail/stream_base.hpp>
#include <beast/http/empty_body.hpp>
#include <beast/http/message.hpp>
#include <beast/http/string_body.hpp>
#include <beast/http/detail/type_traits.hpp>
#include <beast/core/async_result.hpp>
#include <beast/core/buffered_read_stream.hpp>
#include <beast/core/string_view.hpp>
@@ -28,12 +30,12 @@ namespace beast {
namespace websocket {
/// The type of object holding HTTP Upgrade requests
using request_type = http::header<true, http::fields>;
using request_type =
http::message<true, http::empty_body, http::fields>;
/// The type of object holding HTTP Upgrade responses
using response_type =
//http::response_header;
http::response<http::string_body, http::fields>;
http::message<false, http::string_body, http::fields>;
/** Information about a WebSocket frame.
@@ -473,7 +475,12 @@ public:
@throws system_error Thrown on failure.
*/
template<class ConstBufferSequence>
#if BEAST_DOXYGEN
void
#else
typename std::enable_if<! http::detail::is_header<
ConstBufferSequence>::value>::type
#endif
accept(ConstBufferSequence const& buffers);
/** Read and respond to a WebSocket HTTP Upgrade request.
@@ -516,7 +523,12 @@ public:
*/
template<class ConstBufferSequence,
class ResponseDecorator>
#if BEAST_DOXYGEN
void
#else
typename std::enable_if<! http::detail::is_header<
ConstBufferSequence>::value>::type
#endif
accept_ex(ConstBufferSequence const& buffers,
ResponseDecorator const& decorator);
@@ -550,7 +562,12 @@ public:
@param ec Set to indicate what error occurred, if any.
*/
template<class ConstBufferSequence>
#if BEAST_DOXYGEN
void
#else
typename std::enable_if<! http::detail::is_header<
ConstBufferSequence>::value>::type
#endif
accept(ConstBufferSequence const& buffers, error_code& ec);
/** Read and respond to a WebSocket HTTP Upgrade request.
@@ -593,7 +610,12 @@ public:
*/
template<class ConstBufferSequence,
class ResponseDecorator>
#if BEAST_DOXYGEN
void
#else
typename std::enable_if<! http::detail::is_header<
ConstBufferSequence>::value>::type
#endif
accept_ex(ConstBufferSequence const& buffers,
ResponseDecorator const& decorator,
error_code& ec);
@@ -1085,8 +1107,9 @@ public:
#if BEAST_DOXYGEN
void_or_deduced
#else
async_return_type<
AcceptHandler, void(error_code)>
typename std::enable_if<
! http::detail::is_header<ConstBufferSequence>::value,
async_return_type<AcceptHandler, void(error_code)>>::type
#endif
async_accept(ConstBufferSequence const& buffers,
AcceptHandler&& handler);
@@ -1153,8 +1176,9 @@ public:
#if BEAST_DOXYGEN
void_or_deduced
#else
async_return_type<
AcceptHandler, void(error_code)>
typename std::enable_if<
! http::detail::is_header<ConstBufferSequence>::value,
async_return_type<AcceptHandler, void(error_code)>>::type
#endif
async_accept_ex(ConstBufferSequence const& buffers,
ResponseDecorator const& decorator,
@@ -2975,9 +2999,9 @@ private:
string_view const& target,
Decorator const& decorator);
template<class Decorator>
template<class Fields, class Decorator>
response_type
build_response(request_type const& req,
build_response(http::header<true, Fields> const& req,
Decorator const& decorator);
void