From 943830f6ed20e8a3eb597d45aac4b50b9d033646 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Tue, 6 Jun 2017 17:26:11 -0700 Subject: [PATCH] Fields concept work --- CHANGELOG.md | 1 + doc/0_main.qbk | 3 +- doc/7_concepts.qbk | 3 +- doc/concept/Field.qbk | 4 +- doc/concept/FieldSequence.qbk | 133 ------------- doc/concept/Fields.qbk | 112 +++++++++++ doc/concept/FieldsReader.qbk | 78 ++++++++ doc/quickref.xml | 3 +- include/beast/core/buffer_cat.hpp | 6 + include/beast/core/impl/buffer_cat.ipp | 6 + include/beast/http/fields.hpp | 75 +++---- include/beast/http/impl/fields.ipp | 264 +++++++++++++++++++++++-- include/beast/http/impl/message.ipp | 94 +++++++-- include/beast/http/impl/serializer.ipp | 109 ++++------ include/beast/http/impl/write.ipp | 7 +- include/beast/http/message.hpp | 85 ++++---- include/beast/http/parser.hpp | 10 +- include/beast/http/serializer.hpp | 17 +- test/http/doc_http_samples.cpp | 4 +- test/http/message.cpp | 4 +- 20 files changed, 675 insertions(+), 343 deletions(-) delete mode 100644 doc/concept/FieldSequence.qbk create mode 100644 doc/concept/Fields.qbk create mode 100644 doc/concept/FieldsReader.qbk diff --git a/CHANGELOG.md b/CHANGELOG.md index bbfd5855..e710f910 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Version 50 * Use field in basic_fields and call sites * Use field in basic_parser * Tidy up basic_fields, header, and field concepts +* Fields concept work API Changes: diff --git a/doc/0_main.qbk b/doc/0_main.qbk index 3e635a92..b33b0eea 100644 --- a/doc/0_main.qbk +++ b/doc/0_main.qbk @@ -52,7 +52,8 @@ [def __BodyReader__ [link beast.concept.BodyReader [*BodyReader]]] [def __BodyWriter__ [link beast.concept.BodyWriter [*BodyWriter]]] [def __DynamicBuffer__ [link beast.concept.DynamicBuffer [*DynamicBuffer]]] -[def __FieldSequence__ [link beast.concept.FieldSequence [*FieldSequence]]] +[def __Fields__ [link beast.concept.Fields [*Fields]]] +[def __FieldsReader__ [link beast.concept.FieldsReader [*FieldsReader]]] [def __Stream__ [link beast.concept.streams [*Stream]]] [def __SyncStream__ [link beast.concept.streams.SyncStream [*SyncStream]]] diff --git a/doc/7_concepts.qbk b/doc/7_concepts.qbk index 957c3fa6..b34fcba0 100644 --- a/doc/7_concepts.qbk +++ b/doc/7_concepts.qbk @@ -15,7 +15,8 @@ [include concept/BufferSequence.qbk] [include concept/DynamicBuffer.qbk] [include concept/Field.qbk] -[include concept/FieldSequence.qbk] +[include concept/Fields.qbk] +[include concept/FieldsReader.qbk] [include concept/Streams.qbk] [endsect] diff --git a/doc/concept/Field.qbk b/doc/concept/Field.qbk index 155a421e..cf508d35 100644 --- a/doc/concept/Field.qbk +++ b/doc/concept/Field.qbk @@ -19,7 +19,7 @@ In this table: [[expression][type][semantics, pre/post-conditions]] [ [`a.name()`] - [`boost::string_ref`] + [[link beast.ref.string_view `string_view`]] [ This function returns a value implicitly convertible to `boost::string_ref` containing the case-insensitive field @@ -28,7 +28,7 @@ In this table: ] [ [`a.value()`] - [`boost::string_ref`] + [[link beast.ref.string_view `string_view`]] [ This function returns a value implicitly convertible to `boost::string_ref` containing the value for the field. The diff --git a/doc/concept/FieldSequence.qbk b/doc/concept/FieldSequence.qbk deleted file mode 100644 index a8a5b82a..00000000 --- a/doc/concept/FieldSequence.qbk +++ /dev/null @@ -1,133 +0,0 @@ -[/ - Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) - - Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -] - -[section:FieldSequence FieldSequence] - -A [*FieldSequence] is an iterable container whose value type meets -the requirements of [link beast.concept.Field [*Field]]. Objects that meet -these requirements become serializable by the implementation. - -In this table: - -* `X` denotes a type that meets the requirements of [*FieldSequence]. - -* `c` is a value of type `X const`. - -[table FieldSequence requirements -[[expression][type][semantics, pre/post-conditions]] -[ - [`X::value_type`] - [] - [ - A type that meets the requirements of - [link beast.concept.Field [*Field]]. - ] -] -[ - [`X::const_iterator`] - [] - [ - An iterator type whose `reference` type meets the - requirements of [link beast.concept.Field [*Field]], and which - satisfies all the requirements of [*ForwardIterator], - except that: - - [ordered_list - [there is no requirement that `operator->` is provided, and] - [there is no requirement that `reference` be a reference type.] - ] - ] -] -[ - [`c.begin()`] - [`X::const_iterator`] - [ - Returns an iterator to the beginning of the field sequence. - ] -] -[ - [`c.end()`] - [`X::const_iterator`] - [ - Returns an iterator to the end of the field sequence. - ] -] -] - -[endsect] - - - -[/ - -/** OutputFields - - Can be serialized by `beast::http::serializer` -*/ - public: - // This algorithm produces a set of buffers - // corresponding to the serialized representation - // of the fields - // - struct reader - { - using const_buffers_type = implementation-defined - - ... - }; - - protected: - string_view method_impl() const; - string_view target_impl() const; - string_view reason_impl() const; - - bool close_impl () const; - bool chunked_impl () const; - bool content_length_impl () const; - - - -/** InputFields - - Can be parsed by `beast::http::parser` -*/ - public: - void insert (field f, string_view value); - void insert (string_view name, string_view value); - - protected: - void method_impl (string_view s); - void target_impl (string_view s); - void reason_impl (string_view s); - - -/** Fields - - Has a container interface and supports input and output. - `beast::http:basic_fields` is a model. -*/ - - public: - bool exists(field f) const; - bool exists(string_view s) const; - - iterator find(field f) const; - iterator find(string_view s); - - std::size_t erase (field f); - std::size_t erase (string_view s); - iterator erase (iterator); - - protected: - 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(); - - -] diff --git a/doc/concept/Fields.qbk b/doc/concept/Fields.qbk new file mode 100644 index 00000000..f883dc5f --- /dev/null +++ b/doc/concept/Fields.qbk @@ -0,0 +1,112 @@ +[/ + 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) +] + +[section:Fields Fields] + +An instance of [*Fields] is a container for holding HTTP header fields +and their values. The implementation also calls upon the container to +store the request target and non-standard strings for method and obsolete +reason phrase as needed. Types which meet these requirements can always +be serialized. + +In this table: + +* `X` denotes a type that meets the requirements of [*Fields]. + +* `R` denotes a type meeting the requiremnets of __FieldsReader__. + +* `a` denotes a value of type `X`. + +* `c` denotes a (possibly const) value of type `X`. + +* `s` is a value of type [link beast.ref.string_view `string_view`]. + +[table Fields requirements +[[expression][type][semantics, pre/post-conditions]] +[ + [`X::reader`] + [`R`] + [ + A type which meets the requirements of __FieldsReader__. + ] +][ + [`c.has_close_impl()`] + [`bool`] + [ + Returns `true` if the value for Connection has "close" in the list. + ] +][ + [`c.has_chunked_impl()`] + [`bool`] + [ + Returns `true` if "chunked" is the last Transfer-Encoding. + ] +][ + [`c.has_content_length_impl()`] + [`bool`] + [ + Returns `true` if the Content-Length field is present. + ] +][ + [`c.get_method_impl()`] + [`string_view`] + [ + Returns the method text. + The implementation only calls this function for request + headers when retrieving the method text previously set + with a call to `set_method_impl` using a non-empty string. + ] +][ + [`c.get_target_impl()`] + [`string_view`] + [ + Returns the target string. + The implementation only calls this function for request headers. + ] +][ + [`c.get_reason_impl()`] + [`string_view`] + [ + Returns the obsolete request text. + The implementation only calls this for response headers when + retrieving the reason text previously set with a call to + `set_reason_impl` using a non-empty string. + ] +][ + [`a.set_method_impl(s)`] + [] + [ + Stores a copy of `s` as the method text, or erases the previously + stored value if `s` is empty. + The implementation only calls this function for request headers. + This function may throw `std::invalid_argument` if the operation + is not supported by the container. + ] +][ + [`a.set_target_impl(s)`] + [] + [ + Stores a copy of `s` as the target, or erases the previously + stored value if `s` is empty. + The implementation only calls this function for request headers. + This function may throw `std::invalid_argument` if the operation + is not supported by the container. + ] +][ + [`a.set_reason_impl(s)`] + [] + [ + Stores a copy of `s` as the reason text, or erases the previously + stored value of the reason text if `s` is empty. + The implementation only calls this function for request headers. + This function may throw `std::invalid_argument` if the operation + is not supported by the container. + ] +] +] + +[endsect] diff --git a/doc/concept/FieldsReader.qbk b/doc/concept/FieldsReader.qbk new file mode 100644 index 00000000..a6486313 --- /dev/null +++ b/doc/concept/FieldsReader.qbk @@ -0,0 +1,78 @@ +[/ + 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) +] + +[section:FieldsReader FieldsReader] + +A [*FieldsReader] provides a algorithm to obtain a sequence of buffers +representing the complete serialized HTTP/1 header for a set of fields. +The implementation constructs an instance of this type when needed, and +calls into it once to retrieve the buffers. + +In this table: + +* `X` denotes a type that meets the requirements of [*FieldsReader]. + +* `F` denotes a __Fields__ where + `std::is_same::value == `true`. + +* `a` is a value of type `X`. + +* `f` is a value of type `F`. + +* `v` is an integer representing the HTTP version. + +* `c` is an integer representing the HTTP status-code. + +* `m` is a value of type [link beast.ref.http__verb `verb`]. + +[table FieldsReader requirements +[[expression][type][semantics, pre/post-conditions]] +[ + [`X::const_buffers_type`] + [] + [ + A type which meets the requirements of __ConstBufferSequence__. + This is the type of buffer returned by `X::get`. + ] +][ + [`X(f,v,m)`] + [] + [ + The implementatation uses this constructor to indicate + that the fields being serialized form part of an HTTP + request. The lifetime of `f` is guaranteed + to end no earlier than after the `X` is destroyed. + ] +][ + [`X(f,v,c)`] + [] + [ + The implementatation uses this constructor to indicate + that the fields being serialized form part of an HTTP + response. The lifetime of `f` is guaranteed + to end no earlier than after the `X` is destroyed. + ] +][ + [`a.get()`] + [X::const_buffers_type] + [ + Called once after construction, this function returns + a constant buffer sequence containing the serialized + representation of the HTTP request or response including + the final carriage return linefeed sequence (`"\r\n"`). + ] +][ + [`is_fields_reader`] + [`std::true_type`] + [ + An alias for `std::true_type` for `F`, otherwise an alias + for `std::false_type`. + ] +] +] + +[endsect] diff --git a/doc/quickref.xml b/doc/quickref.xml index 25ece1f4..4e0e7e71 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -103,7 +103,8 @@ BodyReader BodyWriter Field - FieldSequence + Fields + FieldsReader diff --git a/include/beast/core/buffer_cat.hpp b/include/beast/core/buffer_cat.hpp index d9234c40..ba33731e 100644 --- a/include/beast/core/buffer_cat.hpp +++ b/include/beast/core/buffer_cat.hpp @@ -21,6 +21,12 @@ namespace beast { template class buffer_cat_view { +#if 0 + static_assert( + detail::is_all_const_buffer_sequence::value, + "BufferSequence requirements not met"); +#endif + std::tuple bn_; public: diff --git a/include/beast/core/impl/buffer_cat.ipp b/include/beast/core/impl/buffer_cat.ipp index 55e289a2..f0f9d45e 100644 --- a/include/beast/core/impl/buffer_cat.ipp +++ b/include/beast/core/impl/buffer_cat.ipp @@ -24,6 +24,12 @@ namespace beast { template class buffer_cat_view::const_iterator { +#if 0 + static_assert( + detail::is_all_const_buffer_sequence::value, + "BufferSequence requirements not met"); +#endif + std::size_t n_; std::tuple const* bn_; std::array #include #include +#include #include #include #include @@ -40,7 +41,7 @@ namespace http { as a `std::multiset`; there will be a separate value for each occurrence of the field name. - @note Meets the requirements of @b FieldSequence. + @note Meets the requirements of @b Fields */ template class basic_fields @@ -259,8 +260,6 @@ public: @param f The known field constant. - @param name The name of the field. - @param value A string holding the value of the field. */ void @@ -337,54 +336,55 @@ public: void swap(basic_fields& lhs, basic_fields& rhs); + /// The algorithm used to serialize the header + class reader; + protected: + /// Returns `true` if the value for Connection has "close" in the list. + bool has_close_impl() const; - //-------------------------------------------------------------------------- - // - // for serializing - // + /// Returns `true` if "chunked" is the last Transfer-Encoding + bool has_chunked_impl() const; - /** Returns the stored request-method string. + /// Returns `true` if the Content-Length field is present + bool has_content_length_impl() const; - @note This is called by the @ref header implementation. - */ - string_view get_method_impl() const; + /** Set or clear the method string. - /** Returns the stored request-target string. - - @note This is called by the @ref header implementation. - */ - string_view get_target_impl() const; - - /** Returns the stored obsolete reason-phrase string. - - @note This is called by the @ref header implementation. - */ - string_view get_reason_impl() const; - - //-------------------------------------------------------------------------- - // - // for parsing - // - - /** Set or clear the stored request-method string. - - @note This is called by the @ref header implementation. + @note Only called for requests. */ void set_method_impl(string_view s); - /** Set or clear the stored request-target string. + /** Set or clear the target string. - @note This is called by the @ref header implementation. + @note Only called for requests. */ void set_target_impl(string_view s); - /** Set or clear the stored obsolete reason-phrase string. + /** Set or clear the reason string. - @note This is called by the @ref header implementation. + @note Only called for responses. */ void set_reason_impl(string_view s); + /** Returns the request-method string. + + @note Only called for requests. + */ + string_view get_method_impl() const; + + /** Returns the request-target string. + + @note Only called for requests. + */ + string_view get_target_impl() const; + + /** Returns the response reason-phrase string. + + @note Only called for responses. + */ + string_view get_reason_impl() const; + //-------------------------------------------------------------------------- // // for container @@ -418,7 +418,7 @@ protected: @note This is called by the @ref header implementation. */ - void chunked_impl(); + void set_chunked_impl(bool v); private: class element @@ -441,6 +441,9 @@ private: string_view value() const; + boost::asio::const_buffer + buffer() const; + value_type data; }; diff --git a/include/beast/http/impl/fields.ipp b/include/beast/http/impl/fields.ipp index dac43055..48077d1a 100644 --- a/include/beast/http/impl/fields.ipp +++ b/include/beast/http/impl/fields.ipp @@ -8,8 +8,13 @@ #ifndef BEAST_HTTP_IMPL_FIELDS_IPP #define BEAST_HTTP_IMPL_FIELDS_IPP +#include #include -#include +#include +#include +#include +#include +#include #include #include @@ -106,6 +111,177 @@ public: //------------------------------------------------------------------------------ +template +class basic_fields::reader +{ +public: + using iter_type = typename list_t::const_iterator; + + struct field_iterator + { + iter_type it_; + + using value_type = boost::asio::const_buffer; + using pointer = value_type const*; + using reference = value_type const; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::bidirectional_iterator_tag; + + field_iterator() = default; + field_iterator(field_iterator&& other) = default; + field_iterator(field_iterator const& other) = default; + field_iterator& operator=(field_iterator&& other) = default; + field_iterator& operator=(field_iterator const& other) = default; + + explicit + field_iterator(iter_type it) + : it_(it) + { + } + + bool + operator==(field_iterator const& other) const + { + return it_ == other.it_; + } + + bool + operator!=(field_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return it_->buffer(); + } + + field_iterator& + operator++() + { + ++it_; + return *this; + } + + field_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + field_iterator& + operator--() + { + --it_; + return *this; + } + + field_iterator + operator--(int) + { + auto temp = *this; + --(*this); + return temp; + } + }; + + class field_range + { + field_iterator first_; + field_iterator last_; + + public: + using const_iterator = + field_iterator; + + using value_type = + typename const_iterator::value_type; + + field_range(field_range const&) = default; + + field_range(iter_type first, iter_type last) + : first_(first) + , last_(last) + { + } + + const_iterator + begin() const + { + return first_; + } + + const_iterator + end() const + { + return last_; + } + }; + + basic_fields const& f_; + std::string s_; + +public: + using const_buffers_type = + buffer_cat_view< + boost::asio::const_buffers_1, + field_range, + boost::asio::const_buffers_1>; + + reader(basic_fields const& f, int version, verb v) + : f_(f) + { + s_ = v == verb::unknown ? + f_.get_method_impl().to_string() : + to_string(v).to_string(); + s_ += " "; + s_ += f_.get_target_impl().to_string(); + if(version == 11) + s_ += " HTTP/1.1"; + else if(version == 10) + s_ += " HTTP/1.0"; + else + s_ += " HTTP/" + + std::to_string(version / 10) + "." + + std::to_string(version % 10); + s_ += "\r\n"; + } + + reader(basic_fields const& f, int version, int code) + : f_(f) + { + if(version == 11) + s_ += "HTTP/1.1 "; + else if(version == 10) + s_ += "HTTP/1.0 "; + else + s_ += "HTTP/" + + std::to_string(version / 10) + "." + + std::to_string(version % 10) + " "; + s_ += std::to_string(code) + " "; + if(int_to_status(code) == status::unknown) + s_ += f_.get_reason_impl().to_string(); + else + s_ += obsolete_reason(int_to_status(code)).to_string(); + s_ += "\r\n"; + } + + const_buffers_type + get() const + { + return buffer_cat( + boost::asio::buffer(s_.data(), s_.size()), + field_range(f_.list_.begin(), f_.list_.end()), + detail::chunk_crlf()); + } +}; + +//------------------------------------------------------------------------------ + template basic_fields:: element:: @@ -150,6 +326,18 @@ value() const static_cast(len_)}; } +template +inline +boost::asio::const_buffer +basic_fields:: +element:: +buffer() const +{ + return boost::asio::const_buffer{ + reinterpret_cast(this + 1), + static_cast(off_) + len_ + 2}; +} + //------------------------------------------------------------------------------ template @@ -361,31 +549,50 @@ replace(string_view name, string_view value) //------------------------------------------------------------------------------ +// Fields + template -inline -string_view +bool basic_fields:: -get_method_impl() const +has_close_impl() const { - return method_; + auto const fit = set_.find( + to_string(field::connection), less{}); + if(fit == set_.end()) + return false; + return token_list{fit->value()}.exists("close"); } template -inline -string_view +bool basic_fields:: -get_target_impl() const +has_chunked_impl() const { - return target_or_reason_; + auto const fit = set_.find(to_string( + field::transfer_encoding), less{}); + if(fit == set_.end()) + return false; + token_list const v{fit->value()}; + auto it = v.begin(); + if(it == v.end()) + return false; + for(;;) + { + auto cur = it++; + if(it == v.end()) + return beast::detail::ci_equal( + *cur, "chunked"); + } } template -inline -string_view +bool basic_fields:: -get_reason_impl() const +has_content_length_impl() const { - return target_or_reason_; + auto const fit = set_.find( + to_string(field::content_length), less{}); + return fit != set_.end(); } template @@ -415,6 +622,33 @@ set_reason_impl(string_view s) realloc_string(target_or_reason_, s); } +template +inline +string_view +basic_fields:: +get_method_impl() const +{ + return method_; +} + +template +inline +string_view +basic_fields:: +get_target_impl() const +{ + return target_or_reason_; +} + +template +inline +string_view +basic_fields:: +get_reason_impl() const +{ + return target_or_reason_; +} + //--- template @@ -474,8 +708,9 @@ template inline void basic_fields:: -chunked_impl() +set_chunked_impl(bool v) { + BOOST_ASSERT(v); auto it = find("Transfer-Encoding"); if(it == end()) this->insert("Transfer-Encoding", "chunked"); @@ -682,7 +917,6 @@ swap(basic_fields& other, std::false_type) swap(target_or_reason_, other.target_or_reason_); } - } // http } // beast diff --git a/include/beast/http/impl/message.ipp b/include/beast/http/impl/message.ipp index b6b3dba9..eff0b46a 100644 --- a/include/beast/http/impl/message.ipp +++ b/include/beast/http/impl/message.ipp @@ -61,7 +61,7 @@ method_string() const template void header:: -method(string_view s) +method_string(string_view s) { method_ = string_to_verb(s); if(method_ != verb::unknown) @@ -190,21 +190,85 @@ swap( //------------------------------------------------------------------------------ template +template +message:: +message(header_type&& base, Args&&... args) + : header_type(std::move(base)) + , body(std::forward(args)...) +{ +} + +template +template +message:: +message(header_type const& base, Args&&... args) + : header_type(base) + , body(std::forward(args)...) +{ +} + +template +template +message:: +message(U&& u) + : body(std::forward(u)) +{ +} + +template +template +message:: +message(U&& u, V&& v) + : header_type(std::forward(v)) + , body(std::forward(u)) +{ +} + +template +template +message:: +message(std::piecewise_construct_t, std::tuple un) + : message(std::piecewise_construct, un, + beast::detail::make_index_sequence{}) +{ +} + +template +template +message:: +message(std::piecewise_construct_t, + std::tuple&& un, std::tuple&& vn) + : message(std::piecewise_construct, un, vn, + beast::detail::make_index_sequence{}, + beast::detail::make_index_sequence{}) +{ +} + +template +inline bool message:: -chunked() const +has_close() 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; + return this->has_close_impl(); +} + +template +inline +bool +message:: +has_chunked() const +{ + return this->has_chunked_impl(); +} + +template +inline +bool +message:: +has_content_length() const +{ + return this->has_content_length_impl(); } template @@ -326,7 +390,7 @@ prepare_payload(std::true_type) } else if(this->version >= 11) { - this->chunked_impl(); + this->set_chunked_impl(true); } } @@ -350,7 +414,7 @@ prepare_payload(std::false_type) if(n) this->content_length_impl(*n); else if(this->version >= 11) - this->chunked_impl(); + this->set_chunked_impl(true); } //------------------------------------------------------------------------------ diff --git a/include/beast/http/impl/serializer.ipp b/include/beast/http/impl/serializer.ipp index 3338d2ce..15b0dcb6 100644 --- a/include/beast/http/impl/serializer.ipp +++ b/include/beast/http/impl/serializer.ipp @@ -15,59 +15,27 @@ namespace beast { namespace http { -namespace detail { -template +template void -write_start_line(std::ostream& os, - header const& msg) +serializer:: +frdinit(std::true_type) { - // VFALCO This should all be done without dynamic allocation - BOOST_ASSERT(msg.version == 10 || msg.version == 11); - os << msg.method() << " " << msg.target(); - switch(msg.version) - { - case 10: os << " HTTP/1.0\r\n"; break; - case 11: os << " HTTP/1.1\r\n"; break; - } + frd_.emplace(m_, m_.version, m_.method()); } -template +template void -write_start_line(std::ostream& os, - header const& msg) +serializer:: +frdinit(std::false_type) { - // VFALCO This should all be done without dynamic allocation - BOOST_ASSERT(msg.version == 10 || msg.version == 11); - switch(msg.version) - { - case 10: os << "HTTP/1.0 "; break; - case 11: os << "HTTP/1.1 "; break; - } - os << msg.result_int() << " " << msg.reason() << "\r\n"; + frd_.emplace(m_, m_.version, m_.result_int()); } -template -void -write_fields(std::ostream& os, - FieldSequence const& fields) -{ - //static_assert(is_FieldSequence::value, - // "FieldSequence requirements not met"); - for(auto const& field : fields) - { - auto const name = field.name(); - BOOST_ASSERT(! name.empty()); - if(name[0] == ':') - continue; - os << field.name() << ": " << field.value() << "\r\n"; - } -} - -} // detail - -//------------------------------------------------------------------------------ - template serializer const& m, ChunkDecorator const& d, Allocator const& alloc) : m_(m) , d_(d) - , b_(1024, alloc) { } @@ -93,15 +60,11 @@ get(error_code& ec, Visit&& visit) { case do_construct: { - chunked_ = token_list{ - m_["Transfer-Encoding"]}.exists("chunked"); - close_ = token_list{m_["Connection"]}.exists("close") || - (m_.version < 11 && ! m_.exists("Content-Length")); - auto os = ostream(b_); - detail::write_start_line(os, m_); - detail::write_fields(os, m_); - os << "\r\n"; - if(chunked_) + frdinit(std::integral_constant{}); + close_ = m_.has_close() || ( + m_.version < 11 && ! m_.has_content_length()); + if(m_.has_chunked()) goto go_init_c; s_ = do_init; // [[fallthrough]] @@ -127,7 +90,7 @@ get(error_code& ec, Visit&& visit) more_ = result->second; v_ = cb0_t{ boost::in_place_init, - b_.data(), + frd_->get(), result->first}; s_ = do_header; // [[fallthrough]] @@ -137,10 +100,11 @@ get(error_code& ec, Visit&& visit) visit(ec, boost::get(v_)); break; - go_header_only: - s_ = do_header_only; + go_header_only: + v_ = ch_t{frd_->get()}; + s_ = do_header_only; case do_header_only: - visit(ec, b_.data()); + visit(ec, boost::get(v_)); break; case do_body: @@ -193,7 +157,7 @@ get(error_code& ec, Visit&& visit) more_ = result->second; v_ = ch0_t{ boost::in_place_init, - b_.data(), + frd_->get(), detail::chunk_header{ buffer_size(result->first)}, [&]() @@ -214,10 +178,11 @@ get(error_code& ec, Visit&& visit) visit(ec, boost::get(v_)); break; - go_header_only_c: - s_ = do_header_only_c; + go_header_only_c: + v_ = ch_t{frd_->get()}; + s_ = do_header_only_c; case do_header_only_c: - visit(ec, b_.data()); + visit(ec, boost::get(v_)); break; case do_body_c: @@ -311,18 +276,18 @@ consume(std::size_t n) break; header_done_ = true; v_ = boost::blank{}; - b_.consume(b_.size()); // VFALCO delete b_? if(! more_) goto go_complete; s_ = do_body + 1; break; case do_header_only: - BOOST_ASSERT(n <= b_.size()); - b_.consume(n); - if(buffer_size(b_.data()) > 0) + BOOST_ASSERT(n <= buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) break; - // VFALCO delete b_? + frd_ = boost::none; header_done_ = true; if(! split_) goto go_complete; @@ -353,7 +318,6 @@ consume(std::size_t n) break; header_done_ = true; v_ = boost::blank{}; - b_.consume(b_.size()); // VFALCO delete b_? if(more_) s_ = do_body_c + 1; else @@ -362,11 +326,12 @@ consume(std::size_t n) case do_header_only_c: { - BOOST_ASSERT(n <= buffer_size(b_.data())); - b_.consume(n); - if(buffer_size(b_.data()) > 0) + BOOST_ASSERT(n <= buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) break; - // VFALCO delete b_? + frd_ = boost::none; header_done_ = true; if(! split_) { diff --git a/include/beast/http/impl/write.ipp b/include/beast/http/impl/write.ipp index a8ecd7ed..53b57b57 100644 --- a/include/beast/http/impl/write.ipp +++ b/include/beast/http/impl/write.ipp @@ -822,16 +822,15 @@ public: } // detail +#if 0 template std::ostream& operator<<(std::ostream& os, header const& msg) { - detail::write_start_line(os, msg); - detail::write_fields(os, msg); - os << "\r\n"; - return os; + // VFALCO TODO } +#endif template std::ostream& diff --git a/include/beast/http/message.hpp b/include/beast/http/message.hpp index 314bb0cc..cef3d990 100644 --- a/include/beast/http/message.hpp +++ b/include/beast/http/message.hpp @@ -119,7 +119,7 @@ struct header : Fields verb method() const; - /** Set the request-method verb. + /** Set the request-method. This function will set the method for requests to a known verb. @@ -127,11 +127,13 @@ struct header : Fields This may not be @ref verb::unknown. @throw std::invalid_argument when `v == verb::unknown`. + + @note This function is only available when `isRequest == true`. */ void method(verb v); - /** Return the request-method string. + /** Return the request-method as a string. @note This function is only available when `isRequest == true`. @@ -140,18 +142,18 @@ struct header : Fields string_view method_string() const; - /** Set the request-method string. + /** Set the request-method. - This function will set the method for requests to a verb - if the string matches a known verb, otherwise it will - store a copy of the passed string as the method. + This function will set the request-method a known verb + if the string matches, otherwise it will store a copy of + the passed string. @param s A string representing the request-method. @note This function is only available when `isRequest == true`. */ void - method(string_view s); + method_string(string_view s); /** Returns the request-target string. @@ -382,31 +384,27 @@ struct message : header /// Copy assignment message& operator=(message const&) = default; - /** Construct a message from a header. + /** Constructor. - Additional arguments, if any, are forwarded to - the constructor of the body member. + @param h The header to move construct from. + + @param args Optional arguments forwarded + to the body constructor. */ template explicit - message(header_type&& base, Args&&... args) - : header_type(std::move(base)) - , body(std::forward(args)...) - { - } + message(header_type&& h, Args&&... args); - /** Construct a message from a header. + /** Constructor. - Additional arguments, if any, are forwarded to - the constructor of the body member. + @param h The header to copy construct from. + + @param args Optional arguments forwarded + to the body constructor. */ template explicit - message(header_type const& base, Args&&... args) - : header_type(base) - , body(std::forward(args)...) - { - } + message(header_type const& h, Args&&... args); /** Construct a message. @@ -423,10 +421,7 @@ struct message : header #endif > explicit - message(U&& u) - : body(std::forward(u)) - { - } + message(U&& u); /** Construct a message. @@ -443,22 +438,14 @@ struct message : header typename std::decay::type, header_type>::value>::type #endif > - message(U&& u, V&& v) - : header_type(std::forward(v)) - , body(std::forward(u)) - { - } + message(U&& u, V&& v); /** Construct a message. @param un A tuple forwarded as a parameter pack to the body constructor. */ template - message(std::piecewise_construct_t, std::tuple un) - : message(std::piecewise_construct, un, - beast::detail::make_index_sequence{}) - { - } + message(std::piecewise_construct_t, std::tuple un); /** Construct a message. @@ -468,17 +455,25 @@ struct message : header */ template message(std::piecewise_construct_t, - std::tuple&& un, std::tuple&& vn) - : message(std::piecewise_construct, un, vn, - beast::detail::make_index_sequence{}, - beast::detail::make_index_sequence{}) - { - } + std::tuple&& un, std::tuple&& vn); - /** Returns `true` if Transfer-Encoding is present, and chunked appears last. + /// Returns `true` if "close" is specified in the Connection field. + bool + has_close() const; + + /// Returns `true` if "chunked" is the last Transfer-Encoding. + bool + has_chunked() const; + + /** Returns `true` if the Content-Length field is present. + + This function checks the fields to determine if the content + length field is present, regardless of the actual value. + + @note The contents of the body payload are not inspected. */ bool - chunked() const; + has_content_length() const; /** Returns the payload size of the body in octets if possible. diff --git a/include/beast/http/parser.hpp b/include/beast/http/parser.hpp index d09bd5f4..1fc63701 100644 --- a/include/beast/http/parser.hpp +++ b/include/beast/http/parser.hpp @@ -189,7 +189,7 @@ private: if(method != verb::unknown) m_.method(method); else - m_.method(method_str); + m_.method_string(method_str); m_.version = version; } @@ -241,17 +241,15 @@ private: } void - on_data(string_view s, - error_code& ec) + on_data(string_view s, error_code& ec) { wr_->put(boost::asio::buffer( s.data(), s.size()), ec); } void - on_chunk( - std::uint64_t, string_view, - error_code&) + on_chunk(std::uint64_t, + string_view, error_code&) { } diff --git a/include/beast/http/serializer.hpp b/include/beast/http/serializer.hpp index c0b53f48..59c5d107 100644 --- a/include/beast/http/serializer.hpp +++ b/include/beast/http/serializer.hpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -155,24 +154,26 @@ class serializer void split(bool, std::true_type) {} void split(bool v, std::false_type) { split_ = v; } - - using buffer_type = - basic_multi_buffer; + void frdinit(std::true_type); + void frdinit(std::false_type); using reader = typename Body::reader; using is_deferred = typename reader::is_deferred; + using ch_t = consuming_buffers; // header + using cb0_t = consuming_buffers>; // body using cb1_t = consuming_buffers< typename reader::const_buffers_type>; // body using ch0_t = consuming_buffers>; // crlf message const& m_; + boost::optional frd_; ChunkDecorator d_; boost::optional rd_; - buffer_type b_; boost::variant v_; + ch_t, cb0_t, cb1_t, ch0_t, ch1_t, ch2_t> v_; int s_ = do_construct; bool split_ = is_deferred::value; bool header_done_ = false; diff --git a/test/http/doc_http_samples.cpp b/test/http/doc_http_samples.cpp index bf528633..72c45462 100644 --- a/test/http/doc_http_samples.cpp +++ b/test/http/doc_http_samples.cpp @@ -68,7 +68,7 @@ public: flat_buffer buffer; request req; req.version = 11; - req.method("POST"); + req.method_string("POST"); req.target("/"); req.insert(field::user_agent, "test"); req.body = "Hello, world!"; @@ -101,7 +101,7 @@ public: { request req; req.version = 11; - req.method("POST"); + req.method_string("POST"); req.target("/"); req.insert(field::user_agent, "test"); req.body = "Hello, world!"; diff --git a/test/http/message.cpp b/test/http/message.cpp index 4522c802..c619f5a3 100644 --- a/test/http/message.cpp +++ b/test/http/message.cpp @@ -134,7 +134,7 @@ public: m1.target("u"); m1.body = "1"; m1.insert("h", "v"); - m2.method("G"); + m2.method_string("G"); m2.body = "2"; swap(m1, m2); BEAST_EXPECT(m1.method_string() == "G"); @@ -268,7 +268,7 @@ public: auto const scheck = [&](string_view s) { - h.method(s); + h.method_string(s); BEAST_EXPECT(h.method() == string_to_verb(s)); BEAST_EXPECT(h.method_string() == s); };