diff --git a/CHANGELOG.md b/CHANGELOG.md index 268e6da6..a36e22a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. -------------------------------------------------------------------------------- diff --git a/doc/8_concepts.qbk b/doc/8_concepts.qbk index 894553a0..7d1f2383 100644 --- a/doc/8_concepts.qbk +++ b/doc/8_concepts.qbk @@ -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] diff --git a/doc/concept/BodyReader.qbk b/doc/concept/BodyReader.qbk index af7b9aa7..ec13aa16 100644 --- a/doc/concept/BodyReader.qbk +++ b/doc/concept/BodyReader.qbk @@ -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]] [ diff --git a/doc/concept/Fields.qbk b/doc/concept/Fields.qbk index 56cc9126..a66a4282 100644 --- a/doc/concept/Fields.qbk +++ b/doc/concept/Fields.qbk @@ -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`. -[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`] - [`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] diff --git a/doc/concept/FieldsReader.qbk b/doc/concept/FieldsReader.qbk index 9eee90a6..1e4c8786 100644 --- a/doc/concept/FieldsReader.qbk +++ b/doc/concept/FieldsReader.qbk @@ -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`] diff --git a/include/beast/http/detail/type_traits.hpp b/include/beast/http/detail/type_traits.hpp index 05a72679..bb10a2d7 100644 --- a/include/beast/http/detail/type_traits.hpp +++ b/include/beast/http/detail/type_traits.hpp @@ -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 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); + void set_keep_alive_impl(unsigned, bool); }; template> @@ -91,60 +95,90 @@ struct is_fields_helper : T { template static auto f1(int) -> decltype( - void(std::declval().set_method_impl(std::declval())), + std::declval() = std::declval().get_method_impl(), std::true_type()); static auto f1(...) -> std::false_type; using t1 = decltype(f1(0)); template static auto f2(int) -> decltype( - void(std::declval().set_target_impl(std::declval())), + std::declval() = std::declval().get_target_impl(), std::true_type()); static auto f2(...) -> std::false_type; using t2 = decltype(f2(0)); template static auto f3(int) -> decltype( - void(std::declval().set_reason_impl(std::declval())), + std::declval() = std::declval().get_reason_impl(), std::true_type()); static auto f3(...) -> std::false_type; using t3 = decltype(f3(0)); template static auto f4(int) -> decltype( - std::declval() = std::declval().get_method_impl(), + std::declval() = std::declval().get_chunked_impl(), std::true_type()); static auto f4(...) -> std::false_type; using t4 = decltype(f4(0)); template static auto f5(int) -> decltype( - std::declval() = std::declval().get_target_impl(), + std::declval() = std::declval().get_keep_alive_impl( + std::declval()), std::true_type()); static auto f5(...) -> std::false_type; using t5 = decltype(f5(0)); template static auto f6(int) -> decltype( - std::declval() = std::declval().get_reason_impl(), + void(std::declval().set_method_impl(std::declval())), std::true_type()); static auto f6(...) -> std::false_type; using t6 = decltype(f6(0)); template static auto f7(int) -> decltype( - void(std::declval().prepare_payload_impl( - std::declval(), - std::declval>())), + void(std::declval().set_target_impl(std::declval())), std::true_type()); static auto f7(...) -> std::false_type; using t7 = decltype(f7(0)); + template + static auto f8(int) -> decltype( + void(std::declval().set_reason_impl(std::declval())), + std::true_type()); + static auto f8(...) -> std::false_type; + using t8 = decltype(f8(0)); + + template + static auto f9(int) -> decltype( + void(std::declval().set_chunked_impl(std::declval())), + std::true_type()); + static auto f9(...) -> std::false_type; + using t9 = decltype(f9(0)); + + template + static auto f10(int) -> decltype( + void(std::declval().set_content_length_impl( + std::declval>())), + std::true_type()); + static auto f10(...) -> std::false_type; + using t10 = decltype(f10(0)); + + template + static auto f11(int) -> decltype( + void(std::declval().set_keep_alive_impl( + std::declval(), + std::declval())), + std::true_type()); + static auto f11(...) -> std::false_type; + using t11 = decltype(f11(0)); + using type = std::integral_constant; + t1::value && t2::value && t3::value && + t4::value && t5::value && t6::value && + t7::value && t8::value && t9::value && + t10::value && t11::value>; }; } // detail diff --git a/include/beast/http/fields.hpp b/include/beast/http/fields.hpp index b2167085..0b8eddb1 100644 --- a/include/beast/http/fields.hpp +++ b/include/beast/http/fields.hpp @@ -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 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 const& value); + + /** Adjusts the Connection field + */ + void + set_keep_alive_impl( + unsigned version, bool keep_alive); private: template diff --git a/include/beast/http/impl/fields.ipp b/include/beast/http/impl/fields.ipp index ccac2979..f5b4a159 100644 --- a/include/beast/http/impl/fields.ipp +++ b/include/beast/http/impl/fields.ipp @@ -19,6 +19,13 @@ #include #include +#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 -bool -basic_fields::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 -bool -basic_fields::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 basic_fields::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::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 +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 +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 +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 -inline -void -basic_fields:: -set_method_impl(string_view s) -{ - realloc_string(method_, s); -} - -template -inline -void -basic_fields:: -set_target_impl(string_view s) -{ - realloc_target( - target_or_reason_, s); -} - -template -inline -void -basic_fields:: -set_reason_impl(string_view s) -{ - realloc_string( - target_or_reason_, s); -} - template 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 -void -without_chunked_last(String& s, string_view const& tokens) +template +bool +basic_fields:: +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 +bool +basic_fields:: +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 +inline +void +basic_fields:: +set_method_impl(string_view s) +{ + realloc_string(method_, s); +} + +template +inline +void +basic_fields:: +set_target_impl(string_view s) +{ + realloc_target( + target_or_reason_, s); +} + +template +inline +void +basic_fields:: +set_reason_impl(string_view s) +{ + realloc_string( + target_or_reason_, s); +} template void basic_fields:: -prepare_payload_impl(bool chunked, - boost::optional 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 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 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; + std::basic_string< + char, + std::char_traits, + 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 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 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; + std::basic_string< + char, + std::char_traits, + 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 +void +basic_fields:: +set_content_length_impl( + boost::optional const& value) +{ + if(! value) erase(field::content_length); + else + set(field::content_length, *value); +} + +template +void +basic_fields:: +set_keep_alive_impl( + unsigned version, bool keep_alive) +{ + // VFALCO What about Proxy-Connection ? + auto const value = (*this)[field::connection]; + try + { + static_string 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; + std::basic_string< + char, + std::char_traits, + 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); } } diff --git a/include/beast/http/impl/message.ipp b/include/beast/http/impl/message.ipp index af3c524a..68af2345 100644 --- a/include/beast/http/impl/message.ipp +++ b/include/beast/http/impl/message.ipp @@ -313,40 +313,25 @@ message(std::piecewise_construct_t, { } -#if 0 template -template +void message:: -message(BodyArg&& body_arg) - : body(std::forward(body_arg)) +chunked(bool value) { + this->set_chunked_impl(value); + this->set_content_length_impl(boost::none); } template -template +void message:: -message(BodyArg&& body_arg, HeaderArg&& header_arg) - : header_type(std::forward(header_arg)) - , body(std::forward(body_arg)) +content_length( + boost::optional const& value) { + this->set_content_length_impl(value); + this->set_chunked_impl(false); } -template -template -message:: -message(std::piecewise_construct_t, - std::tuple&& body_args, - std::tuple&& 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 boost::optional message:: @@ -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); } //------------------------------------------------------------------------------ diff --git a/include/beast/http/impl/serializer.ipp b/include/beast/http/impl/serializer.ipp index 89f5c6da..cc288f68 100644 --- a/include/beast/http/impl/serializer.ipp +++ b/include/beast/http/impl/serializer.ipp @@ -88,8 +88,8 @@ next(error_code& ec, Visit&& visit) { frdinit(std::integral_constant{}); - 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; diff --git a/include/beast/http/message.hpp b/include/beast/http/message.hpp index 93a39ff3..4c3fd644 100644 --- a/include/beast/http/message.hpp +++ b/include/beast/http/message.hpp @@ -642,6 +642,72 @@ struct message : header 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 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 diff --git a/test/exemplars.cpp b/test/exemplars.cpp index 95f99971..727ba10d 100644 --- a/test/exemplars.cpp +++ b/test/exemplars.cpp @@ -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 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); + + /** Adjusts the Connection field + */ + void + set_keep_alive_impl(unsigned version, bool keep_alive); }; -static_assert(is_fields::value, ""); +static_assert(is_fields::value, + "Fields requirements not met"); //] diff --git a/test/http/basic_parser.cpp b/test/http/basic_parser.cpp index 8f5fee59..6303fa7f 100644 --- a/test/http/basic_parser.cpp +++ b/test/http/basic_parser.cpp @@ -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; diff --git a/test/http/fields.cpp b/test/http/fields.cpp index d8b7e654..0958952d 100644 --- a/test/http/fields.cpp +++ b/test/http/fields.cpp @@ -584,7 +584,323 @@ public: } } - void run() override + void + testKeepAlive() + { + response 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 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 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(); } }; diff --git a/test/http/message.cpp b/test/http/message.cpp index b4aa8ac5..18159fee 100644 --- a/test/http/message.cpp +++ b/test/http/message.cpp @@ -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) {} + 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) {} + void set_keep_alive_impl(unsigned, bool) {} }; void