Add message keep_alive, chunked, content_length members

This commit is contained in:
Vinnie Falco
2017-07-09 05:26:27 -07:00
parent aa473a6da4
commit 6a46e48424
15 changed files with 1025 additions and 325 deletions

View File

@@ -10,6 +10,17 @@ HTTP:
* Add vector_body
* span, string, vector bodies are public
* Fix spurious uninitialized warning
* fields temp string uses allocator
API Changes:
* Add message::keep_alive()
* Add message::chunked() and message::content_length()
Actions Required:
* Change user defined implementations of Fields and
FieldsReader to meet the new requirements.
--------------------------------------------------------------------------------

View File

@@ -13,10 +13,10 @@ This section describes all of the concepts defined by the library.
[include concept/BodyReader.qbk]
[include concept/BodyWriter.qbk]
[include concept/BufferSequence.qbk]
[include concept/File.qbk]
[include concept/DynamicBuffer.qbk]
[include concept/Fields.qbk]
[include concept/FieldsReader.qbk]
[include concept/File.qbk]
[include concept/Streams.qbk]
[endsect]

View File

@@ -39,9 +39,10 @@ In this table:
[heading Associated Types]
* __Body__
* [link beast.ref.beast__http__is_body_reader `is_body_reader`]
[heading BodyReader requirements]
[heading BodyReader requirements]
[table Valid Expressions
[[Expression] [Type] [Semantics, Pre/Post-conditions]]
[

View File

@@ -13,26 +13,36 @@ store the request target and non-standard strings for method and obsolete
reason phrase as needed. Types which meet these requirements can always
be serialized.
[heading Associated Types]
* __FieldsReader__
* [link beast.ref.beast__http__is_fields `is_fields`]
[heading Requirements]
In this table:
* `X` denotes a type that meets the requirements of [*Fields].
* `F` denotes a type that meets the requirements of [*Fields].
* `R` denotes a type meeting the requirements of __FieldsReader__.
* `a` denotes a value of type `X`.
* `a` denotes a value of type `F`.
* `c` denotes a (possibly const) value of type `X`.
* `s` is a value of type [link beast.ref.beast__string_view `string_view`].
* `c` denotes a (possibly const) value of type `F`.
* `b` is a value of type `bool`
* `n` is a value of type `boost::optional<std::uint64_t>`.
[table Fields requirements
[[expression][type][semantics, pre/post-conditions]]
* `s` is a value of type [link beast.ref.beast__string_view `string_view`].
* `v` is a value of type `unsigned int` representing the HTTP-version.
[table Valid expressions
[[Expression] [Type] [Semantics, Pre/Post-conditions]]
[
[`X::reader`]
[`F::reader`]
[`R`]
[
A type which meets the requirements of __FieldsReader__.
@@ -62,6 +72,41 @@ In this table:
retrieving the reason text previously set with a call to
`set_reason_impl` using a non-empty string.
]
][
[`c.get_chunked_impl()`]
[`bool`]
[
Returns `true` if the
[@https://tools.ietf.org/html/rfc7230#section-3.3.1 [*Transfer-Encoding]]
field value indicates that the payload is chunk encoded. Both
of these conditions must be true:
[itemized_list
[
The Transfer-Encoding field is present in the message.
][
The last item the value of the field is "chunked".
]]
]
][
[`c.get_keep_alive_impl(v)`]
[`bool`]
[
Returns `true` if the semantics of the
[@https://tools.ietf.org/html/rfc7230#section-6.1 [*Connection]]
field and version indicate that the connection should remain
open after the corresponding response is transmitted or received:
[itemized_list
[
If `(v < 11)` the function returns `true` if the "keep-alive"
token is present in the Connection field value. Otherwise the
function returns `false`.
][
If `(v == 11)`, the function returns `false` if the "close"
token is present in the Connection field value. Otherwise the
function returns `true`.
]]
]
][
[`a.set_method_impl(s)`]
[]
@@ -93,37 +138,80 @@ In this table:
is not supported by the container.
]
][
[`a.prepare_payload_impl(b,n)`]
[`a.set_chunked_impl(b)`]
[]
[
Adjusts the Content-Length and Transfer-Encoding fields to
account for the payload metadata indicated by `b` and `n` as
follows:
Adjusts the
[@https://tools.ietf.org/html/rfc7230#section-3.3.1 [*Transfer-Encoding]]
field as follows:
* If `b` is `true`, the chunked Transfer-Encoding should be applied
to the end of the list of encodings if it is not already there
or if the field was not present. Any Content-Length fields should
be removed.
* If `b` is `false` and `n` contains a value, set the Content-Length
field to `*n`, replacing any previous Content-Length fields. Remove
the chunked encoding from the end of the Transfer-Encoding field
if the field is present ahd chunked is the last encoding.
* If `b` is `false` and `n` contains no value, remove all Content-Length
fields, and remove the chunked encoding from the end of the
Transfer-Encoding field if the field is present ahd chunked is the
last encoding.
[itemized_list
[
If `b` is `true`, the "chunked" token is appended
to the list of encodings if it does not already appear
last in the list.
If the Transfer-Encoding field is absent, the field will
be inserted to the container with the value "chunked".
][
If `b` is `false, the "chunked" token is removed from the
list of encodings if it appears last in the list.
If the result of the removal leaves the list of encodings
empty, the Transfer-Encoding field shall not appear when
the associated __FieldsReader__ serializes the fields.
]]
]
][
[`is_fields<X>`]
[`std::true_type`]
[`a.set_content_length_impl(n)`]
[]
[
An alias for `std::true_type` for `X`, otherwise an alias
for `std::false_type`.
Adjusts the
[@https://tools.ietf.org/html/rfc7230#section-3.3.2 [*Content-Length]]
field as follows:
[itemized_list
[
If `n` contains a value, the Content-Length field
will be set to the text representation of the value.
Any previous Content-Length fields are removed from
the container.
][
If `n` does not contain a value, any present Content-Length
fields are removed from the container.
]]
]
]
]
][
[`a.set_keep_alive_impl(v,b)`]
[]
[
Adjusts the
[@https://tools.ietf.org/html/rfc7230#section-6.1 [*Connection]]
field value depending on the values of `v` and `b`. The field
value is treated as
[@https://tools.ietf.org/html/rfc7230#section-6.1 ['connection-option]]
(rfc7230).
[itemized_list
[
If `(v < 11 && b)`, then all "close" tokens present in the
value are removed, and the "keep-alive" token is added to
the valueif it is not already present.
][
If `(v < 11 && ! b)`, then all "close" and "keep-alive"
tokens present in the value are removed.
][
If `(v == 11 && b)`, then all "keep-alive" and "close"
tokens present in the value are removed.
][
If `(v == 11 && ! b)`, then all "keep-alive" tokens present
in the value are removed, and the "close" token is added to
the value if it is not already present.
]]
]
]]
[heading Exemplar]

View File

@@ -64,19 +64,6 @@ In this table:
if the [*Transfer-Encoding] field is present, and the last
token in its value is "chunked".
]
][
[`a.keep_alive()`]
[`bool`]
[
Called once after construction, this function returns `true`
if the semantics of the mesage indicate that the connection
should remain open after processing the message. When the
HTTP version is below 1.1, the function returns `true` only
if the [*Connection] field is present, and its value contains
the "keep-alive" token. For HTTP versions 1.1 and above, the
function returns `true` if the [*Connection] field is absent,
or if the field value does not contain the "close" token.
]
][
[`a.get()`]
[`X::const_buffers_type`]

View File

@@ -53,13 +53,17 @@ struct fields_model
string_view target() const;
protected:
void set_method_impl(string_view s);
void set_target_impl(string_view s);
void set_reason_impl(string_view s);
string_view get_method_impl() const;
string_view get_target_impl() const;
string_view get_reason_impl() const;
void prepare_payload_impl(bool b, boost::optional<std::uint64_t> n);
bool get_chunked_impl() const;
bool get_keep_alive_impl(unsigned) const;
void set_method_impl(string_view);
void set_target_impl(string_view);
void set_reason_impl(string_view);
void set_chunked_impl(bool);
void set_content_length_impl(boost::optional<std::uint64_t>);
void set_keep_alive_impl(unsigned, bool);
};
template<class T, class = beast::detail::void_t<>>
@@ -91,60 +95,90 @@ struct is_fields_helper : T
{
template<class U = is_fields_helper>
static auto f1(int) -> decltype(
void(std::declval<U&>().set_method_impl(std::declval<string_view>())),
std::declval<string_view&>() = std::declval<U const&>().get_method_impl(),
std::true_type());
static auto f1(...) -> std::false_type;
using t1 = decltype(f1(0));
template<class U = is_fields_helper>
static auto f2(int) -> decltype(
void(std::declval<U&>().set_target_impl(std::declval<string_view>())),
std::declval<string_view&>() = std::declval<U const&>().get_target_impl(),
std::true_type());
static auto f2(...) -> std::false_type;
using t2 = decltype(f2(0));
template<class U = is_fields_helper>
static auto f3(int) -> decltype(
void(std::declval<U&>().set_reason_impl(std::declval<string_view>())),
std::declval<string_view&>() = std::declval<U const&>().get_reason_impl(),
std::true_type());
static auto f3(...) -> std::false_type;
using t3 = decltype(f3(0));
template<class U = is_fields_helper>
static auto f4(int) -> decltype(
std::declval<string_view&>() = std::declval<U&>().get_method_impl(),
std::declval<bool&>() = std::declval<U const&>().get_chunked_impl(),
std::true_type());
static auto f4(...) -> std::false_type;
using t4 = decltype(f4(0));
template<class U = is_fields_helper>
static auto f5(int) -> decltype(
std::declval<string_view&>() = std::declval<U&>().get_target_impl(),
std::declval<bool&>() = std::declval<U const&>().get_keep_alive_impl(
std::declval<unsigned>()),
std::true_type());
static auto f5(...) -> std::false_type;
using t5 = decltype(f5(0));
template<class U = is_fields_helper>
static auto f6(int) -> decltype(
std::declval<string_view&>() = std::declval<U&>().get_reason_impl(),
void(std::declval<U&>().set_method_impl(std::declval<string_view>())),
std::true_type());
static auto f6(...) -> std::false_type;
using t6 = decltype(f6(0));
template<class U = is_fields_helper>
static auto f7(int) -> decltype(
void(std::declval<U&>().prepare_payload_impl(
std::declval<bool>(),
std::declval<boost::optional<std::uint64_t>>())),
void(std::declval<U&>().set_target_impl(std::declval<string_view>())),
std::true_type());
static auto f7(...) -> std::false_type;
using t7 = decltype(f7(0));
template<class U = is_fields_helper>
static auto f8(int) -> decltype(
void(std::declval<U&>().set_reason_impl(std::declval<string_view>())),
std::true_type());
static auto f8(...) -> std::false_type;
using t8 = decltype(f8(0));
template<class U = is_fields_helper>
static auto f9(int) -> decltype(
void(std::declval<U&>().set_chunked_impl(std::declval<bool>())),
std::true_type());
static auto f9(...) -> std::false_type;
using t9 = decltype(f9(0));
template<class U = is_fields_helper>
static auto f10(int) -> decltype(
void(std::declval<U&>().set_content_length_impl(
std::declval<boost::optional<std::uint64_t>>())),
std::true_type());
static auto f10(...) -> std::false_type;
using t10 = decltype(f10(0));
template<class U = is_fields_helper>
static auto f11(int) -> decltype(
void(std::declval<U&>().set_keep_alive_impl(
std::declval<unsigned>(),
std::declval<bool>())),
std::true_type());
static auto f11(...) -> std::false_type;
using t11 = decltype(f11(0));
using type = std::integral_constant<bool,
t1::value && t2::value && t3::value &&
t4::value && t5::value && t6::value &&
t7::value
>;
t1::value && t2::value && t3::value &&
t4::value && t5::value && t6::value &&
t7::value && t8::value && t9::value &&
t10::value && t11::value>;
};
} // detail

View File

@@ -567,47 +567,74 @@ public:
}
protected:
/** Set or clear the method string.
@note Only called for requests.
*/
void set_method_impl(string_view s);
/** Set or clear the target string.
@note Only called for requests.
*/
void set_target_impl(string_view s);
/** Set or clear the reason string.
@note Only called for responses.
*/
void set_reason_impl(string_view s);
/** Returns the request-method string.
@note Only called for requests.
*/
string_view get_method_impl() const;
string_view
get_method_impl() const;
/** Returns the request-target string.
@note Only called for requests.
*/
string_view get_target_impl() const;
string_view
get_target_impl() const;
/** Returns the response reason-phrase string.
@note Only called for responses.
*/
string_view get_reason_impl() const;
string_view
get_reason_impl() const;
/** Adjusts the payload related fields
/** Returns the chunked Transfer-Encoding setting
*/
bool
get_chunked_impl() const;
/** Returns the keep-alive setting
*/
bool
get_keep_alive_impl(unsigned version) const;
/** Set or clear the method string.
@note Only called for requests.
*/
void
prepare_payload_impl(bool chunked,
boost::optional<std::uint64_t> size);
set_method_impl(string_view s);
/** Set or clear the target string.
@note Only called for requests.
*/
void
set_target_impl(string_view s);
/** Set or clear the reason string.
@note Only called for responses.
*/
void
set_reason_impl(string_view s);
/** Adjusts the chunked Transfer-Encoding value
*/
void
set_chunked_impl(bool value);
/** Sets or clears the Content-Length field
*/
void
set_content_length_impl(
boost::optional<std::uint64_t> const& value);
/** Adjusts the Connection field
*/
void
set_keep_alive_impl(
unsigned version, bool keep_alive);
private:
template<class OtherAlloc>

View File

@@ -19,6 +19,13 @@
#include <stdexcept>
#include <string>
#if defined(BOOST_LIBSTDCXX_VERSION) && BOOST_LIBSTDCXX_VERSION < 60000
// Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437
#ifndef BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR
#define BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR
#endif
#endif
namespace beast {
namespace http {
@@ -133,17 +140,9 @@ public:
}
};
bool
get_chunked() const;
bool
get_keep_alive(int version) const;
basic_fields const& f_;
boost::asio::const_buffer cb_[3];
char buf_[13];
bool chunked_;
bool keep_alive_;
public:
using const_buffers_type =
@@ -160,18 +159,6 @@ public:
reader(basic_fields const& f,
unsigned version, unsigned code);
bool
chunked()
{
return chunked_;
}
bool
keep_alive()
{
return keep_alive_;
}
const_buffers_type
get() const
{
@@ -184,48 +171,11 @@ public:
}
};
template<class Allocator>
bool
basic_fields<Allocator>::reader::
get_chunked() const
{
auto const te = token_list{
f_[field::transfer_encoding]};
for(auto it = te.begin(); it != te.end();)
{
auto const next = std::next(it);
if(next == te.end())
return iequals(*it, "chunked");
it = next;
}
return false;
}
template<class Allocator>
bool
basic_fields<Allocator>::reader::
get_keep_alive(int version) const
{
if(version < 11)
{
auto const it = f_.find(field::connection);
if(it == f_.end())
return false;
return token_list{it->value()}.exists("keep-alive");
}
auto const it = f_.find(field::connection);
if(it == f_.end())
return true;
return ! token_list{it->value()}.exists("close");
}
template<class Allocator>
basic_fields<Allocator>::reader::
reader(basic_fields const& f,
unsigned version, verb v)
: f_(f)
, chunked_(get_chunked())
, keep_alive_(get_keep_alive(version))
{
/*
request
@@ -264,8 +214,6 @@ basic_fields<Allocator>::reader::
reader(basic_fields const& f,
unsigned version, unsigned code)
: f_(f)
, chunked_(get_chunked())
, keep_alive_(get_keep_alive(version))
{
/*
response
@@ -761,37 +709,145 @@ equal_range(string_view name) const ->
//------------------------------------------------------------------------------
namespace detail {
// Filter a token list
//
template<class String, class Pred>
void
filter_token_list(
String& s,
string_view const& value,
Pred&& pred)
{
token_list te{value};
auto it = te.begin();
auto last = te.end();
if(it == last)
return;
while(pred(*it))
if(++it == last)
return;
s.append(it->data(), it->size());
while(++it != last)
{
if(! pred(*it))
{
s.append(", ");
s.append(it->data(), it->size());
}
}
}
// Filter the last item in a token list
template<class String, class Pred>
void
filter_token_list_last(
String& s,
string_view const& value,
Pred&& pred)
{
token_list te{value};
if(te.begin() != te.end())
{
auto it = te.begin();
auto next = std::next(it);
if(next == te.end())
{
if(! pred(*it))
s.append(it->data(), it->size());
return;
}
s.append(it->data(), it->size());
for(;;)
{
it = next;
next = std::next(it);
if(next == te.end())
{
if(! pred(*it))
{
s.append(", ");
s.append(it->data(), it->size());
}
return;
}
s.append(", ");
s.append(it->data(), it->size());
}
}
}
template<class String>
void
keep_alive_impl(
String& s, string_view const& value,
unsigned version, bool keep_alive)
{
if(version < 11)
{
if(keep_alive)
{
// remove close
filter_token_list(s, value,
[](string_view s)
{
return iequals(s, "close");
});
// add keep-alive
if(s.empty())
s.append("keep-alive");
else if(! token_list{value}.exists("keep-alive"))
s.append(", keep-alive");
}
else
{
// remove close and keep-alive
filter_token_list(s, value,
[](string_view s)
{
return
iequals(s, "close") ||
iequals(s, "keep-alive");
});
}
}
else
{
if(keep_alive)
{
// remove close and keep-alive
filter_token_list(s, value,
[](string_view s)
{
return
iequals(s, "close") ||
iequals(s, "keep-alive");
});
}
else
{
// remove keep-alive
filter_token_list(s, value,
[](string_view s)
{
return iequals(s, "keep-alive");
});
// add close
if(s.empty())
s.append("close");
else if(! token_list{value}.exists("close"))
s.append(", close");
}
}
}
} // detail
//------------------------------------------------------------------------------
// Fields
template<class Allocator>
inline
void
basic_fields<Allocator>::
set_method_impl(string_view s)
{
realloc_string(method_, s);
}
template<class Allocator>
inline
void
basic_fields<Allocator>::
set_target_impl(string_view s)
{
realloc_target(
target_or_reason_, s);
}
template<class Allocator>
inline
void
basic_fields<Allocator>::
set_reason_impl(string_view s)
{
realloc_string(
target_or_reason_, s);
}
template<class Allocator>
inline
string_view
@@ -823,73 +879,118 @@ get_reason_impl() const
return target_or_reason_;
}
namespace detail {
// Builds a new string with "chunked" taken off the end if present
template<class String>
void
without_chunked_last(String& s, string_view const& tokens)
template<class Allocator>
bool
basic_fields<Allocator>::
get_chunked_impl() const
{
token_list te{tokens};
if(te.begin() != te.end())
auto const te = token_list{
(*this)[field::transfer_encoding]};
for(auto it = te.begin(); it != te.end();)
{
auto it = te.begin();
auto next = std::next(it);
auto const next = std::next(it);
if(next == te.end())
{
if(! iequals(*it, "chunked"))
s.append(it->data(), it->size());
return;
}
s.append(it->data(), it->size());
for(;;)
{
it = next;
next = std::next(it);
if(next == te.end())
{
if(! iequals(*it, "chunked"))
{
s.append(", ");
s.append(it->data(), it->size());
}
return;
}
s.append(", ");
s.append(it->data(), it->size());
}
return iequals(*it, "chunked");
it = next;
}
return false;
}
} // detail
template<class Allocator>
bool
basic_fields<Allocator>::
get_keep_alive_impl(unsigned version) const
{
auto const it = find(field::connection);
if(version < 11)
{
if(it == end())
return false;
return token_list{
it->value()}.exists("keep-alive");
}
if(it == end())
return true;
return ! token_list{
it->value()}.exists("close");
}
template<class Allocator>
inline
void
basic_fields<Allocator>::
set_method_impl(string_view s)
{
realloc_string(method_, s);
}
template<class Allocator>
inline
void
basic_fields<Allocator>::
set_target_impl(string_view s)
{
realloc_target(
target_or_reason_, s);
}
template<class Allocator>
inline
void
basic_fields<Allocator>::
set_reason_impl(string_view s)
{
realloc_string(
target_or_reason_, s);
}
template<class Allocator>
void
basic_fields<Allocator>::
prepare_payload_impl(bool chunked,
boost::optional<std::uint64_t> size)
set_chunked_impl(bool value)
{
if(chunked)
auto it = find(field::transfer_encoding);
if(value)
{
BOOST_ASSERT(! size);
erase(field::content_length);
auto it = find(field::transfer_encoding);
// append "chunked"
if(it == end())
{
set(field::transfer_encoding, "chunked");
return;
}
static_string<max_static_buffer> temp;
if(it->value().size() <= temp.size() + 9)
auto const te = token_list{it->value()};
for(auto itt = te.begin();;)
{
temp.append(it->value().data(), it->value().size());
temp.append(", chunked", 9);
set(field::transfer_encoding, temp);
auto const next = std::next(itt);
if(next == te.end())
{
if(iequals(*itt, "chunked"))
return; // already set
break;
}
itt = next;
}
static_string<max_static_buffer> buf;
if(it->value().size() <= buf.size() + 9)
{
buf.append(it->value().data(), it->value().size());
buf.append(", chunked", 9);
set(field::transfer_encoding, buf);
}
else
{
#ifdef BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR
// Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437
std::string s;
#else
using rebind_type =
typename std::allocator_traits<
Allocator>::template rebind_alloc<char>;
std::basic_string<
char,
std::char_traits<char>,
rebind_type> s{rebind_type{alloc_}};
#endif
s.reserve(it->value().size() + 9);
s.append(it->value().data(), it->value().size());
s.append(", chunked", 9);
@@ -897,44 +998,100 @@ prepare_payload_impl(bool chunked,
}
return;
}
auto const clear_chunked =
[this]()
{
auto it = find(field::transfer_encoding);
if(it == end())
return;
// We have to just try it because we can't
// know ahead of time if there's enough room.
try
{
static_string<max_static_buffer> temp;
detail::without_chunked_last(temp, it->value());
if(! temp.empty())
set(field::transfer_encoding, temp);
else
erase(field::transfer_encoding);
}
catch(std::length_error const&)
{
std::string s;
s.reserve(it->value().size());
detail::without_chunked_last(s, it->value());
if(! s.empty())
set(field::transfer_encoding, s);
else
erase(field::transfer_encoding);
}
};
if(size)
// filter "chunked"
if(it == end())
return;
try
{
clear_chunked();
set(field::content_length, *size);
static_string<max_static_buffer> buf;
detail::filter_token_list_last(buf, it->value(),
[](string_view const& s)
{
return iequals(s, "chunked");
});
if(! buf.empty())
set(field::transfer_encoding, buf);
else
erase(field::transfer_encoding);
}
else
catch(std::length_error const&)
{
clear_chunked();
#ifdef BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR
// Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437
std::string s;
#else
using rebind_type =
typename std::allocator_traits<
Allocator>::template rebind_alloc<char>;
std::basic_string<
char,
std::char_traits<char>,
rebind_type> s{rebind_type{alloc_}};
#endif
s.reserve(it->value().size());
detail::filter_token_list_last(s, it->value(),
[](string_view const& s)
{
return iequals(s, "chunked");
});
if(! s.empty())
set(field::transfer_encoding, s);
else
erase(field::transfer_encoding);
}
}
template<class Allocator>
void
basic_fields<Allocator>::
set_content_length_impl(
boost::optional<std::uint64_t> const& value)
{
if(! value)
erase(field::content_length);
else
set(field::content_length, *value);
}
template<class Allocator>
void
basic_fields<Allocator>::
set_keep_alive_impl(
unsigned version, bool keep_alive)
{
// VFALCO What about Proxy-Connection ?
auto const value = (*this)[field::connection];
try
{
static_string<max_static_buffer> buf;
detail::keep_alive_impl(
buf, value, version, keep_alive);
if(buf.empty())
erase(field::connection);
else
set(field::connection, buf);
}
catch(std::length_error const&)
{
#ifdef BEAST_HTTP_NO_FIELDS_BASIC_STRING_ALLOCATOR
// Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56437
std::string s;
#else
using rebind_type =
typename std::allocator_traits<
Allocator>::template rebind_alloc<char>;
std::basic_string<
char,
std::char_traits<char>,
rebind_type> s{rebind_type{alloc_}};
#endif
s.reserve(value.size());
detail::keep_alive_impl(
s, value, version, keep_alive);
if(s.empty())
erase(field::connection);
else
set(field::connection, s);
}
}

View File

@@ -313,40 +313,25 @@ message(std::piecewise_construct_t,
{
}
#if 0
template<bool isRequest, class Body, class Fields>
template<class BodyArg, class>
void
message<isRequest, Body, Fields>::
message(BodyArg&& body_arg)
: body(std::forward<BodyArg>(body_arg))
chunked(bool value)
{
this->set_chunked_impl(value);
this->set_content_length_impl(boost::none);
}
template<bool isRequest, class Body, class Fields>
template<class BodyArg, class HeaderArg, class>
void
message<isRequest, Body, Fields>::
message(BodyArg&& body_arg, HeaderArg&& header_arg)
: header_type(std::forward<HeaderArg>(header_arg))
, body(std::forward<BodyArg>(body_arg))
content_length(
boost::optional<std::uint64_t> const& value)
{
this->set_content_length_impl(value);
this->set_chunked_impl(false);
}
template<bool isRequest, class Body, class Fields>
template<class... BodyArgs, class... HeaderArgs>
message<isRequest, Body, Fields>::
message(std::piecewise_construct_t,
std::tuple<BodyArgs...>&& body_args,
std::tuple<HeaderArgs...>&& header_args)
: message(std::piecewise_construct,
body_args, header_args,
beast::detail::make_index_sequence<
sizeof...(BodyArgs)>{},
beast::detail::make_index_sequence<
sizeof...(HeaderArgs)>{})
{
}
#endif
template<bool isRequest, class Body, class Fields>
boost::optional<std::uint64_t>
message<isRequest, Body, Fields>::
@@ -371,20 +356,20 @@ prepare_payload(std::true_type)
this->method() == verb::put ||
this->method() == verb::post)
{
this->prepare_payload_impl(false, *n);
this->content_length(n);
}
else
{
this->prepare_payload_impl(false, boost::none);
this->chunked(false);
}
}
else if(this->version >= 11)
{
this->prepare_payload_impl(true, boost::none);
this->chunked(true);
}
else
{
this->prepare_payload_impl(false, boost::none);
this->chunked(false);
}
}
@@ -404,13 +389,9 @@ prepare_payload(std::false_type)
"invalid response body"});
}
if(n)
{
this->prepare_payload_impl(false, *n);
}
this->content_length(n);
else
{
this->prepare_payload_impl(true, boost::none);
}
this->chunked(true);
}
//------------------------------------------------------------------------------

View File

@@ -88,8 +88,8 @@ next(error_code& ec, Visit&& visit)
{
frdinit(std::integral_constant<bool,
isRequest>{});
keep_alive_ = frd_->keep_alive();
chunked_ = frd_->chunked();
keep_alive_ = m_.keep_alive();
chunked_ = m_.chunked();
if(chunked_)
goto go_init_c;
s_ = do_init;

View File

@@ -642,6 +642,72 @@ struct message : header<isRequest, Fields>
return *this;
}
/// Returns `true` if the chunked Transfer-Encoding is specified
bool
chunked() const
{
return this->get_chunked_impl();
}
/** Set or clear the chunked Transfer-Encoding
This function will set or removed the "chunked" transfer
encoding as the last item in the list of encodings in the
field.
If the result of removing the chunked token results in an
empty string, the field is erased.
The Content-Length field is erased unconditionally.
*/
void
chunked(bool value);
/** Set or clear the Content-Length field
This function adjusts the Content-Length field as follows:
@li If `value` specifies a value, the Content-Length field
is set to the value. Otherwise
@li The Content-Length field is erased.
If "chunked" token appears as the last item in the
Transfer-Encoding field it is unconditionally removed.
@param value The value to set for Content-Length.
*/
void
content_length(boost::optional<std::uint64_t> const& value);
/** Returns `true` if the message semantics indicate keep-alive
The value depends on the version in the message, which must
be set to the final value before this function is called or
else the return value is unreliable.
*/
bool
keep_alive() const
{
return this->get_keep_alive_impl(this->version);
}
/** Set the keep-alive message semantic option
This function adjusts the Connection field to indicate
whether or not the connection should be kept open after
the corresponding response. The result depends on the
version set on the message, which must be set to the
final value before making this call.
@param value `true` if the connection should persist.
*/
void
keep_alive(bool value)
{
this->set_keep_alive_impl(this->version, value);
}
/** Returns the payload size of the body in octets if possible.
This function invokes the @b Body algorithm to measure

View File

@@ -190,52 +190,76 @@ public:
struct reader;
protected:
/** Set or clear the method string.
@note Only called for requests.
*/
void set_method_impl(string_view s);
/** Set or clear the target string.
@note Only called for requests.
*/
void set_target_impl(string_view s);
/** Set or clear the reason string.
@note Only called for responses.
*/
void set_reason_impl(string_view s);
/** Returns the request-method string.
@note Only called for requests.
*/
string_view get_method_impl() const;
string_view
get_method_impl() const;
/** Returns the request-target string.
@note Only called for requests.
*/
string_view get_target_impl() const;
string_view
get_target_impl() const;
/** Returns the response reason-phrase string.
@note Only called for responses.
*/
string_view get_reason_impl() const;
string_view
get_reason_impl() const;
/** Updates the payload metadata.
@param b `true` if chunked
@param n The content length if known, otherwise `boost::none`
/** Returns the chunked Transfer-Encoding setting
*/
void prepare_payload_impl(bool b, boost::optional<std::uint64_t> n);
bool
get_chunked_impl() const;
/** Returns the keep-alive setting
*/
bool
get_keep_alive_impl(unsigned version) const;
/** Set or clear the method string.
@note Only called for requests.
*/
void
set_method_impl(string_view s);
/** Set or clear the target string.
@note Only called for requests.
*/
void
set_target_impl(string_view s);
/** Set or clear the reason string.
@note Only called for responses.
*/
void
set_reason_impl(string_view s);
/** Sets or clears the chunked Transfer-Encoding value
*/
void
set_chunked_impl(bool value);
/** Sets or clears the Content-Length field
*/
void
set_content_length_impl(boost::optional<std::uint64_t>);
/** Adjusts the Connection field
*/
void
set_keep_alive_impl(unsigned version, bool keep_alive);
};
static_assert(is_fields<Fields>::value, "");
static_assert(is_fields<Fields>::value,
"Fields requirements not met");
//]

View File

@@ -1124,7 +1124,7 @@ public:
0x0A,0x49,0x66,0x3A,0x20,0x40,0xC1,0x50,0x5C,0xD6,0xC3,0x86,0xFC,0x8D,0x5C,0x7C,0x96,0x45,0x0D,0x0A,0x4D,0x4D,0x48,0x53,
0x2D,0x45,0x78,0x65,0x6D,0x70,0x74,0x65,0x64,0x2D,0x41,0x64,0x64,0x72,0x65,0x73,0x73,0x3A,0x0D,0x0A,0x49,0x6E,0x6A,0x65,
0x63,0x74,0x69,0x6F,0x6E,0x2D,0x49,0x6E,0x66,0x6F,0x3A,0x20,0x0D,0x0A,0x43,0x6F,0x6E,0x74,0x65,0x74,0x6E,0x2D,0x4C,0x65,
0x6E,0x67,0x74,0x68,0x3A,0x20,0x30,0x0D,0x0A,0x0D,0x0A
0x6E,0x67,0x74,0x68,0x3A,0x20,0x30,0x0D,0x0A,0x0D,0x0A
};
error_code ec;

View File

@@ -584,7 +584,323 @@ public:
}
}
void run() override
void
testKeepAlive()
{
response<empty_body> res;
auto const keep_alive =
[&](bool v)
{
res.keep_alive(v);
BEAST_EXPECT(
(res.keep_alive() && v) ||
(! res.keep_alive() && ! v));
};
BOOST_STATIC_ASSERT(fields::max_static_buffer == 4096);
std::string const big(4096 + 1, 'a');
// HTTP/1.0
res.version = 10;
res.erase(field::connection);
keep_alive(false);
BEAST_EXPECT(res.count(field::connection) == 0);
res.set(field::connection, "close");
keep_alive(false);
BEAST_EXPECT(res.count(field::connection) == 0);
res.set(field::connection, "keep-alive");
keep_alive(false);
BEAST_EXPECT(res.count(field::connection) == 0);
res.set(field::connection, "keep-alive, close");
keep_alive(false);
BEAST_EXPECT(res.count(field::connection) == 0);
res.erase(field::connection);
keep_alive(true);
BEAST_EXPECT(res[field::connection] == "keep-alive");
res.set(field::connection, "close");
keep_alive(true);
BEAST_EXPECT(res[field::connection] == "keep-alive");
res.set(field::connection, "keep-alive");
keep_alive(true);
BEAST_EXPECT(res[field::connection] == "keep-alive");
res.set(field::connection, "keep-alive, close");
keep_alive(true);
BEAST_EXPECT(res[field::connection] == "keep-alive");
auto const test10 =
[&](std::string s)
{
res.set(field::connection, s);
keep_alive(false);
BEAST_EXPECT(res[field::connection] == s);
res.set(field::connection, s + ", close");
keep_alive(false);
BEAST_EXPECT(res[field::connection] == s);
res.set(field::connection, "keep-alive, " + s);
keep_alive(false);
BEAST_EXPECT(res[field::connection] == s);
res.set(field::connection, "keep-alive, " + s + ", close");
keep_alive(false);
BEAST_EXPECT(res[field::connection] == s);
res.set(field::connection, s);
keep_alive(true);
BEAST_EXPECT(res[field::connection] == s + ", keep-alive");
res.set(field::connection, s + ", close");
keep_alive(true);
BEAST_EXPECT(res[field::connection] == s + ", keep-alive");
res.set(field::connection, "keep-alive, " + s);
keep_alive(true);
BEAST_EXPECT(res[field::connection] == "keep-alive, " + s);
res.set(field::connection, "keep-alive, " + s+ ", close");
keep_alive(true);
BEAST_EXPECT(res[field::connection] == "keep-alive, " + s);
};
test10("foo");
test10(big);
// HTTP/1.1
res.version = 11;
res.erase(field::connection);
keep_alive(true);
BEAST_EXPECT(res.count(field::connection) == 0);
res.set(field::connection, "close");
keep_alive(true);
BEAST_EXPECT(res.count(field::connection) == 0);
res.set(field::connection, "keep-alive");
keep_alive(true);
BEAST_EXPECT(res.count(field::connection) == 0);
res.set(field::connection, "keep-alive, close");
keep_alive(true);
BEAST_EXPECT(res.count(field::connection) == 0);
res.erase(field::connection);
keep_alive(false);
BEAST_EXPECT(res[field::connection] == "close");
res.set(field::connection, "close");
keep_alive(false);
BEAST_EXPECT(res[field::connection] == "close");
res.set(field::connection, "keep-alive");
keep_alive(false);
BEAST_EXPECT(res[field::connection] == "close");
res.set(field::connection, "keep-alive, close");
keep_alive(false);
BEAST_EXPECT(res[field::connection] == "close");
auto const test11 =
[&](std::string s)
{
res.set(field::connection, s);
keep_alive(true);
BEAST_EXPECT(res[field::connection] == s);
res.set(field::connection, s + ", close");
keep_alive(true);
BEAST_EXPECT(res[field::connection] == s);
res.set(field::connection, "keep-alive, " + s);
keep_alive(true);
BEAST_EXPECT(res[field::connection] == s);
res.set(field::connection, "keep-alive, " + s + ", close");
keep_alive(true);
BEAST_EXPECT(res[field::connection] == s);
res.set(field::connection, s);
keep_alive(false);
BEAST_EXPECT(res[field::connection] == s + ", close");
res.set(field::connection, "close, " + s);
keep_alive(false);
BEAST_EXPECT(res[field::connection] == "close, " + s);
res.set(field::connection, "keep-alive, " + s);
keep_alive(false);
BEAST_EXPECT(res[field::connection] == s + ", close");
res.set(field::connection, "close, " + s + ", keep-alive");
keep_alive(false);
BEAST_EXPECT(res[field::connection] == "close, " + s);
};
test11("foo");
test11(big);
}
void
testContentLength()
{
response<empty_body> res{status::ok, 11};
BEAST_EXPECT(res.count(field::content_length) == 0);
BEAST_EXPECT(res.count(field::transfer_encoding) == 0);
res.content_length(0);
BEAST_EXPECT(res[field::content_length] == "0");
res.content_length(100);
BEAST_EXPECT(res[field::content_length] == "100");
res.content_length(boost::none);
BEAST_EXPECT(res.count(field::content_length) == 0);
res.set(field::transfer_encoding, "chunked");
res.content_length(0);
BEAST_EXPECT(res[field::content_length] == "0");
BEAST_EXPECT(res.count(field::transfer_encoding) == 0);
res.set(field::transfer_encoding, "chunked");
res.content_length(100);
BEAST_EXPECT(res[field::content_length] == "100");
BEAST_EXPECT(res.count(field::transfer_encoding) == 0);
res.set(field::transfer_encoding, "chunked");
res.content_length(boost::none);
BEAST_EXPECT(res.count(field::content_length) == 0);
BEAST_EXPECT(res.count(field::transfer_encoding) == 0);
auto const check = [&](std::string s)
{
res.set(field::transfer_encoding, s);
res.content_length(0);
BEAST_EXPECT(res[field::content_length] == "0");
BEAST_EXPECT(res[field::transfer_encoding] == s);
res.set(field::transfer_encoding, s);
res.content_length(100);
BEAST_EXPECT(res[field::content_length] == "100");
BEAST_EXPECT(res[field::transfer_encoding] == s);
res.set(field::transfer_encoding, s);
res.content_length(boost::none);
BEAST_EXPECT(res.count(field::content_length) == 0);
BEAST_EXPECT(res[field::transfer_encoding] == s);
res.set(field::transfer_encoding, s + ", chunked");
res.content_length(0);
BEAST_EXPECT(res[field::content_length] == "0");
BEAST_EXPECT(res[field::transfer_encoding] == s);
res.set(field::transfer_encoding, s + ", chunked");
res.content_length(100);
BEAST_EXPECT(res[field::content_length] == "100");
BEAST_EXPECT(res[field::transfer_encoding] == s);
res.set(field::transfer_encoding, s + ", chunked");
res.content_length(boost::none);
BEAST_EXPECT(res.count(field::content_length) == 0);
BEAST_EXPECT(res[field::transfer_encoding] == s);
res.set(field::transfer_encoding, "chunked, " + s);
res.content_length(0);
BEAST_EXPECT(res[field::content_length] == "0");
BEAST_EXPECT(res[field::transfer_encoding] == "chunked, " + s);
res.set(field::transfer_encoding, "chunked, " + s);
res.content_length(100);
BEAST_EXPECT(res[field::content_length] == "100");
BEAST_EXPECT(res[field::transfer_encoding] == "chunked, " + s);
res.set(field::transfer_encoding, "chunked, " + s);
res.content_length(boost::none);
BEAST_EXPECT(res.count(field::content_length) == 0);
BEAST_EXPECT(res[field::transfer_encoding] == "chunked, " + s);
};
check("foo");
BOOST_STATIC_ASSERT(fields::max_static_buffer == 4096);
std::string const big(4096 + 1, 'a');
check(big);
}
void
testChunked()
{
response<empty_body> res{status::ok, 11};
BEAST_EXPECT(res.count(field::content_length) == 0);
BEAST_EXPECT(res.count(field::transfer_encoding) == 0);
auto const chunked =
[&](bool v)
{
res.chunked(v);
BEAST_EXPECT(
(res.chunked() && v) ||
(! res.chunked() && ! v));
BEAST_EXPECT(res.count(
field::content_length) == 0);
};
res.erase(field::transfer_encoding);
res.set(field::content_length, 32);
chunked(true);
BEAST_EXPECT(res[field::transfer_encoding] == "chunked");
res.set(field::transfer_encoding, "chunked");
chunked(true);
BEAST_EXPECT(res[field::transfer_encoding] == "chunked");
res.erase(field::transfer_encoding);
res.set(field::content_length, 32);
chunked(false);
BEAST_EXPECT(res.count(field::transfer_encoding) == 0);
res.set(field::transfer_encoding, "chunked");
chunked(false);
BEAST_EXPECT(res.count(field::transfer_encoding) == 0);
res.set(field::transfer_encoding, "foo");
chunked(true);
BEAST_EXPECT(res[field::transfer_encoding] == "foo, chunked");
res.set(field::transfer_encoding, "chunked, foo");
chunked(true);
BEAST_EXPECT(res[field::transfer_encoding] == "chunked, foo, chunked");
res.set(field::transfer_encoding, "chunked, foo, chunked");
chunked(true);
BEAST_EXPECT(res[field::transfer_encoding] == "chunked, foo, chunked");
res.set(field::transfer_encoding, "foo, chunked");
chunked(false);
BEAST_EXPECT(res[field::transfer_encoding] == "foo");
res.set(field::transfer_encoding, "chunked, foo");
chunked(false);
BEAST_EXPECT(res[field::transfer_encoding] == "chunked, foo");
res.set(field::transfer_encoding, "chunked, foo, chunked");
chunked(false);
BEAST_EXPECT(res[field::transfer_encoding] == "chunked, foo");
}
void
run() override
{
testMembers();
testHeaders();
@@ -592,6 +908,10 @@ public:
testErase();
testContainer();
testPreparePayload();
testKeepAlive();
testContentLength();
testChunked();
}
};

View File

@@ -191,13 +191,17 @@ public:
test_fields() = delete;
test_fields(token) {}
void set_method_impl(string_view) {}
void set_target_impl(string_view s) { target = s.to_string(); }
void set_reason_impl(string_view) {}
string_view get_method_impl() const { return {}; }
string_view get_target_impl() const { return target; }
string_view get_reason_impl() const { return {}; }
void prepare_payload_impl(bool, boost::optional<std::uint64_t>) {}
bool get_chunked_impl() const { return false; }
bool get_keep_alive_impl(unsigned) const { return true; }
void set_method_impl(string_view) {}
void set_target_impl(string_view s) { target = s.to_string(); }
void set_reason_impl(string_view) {}
void set_chunked_impl(bool) {}
void set_content_length_impl(boost::optional<std::uint64_t>) {}
void set_keep_alive_impl(unsigned, bool) {}
};
void