Refactor prepare (API Change)

This commit is contained in:
Vinnie Falco
2017-06-05 21:11:33 -07:00
parent 2af19481a9
commit 2df6783468
26 changed files with 543 additions and 362 deletions

View File

@ -8,6 +8,7 @@ API Changes:
* Remove header_parser
* Add verb to on_request for parsers
* Refactor prepare
--------------------------------------------------------------------------------

View File

@ -154,7 +154,7 @@ objects:
```
][
```
200 OK HTTP/1.1\r\n
HTTP/1.1 200 OK\r\n
Server: Beast\r\n
Content-Length: 13\r\n
\r\n

View File

@ -62,7 +62,6 @@
<member><link linkend="beast.ref.http__make_serializer">make_serializer</link></member>
<member><link linkend="beast.ref.http__obsolete_reason">obsolete_reason</link></member>
<member><link linkend="beast.ref.http__operator_ls_">operator&lt;&lt;</link></member>
<member><link linkend="beast.ref.http__prepare">prepare</link></member>
<member><link linkend="beast.ref.http__read">read</link></member>
<member><link linkend="beast.ref.http__read_header">read_header</link></member>
<member><link linkend="beast.ref.http__read_some">read_some</link></member>

View File

@ -449,9 +449,10 @@ do_head_request(
@param transform The header transformation to apply. The function will
be called with this signature:
@code
void transform(
header<isRequest, Fields>&, // The header to transform
error_code&); // Set to the error, if any
template<class Body>
void transform(message<
isRequest, Body, Fields>&, // The message to transform
error_code&); // Set to the error, if any
@endcode
@param ec Set to the error if any occurred.
@ -496,8 +497,7 @@ relay(
return;
// Apply the caller's header tranformation
// base() returns a reference to the header portion of the message.
transform(p.get().base(), ec);
transform(p.get(), ec);
if(ec)
return;

View File

@ -24,6 +24,16 @@ struct file_body
{
using value_type = std::string;
/// Returns the content length of the body in a message.
template<bool isRequest, class Fields>
static
std::uint64_t
size(
message<isRequest, file_body, Fields> const& m)
{
return boost::filesystem::file_size(m.body.c_str());
}
class reader
{
std::uint64_t size_ = 0;
@ -67,12 +77,6 @@ struct file_body
size_ = boost::filesystem::file_size(path_);
}
std::uint64_t
content_length() const
{
return size_;
}
boost::optional<std::pair<const_buffers_type, bool>>
get(error_code& ec)
{

View File

@ -239,7 +239,7 @@ private:
res.insert("Server", "http_async_server");
res.insert("Content-Type", "text/html");
res.body = "The file '" + path + "' was not found";
prepare(res);
res.prepare();
async_write(sock_, std::move(res),
std::bind(&peer::on_write, shared_from_this(),
std::placeholders::_1));
@ -253,7 +253,7 @@ private:
res.insert("Server", "http_async_server");
res.insert("Content-Type", mime_type(path));
res.body = path;
prepare(res);
res.prepare();
async_write(sock_, std::move(res),
std::bind(&peer::on_write, shared_from_this(),
std::placeholders::_1));
@ -267,7 +267,7 @@ private:
res.insert("Content-Type", "text/html");
res.body =
std::string{"An internal error occurred"} + e.what();
prepare(res);
res.prepare();
async_write(sock_, std::move(res),
std::bind(&peer::on_write, shared_from_this(),
std::placeholders::_1));

View File

@ -38,12 +38,12 @@ int main(int, char const*[])
auto ep = sock.remote_endpoint();
request<string_body> req;
req.method(verb::get);
req.target("/");
req.version = 11;
req.target("/");
req.insert("Host", host + std::string(":") +
boost::lexical_cast<std::string>(ep.port()));
req.insert("User-Agent", "beast/http");
prepare(req);
req.prepare();
write(sock, req);
response<string_body> res;
beast::multi_buffer b;

View File

@ -32,7 +32,7 @@ int main()
req.replace("Host", host + ":" +
boost::lexical_cast<std::string>(sock.remote_endpoint().port()));
req.replace("User-Agent", "Beast");
beast::http::prepare(req);
req.prepare();
beast::http::write(sock, req);
// Receive and print HTTP response using beast

View File

@ -169,7 +169,7 @@ private:
res.insert("Server", "http_sync_server");
res.insert("Content-Type", "text/html");
res.body = "The file '" + path + "' was not found";
prepare(res);
res.prepare();
write(sock, res, ec);
if(ec)
break;
@ -184,7 +184,7 @@ private:
res.insert("Server", "http_sync_server");
res.insert("Content-Type", mime_type(path));
res.body = path;
prepare(res);
res.prepare();
write(sock, res, ec);
if(ec)
break;
@ -199,7 +199,7 @@ private:
res.insert("Content-Type", "text/html");
res.body =
std::string{"An internal error occurred: "} + e.what();
prepare(res);
res.prepare();
write(sock, res, ec);
if(ec)
break;

View File

@ -41,7 +41,7 @@ int main()
req.insert("Host", host + ":" +
boost::lexical_cast<std::string>(sock.remote_endpoint().port()));
req.insert("User-Agent", "Beast");
beast::http::prepare(req);
req.prepare();
beast::http::write(stream, req);
// Receive and print HTTP response using Beast

View File

@ -12,9 +12,11 @@
#include <beast/http/basic_parser.hpp>
#include <beast/http/buffer_body.hpp>
#include <beast/http/connection.hpp>
#include <beast/http/dynamic_body.hpp>
#include <beast/http/empty_body.hpp>
#include <beast/http/error.hpp>
#include <beast/http/field.hpp>
#include <beast/http/fields.hpp>
#include <beast/http/message.hpp>
#include <beast/http/parser.hpp>

View File

@ -0,0 +1,88 @@
//
// 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_CONNECTION_HPP
#define BEAST_HTTP_CONNECTION_HPP
#include <beast/config.hpp>
namespace beast {
namespace http {
/// The type of @ref connection::close
struct close_t {};
/// The type of @ref connection::upgrade
struct upgrade_t {};
/// The type of @ref connection::keep_alive
struct keep_alive_t {};
namespace detail {
template<class = void>
struct connection_impl
{
static close_t constexpr close{};
static keep_alive_t constexpr keep_alive{};
static upgrade_t constexpr upgrade{};
};
template<class _>
constexpr
close_t
connection_impl<_>::close;
template<class _>
constexpr
keep_alive_t
connection_impl<_>::keep_alive;
template<class _>
constexpr
upgrade_t
connection_impl<_>::upgrade;
} // detail
/** HTTP/1 Connection prepare options.
Each value is an individual settings, they can be combined.
@par Example
@code
request<empty_body> req;
req.version = 11;
req.method(verb::upgrade);
req.target("/");
req.insert("User-Agent", "Beast");
req.prepare(connection::close, connection::upgrade);
@endcode
@note These values are used with @ref message::prepare.
*/
struct connection
#if ! BEAST_DOXYGEN
: detail::connection_impl<>
#endif
{
#if BEAST_DOXYGEN
/// Specify connection close semantics.
static close_t constexpr close;
/// Specify connection keep-alive semantics.
static keep_alive_t constexpr keep_alive;
/// Specify Connection: upgrade.
static upgrade_t constexpr upgrade;
#endif
};
} // http
} // beast
#endif

View File

@ -59,19 +59,21 @@ struct has_value_type<T, beast::detail::void_t<
typename T::value_type
> > : std::true_type {};
template<class T, class = beast::detail::void_t<>>
struct has_content_length : std::false_type {};
/** Determine if a @b Body type has a size
template<class T>
struct has_content_length<T, beast::detail::void_t<decltype(
std::declval<T>().content_length()
)> > : std::true_type
{
static_assert(std::is_convertible<
decltype(std::declval<T>().content_length()),
std::uint64_t>::value,
"Writer::content_length requirements not met");
};
This metafunction is equivalent to `std::true_type` if
Body contains a static member function called `content_lengeth`.
*/
template<class T, class M, class = void>
struct is_body_sized : std::false_type {};
template<class T, class M>
struct is_body_sized<T, M, beast::detail::void_t<
typename T::value_type,
decltype(
std::declval<std::uint64_t&>() =
T::size(std::declval<M const&>()),
(void)0)>> : std::true_type {};
} // detail
} // http

View File

@ -28,6 +28,16 @@ struct basic_dynamic_body
/// The type of the body member when used in a message.
using value_type = DynamicBuffer;
/// Returns the content length of this body in a message.
template<bool isRequest, class Fields>
static
std::uint64_t
size(message<isRequest,
basic_dynamic_body, Fields> const& m)
{
return m.body.size();
}
#if BEAST_DOXYGEN
/// The algorithm to obtain buffers representing the body
using reader = implementation_defined;
@ -55,12 +65,6 @@ struct basic_dynamic_body
{
}
std::uint64_t
content_length() const
{
return body_.size();
}
boost::optional<std::pair<const_buffers_type, bool>>
get(error_code& ec)
{

View File

@ -35,6 +35,15 @@ struct empty_body
// for the content length here, set on init()
};
/// Returns the content length of the body in a message.
template<bool isRequest, class Fields>
static
std::uint64_t
size(message<isRequest, empty_body, Fields> const& m)
{
return 0;
}
#if BEAST_DOXYGEN
/// The algorithm to obtain buffers representing the body
using reader = implementation_defined;
@ -58,12 +67,6 @@ struct empty_body
{
}
std::uint64_t
content_length() const
{
return 0;
}
boost::optional<std::pair<const_buffers_type, bool>>
get(error_code& ec)
{

View File

@ -11,6 +11,7 @@
#include <beast/config.hpp>
#include <beast/core/string_view.hpp>
#include <beast/core/detail/ci_char_traits.hpp>
#include <beast/http/connection.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/intrusive/list.hpp>
#include <boost/intrusive/set.hpp>
@ -276,6 +277,11 @@ protected:
void method_impl(string_view s);
void target_impl(string_view s);
void reason_impl(string_view s);
void content_length_impl(std::uint64_t n);
void connection_impl(close_t);
void connection_impl(keep_alive_t);
void connection_impl(upgrade_t);
void chunked_impl();
private:
struct element

View File

@ -8,6 +8,7 @@
#ifndef BEAST_HTTP_IMPL_FIELDS_IPP
#define BEAST_HTTP_IMPL_FIELDS_IPP
#include <beast/core/static_string.hpp>
#include <beast/http/detail/rfc7230.hpp>
#include <boost/throw_exception.hpp>
#include <algorithm>
@ -74,7 +75,7 @@ target_impl(string_view s)
if(s.empty())
this->erase(":target");
else
return this->replace(":target", s);
this->replace(":target", s);
}
template<class Allocator>
@ -89,6 +90,73 @@ reason_impl(string_view s)
this->replace(":reason", s);
}
template<class Allocator>
inline
void
basic_fields<Allocator>::
content_length_impl(std::uint64_t n)
{
this->erase("Content-Length");
this->insert("Content-Length",
to_static_string(n));
}
template<class Allocator>
inline
void
basic_fields<Allocator>::
connection_impl(close_t)
{
auto it = find("Connection");
if(it == end())
this->insert("Connection", "close");
else
this->replace("Connection",
it->value().to_string() + ", close");
}
template<class Allocator>
inline
void
basic_fields<Allocator>::
connection_impl(keep_alive_t)
{
auto it = find("Connection");
if(it == end())
this->insert("Connection", "keep-alive");
else
this->replace("Connection",
it->value().to_string() + ", keep-alive");
}
template<class Allocator>
inline
void
basic_fields<Allocator>::
connection_impl(upgrade_t)
{
auto it = find("Connection");
if(it == end())
this->insert("Connection", "upgrade");
else
this->replace("Connection",
it->value().to_string() + ", upgrade");
}
template<class Allocator>
inline
void
basic_fields<Allocator>::
chunked_impl()
{
auto it = find("Transfer-Encoding");
if(it == end())
this->insert("Transfer-Encoding", "chunked");
else
this->replace("Transfer-Encoding",
it->value().to_string() + ", chunked");
}
//------------------------------------------------------------------------------
template<class Allocator>

View File

@ -9,14 +9,10 @@
#define BEAST_HTTP_IMPL_MESSAGE_IPP
#include <beast/core/error.hpp>
#include <beast/core/string_view.hpp>
#include <beast/http/type_traits.hpp>
#include <beast/http/rfc7230.hpp>
#include <beast/core/detail/ci_char_traits.hpp>
#include <beast/core/detail/type_traits.hpp>
#include <boost/assert.hpp>
#include <boost/optional.hpp>
#include <boost/throw_exception.hpp>
#include <stdexcept>
namespace beast {
@ -71,6 +67,170 @@ get_reason() const
return obsolete_reason(result_);
}
//------------------------------------------------------------------------------
namespace detail {
} // detail
template<bool isRequest, class Body, class Fields>
bool
message<isRequest, Body, Fields>::
chunked() const
{
auto const it0 = this->find("Transfer-Encoding");
if(it0 == this->end())
return false;
token_list value{*it0};
for(auto it = value.begin(); it != value.end();)
{
auto cur = it++;
if(it == value.end())
return *cur == "chunked";
}
return false;
}
template<bool isRequest, class Body, class Fields>
boost::optional<std::uint64_t>
message<isRequest, Body, Fields>::
size() const
{
static_assert(is_body_reader<Body>::value,
"BodyReader requirements not met");
return size(detail::is_body_sized<
Body, decltype(*this)>{});
}
template<bool isRequest, class Body, class Fields>
template<class... Args>
void
message<isRequest, Body, Fields>::
prepare(Args const&... args)
{
static_assert(is_body_reader<Body>::value,
"BodyReader requirements not met");
unsigned f = 0;
prepare_opt(f, args...);
if(f & 1)
{
if(this->version > 10)
this->connection_impl(connection::close);
}
if(f & 2)
{
if(this->version < 11)
this->connection_impl(connection::keep_alive);
}
if(f & 4)
{
if(this->version < 11)
BOOST_THROW_EXCEPTION(std::invalid_argument{
"invalid connection upgrade"});
this->connection_impl(connection::upgrade);
}
prepare_payload(typename header<
isRequest, Fields>::is_request{});
}
template<bool isRequest, class Body, class Fields>
template<class Arg, class... Args>
inline
void
message<isRequest, Body, Fields>::
prepare_opt(unsigned& f,
Arg const& arg, Args const&... args)
{
prepare_opt(f, arg);
prepare_opt(f, args...);
}
template<bool isRequest, class Body, class Fields>
inline
void
message<isRequest, Body, Fields>::
prepare_opt(unsigned& f, close_t)
{
f |= 1;
}
template<bool isRequest, class Body, class Fields>
inline
void
message<isRequest, Body, Fields>::
prepare_opt(unsigned& f, keep_alive_t)
{
f |= 2;
}
template<bool isRequest, class Body, class Fields>
inline
void
message<isRequest, Body, Fields>::
prepare_opt(unsigned& f, upgrade_t)
{
f |= 4;
}
template<bool isRequest, class Body, class Fields>
inline
void
message<isRequest, Body, Fields>::
prepare_payload(std::true_type)
{
auto const n = size();
if(this->method_ == verb::trace &&
(! n || *n > 0))
BOOST_THROW_EXCEPTION(std::invalid_argument{
"invalid request body"});
if(n)
{
if(*n > 0 ||
this->method_ == verb::options ||
this->method_ == verb::put ||
this->method_ == verb::post
)
{
this->content_length_impl(*n);
}
}
else if(this->version >= 11)
{
this->chunked_impl();
}
}
template<bool isRequest, class Body, class Fields>
inline
void
message<isRequest, Body, Fields>::
prepare_payload(std::false_type)
{
auto const n = size();
if((status_class(this->result_) ==
status_class::informational ||
this->result_ == status::no_content ||
this->result_ == status::not_modified))
{
if(! n || *n > 0)
// The response body MUST BE empty for this case
BOOST_THROW_EXCEPTION(std::invalid_argument{
"invalid response body"});
}
if(n)
this->content_length_impl(*n);
else if(this->version >= 11)
this->chunked_impl();
}
//------------------------------------------------------------------------------
template<class Fields>
void
swap(
@ -106,7 +266,9 @@ swap(
message<isRequest, Body, Fields>& m2)
{
using std::swap;
swap(m1.base(), m2.base());
swap(
static_cast<header<isRequest, Fields>&>(m1),
static_cast<header<isRequest, Fields>&>(m2));
swap(m1.body, m2.body);
}
@ -138,176 +300,6 @@ is_upgrade(header<isRequest, Fields> const& msg)
return false;
}
namespace detail {
struct prepare_info
{
boost::optional<connection> connection_value;
boost::optional<std::uint64_t> content_length;
};
template<bool isRequest, class Body, class Fields>
inline
void
prepare_options(prepare_info& pi,
message<isRequest, Body, Fields>& msg)
{
beast::detail::ignore_unused(pi, msg);
}
template<bool isRequest, class Body, class Fields>
void
prepare_option(prepare_info& pi,
message<isRequest, Body, Fields>& msg,
connection value)
{
beast::detail::ignore_unused(msg);
pi.connection_value = value;
}
template<
bool isRequest, class Body, class Fields,
class Opt, class... Opts>
void
prepare_options(prepare_info& pi,
message<isRequest, Body, Fields>& msg,
Opt&& opt, Opts&&... opts)
{
prepare_option(pi, msg, opt);
prepare_options(pi, msg,
std::forward<Opts>(opts)...);
}
template<bool isRequest, class Body, class Fields>
void
prepare_content_length(prepare_info& pi,
message<isRequest, Body, Fields> const& msg,
std::true_type)
{
typename Body::reader w{msg};
// VFALCO This is a design problem!
error_code ec;
w.init(ec);
if(ec)
BOOST_THROW_EXCEPTION(system_error{ec});
pi.content_length = w.content_length();
}
template<bool isRequest, class Body, class Fields>
void
prepare_content_length(prepare_info& pi,
message<isRequest, Body, Fields> const& msg,
std::false_type)
{
beast::detail::ignore_unused(msg);
pi.content_length = boost::none;
}
} // detail
template<
bool isRequest, class Body, class Fields,
class... Options>
void
prepare(message<isRequest, Body, Fields>& msg,
Options&&... options)
{
// VFALCO TODO
static_assert(is_body<Body>::value,
"Body requirements not met");
static_assert(is_body_reader<Body>::value,
"BodyReader requirements not met");
detail::prepare_info pi;
detail::prepare_content_length(pi, msg,
detail::has_content_length<typename Body::reader>{});
detail::prepare_options(pi, msg,
std::forward<Options>(options)...);
if(msg.exists("Connection"))
BOOST_THROW_EXCEPTION(std::invalid_argument{
"prepare called with Connection field set"});
if(msg.exists("Content-Length"))
BOOST_THROW_EXCEPTION(std::invalid_argument{
"prepare called with Content-Length field set"});
if(token_list{msg["Transfer-Encoding"]}.exists("chunked"))
BOOST_THROW_EXCEPTION(std::invalid_argument{
"prepare called with Transfer-Encoding: chunked set"});
if(pi.connection_value != connection::upgrade)
{
if(pi.content_length)
{
struct set_field
{
void
operator()(message<true, Body, Fields>& msg,
detail::prepare_info const& pi) const
{
using beast::detail::ci_equal;
if(*pi.content_length > 0 ||
msg.method() == verb::post)
{
msg.insert(
"Content-Length", *pi.content_length);
}
}
void
operator()(message<false, Body, Fields>& msg,
detail::prepare_info const& pi) const
{
if(to_status_class(msg.result()) != status_class::informational &&
msg.result() != status::no_content &&
msg.result() != status::not_modified)
{
msg.insert(
"Content-Length", *pi.content_length);
}
}
};
set_field{}(msg, pi);
}
else if(msg.version >= 11)
{
msg.insert("Transfer-Encoding", "chunked");
}
}
auto const content_length =
msg.exists("Content-Length");
if(pi.connection_value)
{
switch(*pi.connection_value)
{
case connection::upgrade:
msg.insert("Connection", "upgrade");
break;
case connection::keep_alive:
if(msg.version < 11)
{
if(content_length)
msg.insert("Connection", "keep-alive");
}
break;
case connection::close:
if(msg.version >= 11)
msg.insert("Connection", "close");
break;
}
}
// rfc7230 6.7.
if(msg.version < 11 && token_list{
msg["Connection"]}.exists("upgrade"))
BOOST_THROW_EXCEPTION(std::invalid_argument{
"invalid version for Connection: upgrade"});
}
} // http
} // beast

View File

@ -29,8 +29,7 @@ parser<isRequest, Body, Fields>::
parser(parser<isRequest, OtherBody, Fields>&& parser,
Args&&... args)
: base_type(std::move(parser))
, m_(parser.release().base(),
std::forward<Args>(args)...)
, m_(parser.release(), std::forward<Args>(args)...)
{
if(parser.wr_)
BOOST_THROW_EXCEPTION(std::invalid_argument{

View File

@ -9,11 +9,14 @@
#define BEAST_HTTP_MESSAGE_HPP
#include <beast/config.hpp>
#include <beast/http/connection.hpp>
#include <beast/http/fields.hpp>
#include <beast/http/verb.hpp>
#include <beast/http/status.hpp>
#include <beast/http/type_traits.hpp>
#include <beast/core/string_view.hpp>
#include <beast/core/detail/integer_sequence.hpp>
#include <boost/optional.hpp>
#include <boost/throw_exception.hpp>
#include <memory>
#include <stdexcept>
@ -49,12 +52,7 @@ struct header<true, Fields> : Fields
#endif
{
/// Indicates if the header is a request or response.
#if BEAST_DOXYGEN
static bool constexpr is_request = isRequest;
#else
static bool constexpr is_request = true;
#endif
using is_request = std::true_type;
/// The type representing the fields.
using fields_type = Fields;
@ -194,6 +192,9 @@ struct header<true, Fields> : Fields
}
private:
template<bool, class, class>
friend struct message;
template<class T>
friend
void
@ -230,7 +231,7 @@ template<class Fields>
struct header<false, Fields> : Fields
{
/// Indicates if the header is a request or response.
static bool constexpr is_request = false;
using is_request = std::false_type;
/// The type representing the fields.
using fields_type = Fields;
@ -377,6 +378,9 @@ struct header<false, Fields> : Fields
}
private:
template<bool, class, class>
friend struct message;
template<class T>
friend
void
@ -412,7 +416,7 @@ template<bool isRequest, class Body, class Fields = fields>
struct message : header<isRequest, Fields>
{
/// The base class used to hold the header portion of the message.
using base_type = header<isRequest, Fields>;
using header_type = header<isRequest, Fields>;
/** The type providing the body traits.
@ -445,8 +449,8 @@ struct message : header<isRequest, Fields>
*/
template<class... Args>
explicit
message(base_type&& base, Args&&... args)
: base_type(std::move(base))
message(header_type&& base, Args&&... args)
: header_type(std::move(base))
, body(std::forward<Args>(args)...)
{
}
@ -458,8 +462,8 @@ struct message : header<isRequest, Fields>
*/
template<class... Args>
explicit
message(base_type const& base, Args&&... args)
: base_type(base)
message(header_type const& base, Args&&... args)
: header_type(base)
, body(std::forward<Args>(args)...)
{
}
@ -469,13 +473,13 @@ struct message : header<isRequest, Fields>
@param u An argument forwarded to the body constructor.
@note This constructor participates in overload resolution
only if `u` is not convertible to `base_type`.
only if `u` is not convertible to `header_type`.
*/
template<class U
#if ! BEAST_DOXYGEN
, class = typename std::enable_if<
! std::is_convertible<typename
std::decay<U>::type, base_type>::value>::type
std::decay<U>::type, header_type>::value>::type
#endif
>
explicit
@ -491,16 +495,16 @@ struct message : header<isRequest, Fields>
@param v An argument forwarded to the fields constructor.
@note This constructor participates in overload resolution
only if `u` is not convertible to `base_type`.
only if `u` is not convertible to `header_type`.
*/
template<class U, class V
#if ! BEAST_DOXYGEN
,class = typename std::enable_if<! std::is_convertible<
typename std::decay<U>::type, base_type>::value>::type
typename std::decay<U>::type, header_type>::value>::type
#endif
>
message(U&& u, V&& v)
: base_type(std::forward<V>(v))
: header_type(std::forward<V>(v))
, body(std::forward<U>(u))
{
}
@ -531,24 +535,71 @@ struct message : header<isRequest, Fields>
{
}
/// Returns the header portion of the message
base_type&
base()
{
return *this;
}
/** Returns `true` if Transfer-Encoding is present, and chunked appears last.
*/
bool
chunked() const;
/// Returns the header portion of the message
base_type const&
base() const
{
return *this;
}
/** Returns the payload size of the body in octets if possible.
This function invokes the @b Body algorithm to measure
the number of octets in the serialized body container. If
there is no body, this will return zero. Otherwise, if the
body exists but is not known ahead of time, `boost::none`
is returned (usually indicating that a chunked Transfer-Encoding
will be used).
@note The value of the Content-Length field in the message
is not inspected.
*/
boost::optional<std::uint64_t>
size() const;
/** Set the Content-Length field.
The value of the Content-Length field will be unconditionally
set to the specified number of octets.
*/
void
content_length(std::uint64_t n);
/** Prepare some fields automatically.
This function will adjust the Connection, Content-Length
and Transfer-Encoding, fields of the message based on the
properties of the body and the options passed in.
@par Example
@code
request<empty_body> req;
req.version = 11;
req.method(verb::upgrade);
req.target("/");
req.insert("User-Agent", "Beast");
req.prepare(connection::close, connection::upgrade);
@endcode
@param args An list of zero or more options to use.
@throw std::invalid_argument if the values of certain
fields detectably violate the semantic requirements of HTTP.
@note Undefined behavior if called more than once.
@see @ref connection
*/
template<class... Args>
void
prepare(Args const&... args);
private:
static_assert(is_body<Body>::value,
"Body requirements not met");
template<class... Un, size_t... IUn>
message(std::piecewise_construct_t,
std::tuple<Un...>& tu, beast::detail::index_sequence<IUn...>)
std::tuple<Un...>& tu,
beast::detail::index_sequence<IUn...>)
: body(std::forward<Un>(std::get<IUn>(tu))...)
{
}
@ -559,10 +610,46 @@ private:
std::tuple<Un...>& tu, std::tuple<Vn...>& tv,
beast::detail::index_sequence<IUn...>,
beast::detail::index_sequence<IVn...>)
: base_type(std::forward<Vn>(std::get<IVn>(tv))...)
: header_type(std::forward<Vn>(std::get<IVn>(tv))...)
, body(std::forward<Un>(std::get<IUn>(tu))...)
{
}
boost::optional<std::uint64_t>
size(std::true_type) const
{
return Body::size(*this);
}
boost::optional<std::uint64_t>
size(std::false_type) const
{
return boost::none;
}
template<class Arg, class... Args>
void
prepare_opt(unsigned&, Arg const&, Args const&...);
void
prepare_opt(unsigned&)
{
}
void
prepare_opt(unsigned&, close_t);
void
prepare_opt(unsigned&, keep_alive_t);
void
prepare_opt(unsigned&, upgrade_t);
void
prepare_payload(std::true_type);
void
prepare_payload(std::false_type);
};
/// A typical HTTP request
@ -617,39 +704,6 @@ template<bool isRequest, class Fields>
bool
is_upgrade(header<isRequest, Fields> const& msg);
/** HTTP/1 connection prepare options.
@note These values are used with @ref prepare.
*/
enum class connection
{
/// Specify Connection: close.
close,
/// Specify Connection: keep-alive where possible.
keep_alive,
/// Specify Connection: upgrade.
upgrade
};
/** Prepare an HTTP message.
This function will adjust the Content-Length, Transfer-Encoding,
and Connection fields of the message based on the properties of
the body and the options passed in.
@param msg The message to prepare. The fields may be modified.
@param options A list of prepare options.
*/
template<
bool isRequest, class Body, class Fields,
class... Options>
void
prepare(message<isRequest, Body, Fields>& msg,
Options&&... options);
} // http
} // beast

View File

@ -30,6 +30,16 @@ struct string_body
/// The type of the body member when used in a message.
using value_type = std::string;
/// Returns the content length of the body in a message.
template<bool isRequest, class Fields>
static
std::uint64_t
size(
message<isRequest, string_body, Fields> const& m)
{
return m.body.size();
}
#if BEAST_DOXYGEN
/// The algorithm to obtain buffers representing the body
using reader = implementation_defined;
@ -53,19 +63,12 @@ struct string_body
}
void
init(error_code& ec)
init(error_code&)
{
beast::detail::ignore_unused(ec);
}
std::uint64_t
content_length() const
{
return body_.size();
}
boost::optional<std::pair<const_buffers_type, bool>>
get(error_code& ec)
get(error_code&)
{
return {{const_buffers_type{
body_.data(), body_.size()}, false}};

View File

@ -21,6 +21,9 @@
namespace beast {
namespace http {
template<bool, class, class>
struct message;
/** Determine if `T` meets the requirements of @b Body.
This metafunction is equivalent to `std::true_type`

View File

@ -213,10 +213,10 @@ build_response(http::header<true, Fields> const& req,
[&](std::string const& text)
{
response_type res;
res.result(http::status::bad_request);
res.version = req.version;
res.result(http::status::bad_request);
res.body = text;
prepare(res);
res.prepare();
decorate(res);
return res;
};
@ -246,7 +246,7 @@ build_response(http::header<true, Fields> const& req,
res.result(http::status::upgrade_required);
res.version = req.version;
res.insert("Sec-WebSocket-Version", "13");
prepare(res);
res.prepare();
decorate(res);
return res;
}

View File

@ -72,7 +72,7 @@ public:
req.target("/");
req.insert("User-Agent", "test");
req.body = "Hello, world!";
prepare(req);
req.prepare();
error_code ec;
send_expect_100_continue(
@ -105,7 +105,7 @@ public:
req.target("/");
req.insert("User-Agent", "test");
req.body = "Hello, world!";
prepare(req);
req.prepare();
test::pipe downstream{ios_};
downstream.server.read_size(3);

View File

@ -205,7 +205,7 @@ public:
m.insert("Upgrade", "test");
BEAST_EXPECT(! is_upgrade(m));
prepare(m, connection::upgrade);
m.prepare(connection::upgrade);
BEAST_EXPECT(is_upgrade(m));
BEAST_EXPECT(m["Connection"] == "upgrade");
@ -214,49 +214,6 @@ public:
}
}
void
testPrepare()
{
request<string_body> m;
m.version = 10;
BEAST_EXPECT(! is_upgrade(m));
m.insert("Transfer-Encoding", "chunked");
try
{
prepare(m);
fail();
}
catch(std::exception const&)
{
}
m.erase("Transfer-Encoding");
m.insert("Content-Length", "0");
try
{
prepare(m);
fail();
}
catch(std::exception const&)
{
pass();
}
m.erase("Content-Length");
m.insert("Connection", "keep-alive");
try
{
prepare(m);
fail();
}
catch(std::exception const&)
{
pass();
}
m.version = 11;
m.erase("Connection");
m.insert("Connection", "close");
BEAST_EXPECT(! is_keep_alive(m));
}
void
testSwap()
{
@ -357,7 +314,6 @@ public:
testMessage();
testHeaders();
testFreeFunctions();
testPrepare();
testSwap();
testSpecialMembers();
testMethod();

View File

@ -55,9 +55,8 @@ public:
}
void
init(error_code& ec)
init(error_code&)
{
beast::detail::ignore_unused(ec);
}
boost::optional<std::pair<const_buffers_type, bool>>
@ -502,7 +501,7 @@ public:
m.version = 10;
m.insert("User-Agent", "test");
m.body = "*";
prepare(m);
m.prepare();
BEAST_EXPECT(str(m) ==
"GET / HTTP/1.0\r\n"
"User-Agent: test\r\n"
@ -519,12 +518,12 @@ public:
m.version = 10;
m.insert("User-Agent", "test");
m.body = "*";
prepare(m, connection::keep_alive);
m.prepare(connection::keep_alive);
BEAST_EXPECT(str(m) ==
"GET / HTTP/1.0\r\n"
"User-Agent: test\r\n"
"Content-Length: 1\r\n"
"Connection: keep-alive\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*"
);
@ -539,7 +538,7 @@ public:
m.body = "*";
try
{
prepare(m, connection::upgrade);
m.prepare( connection::upgrade);
fail();
}
catch(std::exception const&)
@ -555,7 +554,7 @@ public:
m.version = 10;
m.insert("User-Agent", "test");
m.body = "*";
prepare(m);
m.prepare();
test::string_ostream ss(ios_);
error_code ec;
write(ss, m, ec);
@ -575,7 +574,7 @@ public:
m.version = 11;
m.insert("User-Agent", "test");
m.body = "*";
prepare(m);
m.prepare();
BEAST_EXPECT(str(m) ==
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
@ -592,7 +591,7 @@ public:
m.version = 11;
m.insert("User-Agent", "test");
m.body = "*";
prepare(m, connection::close);
m.prepare(connection::close);
test::string_ostream ss(ios_);
error_code ec;
write(ss, m, ec);
@ -600,8 +599,8 @@ public:
BEAST_EXPECT(ss.str ==
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
"Content-Length: 1\r\n"
"Connection: close\r\n"
"Content-Length: 1\r\n"
"\r\n"
"*"
);
@ -613,7 +612,7 @@ public:
m.target("/");
m.version = 11;
m.insert("User-Agent", "test");
prepare(m, connection::upgrade);
m.prepare(connection::upgrade);
BEAST_EXPECT(str(m) ==
"GET / HTTP/1.1\r\n"
"User-Agent: test\r\n"
@ -629,7 +628,7 @@ public:
m.version = 11;
m.insert("User-Agent", "test");
m.body = "*";
prepare(m);
m.prepare();
test::string_ostream ss(ios_);
error_code ec;
write(ss, m, ec);
@ -656,8 +655,6 @@ public:
m.body = "*";
BEAST_EXPECT(boost::lexical_cast<std::string>(m) ==
"GET / HTTP/1.1\r\nUser-Agent: test\r\n\r\n*");
BEAST_EXPECT(boost::lexical_cast<std::string>(m.base()) ==
"GET / HTTP/1.1\r\nUser-Agent: test\r\n\r\n");
}
// Ensure completion handlers are not leaked