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