mirror of
https://github.com/boostorg/beast.git
synced 2025-08-03 23:04:35 +02:00
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:
@@ -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:
|
||||||
|
@@ -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);
|
||||||
|
@@ -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
|
||||||
|
@@ -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);
|
||||||
|
@@ -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
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
}();
|
}();
|
||||||
|
@@ -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.
|
||||||
|
@@ -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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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");
|
||||||
//]
|
//]
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user