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
* Add string_param
API Changes:
* basic_fields refactor
--------------------------------------------------------------------------------
Version 53:

View File

@@ -9,11 +9,11 @@
#define BEAST_HTTP_FIELDS_HPP
#include <beast/config.hpp>
#include <beast/core/string_param.hpp>
#include <beast/core/string_view.hpp>
#include <beast/core/detail/ci_char_traits.hpp>
#include <beast/http/field.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/intrusive/list.hpp>
#include <boost/intrusive/set.hpp>
#include <algorithm>
@@ -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<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:
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<OtherAlloc> 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<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(basic_fields& other);
/// Swap two field containers
template<class Alloc>
@@ -371,8 +468,83 @@ public:
void
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:
/// Returns `true` if the value for Connection has "close" in the list.
@@ -441,59 +613,9 @@ private:
template<class OtherAlloc>
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
std::allocator_traits<Allocator>::
template rebind_alloc<element>;
template rebind_alloc<value_type>;
using alloc_traits =
std::allocator_traits<alloc_type>;
@@ -501,19 +623,12 @@ private:
using size_type =
typename std::allocator_traits<Allocator>::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<less>>::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);

View File

@@ -16,101 +16,11 @@
#include <beast/http/status.hpp>
#include <beast/http/detail/chunk_encode.hpp>
#include <boost/throw_exception.hpp>
#include <algorithm>
#include <stdexcept>
namespace beast {
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>
class basic_fields<Allocator>::reader
{
@@ -284,30 +194,40 @@ public:
template<class Allocator>
basic_fields<Allocator>::
element::
element(string_view name, string_view value)
: off_(static_cast<off_t>(name.size() + 2))
value_type::
value_type(field name,
string_view sname, string_view value)
: off_(static_cast<off_t>(sname.size() + 2))
, 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);
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<class Allocator>
inline
field
basic_fields<Allocator>::
value_type::
name() const
{
return f_;
}
template<class Allocator>
inline
string_view
basic_fields<Allocator>::
element::
name() const
value_type::
name_string() const
{
return {reinterpret_cast<
char const*>(this + 1),
@@ -318,7 +238,7 @@ template<class Allocator>
inline
string_view
basic_fields<Allocator>::
element::
value_type::
value() const
{
return {reinterpret_cast<
@@ -330,7 +250,7 @@ template<class Allocator>
inline
boost::asio::const_buffer
basic_fields<Allocator>::
element::
value_type::
buffer() const
{
return boost::asio::const_buffer{
@@ -460,47 +380,51 @@ operator=(basic_fields<OtherAlloc> const& other) ->
return *this;
}
//------------------------------------------------------------------------------
//
// Element access
//
//------------------------------------------------------------------------------
template<class Allocator>
std::size_t
string_view
basic_fields<Allocator>::
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::size_t>(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<class Allocator>
auto
string_view
basic_fields<Allocator>::
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<class Allocator>
auto
string_view
basic_fields<Allocator>::
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<class Allocator>
string_view const
string_view
basic_fields<Allocator>::
operator[](string_view name) const
{
@@ -510,33 +434,118 @@ operator[](string_view name) const
return it->value();
}
template<class Allocator>
string_view const
basic_fields<Allocator>::
operator[](field name) const
{
auto const it = find(name);
if(it == end())
return {};
return it->value();
}
//------------------------------------------------------------------------------
//
// Modifiers
//
//------------------------------------------------------------------------------
template<class Allocator>
void
basic_fields<Allocator>::
clear() noexcept
clear()
{
delete_list();
set_.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>
std::size_t
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>
@@ -544,60 +553,105 @@ std::size_t
basic_fields<Allocator>::
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<class Allocator>
void
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>
void
basic_fields<Allocator>::
insert(string_view name, string_view value)
swap(
basic_fields<Allocator>& lhs,
basic_fields<Allocator>& 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<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>
void
std::size_t
basic_fields<Allocator>::
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<class Allocator>
void
inline
auto
basic_fields<Allocator>::
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<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
{
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<Allocator>::
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<Allocator>::
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<Allocator>::
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<class Allocator>
@@ -721,12 +774,13 @@ void
basic_fields<Allocator>::
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<class Allocator>
auto
basic_fields<Allocator>::
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<off_t>::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<off_t>(name.size() + 2);
static_cast<off_t>(sname.size() + 2);
std::uint16_t const len =
static_cast<off_t>(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<class Allocator>
void
basic_fields<Allocator>::
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<Allocator>::
copy_all(basic_fields<OtherAlloc> 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<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>
inline
void

View File

@@ -125,7 +125,7 @@ struct header<true, Fields> : 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<false, Fields> : 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);

View File

@@ -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

View File

@@ -25,7 +25,7 @@ is_upgrade(http::header<true, Fields> 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;
}

View File

@@ -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<true, Fields> 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<true, Fields> 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<false> 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;
}();

View File

@@ -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.

View File

@@ -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();
}
};

View File

@@ -124,7 +124,7 @@ public:
header<true> h;
h.insert(field::user_agent, "test");
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");
}
@@ -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

View File

@@ -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");
//]