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 * Remove header_parser
* Add verb to on_request for parsers * 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 Server: Beast\r\n
Content-Length: 13\r\n Content-Length: 13\r\n
\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__make_serializer">make_serializer</link></member>
<member><link linkend="beast.ref.http__obsolete_reason">obsolete_reason</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__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">read</link></member>
<member><link linkend="beast.ref.http__read_header">read_header</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> <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 @param transform The header transformation to apply. The function will
be called with this signature: be called with this signature:
@code @code
void transform( template<class Body>
header<isRequest, Fields>&, // The header to transform void transform(message<
error_code&); // Set to the error, if any isRequest, Body, Fields>&, // The message to transform
error_code&); // Set to the error, if any
@endcode @endcode
@param ec Set to the error if any occurred. @param ec Set to the error if any occurred.
@ -496,8 +497,7 @@ relay(
return; return;
// Apply the caller's header tranformation // Apply the caller's header tranformation
// base() returns a reference to the header portion of the message. transform(p.get(), ec);
transform(p.get().base(), ec);
if(ec) if(ec)
return; return;

View File

@ -24,6 +24,16 @@ struct file_body
{ {
using value_type = std::string; 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 class reader
{ {
std::uint64_t size_ = 0; std::uint64_t size_ = 0;
@ -67,12 +77,6 @@ struct file_body
size_ = boost::filesystem::file_size(path_); size_ = boost::filesystem::file_size(path_);
} }
std::uint64_t
content_length() const
{
return size_;
}
boost::optional<std::pair<const_buffers_type, bool>> boost::optional<std::pair<const_buffers_type, bool>>
get(error_code& ec) get(error_code& ec)
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,9 +12,11 @@
#include <beast/http/basic_parser.hpp> #include <beast/http/basic_parser.hpp>
#include <beast/http/buffer_body.hpp> #include <beast/http/buffer_body.hpp>
#include <beast/http/connection.hpp>
#include <beast/http/dynamic_body.hpp> #include <beast/http/dynamic_body.hpp>
#include <beast/http/empty_body.hpp> #include <beast/http/empty_body.hpp>
#include <beast/http/error.hpp> #include <beast/http/error.hpp>
#include <beast/http/field.hpp>
#include <beast/http/fields.hpp> #include <beast/http/fields.hpp>
#include <beast/http/message.hpp> #include <beast/http/message.hpp>
#include <beast/http/parser.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 typename T::value_type
> > : std::true_type {}; > > : std::true_type {};
template<class T, class = beast::detail::void_t<>> /** Determine if a @b Body type has a size
struct has_content_length : std::false_type {};
template<class T> This metafunction is equivalent to `std::true_type` if
struct has_content_length<T, beast::detail::void_t<decltype( Body contains a static member function called `content_lengeth`.
std::declval<T>().content_length() */
)> > : std::true_type template<class T, class M, class = void>
{ struct is_body_sized : std::false_type {};
static_assert(std::is_convertible<
decltype(std::declval<T>().content_length()), template<class T, class M>
std::uint64_t>::value, struct is_body_sized<T, M, beast::detail::void_t<
"Writer::content_length requirements not met"); typename T::value_type,
}; decltype(
std::declval<std::uint64_t&>() =
T::size(std::declval<M const&>()),
(void)0)>> : std::true_type {};
} // detail } // detail
} // http } // http

View File

@ -28,6 +28,16 @@ struct basic_dynamic_body
/// The type of the body member when used in a message. /// The type of the body member when used in a message.
using value_type = DynamicBuffer; 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 #if BEAST_DOXYGEN
/// The algorithm to obtain buffers representing the body /// The algorithm to obtain buffers representing the body
using reader = implementation_defined; 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>> boost::optional<std::pair<const_buffers_type, bool>>
get(error_code& ec) get(error_code& ec)
{ {

View File

@ -35,6 +35,15 @@ struct empty_body
// for the content length here, set on init() // 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 #if BEAST_DOXYGEN
/// The algorithm to obtain buffers representing the body /// The algorithm to obtain buffers representing the body
using reader = implementation_defined; 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>> boost::optional<std::pair<const_buffers_type, bool>>
get(error_code& ec) get(error_code& ec)
{ {

View File

@ -11,6 +11,7 @@
#include <beast/config.hpp> #include <beast/config.hpp>
#include <beast/core/string_view.hpp> #include <beast/core/string_view.hpp>
#include <beast/core/detail/ci_char_traits.hpp> #include <beast/core/detail/ci_char_traits.hpp>
#include <beast/http/connection.hpp>
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
#include <boost/intrusive/list.hpp> #include <boost/intrusive/list.hpp>
#include <boost/intrusive/set.hpp> #include <boost/intrusive/set.hpp>
@ -276,6 +277,11 @@ protected:
void method_impl(string_view s); void method_impl(string_view s);
void target_impl(string_view s); void target_impl(string_view s);
void reason_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: private:
struct element struct element

View File

@ -8,6 +8,7 @@
#ifndef BEAST_HTTP_IMPL_FIELDS_IPP #ifndef BEAST_HTTP_IMPL_FIELDS_IPP
#define BEAST_HTTP_IMPL_FIELDS_IPP #define BEAST_HTTP_IMPL_FIELDS_IPP
#include <beast/core/static_string.hpp>
#include <beast/http/detail/rfc7230.hpp> #include <beast/http/detail/rfc7230.hpp>
#include <boost/throw_exception.hpp> #include <boost/throw_exception.hpp>
#include <algorithm> #include <algorithm>
@ -74,7 +75,7 @@ target_impl(string_view s)
if(s.empty()) if(s.empty())
this->erase(":target"); this->erase(":target");
else else
return this->replace(":target", s); this->replace(":target", s);
} }
template<class Allocator> template<class Allocator>
@ -89,6 +90,73 @@ reason_impl(string_view s)
this->replace(":reason", 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> template<class Allocator>

View File

@ -9,14 +9,10 @@
#define BEAST_HTTP_IMPL_MESSAGE_IPP #define BEAST_HTTP_IMPL_MESSAGE_IPP
#include <beast/core/error.hpp> #include <beast/core/error.hpp>
#include <beast/core/string_view.hpp>
#include <beast/http/type_traits.hpp>
#include <beast/http/rfc7230.hpp> #include <beast/http/rfc7230.hpp>
#include <beast/core/detail/ci_char_traits.hpp> #include <beast/core/detail/ci_char_traits.hpp>
#include <beast/core/detail/type_traits.hpp> #include <beast/core/detail/type_traits.hpp>
#include <boost/assert.hpp> #include <boost/assert.hpp>
#include <boost/optional.hpp>
#include <boost/throw_exception.hpp>
#include <stdexcept> #include <stdexcept>
namespace beast { namespace beast {
@ -71,6 +67,170 @@ get_reason() const
return obsolete_reason(result_); 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> template<class Fields>
void void
swap( swap(
@ -106,7 +266,9 @@ swap(
message<isRequest, Body, Fields>& m2) message<isRequest, Body, Fields>& m2)
{ {
using std::swap; 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); swap(m1.body, m2.body);
} }
@ -138,176 +300,6 @@ is_upgrade(header<isRequest, Fields> const& msg)
return false; 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 } // http
} // beast } // beast

View File

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

View File

@ -9,11 +9,14 @@
#define BEAST_HTTP_MESSAGE_HPP #define BEAST_HTTP_MESSAGE_HPP
#include <beast/config.hpp> #include <beast/config.hpp>
#include <beast/http/connection.hpp>
#include <beast/http/fields.hpp> #include <beast/http/fields.hpp>
#include <beast/http/verb.hpp> #include <beast/http/verb.hpp>
#include <beast/http/status.hpp> #include <beast/http/status.hpp>
#include <beast/http/type_traits.hpp>
#include <beast/core/string_view.hpp> #include <beast/core/string_view.hpp>
#include <beast/core/detail/integer_sequence.hpp> #include <beast/core/detail/integer_sequence.hpp>
#include <boost/optional.hpp>
#include <boost/throw_exception.hpp> #include <boost/throw_exception.hpp>
#include <memory> #include <memory>
#include <stdexcept> #include <stdexcept>
@ -49,12 +52,7 @@ struct header<true, Fields> : Fields
#endif #endif
{ {
/// Indicates if the header is a request or response. /// Indicates if the header is a request or response.
#if BEAST_DOXYGEN using is_request = std::true_type;
static bool constexpr is_request = isRequest;
#else
static bool constexpr is_request = true;
#endif
/// The type representing the fields. /// The type representing the fields.
using fields_type = Fields; using fields_type = Fields;
@ -194,6 +192,9 @@ struct header<true, Fields> : Fields
} }
private: private:
template<bool, class, class>
friend struct message;
template<class T> template<class T>
friend friend
void void
@ -230,7 +231,7 @@ template<class Fields>
struct header<false, Fields> : Fields struct header<false, Fields> : Fields
{ {
/// Indicates if the header is a request or response. /// 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. /// The type representing the fields.
using fields_type = Fields; using fields_type = Fields;
@ -377,6 +378,9 @@ struct header<false, Fields> : Fields
} }
private: private:
template<bool, class, class>
friend struct message;
template<class T> template<class T>
friend friend
void void
@ -412,7 +416,7 @@ template<bool isRequest, class Body, class Fields = fields>
struct message : header<isRequest, Fields> struct message : header<isRequest, Fields>
{ {
/// The base class used to hold the header portion of the message. /// 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. /** The type providing the body traits.
@ -445,8 +449,8 @@ struct message : header<isRequest, Fields>
*/ */
template<class... Args> template<class... Args>
explicit explicit
message(base_type&& base, Args&&... args) message(header_type&& base, Args&&... args)
: base_type(std::move(base)) : header_type(std::move(base))
, body(std::forward<Args>(args)...) , body(std::forward<Args>(args)...)
{ {
} }
@ -458,8 +462,8 @@ struct message : header<isRequest, Fields>
*/ */
template<class... Args> template<class... Args>
explicit explicit
message(base_type const& base, Args&&... args) message(header_type const& base, Args&&... args)
: base_type(base) : header_type(base)
, body(std::forward<Args>(args)...) , body(std::forward<Args>(args)...)
{ {
} }
@ -469,13 +473,13 @@ struct message : header<isRequest, Fields>
@param u An argument forwarded to the body constructor. @param u An argument forwarded to the body constructor.
@note This constructor participates in overload resolution @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 template<class U
#if ! BEAST_DOXYGEN #if ! BEAST_DOXYGEN
, class = typename std::enable_if< , class = typename std::enable_if<
! std::is_convertible<typename ! std::is_convertible<typename
std::decay<U>::type, base_type>::value>::type std::decay<U>::type, header_type>::value>::type
#endif #endif
> >
explicit explicit
@ -491,16 +495,16 @@ struct message : header<isRequest, Fields>
@param v An argument forwarded to the fields constructor. @param v An argument forwarded to the fields constructor.
@note This constructor participates in overload resolution @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 template<class U, class V
#if ! BEAST_DOXYGEN #if ! BEAST_DOXYGEN
,class = typename std::enable_if<! std::is_convertible< ,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 #endif
> >
message(U&& u, V&& v) message(U&& u, V&& v)
: base_type(std::forward<V>(v)) : header_type(std::forward<V>(v))
, body(std::forward<U>(u)) , body(std::forward<U>(u))
{ {
} }
@ -531,24 +535,71 @@ struct message : header<isRequest, Fields>
{ {
} }
/// Returns the header portion of the message /** Returns `true` if Transfer-Encoding is present, and chunked appears last.
base_type& */
base() bool
{ chunked() const;
return *this;
}
/// Returns the header portion of the message /** Returns the payload size of the body in octets if possible.
base_type const&
base() const This function invokes the @b Body algorithm to measure
{ the number of octets in the serialized body container. If
return *this; 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: private:
static_assert(is_body<Body>::value,
"Body requirements not met");
template<class... Un, size_t... IUn> template<class... Un, size_t... IUn>
message(std::piecewise_construct_t, 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))...) : body(std::forward<Un>(std::get<IUn>(tu))...)
{ {
} }
@ -559,10 +610,46 @@ private:
std::tuple<Un...>& tu, std::tuple<Vn...>& tv, std::tuple<Un...>& tu, std::tuple<Vn...>& tv,
beast::detail::index_sequence<IUn...>, beast::detail::index_sequence<IUn...>,
beast::detail::index_sequence<IVn...>) 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))...) , 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 /// A typical HTTP request
@ -617,39 +704,6 @@ template<bool isRequest, class Fields>
bool bool
is_upgrade(header<isRequest, Fields> const& msg); 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 } // http
} // beast } // beast

View File

@ -30,6 +30,16 @@ struct string_body
/// The type of the body member when used in a message. /// The type of the body member when used in a message.
using value_type = std::string; 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 #if BEAST_DOXYGEN
/// The algorithm to obtain buffers representing the body /// The algorithm to obtain buffers representing the body
using reader = implementation_defined; using reader = implementation_defined;
@ -53,19 +63,12 @@ struct string_body
} }
void 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>> boost::optional<std::pair<const_buffers_type, bool>>
get(error_code& ec) get(error_code&)
{ {
return {{const_buffers_type{ return {{const_buffers_type{
body_.data(), body_.size()}, false}}; body_.data(), body_.size()}, false}};

View File

@ -21,6 +21,9 @@
namespace beast { namespace beast {
namespace http { namespace http {
template<bool, class, class>
struct message;
/** Determine if `T` meets the requirements of @b Body. /** Determine if `T` meets the requirements of @b Body.
This metafunction is equivalent to `std::true_type` 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) [&](std::string const& text)
{ {
response_type res; response_type res;
res.result(http::status::bad_request);
res.version = req.version; res.version = req.version;
res.result(http::status::bad_request);
res.body = text; res.body = text;
prepare(res); res.prepare();
decorate(res); decorate(res);
return res; return res;
}; };
@ -246,7 +246,7 @@ build_response(http::header<true, Fields> const& req,
res.result(http::status::upgrade_required); res.result(http::status::upgrade_required);
res.version = req.version; res.version = req.version;
res.insert("Sec-WebSocket-Version", "13"); res.insert("Sec-WebSocket-Version", "13");
prepare(res); res.prepare();
decorate(res); decorate(res);
return res; return res;
} }

View File

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

View File

@ -205,7 +205,7 @@ public:
m.insert("Upgrade", "test"); m.insert("Upgrade", "test");
BEAST_EXPECT(! is_upgrade(m)); BEAST_EXPECT(! is_upgrade(m));
prepare(m, connection::upgrade); m.prepare(connection::upgrade);
BEAST_EXPECT(is_upgrade(m)); BEAST_EXPECT(is_upgrade(m));
BEAST_EXPECT(m["Connection"] == "upgrade"); 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 void
testSwap() testSwap()
{ {
@ -357,7 +314,6 @@ public:
testMessage(); testMessage();
testHeaders(); testHeaders();
testFreeFunctions(); testFreeFunctions();
testPrepare();
testSwap(); testSwap();
testSpecialMembers(); testSpecialMembers();
testMethod(); testMethod();

View File

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