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.
This commit is contained in:
Vinnie Falco
2017-06-10 15:05:32 -07:00
parent 0d70d90b75
commit 8c4136bb73
11 changed files with 692 additions and 493 deletions

View File

@@ -7,6 +7,10 @@ Version 54:
* basic_fields members and coverage * basic_fields members and coverage
* Add string_param * Add string_param
API Changes:
* basic_fields refactor
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
Version 53: Version 53:

View File

@@ -9,11 +9,11 @@
#define BEAST_HTTP_FIELDS_HPP #define BEAST_HTTP_FIELDS_HPP
#include <beast/config.hpp> #include <beast/config.hpp>
#include <beast/core/string_param.hpp>
#include <beast/core/string_view.hpp> #include <beast/core/string_view.hpp>
#include <beast/core/detail/ci_char_traits.hpp> #include <beast/core/detail/ci_char_traits.hpp>
#include <beast/http/field.hpp> #include <beast/http/field.hpp>
#include <boost/asio/buffer.hpp> #include <boost/asio/buffer.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/intrusive/list.hpp> #include <boost/intrusive/list.hpp>
#include <boost/intrusive/set.hpp> #include <boost/intrusive/set.hpp>
#include <algorithm> #include <algorithm>
@@ -49,29 +49,96 @@ private:
using off_t = std::uint16_t; using off_t = std::uint16_t;
public: public:
/** The value type of the field sequence. /// The type of allocator used.
using allocator_type = Allocator;
Meets the requirements of @b Field. /// The type of element used to represent a field
*/ class value_type
struct value_type
{ {
friend class basic_fields;
boost::asio::const_buffer
buffer() const;
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 string_view
name() const name_string() const;
{
return {first, off - 2u};
}
/// Returns the value of the field
string_view string_view
value() const value() const;
{
return {first + off, len};
}
char const* first;
off_t off;
off_t len;
}; };
/// 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<class String>
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<class String>
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,
boost::intrusive::set_member_hook<
boost::intrusive::link_mode<
boost::intrusive::normal_link>>,
&value_type::set_hook_>,
boost::intrusive::constant_time_size<true>,
boost::intrusive::compare<key_compare>>::type;
protected: protected:
friend class fields_test; // for `header` friend class fields_test; // for `header`
@@ -90,13 +157,15 @@ protected:
/** Move constructor. /** 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&&); basic_fields(basic_fields&&);
/** Move constructor. /** 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. @param alloc The allocator to use.
*/ */
@@ -125,7 +194,8 @@ protected:
/** Move assignment. /** 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&&); basic_fields& operator=(basic_fields&&);
@@ -137,11 +207,12 @@ protected:
basic_fields& operator=(basic_fields<OtherAlloc> const&); basic_fields& operator=(basic_fields<OtherAlloc> const&);
public: public:
/// The type of allocator used.
using allocator_type = Allocator;
/// A constant iterator to the field sequence. /// 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. /// A constant iterator to the field sequence.
using iterator = const_iterator; using iterator = const_iterator;
@@ -152,9 +223,63 @@ public:
{ {
return typename std::allocator_traits< return typename std::allocator_traits<
Allocator>::template rebind_alloc< 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. /// Return a const iterator to the beginning of the field sequence.
const_iterator const_iterator
begin() const begin() const
@@ -183,187 +308,159 @@ public:
return list_.cend(); 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 bool
exists(field f) const empty() const
{ {
// VFALCO Should we throw here? return list_.empty();
if(f == field::unknown)
return false;
return set_.find(to_string(f), less{}) != set_.end();
} }
public:
/// Return `true` if the specified field exists. //--------------------------------------------------------------------------
bool //
exists(string_view name) const // Modifiers
{ //
return set_.find(name, less{}) != set_.end(); //--------------------------------------------------------------------------
}
/// Return the number of values for the specified field. private:
std::size_t // VFALCO But this leaves behind the method, target, and reason!
count(field name) const /** Remove all fields from the container
{
return count(to_string(name));
}
/// Return the number of values for the specified field. All references, pointers, or iterators referring to contained
std::size_t elements are invalidated. All past-the-end iterators are also
count(string_view name) const; invalidated.
/** 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.
*/ */
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 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 If one or more fields with the same name already exist,
matching fields will be removed. 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. @return The number of fields removed.
*/ */
std::size_t 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 All fields with the same field name are erased from the
matching fields will be removed. 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. @return The number of fields removed.
*/ */
std::size_t std::size_t
erase(string_view name); erase(string_view name);
/** Insert a value for a known field. /// Swap this container with another
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.
*/
void void
insert(field f, string_view value); swap(basic_fields& other);
/** 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<class T>
typename std::enable_if<
! std::is_constructible<string_view, T>::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<std::string>(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<class T>
typename std::enable_if<
! std::is_constructible<string_view, T>::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<std::string>(value));
}
/// Swap two field containers /// Swap two field containers
template<class Alloc> template<class Alloc>
@@ -371,8 +468,83 @@ public:
void void
swap(basic_fields<Alloc>& lhs, basic_fields<Alloc>& rhs); swap(basic_fields<Alloc>& lhs, basic_fields<Alloc>& 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<const_iterator, const_iterator>
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<const_iterator, const_iterator>
equal_range(string_view name) const;
//--------------------------------------------------------------------------
//
// Observers
//
//--------------------------------------------------------------------------
key_compare
key_comp() const
{
return key_compare{};
}
protected: protected:
/// Returns `true` if the value for Connection has "close" in the list. /// Returns `true` if the value for Connection has "close" in the list.
@@ -441,59 +613,9 @@ private:
template<class OtherAlloc> template<class OtherAlloc>
friend class basic_fields; 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<class String>
bool
operator()(String const& lhs, element const& rhs) const
{
return ci_less::operator()(lhs, rhs.data.name());
}
template<class String>
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 using alloc_type = typename
std::allocator_traits<Allocator>:: std::allocator_traits<Allocator>::
template rebind_alloc<element>; template rebind_alloc<value_type>;
using alloc_traits = using alloc_traits =
std::allocator_traits<alloc_type>; std::allocator_traits<alloc_type>;
@@ -501,19 +623,12 @@ private:
using size_type = using size_type =
typename std::allocator_traits<Allocator>::size_type; typename std::allocator_traits<Allocator>::size_type;
using list_t = typename boost::intrusive::make_list< value_type&
element, boost::intrusive::constant_time_size< new_element(field name,
false>>::type; string_view sname, string_view value);
using set_t = typename boost::intrusive::make_multiset<
element, boost::intrusive::constant_time_size<
true>, boost::intrusive::compare<less>>::type;
element&
new_element(string_view name, string_view value);
void void
delete_element(element& e); delete_element(value_type& e);
void void
realloc_string(string_view& dest, string_view s); realloc_string(string_view& dest, string_view s);

View File

@@ -16,101 +16,11 @@
#include <beast/http/status.hpp> #include <beast/http/status.hpp>
#include <beast/http/detail/chunk_encode.hpp> #include <beast/http/detail/chunk_encode.hpp>
#include <boost/throw_exception.hpp> #include <boost/throw_exception.hpp>
#include <algorithm> #include <stdexcept>
namespace beast { namespace beast {
namespace http { namespace http {
//------------------------------------------------------------------------------
template<class Allocator>
class basic_fields<Allocator>::
const_iterator
{
using iter_type = typename list_t::const_iterator;
iter_type it_;
template<class Alloc>
friend class beast::http::basic_fields;
const_iterator(iter_type it)
: it_(it)
{
}
public:
using value_type = typename
basic_fields<Allocator>::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 Allocator> template<class Allocator>
class basic_fields<Allocator>::reader class basic_fields<Allocator>::reader
{ {
@@ -284,30 +194,40 @@ public:
template<class Allocator> template<class Allocator>
basic_fields<Allocator>:: basic_fields<Allocator>::
element:: value_type::
element(string_view name, string_view value) value_type(field name,
: off_(static_cast<off_t>(name.size() + 2)) string_view sname, string_view value)
: off_(static_cast<off_t>(sname.size() + 2))
, len_(static_cast<off_t>(value.size())) , len_(static_cast<off_t>(value.size()))
, f_(name)
{ {
//BOOST_ASSERT(name == field::unknown ||
// detail::ci_equal(sname, to_string(name)));
char* p = reinterpret_cast<char*>(this + 1); char* p = reinterpret_cast<char*>(this + 1);
data.first = p;
data.off = off_;
data.len = len_;
p[off_-2] = ':'; p[off_-2] = ':';
p[off_-1] = ' '; p[off_-1] = ' ';
p[off_ + len_] = '\r'; p[off_ + len_] = '\r';
p[off_ + len_ + 1] = '\n'; 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()); std::memcpy(p + off_, value.data(), value.size());
} }
template<class Allocator>
inline
field
basic_fields<Allocator>::
value_type::
name() const
{
return f_;
}
template<class Allocator> template<class Allocator>
inline inline
string_view string_view
basic_fields<Allocator>:: basic_fields<Allocator>::
element:: value_type::
name() const name_string() const
{ {
return {reinterpret_cast< return {reinterpret_cast<
char const*>(this + 1), char const*>(this + 1),
@@ -318,7 +238,7 @@ template<class Allocator>
inline inline
string_view string_view
basic_fields<Allocator>:: basic_fields<Allocator>::
element:: value_type::
value() const value() const
{ {
return {reinterpret_cast< return {reinterpret_cast<
@@ -330,7 +250,7 @@ template<class Allocator>
inline inline
boost::asio::const_buffer boost::asio::const_buffer
basic_fields<Allocator>:: basic_fields<Allocator>::
element:: value_type::
buffer() const buffer() const
{ {
return boost::asio::const_buffer{ return boost::asio::const_buffer{
@@ -460,47 +380,51 @@ operator=(basic_fields<OtherAlloc> const& other) ->
return *this; return *this;
} }
//------------------------------------------------------------------------------
//
// Element access
//
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
template<class Allocator> template<class Allocator>
std::size_t string_view
basic_fields<Allocator>:: basic_fields<Allocator>::
count(string_view name) const at(field name) const
{ {
auto const it = set_.find(name, less{}); BOOST_ASSERT(name != field::unknown);
if(it == set_.end()) auto const it = find(name);
return 0; if(it == end())
auto const last = set_.upper_bound(name, less{}); BOOST_THROW_EXCEPTION(std::out_of_range{
return static_cast<std::size_t>(std::distance(it, last)); "field not found"});
return it->value();
} }
template<class Allocator> template<class Allocator>
auto string_view
basic_fields<Allocator>:: basic_fields<Allocator>::
find(string_view name) const -> at(string_view name) const
iterator
{ {
auto const it = set_.find(name, less{}); auto const it = find(name);
if(it == set_.end()) if(it == end())
return list_.end(); BOOST_THROW_EXCEPTION(std::out_of_range{
return list_.iterator_to(*it); "field not found"});
return it->value();
} }
template<class Allocator> template<class Allocator>
auto string_view
basic_fields<Allocator>:: basic_fields<Allocator>::
find(field name) const -> operator[](field name) const
iterator
{ {
auto const it = set_.find( BOOST_ASSERT(name != field::unknown);
to_string(name), less{}); auto const it = find(name);
if(it == set_.end()) if(it == end())
return list_.end(); return {};
return list_.iterator_to(*it); return it->value();
} }
template<class Allocator> template<class Allocator>
string_view const string_view
basic_fields<Allocator>:: basic_fields<Allocator>::
operator[](string_view name) const operator[](string_view name) const
{ {
@@ -510,33 +434,118 @@ operator[](string_view name) const
return it->value(); return it->value();
} }
template<class Allocator> //------------------------------------------------------------------------------
string_view const //
basic_fields<Allocator>:: // Modifiers
operator[](field name) const //
{ //------------------------------------------------------------------------------
auto const it = find(name);
if(it == end())
return {};
return it->value();
}
template<class Allocator> template<class Allocator>
void void
basic_fields<Allocator>:: basic_fields<Allocator>::
clear() noexcept clear()
{ {
delete_list(); delete_list();
set_.clear(); set_.clear();
list_.clear(); list_.clear();
} }
template<class Allocator>
inline
void
basic_fields<Allocator>::
insert(field name, string_param const& value)
{
BOOST_ASSERT(name != field::unknown);
insert(name, to_string(name), value);
}
template<class Allocator>
void
basic_fields<Allocator>::
insert(string_view sname, string_param const& value)
{
auto const name =
string_to_field(sname);
insert(name, sname, value);
}
template<class Allocator>
void
basic_fields<Allocator>::
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<class Allocator>
void
basic_fields<Allocator>::
replace(field name, string_param const& value)
{
BOOST_ASSERT(name != field::unknown);
erase(name);
insert(name, value);
}
template<class Allocator>
void
basic_fields<Allocator>::
replace(string_view sname, string_param const& value)
{
auto const name = string_to_field(sname);
erase(sname);
insert(name, sname, value);
}
template<class Allocator>
auto
basic_fields<Allocator>::
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<class Allocator> template<class Allocator>
std::size_t std::size_t
basic_fields<Allocator>:: basic_fields<Allocator>::
erase(field f) erase(field name)
{ {
return erase(to_string(f)); BOOST_ASSERT(name != field::unknown);
return erase(to_string(name));
} }
template<class Allocator> template<class Allocator>
@@ -544,60 +553,105 @@ std::size_t
basic_fields<Allocator>:: basic_fields<Allocator>::
erase(string_view name) erase(string_view name)
{ {
auto it = set_.find(name, less{}); std::size_t n =0;
if(it == set_.end()) set_.erase_and_dispose(name, key_compare{},
return 0; [&](value_type* e)
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; ++n;
} list_.erase(list_.iterator_to(*e));
delete_element(*e);
});
return n; return n;
} }
template<class Allocator> template<class Allocator>
void void
basic_fields<Allocator>:: basic_fields<Allocator>::
insert(field f, string_view value) swap(basic_fields<Allocator>& other)
{ {
insert(to_string(f), value); swap(other, typename alloc_traits::
propagate_on_container_swap{});
} }
template<class Allocator> template<class Allocator>
void void
basic_fields<Allocator>:: swap(
insert(string_view name, string_view value) basic_fields<Allocator>& lhs,
basic_fields<Allocator>& rhs)
{ {
auto& e = new_element(name, value); lhs.swap(rhs);
set_.insert_before(set_.upper_bound(name, less{}), e); }
list_.push_back(e);
//------------------------------------------------------------------------------
//
// Lookup
//
//------------------------------------------------------------------------------
template<class Allocator>
inline
std::size_t
basic_fields<Allocator>::
count(field name) const
{
BOOST_ASSERT(name != field::unknown);
return count(to_string(name));
} }
template<class Allocator> template<class Allocator>
void std::size_t
basic_fields<Allocator>:: basic_fields<Allocator>::
replace(string_view name, string_view value) count(string_view name) const
{ {
value = detail::trim(value); return set_.count(name, key_compare{});
erase(name);
insert(name, value);
} }
template<class Allocator> template<class Allocator>
void inline
auto
basic_fields<Allocator>:: basic_fields<Allocator>::
replace(field name, string_view value) find(field name) const ->
const_iterator
{ {
value = detail::trim(value); BOOST_ASSERT(name != field::unknown);
erase(name); return find(to_string(name));
insert(name, value); }
template<class Allocator>
auto
basic_fields<Allocator>::
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<class Allocator>
inline
auto
basic_fields<Allocator>::
equal_range(field name) const ->
std::pair<const_iterator, const_iterator>
{
BOOST_ASSERT(name != field::unknown);
return equal_range(to_string(name));
}
template<class Allocator>
auto
basic_fields<Allocator>::
equal_range(string_view name) const ->
std::pair<const_iterator, const_iterator>
{
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<Allocator>::
has_close_impl() const has_close_impl() const
{ {
auto const fit = set_.find( auto const fit = set_.find(
to_string(field::connection), less{}); to_string(field::connection), key_compare{});
if(fit == set_.end()) if(fit == set_.end())
return false; return false;
return token_list{fit->value()}.exists("close"); return token_list{fit->value()}.exists("close");
@@ -622,7 +676,7 @@ basic_fields<Allocator>::
has_chunked_impl() const has_chunked_impl() const
{ {
auto const fit = set_.find(to_string( auto const fit = set_.find(to_string(
field::transfer_encoding), less{}); field::transfer_encoding), key_compare{});
if(fit == set_.end()) if(fit == set_.end())
return false; return false;
token_list const v{fit->value()}; token_list const v{fit->value()};
@@ -644,7 +698,7 @@ basic_fields<Allocator>::
has_content_length_impl() const has_content_length_impl() const
{ {
auto const fit = set_.find( auto const fit = set_.find(
to_string(field::content_length), less{}); to_string(field::content_length), key_compare{});
return fit != set_.end(); return fit != set_.end();
} }
@@ -710,9 +764,8 @@ void
basic_fields<Allocator>:: basic_fields<Allocator>::
content_length_impl(std::uint64_t n) content_length_impl(std::uint64_t n)
{ {
this->erase("Content-Length"); erase(field::content_length);
this->insert("Content-Length", insert(field::content_length, n);
to_static_string(n));
} }
template<class Allocator> template<class Allocator>
@@ -721,12 +774,13 @@ void
basic_fields<Allocator>:: basic_fields<Allocator>::
set_chunked_impl(bool v) set_chunked_impl(bool v)
{ {
// VFALCO We need to handle removing the chunked as well
BOOST_ASSERT(v); BOOST_ASSERT(v);
auto it = find("Transfer-Encoding"); auto it = find(field::transfer_encoding);
if(it == end()) if(it == end())
this->insert("Transfer-Encoding", "chunked"); this->insert(field::transfer_encoding, "chunked");
else else
this->replace("Transfer-Encoding", this->replace(field::transfer_encoding,
it->value().to_string() + ", chunked"); it->value().to_string() + ", chunked");
} }
@@ -735,10 +789,11 @@ set_chunked_impl(bool v)
template<class Allocator> template<class Allocator>
auto auto
basic_fields<Allocator>:: basic_fields<Allocator>::
new_element(string_view name, string_view value) -> new_element(field name,
element& string_view sname, string_view value) ->
value_type&
{ {
if(name.size() + 2 > if(sname.size() + 2 >
(std::numeric_limits<off_t>::max)()) (std::numeric_limits<off_t>::max)())
BOOST_THROW_EXCEPTION(std::length_error{ BOOST_THROW_EXCEPTION(std::length_error{
"field name too large"}); "field name too large"});
@@ -748,24 +803,26 @@ new_element(string_view name, string_view value) ->
"field value too large"}); "field value too large"});
value = detail::trim(value); value = detail::trim(value);
std::uint16_t const off = std::uint16_t const off =
static_cast<off_t>(name.size() + 2); static_cast<off_t>(sname.size() + 2);
std::uint16_t const len = std::uint16_t const len =
static_cast<off_t>(value.size()); static_cast<off_t>(value.size());
auto const p = alloc_traits::allocate(alloc_, auto const p = alloc_traits::allocate(alloc_,
1 + (off + len + 2 + sizeof(element) - 1) / 1 + (off + len + 2 + sizeof(value_type) - 1) /
sizeof(element)); sizeof(value_type));
alloc_traits::construct(alloc_, p, name, value); // 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; return *p;
} }
template<class Allocator> template<class Allocator>
void void
basic_fields<Allocator>:: basic_fields<Allocator>::
delete_element(element& e) delete_element(value_type& e)
{ {
auto const n = 1 + auto const n = 1 +
(e.data.off + e.data.len + 2 + (e.off_ + e.len_ + 2 +
sizeof(element) - 1) / sizeof(element); sizeof(value_type) - 1) / sizeof(value_type);
alloc_traits::destroy(alloc_, &e); alloc_traits::destroy(alloc_, &e);
alloc_traits::deallocate(alloc_, &e, n); alloc_traits::deallocate(alloc_, &e, n);
} }
@@ -802,7 +859,7 @@ basic_fields<Allocator>::
copy_all(basic_fields<OtherAlloc> const& other) copy_all(basic_fields<OtherAlloc> const& other)
{ {
for(auto const& e : other.list_) 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(method_, other.method_);
realloc_string(target_or_reason_, realloc_string(target_or_reason_,
other.target_or_reason_); other.target_or_reason_);
@@ -889,17 +946,6 @@ copy_assign(basic_fields const& other, std::false_type)
copy_all(other); copy_all(other);
} }
template<class Allocator>
void
swap(basic_fields<Allocator>& lhs,
basic_fields<Allocator>& rhs)
{
using alloc_traits = typename
basic_fields<Allocator>::alloc_traits;
lhs.swap(rhs, typename alloc_traits::
propagate_on_container_swap{});
}
template<class Allocator> template<class Allocator>
inline inline
void void

View File

@@ -125,7 +125,7 @@ struct header<true, Fields> : Fields
@param v The request method verb to set. @param v The request method verb to set.
This may not be @ref verb::unknown. 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`. @note This function is only available when `isRequest == true`.
*/ */
@@ -277,7 +277,7 @@ struct header<false, Fields> : Fields
@param v The status-code integer to set. @param v The status-code integer to set.
@throw std::invalid_argument if `v > 999`. @throws std::invalid_argument if `v > 999`.
*/ */
void void
result(unsigned v); result(unsigned v);

View File

@@ -204,26 +204,10 @@ private:
} }
void void
on_field(field f, string_view name, on_field(field name, string_view name_string,
string_view value, error_code&) string_view value, error_code&)
{ {
if(f != field::unknown) m_.insert(name, name_string, value);
{
#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);
}
} }
void void

View File

@@ -25,7 +25,7 @@ is_upgrade(http::header<true, Fields> const& req)
return false; return false;
if(! http::token_list{req["Upgrade"]}.exists("websocket")) if(! http::token_list{req["Upgrade"]}.exists("websocket"))
return false; return false;
if(! req.exists("Sec-WebSocket-Version")) if(! req.count(http::field::sec_websocket_version))
return false; return false;
return true; return true;
} }

View File

@@ -185,11 +185,9 @@ build_request(detail::sec_ws_key_type& key,
detail::pmd_write(req, config); detail::pmd_write(req, config);
} }
decorator(req); decorator(req);
if(! req.exists("User-Agent")) if(! req.count(http::field::user_agent))
{ req.insert(http::field::user_agent,
static_string<20> s(BEAST_VERSION_STRING); BEAST_VERSION_STRING);
req.insert(http::field::user_agent, s);
}
return req; return req;
} }
@@ -204,7 +202,7 @@ build_response(http::header<true, Fields> const& req,
[&decorator](response_type& res) [&decorator](response_type& res)
{ {
decorator(res); decorator(res);
if(! res.exists("Server")) if(! res.count(http::field::server))
{ {
BOOST_STATIC_ASSERT(sizeof(BEAST_VERSION_STRING) < 20); BOOST_STATIC_ASSERT(sizeof(BEAST_VERSION_STRING) < 20);
static_string<20> s(BEAST_VERSION_STRING); static_string<20> s(BEAST_VERSION_STRING);
@@ -228,18 +226,18 @@ build_response(http::header<true, Fields> const& req,
return err("Wrong method"); return err("Wrong method");
if(! is_upgrade(req)) if(! is_upgrade(req))
return err("Expected Upgrade request"); return err("Expected Upgrade request");
if(! req.exists("Host")) if(! req.count(http::field::host))
return err("Missing 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"); 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"); 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) if(key.size() > detail::sec_ws_key_type::max_size_n)
return err("Invalid Sec-WebSocket-Key"); return err("Invalid Sec-WebSocket-Key");
{ {
auto const version = auto const version =
req["Sec-WebSocket-Version"]; req[http::field::sec_websocket_version];
if(version.empty()) if(version.empty())
return err("Missing Sec-WebSocket-Version"); return err("Missing Sec-WebSocket-Version");
if(version != "13") if(version != "13")
@@ -286,16 +284,16 @@ do_response(http::header<false> const& res,
return false; return false;
if(res.result() != http::status::switching_protocols) if(res.result() != http::status::switching_protocols)
return false; return false;
if(! http::token_list{res["Connection"]}.exists("upgrade")) if(! http::token_list{res[http::field::connection]}.exists("upgrade"))
return false; return false;
if(! http::token_list{res["Upgrade"]}.exists("websocket")) if(! http::token_list{res[http::field::upgrade]}.exists("websocket"))
return false; return false;
if(! res.exists("Sec-WebSocket-Accept")) if(res.count(http::field::sec_websocket_accept) != 1)
return false; return false;
detail::sec_ws_accept_type accept; detail::sec_ws_accept_type accept;
detail::make_sec_ws_accept(accept, key); detail::make_sec_ws_accept(accept, key);
if(accept.compare( if(accept.compare(
res["Sec-WebSocket-Accept"]) != 0) res[http::field::sec_websocket_accept]) != 0)
return false; return false;
return true; return true;
}(); }();

View File

@@ -340,7 +340,7 @@ public:
@param n The size of the read buffer. @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 @par Example
Setting the read buffer size. Setting the read buffer size.

View File

@@ -273,8 +273,8 @@ public:
{ {
fields f; fields f;
f.insert(field::user_agent, "x"); f.insert(field::user_agent, "x");
BEAST_EXPECT(f.exists(field::user_agent)); BEAST_EXPECT(f.count(field::user_agent));
BEAST_EXPECT(f.exists(to_string(field::user_agent))); BEAST_EXPECT(f.count(to_string(field::user_agent)));
BEAST_EXPECT(f.count(field::user_agent) == 1); BEAST_EXPECT(f.count(field::user_agent) == 1);
BEAST_EXPECT(f.count(to_string(field::user_agent)) == 1); BEAST_EXPECT(f.count(to_string(field::user_agent)) == 1);
f.insert(field::user_agent, "y"); f.insert(field::user_agent, "y");
@@ -326,12 +326,64 @@ public:
BEAST_EXPECT(size(f) == 2); 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 void run() override
{ {
testMembers(); testMembers();
testHeaders(); testHeaders();
testRFC2616(); testRFC2616();
testErase(); testErase();
testContainer();
} }
}; };

View File

@@ -124,7 +124,7 @@ public:
header<true> h; header<true> h;
h.insert(field::user_agent, "test"); h.insert(field::user_agent, "test");
request<one_arg_body> m{Arg1{}, std::move(h)}; request<one_arg_body> 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"); BEAST_EXPECT(m["User-Agent"] == "test");
} }
@@ -143,8 +143,8 @@ public:
BEAST_EXPECT(m2.target() == "u"); BEAST_EXPECT(m2.target() == "u");
BEAST_EXPECT(m1.body == "2"); BEAST_EXPECT(m1.body == "2");
BEAST_EXPECT(m2.body == "1"); BEAST_EXPECT(m2.body == "1");
BEAST_EXPECT(! m1.exists("h")); BEAST_EXPECT(! m1.count("h"));
BEAST_EXPECT(m2.exists("h")); BEAST_EXPECT(m2.count("h"));
} }
struct MoveFields : fields struct MoveFields : fields
@@ -217,8 +217,8 @@ public:
BEAST_EXPECT(m2.version == 10); BEAST_EXPECT(m2.version == 10);
BEAST_EXPECT(m1.body == "2"); BEAST_EXPECT(m1.body == "2");
BEAST_EXPECT(m2.body == "1"); BEAST_EXPECT(m2.body == "1");
BEAST_EXPECT(! m1.exists("h")); BEAST_EXPECT(! m1.count("h"));
BEAST_EXPECT(m2.exists("h")); BEAST_EXPECT(m2.count("h"));
} }
void void

View File

@@ -88,7 +88,7 @@ boost::asio::ip::tcp::socket sock{ios};
//[ws_snippet_10 //[ws_snippet_10
response_type res; response_type res;
ws.handshake(res, "localhost", "/"); 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"); throw std::invalid_argument("missing subprotocols");
//] //]