diff --git a/CHANGELOG.md b/CHANGELOG.md
index ed253bc0..1f8c7572 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ API Changes:
* Remove header_parser
* Add verb to on_request for parsers
+* Refactor prepare
--------------------------------------------------------------------------------
diff --git a/doc/4_02_message.qbk b/doc/4_02_message.qbk
index c8ddd429..4bcb1a26 100644
--- a/doc/4_02_message.qbk
+++ b/doc/4_02_message.qbk
@@ -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
diff --git a/doc/quickref.xml b/doc/quickref.xml
index 3a290303..9d70480f 100644
--- a/doc/quickref.xml
+++ b/doc/quickref.xml
@@ -62,7 +62,6 @@
make_serializer
obsolete_reason
operator<<
- prepare
read
read_header
read_some
diff --git a/examples/doc_http_samples.hpp b/examples/doc_http_samples.hpp
index af6c9315..c319cf3e 100644
--- a/examples/doc_http_samples.hpp
+++ b/examples/doc_http_samples.hpp
@@ -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&, // The header to transform
- error_code&); // Set to the error, if any
+ template
+ 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;
diff --git a/examples/file_body.hpp b/examples/file_body.hpp
index c130a03a..7a9d7eb5 100644
--- a/examples/file_body.hpp
+++ b/examples/file_body.hpp
@@ -24,6 +24,16 @@ struct file_body
{
using value_type = std::string;
+ /// Returns the content length of the body in a message.
+ template
+ static
+ std::uint64_t
+ size(
+ message 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>
get(error_code& ec)
{
diff --git a/examples/http_async_server.hpp b/examples/http_async_server.hpp
index a1af3133..ac29b129 100644
--- a/examples/http_async_server.hpp
+++ b/examples/http_async_server.hpp
@@ -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));
diff --git a/examples/http_crawl.cpp b/examples/http_crawl.cpp
index 85fa39b8..0df6884b 100644
--- a/examples/http_crawl.cpp
+++ b/examples/http_crawl.cpp
@@ -38,12 +38,12 @@ int main(int, char const*[])
auto ep = sock.remote_endpoint();
request req;
req.method(verb::get);
- req.target("/");
req.version = 11;
+ req.target("/");
req.insert("Host", host + std::string(":") +
boost::lexical_cast(ep.port()));
req.insert("User-Agent", "beast/http");
- prepare(req);
+ req.prepare();
write(sock, req);
response res;
beast::multi_buffer b;
diff --git a/examples/http_example.cpp b/examples/http_example.cpp
index 4be99e3d..a190bd7a 100644
--- a/examples/http_example.cpp
+++ b/examples/http_example.cpp
@@ -32,7 +32,7 @@ int main()
req.replace("Host", host + ":" +
boost::lexical_cast(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
diff --git a/examples/http_sync_server.hpp b/examples/http_sync_server.hpp
index 7c175f0a..98f8bfaa 100644
--- a/examples/http_sync_server.hpp
+++ b/examples/http_sync_server.hpp
@@ -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;
diff --git a/examples/ssl/http_ssl_example.cpp b/examples/ssl/http_ssl_example.cpp
index 0dc40d0f..9a3749c4 100644
--- a/examples/ssl/http_ssl_example.cpp
+++ b/examples/ssl/http_ssl_example.cpp
@@ -41,7 +41,7 @@ int main()
req.insert("Host", host + ":" +
boost::lexical_cast(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
diff --git a/include/beast/http.hpp b/include/beast/http.hpp
index 87c92258..6f6063c5 100644
--- a/include/beast/http.hpp
+++ b/include/beast/http.hpp
@@ -12,9 +12,11 @@
#include
#include
+#include
#include
#include
#include
+#include
#include
#include
#include
diff --git a/include/beast/http/connection.hpp b/include/beast/http/connection.hpp
new file mode 100644
index 00000000..68a9e291
--- /dev/null
+++ b/include/beast/http/connection.hpp
@@ -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
+
+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
+struct connection_impl
+{
+ static close_t constexpr close{};
+ static keep_alive_t constexpr keep_alive{};
+ static upgrade_t constexpr upgrade{};
+};
+
+template
+constexpr
+close_t
+connection_impl<_>::close;
+
+template
+constexpr
+keep_alive_t
+connection_impl<_>::keep_alive;
+
+template
+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 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
diff --git a/include/beast/http/detail/type_traits.hpp b/include/beast/http/detail/type_traits.hpp
index 6c9b62cc..4479f2ae 100644
--- a/include/beast/http/detail/type_traits.hpp
+++ b/include/beast/http/detail/type_traits.hpp
@@ -59,19 +59,21 @@ struct has_value_type > : std::true_type {};
-template>
-struct has_content_length : std::false_type {};
+/** Determine if a @b Body type has a size
-template
-struct has_content_length().content_length()
- )> > : std::true_type
-{
- static_assert(std::is_convertible<
- decltype(std::declval().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
+struct is_body_sized : std::false_type {};
+
+template
+struct is_body_sized() =
+ T::size(std::declval()),
+ (void)0)>> : std::true_type {};
} // detail
} // http
diff --git a/include/beast/http/dynamic_body.hpp b/include/beast/http/dynamic_body.hpp
index 09f38ab9..ffaddebb 100644
--- a/include/beast/http/dynamic_body.hpp
+++ b/include/beast/http/dynamic_body.hpp
@@ -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
+ static
+ std::uint64_t
+ size(message 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>
get(error_code& ec)
{
diff --git a/include/beast/http/empty_body.hpp b/include/beast/http/empty_body.hpp
index 71c1bf72..60575b17 100644
--- a/include/beast/http/empty_body.hpp
+++ b/include/beast/http/empty_body.hpp
@@ -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
+ static
+ std::uint64_t
+ size(message 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>
get(error_code& ec)
{
diff --git a/include/beast/http/fields.hpp b/include/beast/http/fields.hpp
index 0eb52850..ee55df27 100644
--- a/include/beast/http/fields.hpp
+++ b/include/beast/http/fields.hpp
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -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
diff --git a/include/beast/http/impl/fields.ipp b/include/beast/http/impl/fields.ipp
index 703f0ed3..267773b4 100644
--- a/include/beast/http/impl/fields.ipp
+++ b/include/beast/http/impl/fields.ipp
@@ -8,6 +8,7 @@
#ifndef BEAST_HTTP_IMPL_FIELDS_IPP
#define BEAST_HTTP_IMPL_FIELDS_IPP
+#include
#include
#include
#include
@@ -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
@@ -89,6 +90,73 @@ reason_impl(string_view s)
this->replace(":reason", s);
}
+template
+inline
+void
+basic_fields::
+content_length_impl(std::uint64_t n)
+{
+ this->erase("Content-Length");
+ this->insert("Content-Length",
+ to_static_string(n));
+}
+
+template
+inline
+void
+basic_fields::
+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
+inline
+void
+basic_fields::
+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
+inline
+void
+basic_fields::
+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
+inline
+void
+basic_fields::
+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
diff --git a/include/beast/http/impl/message.ipp b/include/beast/http/impl/message.ipp
index 0710dd16..12600c50 100644
--- a/include/beast/http/impl/message.ipp
+++ b/include/beast/http/impl/message.ipp
@@ -9,14 +9,10 @@
#define BEAST_HTTP_IMPL_MESSAGE_IPP
#include
-#include
-#include
#include
#include
#include
#include
-#include
-#include
#include
namespace beast {
@@ -71,6 +67,170 @@ get_reason() const
return obsolete_reason(result_);
}
+//------------------------------------------------------------------------------
+
+namespace detail {
+
+} // detail
+
+template
+bool
+message::
+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
+boost::optional
+message::
+size() const
+{
+ static_assert(is_body_reader::value,
+ "BodyReader requirements not met");
+
+ return size(detail::is_body_sized<
+ Body, decltype(*this)>{});
+}
+
+template
+template
+void
+message::
+prepare(Args const&... args)
+{
+ static_assert(is_body_reader::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
+template
+inline
+void
+message::
+prepare_opt(unsigned& f,
+ Arg const& arg, Args const&... args)
+{
+ prepare_opt(f, arg);
+ prepare_opt(f, args...);
+}
+
+template
+inline
+void
+message::
+prepare_opt(unsigned& f, close_t)
+{
+ f |= 1;
+}
+
+template
+inline
+void
+message::
+prepare_opt(unsigned& f, keep_alive_t)
+{
+ f |= 2;
+}
+
+template
+inline
+void
+message::
+prepare_opt(unsigned& f, upgrade_t)
+{
+ f |= 4;
+}
+
+template
+inline
+void
+message::
+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
+inline
+void
+message::
+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
void
swap(
@@ -106,7 +266,9 @@ swap(
message& m2)
{
using std::swap;
- swap(m1.base(), m2.base());
+ swap(
+ static_cast&>(m1),
+ static_cast&>(m2));
swap(m1.body, m2.body);
}
@@ -138,176 +300,6 @@ is_upgrade(header const& msg)
return false;
}
-namespace detail {
-
-struct prepare_info
-{
- boost::optional connection_value;
- boost::optional content_length;
-};
-
-template
-inline
-void
-prepare_options(prepare_info& pi,
- message& msg)
-{
- beast::detail::ignore_unused(pi, msg);
-}
-
-template
-void
-prepare_option(prepare_info& pi,
- message& 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& msg,
- Opt&& opt, Opts&&... opts)
-{
- prepare_option(pi, msg, opt);
- prepare_options(pi, msg,
- std::forward(opts)...);
-}
-
-template
-void
-prepare_content_length(prepare_info& pi,
- message 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
-void
-prepare_content_length(prepare_info& pi,
- message 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& msg,
- Options&&... options)
-{
- // VFALCO TODO
- static_assert(is_body::value,
- "Body requirements not met");
- static_assert(is_body_reader::value,
- "BodyReader requirements not met");
- detail::prepare_info pi;
- detail::prepare_content_length(pi, msg,
- detail::has_content_length{});
- detail::prepare_options(pi, msg,
- std::forward(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& 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& 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
diff --git a/include/beast/http/impl/parser.ipp b/include/beast/http/impl/parser.ipp
index 90f4abbc..ca14e493 100644
--- a/include/beast/http/impl/parser.ipp
+++ b/include/beast/http/impl/parser.ipp
@@ -29,8 +29,7 @@ parser::
parser(parser&& parser,
Args&&... args)
: base_type(std::move(parser))
- , m_(parser.release().base(),
- std::forward(args)...)
+ , m_(parser.release(), std::forward(args)...)
{
if(parser.wr_)
BOOST_THROW_EXCEPTION(std::invalid_argument{
diff --git a/include/beast/http/message.hpp b/include/beast/http/message.hpp
index 33f02bc0..b94f8b38 100644
--- a/include/beast/http/message.hpp
+++ b/include/beast/http/message.hpp
@@ -9,11 +9,14 @@
#define BEAST_HTTP_MESSAGE_HPP
#include
+#include
#include
#include
#include
+#include
#include
#include
+#include
#include
#include
#include
@@ -49,12 +52,7 @@ struct header : 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 : Fields
}
private:
+ template
+ friend struct message;
+
template
friend
void
@@ -230,7 +231,7 @@ template
struct header : 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 : Fields
}
private:
+ template
+ friend struct message;
+
template
friend
void
@@ -412,7 +416,7 @@ template
struct message : header
{
/// The base class used to hold the header portion of the message.
- using base_type = header;
+ using header_type = header;
/** The type providing the body traits.
@@ -445,8 +449,8 @@ struct message : header
*/
template
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)...)
{
}
@@ -458,8 +462,8 @@ struct message : header
*/
template
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)...)
{
}
@@ -469,13 +473,13 @@ struct message : header
@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::type, base_type>::value>::type
+ std::decay::type, header_type>::value>::type
#endif
>
explicit
@@ -491,16 +495,16 @@ struct message : header
@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::type, base_type>::value>::type
+ typename std::decay::type, header_type>::value>::type
#endif
>
message(U&& u, V&& v)
- : base_type(std::forward(v))
+ : header_type(std::forward(v))
, body(std::forward(u))
{
}
@@ -531,24 +535,71 @@ struct message : header
{
}
- /// 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
+ 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 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
+ void
+ prepare(Args const&... args);
private:
+ static_assert(is_body::value,
+ "Body requirements not met");
+
template
message(std::piecewise_construct_t,
- std::tuple& tu, beast::detail::index_sequence)
+ std::tuple& tu,
+ beast::detail::index_sequence)
: body(std::forward(std::get(tu))...)
{
}
@@ -559,10 +610,46 @@ private:
std::tuple& tu, std::tuple& tv,
beast::detail::index_sequence,
beast::detail::index_sequence)
- : base_type(std::forward(std::get(tv))...)
+ : header_type(std::forward(std::get(tv))...)
, body(std::forward(std::get(tu))...)
{
}
+
+ boost::optional
+ size(std::true_type) const
+ {
+ return Body::size(*this);
+ }
+
+ boost::optional
+ size(std::false_type) const
+ {
+ return boost::none;
+ }
+
+ template
+ 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
is_upgrade(header 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& msg,
- Options&&... options);
-
} // http
} // beast
diff --git a/include/beast/http/string_body.hpp b/include/beast/http/string_body.hpp
index ab8f026b..a960ee58 100644
--- a/include/beast/http/string_body.hpp
+++ b/include/beast/http/string_body.hpp
@@ -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
+ static
+ std::uint64_t
+ size(
+ message 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>
- get(error_code& ec)
+ get(error_code&)
{
return {{const_buffers_type{
body_.data(), body_.size()}, false}};
diff --git a/include/beast/http/type_traits.hpp b/include/beast/http/type_traits.hpp
index 20a74930..7ae997a1 100644
--- a/include/beast/http/type_traits.hpp
+++ b/include/beast/http/type_traits.hpp
@@ -21,6 +21,9 @@
namespace beast {
namespace http {
+template
+struct message;
+
/** Determine if `T` meets the requirements of @b Body.
This metafunction is equivalent to `std::true_type`
diff --git a/include/beast/websocket/impl/stream.ipp b/include/beast/websocket/impl/stream.ipp
index 75adb35f..9d9cc9f1 100644
--- a/include/beast/websocket/impl/stream.ipp
+++ b/include/beast/websocket/impl/stream.ipp
@@ -213,10 +213,10 @@ build_response(http::header 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 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;
}
diff --git a/test/http/doc_http_samples.cpp b/test/http/doc_http_samples.cpp
index 0f557494..6afab8e6 100644
--- a/test/http/doc_http_samples.cpp
+++ b/test/http/doc_http_samples.cpp
@@ -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);
diff --git a/test/http/message.cpp b/test/http/message.cpp
index 23a0bb5e..17bb1273 100644
--- a/test/http/message.cpp
+++ b/test/http/message.cpp
@@ -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 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();
diff --git a/test/http/write.cpp b/test/http/write.cpp
index a9a7441e..1119df31 100644
--- a/test/http/write.cpp
+++ b/test/http/write.cpp
@@ -55,9 +55,8 @@ public:
}
void
- init(error_code& ec)
+ init(error_code&)
{
- beast::detail::ignore_unused(ec);
}
boost::optional>
@@ -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(m) ==
"GET / HTTP/1.1\r\nUser-Agent: test\r\n\r\n*");
- BEAST_EXPECT(boost::lexical_cast(m.base()) ==
- "GET / HTTP/1.1\r\nUser-Agent: test\r\n\r\n");
}
// Ensure completion handlers are not leaked