From 8c4136bb7312147596f4fecdc8480d2330035ad8 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 10 Jun 2017 15:05:32 -0700 Subject: [PATCH] basic_fields refactor (API Change): * Container interface more closely matches std::vector * While preserving the invariant that duplicate fields with the same case-insensitive name have their order preserved. --- CHANGELOG.md | 4 + include/beast/http/fields.hpp | 607 ++++++++++++++--------- include/beast/http/impl/fields.ipp | 450 +++++++++-------- include/beast/http/message.hpp | 4 +- include/beast/http/parser.hpp | 20 +- include/beast/websocket/impl/rfc6455.ipp | 2 +- include/beast/websocket/impl/stream.ipp | 28 +- include/beast/websocket/stream.hpp | 2 +- test/http/fields.cpp | 56 ++- test/http/message.cpp | 10 +- test/websocket/doc_snippets.cpp | 2 +- 11 files changed, 692 insertions(+), 493 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c94d155..733d8ce9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ Version 54: * basic_fields members and coverage * Add string_param +API Changes: + +* basic_fields refactor + -------------------------------------------------------------------------------- Version 53: diff --git a/include/beast/http/fields.hpp b/include/beast/http/fields.hpp index 6dc5e2f6..92ca2622 100644 --- a/include/beast/http/fields.hpp +++ b/include/beast/http/fields.hpp @@ -9,11 +9,11 @@ #define BEAST_HTTP_FIELDS_HPP #include +#include #include #include #include #include -#include #include #include #include @@ -49,29 +49,96 @@ private: using off_t = std::uint16_t; public: - /** The value type of the field sequence. + /// The type of allocator used. + using allocator_type = Allocator; - Meets the requirements of @b Field. - */ - struct value_type + /// The type of element used to represent a field + class value_type { - string_view - name() const - { - return {first, off - 2u}; - } + friend class basic_fields; - string_view - value() const - { - return {first + off, len}; - } + boost::asio::const_buffer + buffer() const; - char const* first; - off_t off; - off_t len; + value_type(field name, + string_view sname, string_view value); + + boost::intrusive::list_member_hook< + boost::intrusive::link_mode< + boost::intrusive::normal_link>> + list_hook_; + boost::intrusive::set_member_hook< + boost::intrusive::link_mode< + boost::intrusive::normal_link>> + set_hook_; + off_t off_; + off_t len_; + field f_; + + public: + /// Returns the field enum, which can be @ref field::unknown + field + name() const; + + /// Returns the field name as a string + string_view + name_string() const; + + /// Returns the value of the field + string_view + value() const; }; + /// A function that compares keys as LessThanComparable + struct key_compare : private beast::detail::ci_less + { + /// Returns `true` if lhs is less than rhs using a strict ordering + template + bool + operator()(String const& lhs, value_type const& rhs) const + { + return ci_less::operator()(lhs, rhs.name_string()); + } + + /// Returns `true` if lhs is less than rhs using a strict ordering + template + bool + operator()(value_type const& lhs, String const& rhs) const + { + return ci_less::operator()(lhs.name_string(), rhs); + } + + /// Returns `true` if lhs is less than rhs using a strict ordering + bool + operator()(value_type const& lhs, value_type const& rhs) const + { + return ci_less::operator()( + lhs.name(), rhs.name()); + } + }; + + /// The algorithm used to serialize the header + class reader; + +private: + using list_t = typename boost::intrusive::make_list< + value_type, boost::intrusive::member_hook< + value_type, boost::intrusive::list_member_hook< + boost::intrusive::link_mode< + boost::intrusive::normal_link>>, + &value_type::list_hook_>, + boost::intrusive::constant_time_size< + false>>::type; + + using set_t = typename boost::intrusive::make_multiset< + value_type, boost::intrusive::member_hook>, + &value_type::set_hook_>, + boost::intrusive::constant_time_size, + boost::intrusive::compare>::type; + protected: friend class fields_test; // for `header` @@ -90,13 +157,15 @@ protected: /** Move constructor. - The moved-from object behaves as if by call to @ref clear. + The state of the moved-from object is + as if constructed using the same allocator. */ basic_fields(basic_fields&&); /** Move constructor. - The moved-from object behaves as if by call to @ref clear. + The state of the moved-from object is + as if constructed using the same allocator. @param alloc The allocator to use. */ @@ -125,7 +194,8 @@ protected: /** Move assignment. - The moved-from object behaves as if by call to @ref clear. + The state of the moved-from object is + as if constructed using the same allocator. */ basic_fields& operator=(basic_fields&&); @@ -137,11 +207,12 @@ protected: basic_fields& operator=(basic_fields const&); public: - /// The type of allocator used. - using allocator_type = Allocator; - /// A constant iterator to the field sequence. - class const_iterator; +#if BEAST_DOXYGEN + using const_iterator = implementation_defined; +#else + using const_iterator = typename list_t::const_iterator; +#endif /// A constant iterator to the field sequence. using iterator = const_iterator; @@ -152,9 +223,63 @@ public: { return typename std::allocator_traits< Allocator>::template rebind_alloc< - element>(alloc_); + value_type>(alloc_); } + //-------------------------------------------------------------------------- + // + // Element access + // + //-------------------------------------------------------------------------- + + /** Returns the value for a field, or throws an exception. + + @param name The name of the field. + + @return The field value. + + @throws std::out_of_range if the field is not found. + */ + string_view + at(field name) const; + + /** Returns the value for a field, or throws an exception. + + @param name The name of the field. + + @return The field value. + + @throws std::out_of_range if the field is not found. + */ + string_view + at(string_view name) const; + + /** Returns the value for a field, or `""` if it does not exist. + + If more than one field with the specified name exists, the + first field defined by insertion order is returned. + + @param name The name of the field. + */ + string_view + operator[](field name) const; + + /** Returns the value for a case-insensitive matching header, or `""`. + + If more than one field with the specified name exists, the + first field defined by insertion order is returned. + + @param name The name of the field. + */ + string_view + operator[](string_view name) const; + + //-------------------------------------------------------------------------- + // + // Iterators + // + //-------------------------------------------------------------------------- + /// Return a const iterator to the beginning of the field sequence. const_iterator begin() const @@ -183,187 +308,159 @@ public: return list_.cend(); } - /// Return `true` if the specified field exists. + //-------------------------------------------------------------------------- + // + // Capacity + // + //-------------------------------------------------------------------------- + +private: + // VFALCO Since the header and message derive from Fields, + // what does the expression m.empty() mean? Its confusing. bool - exists(field f) const + empty() const { - // VFALCO Should we throw here? - if(f == field::unknown) - return false; - return set_.find(to_string(f), less{}) != set_.end(); + return list_.empty(); } +public: - /// Return `true` if the specified field exists. - bool - exists(string_view name) const - { - return set_.find(name, less{}) != set_.end(); - } + //-------------------------------------------------------------------------- + // + // Modifiers + // + //-------------------------------------------------------------------------- - /// Return the number of values for the specified field. - std::size_t - count(field name) const - { - return count(to_string(name)); - } +private: + // VFALCO But this leaves behind the method, target, and reason! + /** Remove all fields from the container - /// Return the number of values for the specified field. - std::size_t - count(string_view name) const; - - /** Returns an iterator to the case-insensitive matching field name. - - If more than one field with the specified name exists, the - first field defined by insertion order is returned. + All references, pointers, or iterators referring to contained + elements are invalidated. All past-the-end iterators are also + invalidated. */ - iterator - find(string_view name) const; - - /** Returns an iterator to the case-insensitive matching field. - - If more than one field with the specified name exists, the - first field defined by insertion order is returned. - - @param name The field to find. - */ - iterator - find(field name) const; - - /** Returns the value for a case-insensitive matching header, or `""`. - - If more than one field with the specified name exists, the - first field defined by insertion order is returned. - */ - string_view const - operator[](string_view name) const; - - /** Returns the value for a field, or `""` if it does not exist. - - If more than one field with the specified name exists, the - first field defined by insertion order is returned. - - @param name The field to retrieve. - */ - string_view const - operator[](field name) const; - - /// Clear the contents of the basic_fields. void - clear() noexcept; + clear(); +public: - /** Remove zero or more known fields. + /** Insert a field. - If more than one field with the specified name exists, all - matching fields will be removed. + If one or more fields with the same name already exist, + the new field will be inserted after the last field with + the matching name, in serialization order. - @param f The known field constant. + @param name The field name. + + @param value The value of the field, as a @ref string_param + */ + void + insert(field name, string_param const& value); + + /** Insert a field. + + If one or more fields with the same name already exist, + the new field will be inserted after the last field with + the matching name, in serialization order. + + @param name The field name. + + @param value The value of the field, as a @ref string_param + */ + void + insert(string_view name, string_param const& value); + + /** Insert a field. + + If one or more fields with the same name already exist, + the new field will be inserted after the last field with + the matching name, in serialization order. + + @param name The field name. + + @param name_string The literal text corresponding to the + field name. If `name != field::unknown`, then this value + must be equal to `to_string(name)` using a case-insensitive + comparison, otherwise the behavior is undefined. + + @param value The value of the field, as a @ref string_param + */ + void + insert(field name, string_view name_string, + string_param const& value); + + /** Replace a field value. + + First removes any values with matching field names, then + inserts the new field value. + + @param name The field name. + + @param value The value of the field, as a @ref string_param + + @return The field value. + */ + void + replace(field name, string_param const& value); + + /** Replace a field value. + + First removes any values with matching field names, then + inserts the new field value. + + @param name The field name. + + @param value The value of the field, as a @ref string_param + */ + void + replace(string_view name, string_param const& value); + + /** Remove a field. + + References and iterators to the erased elements are + invalidated. Other references and iterators are not + affected. + + @param pos An iterator to the element to remove. + + @return An iterator following the last removed element. + If the iterator refers to the last element, the end() + iterator is returned. + */ + const_iterator + erase(const_iterator pos); + + /** Remove all fields with the specified name. + + All fields with the same field name are erased from the + container. + References and iterators to the erased elements are + invalidated. Other references and iterators are not + affected. + + @param name The field name. @return The number of fields removed. */ std::size_t - erase(field f); + erase(field name); - /** Remove zero or more fields by name. + /** Remove all fields with the specified name. - If more than one field with the specified name exists, all - matching fields will be removed. + All fields with the same field name are erased from the + container. + References and iterators to the erased elements are + invalidated. Other references and iterators are not + affected. - @param name The name of the field(s) to remove. + @param name The field name. @return The number of fields removed. */ std::size_t erase(string_view name); - /** Insert a value for a known field. - - If a field with the same name already exists, the - existing field is untouched and a new field value pair - is inserted into the container. - - @param f The known field constant. - - @param value A string holding the value of the field. - */ + /// Swap this container with another void - insert(field f, string_view value); - - /** Insert a value for a field by name. - - If a field with the same name already exists, the - existing field is untouched and a new field value pair - is inserted into the container. - - @param name The name of the field. - - @param value A string holding the value of the field. - */ - void - insert(string_view name, string_view value); - - /** Insert a field value. - - If a field with the same name already exists, the - existing field is untouched and a new field value pair - is inserted into the container. - - @param name The name of the field - - @param value The value of the field. The object will be - converted to a string using `boost::lexical_cast`. - */ - template - typename std::enable_if< - ! std::is_constructible::value>::type - insert(string_view name, T const& value) - { - // VFALCO This should use a static buffer, see lexical_cast doc - insert(name, boost::lexical_cast(value)); - } - - /** Replace a field value. - - First removes any values with matching field names, then - inserts the new field value. - - @param name The name of the field. - - @param value A string holding the value of the field. - */ - void - replace(string_view name, string_view value); - - /** Replace a field value. - - First removes any values with matching field names, then - inserts the new field value. - - @param name The field to replace. - - @param value A string holding the value of the field. - */ - void - replace(field name, string_view value); - - /** Replace a field value. - - First removes any values with matching field names, then - inserts the new field value. - - @param name The name of the field - - @param value The value of the field. The object will be - converted to a string using `boost::lexical_cast`. - */ - template - typename std::enable_if< - ! std::is_constructible::value>::type - replace(string_view name, T const& value) - { - // VFALCO This should use a static buffer, see lexical_cast doc - replace(name, - boost::lexical_cast(value)); - } + swap(basic_fields& other); /// Swap two field containers template @@ -371,8 +468,83 @@ public: void swap(basic_fields& lhs, basic_fields& rhs); - /// The algorithm used to serialize the header - class reader; + //-------------------------------------------------------------------------- + // + // Lookup + // + //-------------------------------------------------------------------------- + + /** Return the number of fields with the specified name. + + @param name The field name. + */ + std::size_t + count(field name) const; + + /** Return the number of fields with the specified name. + + @param name The field name. + */ + std::size_t + count(string_view name) const; + + /** Returns an iterator to the case-insensitive matching field. + + If more than one field with the specified name exists, the + first field defined by insertion order is returned. + + @param name The field name. + + @return An iterator to the matching field, or `end()` if + no match was found. + */ + const_iterator + find(field name) const; + + /** Returns an iterator to the case-insensitive matching field name. + + If more than one field with the specified name exists, the + first field defined by insertion order is returned. + + @param name The field name. + + @return An iterator to the matching field, or `end()` if + no match was found. + */ + const_iterator + find(string_view name) const; + + /** Returns a range of iterators to the fields with the specified name. + + @param name The field name. + + @return A range of iterators to fields with the same name, + otherwise an empty range. + */ + std::pair + equal_range(field name) const; + + /** Returns a range of iterators to the fields with the specified name. + + @param name The field name. + + @return A range of iterators to fields with the same name, + otherwise an empty range. + */ + std::pair + equal_range(string_view name) const; + + //-------------------------------------------------------------------------- + // + // Observers + // + //-------------------------------------------------------------------------- + + key_compare + key_comp() const + { + return key_compare{}; + } protected: /// Returns `true` if the value for Connection has "close" in the list. @@ -441,59 +613,9 @@ private: template friend class basic_fields; - class element - : public boost::intrusive::set_base_hook < - boost::intrusive::link_mode < - boost::intrusive::normal_link>> - , public boost::intrusive::list_base_hook < - boost::intrusive::link_mode < - boost::intrusive::normal_link>> - { - off_t off_; - off_t len_; - - public: - element(string_view name, string_view value); - - string_view - name() const; - - string_view - value() const; - - boost::asio::const_buffer - buffer() const; - - value_type data; - }; - - struct less : private beast::detail::ci_less - { - template - bool - operator()(String const& lhs, element const& rhs) const - { - return ci_less::operator()(lhs, rhs.data.name()); - } - - template - bool - operator()(element const& lhs, String const& rhs) const - { - return ci_less::operator()(lhs.data.name(), rhs); - } - - bool - operator()(element const& lhs, element const& rhs) const - { - return ci_less::operator()( - lhs.data.name(), rhs.data.name()); - } - }; - using alloc_type = typename std::allocator_traits:: - template rebind_alloc; + template rebind_alloc; using alloc_traits = std::allocator_traits; @@ -501,19 +623,12 @@ private: using size_type = typename std::allocator_traits::size_type; - using list_t = typename boost::intrusive::make_list< - element, boost::intrusive::constant_time_size< - false>>::type; - - using set_t = typename boost::intrusive::make_multiset< - element, boost::intrusive::constant_time_size< - true>, boost::intrusive::compare>::type; - - element& - new_element(string_view name, string_view value); + value_type& + new_element(field name, + string_view sname, string_view value); void - delete_element(element& e); + delete_element(value_type& e); void realloc_string(string_view& dest, string_view s); diff --git a/include/beast/http/impl/fields.ipp b/include/beast/http/impl/fields.ipp index 5f14f753..dac9c54d 100644 --- a/include/beast/http/impl/fields.ipp +++ b/include/beast/http/impl/fields.ipp @@ -16,101 +16,11 @@ #include #include #include -#include +#include namespace beast { namespace http { -//------------------------------------------------------------------------------ - -template -class basic_fields:: - const_iterator -{ - using iter_type = typename list_t::const_iterator; - - iter_type it_; - - template - friend class beast::http::basic_fields; - - const_iterator(iter_type it) - : it_(it) - { - } - -public: - using value_type = typename - basic_fields::value_type; - using pointer = value_type const*; - using reference = value_type const&; - using difference_type = std::ptrdiff_t; - using iterator_category = - std::bidirectional_iterator_tag; - - const_iterator() = default; - const_iterator(const_iterator&& other) = default; - const_iterator(const_iterator const& other) = default; - const_iterator& operator=(const_iterator&& other) = default; - const_iterator& operator=(const_iterator const& other) = default; - - bool - operator==(const_iterator const& other) const - { - return it_ == other.it_; - } - - bool - operator!=(const_iterator const& other) const - { - return !(*this == other); - } - - reference - operator*() const - { - return it_->data; - } - - pointer - operator->() const - { - return &**this; - } - - const_iterator& - operator++() - { - ++it_; - return *this; - } - - const_iterator - operator++(int) - { - auto temp = *this; - ++(*this); - return temp; - } - - const_iterator& - operator--() - { - --it_; - return *this; - } - - const_iterator - operator--(int) - { - auto temp = *this; - --(*this); - return temp; - } -}; - -//------------------------------------------------------------------------------ - template class basic_fields::reader { @@ -284,30 +194,40 @@ public: template basic_fields:: -element:: -element(string_view name, string_view value) - : off_(static_cast(name.size() + 2)) +value_type:: +value_type(field name, + string_view sname, string_view value) + : off_(static_cast(sname.size() + 2)) , len_(static_cast(value.size())) + , f_(name) { + //BOOST_ASSERT(name == field::unknown || + // detail::ci_equal(sname, to_string(name))); char* p = reinterpret_cast(this + 1); - data.first = p; - data.off = off_; - data.len = len_; - p[off_-2] = ':'; p[off_-1] = ' '; p[off_ + len_] = '\r'; p[off_ + len_ + 1] = '\n'; - std::memcpy(p, name.data(), name.size()); + std::memcpy(p, sname.data(), sname.size()); std::memcpy(p + off_, value.data(), value.size()); } +template +inline +field +basic_fields:: +value_type:: +name() const +{ + return f_; +} + template inline string_view basic_fields:: -element:: -name() const +value_type:: +name_string() const { return {reinterpret_cast< char const*>(this + 1), @@ -318,7 +238,7 @@ template inline string_view basic_fields:: -element:: +value_type:: value() const { return {reinterpret_cast< @@ -330,7 +250,7 @@ template inline boost::asio::const_buffer basic_fields:: -element:: +value_type:: buffer() const { return boost::asio::const_buffer{ @@ -460,47 +380,51 @@ operator=(basic_fields const& other) -> return *this; } +//------------------------------------------------------------------------------ +// +// Element access +// //------------------------------------------------------------------------------ template -std::size_t +string_view basic_fields:: -count(string_view name) const +at(field name) const { - auto const it = set_.find(name, less{}); - if(it == set_.end()) - return 0; - auto const last = set_.upper_bound(name, less{}); - return static_cast(std::distance(it, last)); + BOOST_ASSERT(name != field::unknown); + auto const it = find(name); + if(it == end()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "field not found"}); + return it->value(); } template -auto +string_view basic_fields:: -find(string_view name) const -> - iterator +at(string_view name) const { - auto const it = set_.find(name, less{}); - if(it == set_.end()) - return list_.end(); - return list_.iterator_to(*it); + auto const it = find(name); + if(it == end()) + BOOST_THROW_EXCEPTION(std::out_of_range{ + "field not found"}); + return it->value(); } template -auto +string_view basic_fields:: -find(field name) const -> - iterator +operator[](field name) const { - auto const it = set_.find( - to_string(name), less{}); - if(it == set_.end()) - return list_.end(); - return list_.iterator_to(*it); + BOOST_ASSERT(name != field::unknown); + auto const it = find(name); + if(it == end()) + return {}; + return it->value(); } template -string_view const +string_view basic_fields:: operator[](string_view name) const { @@ -510,33 +434,118 @@ operator[](string_view name) const return it->value(); } -template -string_view const -basic_fields:: -operator[](field name) const -{ - auto const it = find(name); - if(it == end()) - return {}; - return it->value(); -} +//------------------------------------------------------------------------------ +// +// Modifiers +// +//------------------------------------------------------------------------------ template void basic_fields:: -clear() noexcept +clear() { delete_list(); set_.clear(); list_.clear(); } +template +inline +void +basic_fields:: +insert(field name, string_param const& value) +{ + BOOST_ASSERT(name != field::unknown); + insert(name, to_string(name), value); +} + +template +void +basic_fields:: +insert(string_view sname, string_param const& value) +{ + auto const name = + string_to_field(sname); + insert(name, sname, value); +} + +template +void +basic_fields:: +insert(field name, + string_view sname, string_param const& value) +{ + auto& e = new_element( + name, sname, value.str()); + auto const before = + set_.upper_bound(sname, key_compare{}); + if(before == set_.end()) + { + set_.push_back(e); + list_.push_back(e); + return; + } + if(before == set_.begin()) + { + set_.push_front(e); + list_.push_back(e); + return; + } + auto const last = std::prev(before); + if(! beast::detail::ci_equal( + sname, last->name_string())) + { + set_.insert_before(before, e); + list_.push_back(e); + return; + } + // count(name_string) > 0 + set_.insert_before(before, e); + list_.insert(list_.iterator_to(*before), e); +} + +template +void +basic_fields:: +replace(field name, string_param const& value) +{ + BOOST_ASSERT(name != field::unknown); + erase(name); + insert(name, value); +} + +template +void +basic_fields:: +replace(string_view sname, string_param const& value) +{ + auto const name = string_to_field(sname); + erase(sname); + insert(name, sname, value); +} + +template +auto +basic_fields:: +erase(const_iterator pos) -> + const_iterator +{ + auto next = pos.iter(); + auto& e = *next++; + set_.erase(e); + list_.erase(e); + delete_element(e); + return next; +} + template std::size_t basic_fields:: -erase(field f) +erase(field name) { - return erase(to_string(f)); + BOOST_ASSERT(name != field::unknown); + return erase(to_string(name)); } template @@ -544,60 +553,105 @@ std::size_t basic_fields:: erase(string_view name) { - auto it = set_.find(name, less{}); - if(it == set_.end()) - return 0; - auto const last = set_.upper_bound(name, less{}); - std::size_t n = 1; - for(;;) - { - auto& e = *it++; - set_.erase(set_.iterator_to(e)); - list_.erase(list_.iterator_to(e)); - delete_element(e); - if(it == last) - break; - ++n; - } + std::size_t n =0; + set_.erase_and_dispose(name, key_compare{}, + [&](value_type* e) + { + ++n; + list_.erase(list_.iterator_to(*e)); + delete_element(*e); + }); return n; } template void basic_fields:: -insert(field f, string_view value) +swap(basic_fields& other) { - insert(to_string(f), value); + swap(other, typename alloc_traits:: + propagate_on_container_swap{}); } template void -basic_fields:: -insert(string_view name, string_view value) +swap( + basic_fields& lhs, + basic_fields& rhs) { - auto& e = new_element(name, value); - set_.insert_before(set_.upper_bound(name, less{}), e); - list_.push_back(e); + lhs.swap(rhs); +} + +//------------------------------------------------------------------------------ +// +// Lookup +// +//------------------------------------------------------------------------------ + +template +inline +std::size_t +basic_fields:: +count(field name) const +{ + BOOST_ASSERT(name != field::unknown); + return count(to_string(name)); } template -void +std::size_t basic_fields:: -replace(string_view name, string_view value) +count(string_view name) const { - value = detail::trim(value); - erase(name); - insert(name, value); + return set_.count(name, key_compare{}); } template -void +inline +auto basic_fields:: -replace(field name, string_view value) +find(field name) const -> + const_iterator { - value = detail::trim(value); - erase(name); - insert(name, value); + BOOST_ASSERT(name != field::unknown); + return find(to_string(name)); +} + +template +auto +basic_fields:: +find(string_view name) const -> + const_iterator +{ + auto const it = set_.find( + name, key_compare{}); + if(it == set_.end()) + return list_.end(); + return list_.iterator_to(*it); +} + +template +inline +auto +basic_fields:: +equal_range(field name) const -> + std::pair +{ + BOOST_ASSERT(name != field::unknown); + return equal_range(to_string(name)); +} + +template +auto +basic_fields:: +equal_range(string_view name) const -> + std::pair +{ + auto const result = + set_.equal_range(name, key_compare{}); + return { + list_.iterator_to(result->first), + list_.iterator_to(result->second)}; } //------------------------------------------------------------------------------ @@ -610,7 +664,7 @@ basic_fields:: has_close_impl() const { auto const fit = set_.find( - to_string(field::connection), less{}); + to_string(field::connection), key_compare{}); if(fit == set_.end()) return false; return token_list{fit->value()}.exists("close"); @@ -622,7 +676,7 @@ basic_fields:: has_chunked_impl() const { auto const fit = set_.find(to_string( - field::transfer_encoding), less{}); + field::transfer_encoding), key_compare{}); if(fit == set_.end()) return false; token_list const v{fit->value()}; @@ -644,7 +698,7 @@ basic_fields:: has_content_length_impl() const { auto const fit = set_.find( - to_string(field::content_length), less{}); + to_string(field::content_length), key_compare{}); return fit != set_.end(); } @@ -710,9 +764,8 @@ void basic_fields:: content_length_impl(std::uint64_t n) { - this->erase("Content-Length"); - this->insert("Content-Length", - to_static_string(n)); + erase(field::content_length); + insert(field::content_length, n); } template @@ -721,12 +774,13 @@ void basic_fields:: set_chunked_impl(bool v) { + // VFALCO We need to handle removing the chunked as well BOOST_ASSERT(v); - auto it = find("Transfer-Encoding"); + auto it = find(field::transfer_encoding); if(it == end()) - this->insert("Transfer-Encoding", "chunked"); + this->insert(field::transfer_encoding, "chunked"); else - this->replace("Transfer-Encoding", + this->replace(field::transfer_encoding, it->value().to_string() + ", chunked"); } @@ -735,10 +789,11 @@ set_chunked_impl(bool v) template auto basic_fields:: -new_element(string_view name, string_view value) -> - element& +new_element(field name, + string_view sname, string_view value) -> + value_type& { - if(name.size() + 2 > + if(sname.size() + 2 > (std::numeric_limits::max)()) BOOST_THROW_EXCEPTION(std::length_error{ "field name too large"}); @@ -748,24 +803,26 @@ new_element(string_view name, string_view value) -> "field value too large"}); value = detail::trim(value); std::uint16_t const off = - static_cast(name.size() + 2); + static_cast(sname.size() + 2); std::uint16_t const len = static_cast(value.size()); auto const p = alloc_traits::allocate(alloc_, - 1 + (off + len + 2 + sizeof(element) - 1) / - sizeof(element)); - alloc_traits::construct(alloc_, p, name, value); + 1 + (off + len + 2 + sizeof(value_type) - 1) / + sizeof(value_type)); + // VFALCO allocator can't call the constructor because its private + //alloc_traits::construct(alloc_, p, name, sname, value); + new(p) value_type{name, sname, value}; return *p; } template void basic_fields:: -delete_element(element& e) +delete_element(value_type& e) { auto const n = 1 + - (e.data.off + e.data.len + 2 + - sizeof(element) - 1) / sizeof(element); + (e.off_ + e.len_ + 2 + + sizeof(value_type) - 1) / sizeof(value_type); alloc_traits::destroy(alloc_, &e); alloc_traits::deallocate(alloc_, &e, n); } @@ -802,7 +859,7 @@ basic_fields:: copy_all(basic_fields const& other) { for(auto const& e : other.list_) - insert(e.name(), e.value()); + insert(e.name(), e.name_string(), e.value()); realloc_string(method_, other.method_); realloc_string(target_or_reason_, other.target_or_reason_); @@ -889,17 +946,6 @@ copy_assign(basic_fields const& other, std::false_type) copy_all(other); } -template -void -swap(basic_fields& lhs, - basic_fields& rhs) -{ - using alloc_traits = typename - basic_fields::alloc_traits; - lhs.swap(rhs, typename alloc_traits:: - propagate_on_container_swap{}); -} - template inline void diff --git a/include/beast/http/message.hpp b/include/beast/http/message.hpp index 82660de5..4d34bb2c 100644 --- a/include/beast/http/message.hpp +++ b/include/beast/http/message.hpp @@ -125,7 +125,7 @@ struct header : Fields @param v The request method verb to set. This may not be @ref verb::unknown. - @throw std::invalid_argument when `v == verb::unknown`. + @throws std::invalid_argument when `v == verb::unknown`. @note This function is only available when `isRequest == true`. */ @@ -277,7 +277,7 @@ struct header : Fields @param v The status-code integer to set. - @throw std::invalid_argument if `v > 999`. + @throws std::invalid_argument if `v > 999`. */ void result(unsigned v); diff --git a/include/beast/http/parser.hpp b/include/beast/http/parser.hpp index 1fc63701..13ae8368 100644 --- a/include/beast/http/parser.hpp +++ b/include/beast/http/parser.hpp @@ -204,26 +204,10 @@ private: } void - on_field(field f, string_view name, + on_field(field name, string_view name_string, string_view value, error_code&) { - if(f != field::unknown) - { - #if 1 - // This preserves capitalization of field names - if(to_string(f) == name) - m_.insert(f, value); - else - m_.insert(name, value); - #else - // This doesn't. - m_.insert(f, value); - #endif - } - else - { - m_.insert(name, value); - } + m_.insert(name, name_string, value); } void diff --git a/include/beast/websocket/impl/rfc6455.ipp b/include/beast/websocket/impl/rfc6455.ipp index a1269ae1..5452f68a 100644 --- a/include/beast/websocket/impl/rfc6455.ipp +++ b/include/beast/websocket/impl/rfc6455.ipp @@ -25,7 +25,7 @@ is_upgrade(http::header const& req) return false; if(! http::token_list{req["Upgrade"]}.exists("websocket")) return false; - if(! req.exists("Sec-WebSocket-Version")) + if(! req.count(http::field::sec_websocket_version)) return false; return true; } diff --git a/include/beast/websocket/impl/stream.ipp b/include/beast/websocket/impl/stream.ipp index 74178fe9..9df74539 100644 --- a/include/beast/websocket/impl/stream.ipp +++ b/include/beast/websocket/impl/stream.ipp @@ -185,11 +185,9 @@ build_request(detail::sec_ws_key_type& key, detail::pmd_write(req, config); } decorator(req); - if(! req.exists("User-Agent")) - { - static_string<20> s(BEAST_VERSION_STRING); - req.insert(http::field::user_agent, s); - } + if(! req.count(http::field::user_agent)) + req.insert(http::field::user_agent, + BEAST_VERSION_STRING); return req; } @@ -204,7 +202,7 @@ build_response(http::header const& req, [&decorator](response_type& res) { decorator(res); - if(! res.exists("Server")) + if(! res.count(http::field::server)) { BOOST_STATIC_ASSERT(sizeof(BEAST_VERSION_STRING) < 20); static_string<20> s(BEAST_VERSION_STRING); @@ -228,18 +226,18 @@ build_response(http::header const& req, return err("Wrong method"); if(! is_upgrade(req)) return err("Expected Upgrade request"); - if(! req.exists("Host")) + if(! req.count(http::field::host)) return err("Missing Host"); - if(! req.exists("Sec-WebSocket-Key")) + if(! req.count(http::field::sec_websocket_key)) return err("Missing Sec-WebSocket-Key"); - if(! http::token_list{req["Upgrade"]}.exists("websocket")) + if(! http::token_list{req[http::field::upgrade]}.exists("websocket")) return err("Missing websocket Upgrade token"); - auto const key = req["Sec-WebSocket-Key"]; + auto const key = req[http::field::sec_websocket_key]; if(key.size() > detail::sec_ws_key_type::max_size_n) return err("Invalid Sec-WebSocket-Key"); { auto const version = - req["Sec-WebSocket-Version"]; + req[http::field::sec_websocket_version]; if(version.empty()) return err("Missing Sec-WebSocket-Version"); if(version != "13") @@ -286,16 +284,16 @@ do_response(http::header const& res, return false; if(res.result() != http::status::switching_protocols) return false; - if(! http::token_list{res["Connection"]}.exists("upgrade")) + if(! http::token_list{res[http::field::connection]}.exists("upgrade")) return false; - if(! http::token_list{res["Upgrade"]}.exists("websocket")) + if(! http::token_list{res[http::field::upgrade]}.exists("websocket")) return false; - if(! res.exists("Sec-WebSocket-Accept")) + if(res.count(http::field::sec_websocket_accept) != 1) return false; detail::sec_ws_accept_type accept; detail::make_sec_ws_accept(accept, key); if(accept.compare( - res["Sec-WebSocket-Accept"]) != 0) + res[http::field::sec_websocket_accept]) != 0) return false; return true; }(); diff --git a/include/beast/websocket/stream.hpp b/include/beast/websocket/stream.hpp index 6dba6e12..d6985dde 100644 --- a/include/beast/websocket/stream.hpp +++ b/include/beast/websocket/stream.hpp @@ -340,7 +340,7 @@ public: @param n The size of the read buffer. - @throw std::invalid_argument If the buffer size is less than 8. + @throws std::invalid_argument If the buffer size is less than 8. @par Example Setting the read buffer size. diff --git a/test/http/fields.cpp b/test/http/fields.cpp index 20d1e987..5e009400 100644 --- a/test/http/fields.cpp +++ b/test/http/fields.cpp @@ -273,8 +273,8 @@ public: { fields f; f.insert(field::user_agent, "x"); - BEAST_EXPECT(f.exists(field::user_agent)); - BEAST_EXPECT(f.exists(to_string(field::user_agent))); + BEAST_EXPECT(f.count(field::user_agent)); + BEAST_EXPECT(f.count(to_string(field::user_agent))); BEAST_EXPECT(f.count(field::user_agent) == 1); BEAST_EXPECT(f.count(to_string(field::user_agent)) == 1); f.insert(field::user_agent, "y"); @@ -326,12 +326,64 @@ public: BEAST_EXPECT(size(f) == 2); } + void + testContainer() + { + { + // group fields + fields f; + f.insert(field::age, 1); + f.insert(field::body, 2); + f.insert(field::close, 3); + f.insert(field::body, 4); + BEAST_EXPECT(std::next(f.begin(), 0)->name() == field::age); + BEAST_EXPECT(std::next(f.begin(), 1)->name() == field::body); + BEAST_EXPECT(std::next(f.begin(), 2)->name() == field::body); + BEAST_EXPECT(std::next(f.begin(), 3)->name() == field::close); + BEAST_EXPECT(std::next(f.begin(), 0)->name_string() == "Age"); + BEAST_EXPECT(std::next(f.begin(), 1)->name_string() == "Body"); + BEAST_EXPECT(std::next(f.begin(), 2)->name_string() == "Body"); + BEAST_EXPECT(std::next(f.begin(), 3)->name_string() == "Close"); + BEAST_EXPECT(std::next(f.begin(), 0)->value() == "1"); + BEAST_EXPECT(std::next(f.begin(), 1)->value() == "2"); + BEAST_EXPECT(std::next(f.begin(), 2)->value() == "4"); + BEAST_EXPECT(std::next(f.begin(), 3)->value() == "3"); + BEAST_EXPECT(f.erase(field::body) == 2); + BEAST_EXPECT(std::next(f.begin(), 0)->name_string() == "Age"); + BEAST_EXPECT(std::next(f.begin(), 1)->name_string() == "Close"); + } + { + // group fields, case insensitive + fields f; + f.insert("a", 1); + f.insert("ab", 2); + f.insert("b", 3); + f.insert("AB", 4); + BEAST_EXPECT(std::next(f.begin(), 0)->name() == field::unknown); + BEAST_EXPECT(std::next(f.begin(), 1)->name() == field::unknown); + BEAST_EXPECT(std::next(f.begin(), 2)->name() == field::unknown); + BEAST_EXPECT(std::next(f.begin(), 3)->name() == field::unknown); + BEAST_EXPECT(std::next(f.begin(), 0)->name_string() == "a"); + BEAST_EXPECT(std::next(f.begin(), 1)->name_string() == "ab"); + BEAST_EXPECT(std::next(f.begin(), 2)->name_string() == "AB"); + BEAST_EXPECT(std::next(f.begin(), 3)->name_string() == "b"); + BEAST_EXPECT(std::next(f.begin(), 0)->value() == "1"); + BEAST_EXPECT(std::next(f.begin(), 1)->value() == "2"); + BEAST_EXPECT(std::next(f.begin(), 2)->value() == "4"); + BEAST_EXPECT(std::next(f.begin(), 3)->value() == "3"); + BEAST_EXPECT(f.erase("Ab") == 2); + BEAST_EXPECT(std::next(f.begin(), 0)->name_string() == "a"); + BEAST_EXPECT(std::next(f.begin(), 1)->name_string() == "b"); + } + } + void run() override { testMembers(); testHeaders(); testRFC2616(); testErase(); + testContainer(); } }; diff --git a/test/http/message.cpp b/test/http/message.cpp index fec951aa..7d085ce8 100644 --- a/test/http/message.cpp +++ b/test/http/message.cpp @@ -124,7 +124,7 @@ public: header h; h.insert(field::user_agent, "test"); request m{Arg1{}, std::move(h)}; - BEAST_EXPECT(! h.exists("User-Agent")); + BEAST_EXPECT(! h.count(http::field::user_agent)); BEAST_EXPECT(m["User-Agent"] == "test"); } @@ -143,8 +143,8 @@ public: BEAST_EXPECT(m2.target() == "u"); BEAST_EXPECT(m1.body == "2"); BEAST_EXPECT(m2.body == "1"); - BEAST_EXPECT(! m1.exists("h")); - BEAST_EXPECT(m2.exists("h")); + BEAST_EXPECT(! m1.count("h")); + BEAST_EXPECT(m2.count("h")); } struct MoveFields : fields @@ -217,8 +217,8 @@ public: BEAST_EXPECT(m2.version == 10); BEAST_EXPECT(m1.body == "2"); BEAST_EXPECT(m2.body == "1"); - BEAST_EXPECT(! m1.exists("h")); - BEAST_EXPECT(m2.exists("h")); + BEAST_EXPECT(! m1.count("h")); + BEAST_EXPECT(m2.count("h")); } void diff --git a/test/websocket/doc_snippets.cpp b/test/websocket/doc_snippets.cpp index d1ee623d..c9cde577 100644 --- a/test/websocket/doc_snippets.cpp +++ b/test/websocket/doc_snippets.cpp @@ -88,7 +88,7 @@ boost::asio::ip::tcp::socket sock{ios}; //[ws_snippet_10 response_type res; ws.handshake(res, "localhost", "/"); - if(! res.exists(http::field::sec_websocket_protocol)) + if(! res.count(http::field::sec_websocket_protocol)) throw std::invalid_argument("missing subprotocols"); //]