diff --git a/doc/local-playbook.yml b/doc/local-playbook.yml index 976874fb..d42a5211 100644 --- a/doc/local-playbook.yml +++ b/doc/local-playbook.yml @@ -14,7 +14,7 @@ content: ui: bundle: - url: https://github.com/boostorg/website-v2-docs/releases/download/ui-master/ui-bundle.zip + url: https://github.com/boostorg/website-v2-docs/releases/download/ui-develop/ui-bundle.zip snapshot: true antora: diff --git a/include/boost/http_proto/detail/array_of_buffers.hpp b/include/boost/http_proto/detail/array_of_buffers.hpp deleted file mode 100644 index f945bb29..00000000 --- a/include/boost/http_proto/detail/array_of_buffers.hpp +++ /dev/null @@ -1,71 +0,0 @@ -// -// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/http_proto -// - -#ifndef BOOST_HTTP_PROTO_DETAIL_ARRAY_OF_BUFFERS_HPP -#define BOOST_HTTP_PROTO_DETAIL_ARRAY_OF_BUFFERS_HPP - -#include -#include - -namespace boost { -namespace http_proto { -namespace detail { - -template -class array_of_buffers -{ -public: - using value_type = typename - std::conditional::type; - using iterator = value_type*; - using const_iterator = iterator; - - array_of_buffers() = default; - array_of_buffers( - array_of_buffers const&) = default; - array_of_buffers& operator=( - array_of_buffers const&) = default; - - array_of_buffers( - value_type* p, - std::size_t n) noexcept; - - bool empty() const noexcept; - value_type* data() const noexcept; - std::size_t size() const noexcept; - std::size_t capacity() const noexcept; - value_type& operator[](std::size_t) const noexcept; - void consume(std::size_t n); - void reset(std::size_t n); - - iterator begin() const noexcept; - iterator end() const noexcept; - -private: - value_type* o_ = nullptr; - value_type* p_ = nullptr; - std::size_t n_ = 0; - std::size_t c_ = 0; -}; - -using array_of_const_buffers = - array_of_buffers; - -using array_of_mutable_buffers = - array_of_buffers; - -} // detail -} // http_proto -} // boost - -#include - -#endif diff --git a/include/boost/http_proto/detail/array_of_const_buffers.hpp b/include/boost/http_proto/detail/array_of_const_buffers.hpp new file mode 100644 index 00000000..4bdece43 --- /dev/null +++ b/include/boost/http_proto/detail/array_of_const_buffers.hpp @@ -0,0 +1,105 @@ +// +// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2025 Mohammad Nejati +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/http_proto +// + +#ifndef BOOST_HTTP_PROTO_DETAIL_ARRAY_OF_BUFFERS_HPP +#define BOOST_HTTP_PROTO_DETAIL_ARRAY_OF_BUFFERS_HPP + +#include + +#include + +namespace boost { +namespace http_proto { +namespace detail { + +class array_of_const_buffers +{ +public: + using value_type = buffers::const_buffer; + using iterator = value_type*; + using const_iterator = iterator; + + array_of_const_buffers() = default; + array_of_const_buffers( + array_of_const_buffers const&) = default; + array_of_const_buffers& + operator=(array_of_const_buffers const&) = default; + + array_of_const_buffers( + value_type* p, + std::uint16_t n) noexcept; + + bool + empty() const noexcept + { + return size_ == 0; + } + + std::uint16_t + size() const noexcept + { + return size_; + } + + std::uint16_t + max_size() const noexcept + { + return cap_; + } + + std::uint16_t + capacity() const noexcept + { + return cap_ - size_; + } + + iterator + begin() const noexcept + { + return base_ + pos_; + } + + iterator + end() const noexcept + { + return base_ + pos_ + size_; + } + + value_type& + operator[]( + std::uint16_t i) const noexcept + { + BOOST_ASSERT(i < cap_ - pos_); + return base_[i + pos_]; + } + + void + consume(std::size_t n); + + void + reset(std::uint16_t n) noexcept; + + void + slide_to_front() noexcept; + + void append(value_type) noexcept; + +private: + value_type* base_ = nullptr; + std::uint16_t cap_ = 0; + std::uint16_t pos_ = 0; + std::uint16_t size_ = 0; +}; + +} // detail +} // http_proto +} // boost + +#endif diff --git a/include/boost/http_proto/detail/impl/array_of_buffers.hpp b/include/boost/http_proto/detail/impl/array_of_buffers.hpp deleted file mode 100644 index f7db1740..00000000 --- a/include/boost/http_proto/detail/impl/array_of_buffers.hpp +++ /dev/null @@ -1,135 +0,0 @@ -// -// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/http_proto -// - -#ifndef BOOST_HTTP_PROTO_DETAIL_IMPL_ARRAY_OF_BUFFERS_HPP -#define BOOST_HTTP_PROTO_DETAIL_IMPL_ARRAY_OF_BUFFERS_HPP - -#include -#include -#include - -namespace boost { -namespace http_proto { -namespace detail { - -template -array_of_buffers:: -array_of_buffers( - value_type* p, - std::size_t n) noexcept - : o_(p) - , p_(p) - , n_(n) - , c_(n) -{ -} - -template -bool -array_of_buffers:: -empty() const noexcept -{ - return n_ == 0; -} - -template -auto -array_of_buffers:: -data() const noexcept -> - value_type* -{ - return p_; -} - -template -std::size_t -array_of_buffers:: -size() const noexcept -{ - return n_; -} - -template -std::size_t -array_of_buffers:: -capacity() const noexcept -{ - return c_; -} - -template -auto -array_of_buffers:: -begin() const noexcept -> - iterator -{ - return p_; -} - -template -auto -array_of_buffers:: -end() const noexcept -> - iterator -{ - return p_ + n_; -} - -template -auto -array_of_buffers:: -operator[]( - std::size_t i) const noexcept -> - value_type& -{ - BOOST_ASSERT(i < n_); - return p_[i]; -} - -template -void -array_of_buffers:: -consume(std::size_t n) -{ - while(n_ > 0) - { - if(n < p_->size()) - { - *p_ = buffers::sans_prefix(*p_, n); - return; - } - n -= p_->size(); - ++p_; - --n_; - if(n == 0) - return; - } - - // n exceeded available size - if(n > 0) - detail::throw_logic_error(); -} - -template -void -array_of_buffers:: -reset(std::size_t n) -{ - BOOST_ASSERT(n <= capacity()); - p_ = o_; - n_ = n; - for( auto p = p_; p < p_ + n; ++p ) - *p = value_type(); -} - -} // detail -} // http_proto -} // boost - -#endif diff --git a/include/boost/http_proto/serializer.hpp b/include/boost/http_proto/serializer.hpp index 49a6c480..ebd309cc 100644 --- a/include/boost/http_proto/serializer.hpp +++ b/include/boost/http_proto/serializer.hpp @@ -11,19 +11,21 @@ #define BOOST_HTTP_PROTO_SERIALIZER_HPP #include -#include +#include #include #include #include #include #include + #include #include #include #include #include -#include + #include +#include #include #include @@ -31,14 +33,10 @@ namespace boost { namespace http_proto { #ifndef BOOST_HTTP_PROTO_DOCS -class request; -class response; -class request_view; -class response_view; class message_view_base; namespace detail { class filter; -} // detail +} // namespace detail #endif /** A serializer for HTTP/1 messages @@ -68,7 +66,8 @@ class filter; class serializer { public: - using const_buffers_type = buffers::const_buffer_span; + using const_buffers_type = + buffers::const_buffer_span; struct stream; @@ -219,36 +218,14 @@ class serializer void consume(std::size_t n); - /** Applies deflate compression to the current message - - After @ref reset is called, compression is not - applied to the next message. - - Must be called before any calls to @ref start. - */ - BOOST_HTTP_PROTO_DECL - void - use_deflate_encoding(); - - /** Applies gzip compression to the current message - - After @ref reset is called, compression is not - applied to the next message. +private: + class const_buf_gen_base; - Must be called before any calls to @ref start. - */ - BOOST_HTTP_PROTO_DECL - void - use_gzip_encoding(); + template + class const_buf_gen; -private: - static void copy( - buffers::const_buffer*, - buffers::const_buffer const*, - std::size_t n) noexcept; - auto - make_array(std::size_t n) -> - detail::array_of_const_buffers; + detail::array_of_const_buffers + make_array(std::size_t n); template< class Source, @@ -279,10 +256,25 @@ class serializer ws_, std::forward(args)...); } - BOOST_HTTP_PROTO_DECL void start_init(message_view_base const&); - BOOST_HTTP_PROTO_DECL void start_empty(message_view_base const&); - BOOST_HTTP_PROTO_DECL void start_buffers(message_view_base const&); - BOOST_HTTP_PROTO_DECL void start_source(message_view_base const&, source*); + BOOST_HTTP_PROTO_DECL + void + start_init( + message_view_base const&); + + BOOST_HTTP_PROTO_DECL + void + start_empty( + message_view_base const&); + + BOOST_HTTP_PROTO_DECL + void + start_buffers( + message_view_base const&); + + BOOST_HTTP_PROTO_DECL + void + start_source( + message_view_base const&); enum class style { @@ -292,68 +284,25 @@ class serializer stream }; - // chunked-body = *chunk - // last-chunk - // trailer-section - // CRLF - - static - constexpr - std::size_t - crlf_len_ = 2; - - // chunk = chunk-size [ chunk-ext ] CRLF - // chunk-data CRLF - static - constexpr - std::size_t - chunk_header_len_ = - 16 + // 16 hex digits => 64 bit number - crlf_len_; - - // last-chunk = 1*("0") [ chunk-ext ] CRLF - static - constexpr - std::size_t - last_chunk_len_ = - 1 + // "0" - crlf_len_ + - crlf_len_; // chunked-body termination requires an extra CRLF - - static - constexpr - std::size_t - chunked_overhead_ = - chunk_header_len_ + - crlf_len_ + // closing chunk data - last_chunk_len_; - - detail::workspace ws_; - detail::array_of_const_buffers buf_; - detail::filter* filter_ = nullptr; - source* src_; context& ctx_; - buffers::circular_buffer tmp0_; - buffers::circular_buffer tmp1_; - detail::array_of_const_buffers prepped_; - - buffers::mutable_buffer chunk_header_; - buffers::mutable_buffer chunk_close_; - buffers::mutable_buffer last_chunk_; + detail::workspace ws_; - buffers::circular_buffer* in_ = nullptr; - buffers::circular_buffer* out_ = nullptr; + const_buf_gen_base* buf_gen_; + detail::filter* filter_; + source* source_; - buffers::const_buffer* hp_; // header + buffers::circular_buffer cb0_; + buffers::circular_buffer cb1_; + detail::array_of_const_buffers prepped_; + buffers::const_buffer tmp_; style st_; - bool more_; + bool more_input_; bool is_done_; bool is_header_done_; bool is_chunked_; - bool is_expect_continue_; - bool is_compressed_ = false; - bool filter_done_ = false; + bool needs_exp100_continue_; + bool filter_done_; }; //------------------------------------------------ @@ -422,17 +371,6 @@ struct serializer::stream std::size_t capacity() const noexcept; - /** Returns the number of octets serialized by this - stream. - - The associated serializer stores stream output in its - internal buffers. The stream returns the size of this - output. - */ - BOOST_HTTP_PROTO_DECL - std::size_t - size() const noexcept; - /** Return true if the stream cannot currently hold additional output data. @@ -503,6 +441,97 @@ struct serializer::stream //--------------------------------------------------------- +class serializer::const_buf_gen_base +{ +public: + // Next non-empty buffer + virtual + buffers::const_buffer + operator()() = 0; + + // Size of remaining buffers + virtual + std::size_t + size() const = 0; + + // Count of remaining non-empty buffers + virtual + std::size_t + count() const = 0; + + // Returns true when there is no buffer or + // the remaining buffers are empty + virtual + bool + is_empty() const = 0; +}; + +template +class serializer::const_buf_gen + : public const_buf_gen_base +{ + using it_t = decltype(buffers::begin( + std::declval())); + + ConstBufferSequence cbs_; + it_t current_; +public: + using const_buffer = + buffers::const_buffer; + + explicit + const_buf_gen(ConstBufferSequence cbs) + : cbs_(std::move(cbs)) + , current_(buffers::begin(cbs_)) + { + } + + const_buffer + operator()() override + { + while(current_ != buffers::end(cbs_)) + { + const_buffer buf = *current_++; + if(buf.size() != 0) + return buf; + } + return {}; + } + + std::size_t + size() const override + { + return std::accumulate( + current_, + buffers::end(cbs_), + std::size_t{}, + [](std::size_t sum, const_buffer cb) { + return sum + cb.size(); }); + } + + std::size_t + count() const override + { + return std::count_if( + current_, + buffers::end(cbs_), + [](const_buffer cb) { + return cb.size() != 0; }); + } + + bool + is_empty() const override + { + return std::all_of( + current_, + buffers::end(cbs_), + [](const_buffer cb) { + return cb.size() == 0; }); + } +}; + +//--------------------------------------------------------- + template< class ConstBufferSequence, class> @@ -510,22 +539,17 @@ void serializer:: start( message_view_base const& m, - ConstBufferSequence&& body) + ConstBufferSequence&& cbs) { - start_init(m); - auto const& bs = - ws_.emplace( - std::forward(body)); - - std::size_t n = std::distance( - buffers::begin(bs), - buffers::end(bs)); - - buf_ = make_array(n); - auto p = buf_.data(); - for(buffers::const_buffer b : buffers::range(bs)) - *p++ = b; + static_assert(buffers::is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence type requirements not met"); + start_init(m); + buf_gen_ = std::addressof( + ws_.emplace::type>>( + std::forward(cbs))); start_buffers(m); } @@ -549,24 +573,11 @@ start( start_init(m); auto& src = construct_source( std::forward(args)...); - start_source(m, std::addressof(src)); + source_ = std::addressof(src); + start_source(m); return src; } -//------------------------------------------------ - -inline -auto -serializer:: -make_array(std::size_t n) -> - detail::array_of_const_buffers -{ - return { - ws_.push_array(n, - buffers::const_buffer{}), - n }; -} - } // http_proto } // boost diff --git a/src/detail/impl/array_of_const_buffers.cpp b/src/detail/impl/array_of_const_buffers.cpp new file mode 100644 index 00000000..e8084817 --- /dev/null +++ b/src/detail/impl/array_of_const_buffers.cpp @@ -0,0 +1,87 @@ +// +// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2025 Mohammad Nejati +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/http_proto +// + +#include +#include + +#include + +namespace boost { +namespace http_proto { +namespace detail { + +array_of_const_buffers:: +array_of_const_buffers( + value_type* p, + std::uint16_t n) noexcept + : base_(p) + , cap_(n) + , pos_(0) + , size_(n) +{ +} + +void +array_of_const_buffers:: +consume(std::size_t n) +{ + while(size_ > 0) + { + auto* p = base_ + pos_; + if(n < p->size()) + { + *p = buffers::sans_prefix(*p, n); + return; + } + n -= p->size(); + ++pos_; + --size_; + if(n == 0) + return; + } + + // n exceeded available size + if(n > 0) + detail::throw_logic_error(); +} + +void +array_of_const_buffers:: +reset(std::uint16_t n) noexcept +{ + BOOST_ASSERT(n <= cap_); + pos_ = 0; + size_ = n; +} + +void +array_of_const_buffers:: +slide_to_front() noexcept +{ + auto* p = base_ + pos_; // begin + auto* e = p + size_; // end + auto* d = base_; // dest + while(p < e) + *d++ = *p++; + pos_ = 0; +} + +void +array_of_const_buffers:: +append(value_type buf) noexcept +{ + BOOST_ASSERT(size_ < cap_); + base_[pos_ + size_] = buf; + ++size_; +} + +} // detail +} // http_proto +} // boost diff --git a/src/detail/impl/filter.hpp b/src/detail/impl/filter.hpp index 8fe15e1f..d5417c23 100644 --- a/src/detail/impl/filter.hpp +++ b/src/detail/impl/filter.hpp @@ -41,8 +41,8 @@ process_impl( auto ib = *it_i++; for(;;) { - // empty buffers may be passed, and this is - // intentional and valid. + // empty input buffers may be passed, and + // this is intentional and valid. results rs = process_impl(ob, ib, more); rv.out_bytes += rs.out_bytes; @@ -56,7 +56,7 @@ process_impl( ob = buffers::sans_prefix(ob, rs.out_bytes); ib = buffers::sans_prefix(ib, rs.in_bytes); - if( ob.size() == 0 ) + while( ob.size() == 0 ) { if( it_o == buffers::end(out) ) return rv; diff --git a/src/serializer.cpp b/src/serializer.cpp index 8682778e..830ee4ed 100644 --- a/src/serializer.cpp +++ b/src/serializer.cpp @@ -14,10 +14,13 @@ #include #include -#include "detail/filter.hpp" +#include "src/detail/filter.hpp" #include +#include #include +#include +#include #include #include @@ -27,6 +30,7 @@ namespace boost { namespace http_proto { namespace { + class deflator_filter : public http_proto::detail::filter { @@ -96,35 +100,32 @@ class deflator_filter } } }; -} // namespace -void -consume_buffers( - buffers::const_buffer*& p, - std::size_t& n, - std::size_t bytes) -{ - while(n > 0) - { - if(bytes < p->size()) - { - *p = buffers::sans_prefix(*p, bytes); - return; - } - bytes -= p->size(); - ++p; - --n; - } +//------------------------------------------------ - // Precondition violation - if(bytes > 0) - detail::throw_invalid_argument(); -} +constexpr +std::size_t +crlf_len = 2; + +constexpr +std::size_t +chunk_header_len = 16 + crlf_len; + +constexpr +std::size_t +final_chunk_len = 1 + crlf_len + crlf_len; + +constexpr +std::size_t +chunked_overhead_ = + chunk_header_len + + crlf_len + + final_chunk_len; -template +template void write_chunk_header( - MutableBuffers const& dest0, + const MutableBufferSequence& mbs, std::size_t size) noexcept { static constexpr char hexdig[] = @@ -139,37 +140,123 @@ write_chunk_header( buf[16] = '\r'; buf[17] = '\n'; auto n = buffers::copy( - dest0, + mbs, buffers::const_buffer( buf, sizeof(buf))); ignore_unused(n); BOOST_ASSERT(n == 18); - BOOST_ASSERT( - buffers::size(dest0) == n); } -template +template void -write_chunk_close(DynamicBuffer& db) +write_crlf( + const MutableBufferSequence& mbs) noexcept { - db.commit( - buffers::copy( - db.prepare(2), - buffers::const_buffer("\r\n", 2))); + auto n = buffers::copy( + mbs, + buffers::const_buffer( + "\r\n", 2)); + ignore_unused(n); + BOOST_ASSERT(n == 2); } -template +template void -write_last_chunk(DynamicBuffer& db) +write_final_chunk( + const MutableBufferSequence& mbs) noexcept { - db.commit( - buffers::copy( - db.prepare(5), - buffers::const_buffer("0\r\n\r\n", 5))); + auto n = buffers::copy( + mbs, + buffers::const_buffer( + "0\r\n\r\n", 5)); + ignore_unused(n); + BOOST_ASSERT(n == 5); } //------------------------------------------------ +class appender +{ + buffers::circular_buffer& cb_; + buffers::mutable_buffer_pair mbp_; + std::size_t n_ = 0; + bool is_chunked_ = false; + bool more_input_ = true; + +public: + appender( + buffers::circular_buffer& cb, + bool is_chunked) + : cb_(cb) + , mbp_(cb.prepare(cb.capacity())) + , is_chunked_(is_chunked) + { + } + + bool + is_full() const noexcept + { + auto remaining = cb_.capacity() - n_; + if(is_chunked_) + return remaining <= chunked_overhead_; + + return remaining == 0; + } + + buffers::mutable_buffer_pair + prepare() noexcept + { + if(is_chunked_) + { + return buffers::sans_suffix( + buffers::sans_prefix( + mbp_, + chunk_header_len + n_) + , final_chunk_len + crlf_len); + } + return buffers::sans_prefix(mbp_, n_); + } + + void + commit(std::size_t n, bool more) noexcept + { + BOOST_ASSERT(more_input_); + n_ += n; + more_input_ = more; + } + + ~appender() + { + if(is_chunked_) + { + if(n_) + { + write_chunk_header(mbp_, n_); + cb_.commit(n_ + chunk_header_len); + + write_crlf( + cb_.prepare(crlf_len)); + cb_.commit(crlf_len); + } + + if(!more_input_) + { + write_final_chunk( + cb_.prepare(final_chunk_len)); + cb_.commit(final_chunk_len); + } + } + else // is_chunked_ == false + { + cb_.commit(n_); + } + } +}; + +} // namespace + +//------------------------------------------------ + serializer:: ~serializer() { @@ -190,8 +277,8 @@ serializer:: serializer( context& ctx, std::size_t buffer_size) - : ws_(buffer_size) - , ctx_(ctx) + : ctx_(ctx) + , ws_(buffer_size) { } @@ -199,18 +286,18 @@ void serializer:: reset() noexcept { - chunk_header_ = {}; - chunk_close_ = {}; - last_chunk_ = {}; filter_ = nullptr; - more_ = false; + + cb0_ = {}; + tmp_ = {}; + + more_input_ = false; is_done_ = false; + is_header_done_ = false; is_chunked_ = false; - is_expect_continue_ = false; - is_compressed_ = false; + needs_exp100_continue_ = false; filter_done_ = false; - in_ = nullptr; - out_ = nullptr; + ws_.clear(); } @@ -219,193 +306,200 @@ reset() noexcept auto serializer:: prepare() -> - system::result< - const_buffers_type> + system::result { // Precondition violation - if( is_done_ ) + if(is_done_) detail::throw_logic_error(); // Expect: 100-continue - if( is_expect_continue_ ) + if(needs_exp100_continue_) { - if( !is_header_done_ ) - return const_buffers_type(hp_, 1); - is_expect_continue_ = false; + if(!is_header_done_) + return const_buffers_type( + prepped_.begin(), + 1); // limit to header + + needs_exp100_continue_ = false; + BOOST_HTTP_PROTO_RETURN_EC( error::expect_100_continue); } - if( st_ == style::empty ) - return const_buffers_type( - prepped_.data(), prepped_.size()); - - if( st_ == style::buffers && !filter_ ) - return const_buffers_type( - prepped_.data(), prepped_.size()); - - // TODO: This is a temporary solution until we refactor - // the implementation for efficient partial buffer consumption. - if( is_chunked_ && buffers::size(prepped_) && is_header_done_ ) - return const_buffers_type( - prepped_.data(), prepped_.size()); - - auto& input = *in_; - auto& output = *out_; - if( st_ == style::source && more_ ) + if(!filter_) { - auto results = src_->read( - input.prepare(input.capacity())); - if(results.ec.failed()) + switch(st_) { - is_done_ = true; - return results.ec; - } - more_ = !results.finished; - input.commit(results.bytes); - } + case style::empty: + return const_buffers_type( + prepped_.begin(), + prepped_.size()); + + case style::buffers: + // add more buffers if prepped_ is half empty. + if(more_input_ && + prepped_.capacity() >= prepped_.size()) + { + prepped_.slide_to_front(); + while(prepped_.capacity() != 0) + { + auto buf = buf_gen_->operator()(); + if(buf.size() != 0) + { + prepped_.append(buf); + } + else // buf_gen_ is empty + { + // crlf and final chunk + if(tmp_.size() != 0) + { + prepped_.append(tmp_); + tmp_ = {}; + } + break; + } + } + if(buf_gen_->is_empty() && tmp_.size() == 0) + more_input_ = false; + } + return const_buffers_type( + prepped_.begin(), + prepped_.size()); - if( st_ == style::stream && - more_ && - in_->size() == 0 ) - BOOST_HTTP_PROTO_RETURN_EC(error::need_data); + case style::source: + { + if(!more_input_) + break; - bool has_avail_out = - ((!filter_ && (more_ || input.size() > 0)) || - (filter_ && !filter_done_)); + // handles chunked payloads automatically + appender apndr(cb0_, is_chunked_); - auto get_input = [&]() -> buffers::const_buffer - { - if( st_ == style::buffers ) - { - if( buffers::size(buf_) == 0 ) - return {}; + if(apndr.is_full()) + break; - auto buf = *(buf_.data()); - BOOST_ASSERT(buf.size() > 0); - return buf; - } - else - { - if( input.size() == 0 ) - return {}; + auto rs = source_->read( + apndr.prepare()); - auto cbs = input.data(); - auto buf = *cbs.begin(); - if( buf.size() == 0 ) + if(rs.ec.failed()) { - auto p = cbs.begin(); - ++p; - buf = *p; + is_done_ = true; + BOOST_HTTP_PROTO_RETURN_EC(rs.ec); } - if( buf.size() == 0 ) - detail::throw_logic_error(); - return buf; - } - }; - auto get_output = [&]() -> buffers::mutable_buffer - { - auto mbs = output.prepare(output.capacity()); - auto buf = *mbs.begin(); - if( buf.size() == 0 ) - { - auto p = mbs.begin(); - ++p; - buf = *p; + more_input_ = !rs.finished; + apndr.commit(rs.bytes, more_input_); + break; } - return buf; - }; - auto consume = [&](std::size_t n) + case style::stream: + if(is_header_done_ && cb0_.size() == 0) + BOOST_HTTP_PROTO_RETURN_EC( + error::need_data); + break; + } + } + else // filter { - if( st_ == style::buffers ) + if(st_ == style::empty) + return const_buffers_type( + prepped_.begin(), + prepped_.size()); + + auto get_input = [&]() { - buf_.consume(n); - if( buffers::size(buf_) == 0 ) - more_ = false; - } - else - input.consume(n); - }; + if(st_ == style::buffers) + { + // TODO: for efficiency of deflator, we might + // need to return multiple buffers at once + if(tmp_.size() == 0) + { + tmp_ = buf_gen_->operator()(); + more_input_ = !buf_gen_->is_empty(); + } + return buffers:: + const_buffer_pair{ tmp_, {} }; + } - std::size_t num_written = 0; - if( !filter_ ) - num_written += input.size(); - else - { - for(;;) + BOOST_ASSERT( + st_ == style::source || + st_ == style::stream); + + if(st_ == style::source && + more_input_ && + cb1_.capacity() != 0) + { + // TODO: handle source error + auto rs = source_->read( + cb1_.prepare(cb1_.capacity())); + if(rs.finished) + more_input_ = false; + cb1_.commit(rs.bytes); + } + + return cb1_.data(); + }; + + auto consume = [&](std::size_t n) { - auto in = get_input(); - auto out = get_output(); - if( out.size() == 0 ) + if(st_ == style::buffers) { - if( output.size() == 0 ) - detail::throw_logic_error(); - break; + tmp_ = buffers::sans_prefix( + tmp_, n); + return; } + BOOST_ASSERT( + st_ == style::source || + st_ == style::stream); + cb1_.consume(n); + }; + + // handles chunked payloads automatically + appender apndr(cb0_, is_chunked_); + for(;;) + { + if(apndr.is_full()) + break; + + auto cbs = get_input(); + + if(more_input_ && buffers::size(cbs) == 0) + break; auto rs = filter_->process( - out, in, more_); + apndr.prepare(), + cbs, + more_input_); if(rs.ec.failed()) { is_done_ = true; - return rs.ec; + BOOST_HTTP_PROTO_RETURN_EC(rs.ec); } - if( rs.finished ) - filter_done_ = true; - consume(rs.in_bytes); + apndr.commit(rs.out_bytes, !rs.finished); - if( rs.out_bytes == 0 ) + if(rs.finished) + { + filter_done_ = true; break; - - num_written += rs.out_bytes; - output.commit(rs.out_bytes); + } } } - // end: - std::size_t n = 0; - if( !is_header_done_ ) - { - BOOST_ASSERT(hp_ == &prepped_[0]); - ++n; - } - else - prepped_.reset(prepped_.capacity()); + prepped_.reset(!is_header_done_); + const auto cbp = cb0_.data(); + if(cbp[0].size() != 0) + prepped_.append(cbp[0]); + if(cbp[1].size() != 0) + prepped_.append(cbp[1]); - if( !is_chunked_ ) - { - for(buffers::const_buffer const& b : output.data()) - prepped_[n++] = b; - } - else - { - if( has_avail_out ) - { - write_chunk_header( - chunk_header_, num_written); - prepped_[n++] = chunk_header_; - - for(buffers::const_buffer const& b : output.data()) - prepped_[n++] = b; - - prepped_[n++] = chunk_close_; - } - - if( (filter_ && filter_done_) || - (!filter_ && !more_) ) - prepped_[n++] = last_chunk_; - } - - auto cbs = const_buffers_type( - prepped_.data(), prepped_.size()); + BOOST_ASSERT( + buffers::size(prepped_) > 0); - BOOST_ASSERT(buffers::size(cbs) > 0); - return cbs; + return const_buffers_type( + prepped_.begin(), + prepped_.size()); } void @@ -414,85 +508,56 @@ consume( std::size_t n) { // Precondition violation - if( is_done_ && n != 0 ) + if(is_done_ && n != 0) detail::throw_logic_error(); - if( is_expect_continue_ ) - { - // Cannot consume more than - // the header on 100-continue - if( n > hp_->size() ) - detail::throw_invalid_argument(); - } - - if( !is_header_done_ ) + if(!is_header_done_) { - // consume header - if( n < hp_->size() ) + const auto header_remain = + prepped_[0].size(); + if(n < header_remain) { prepped_.consume(n); return; } - n -= hp_->size(); - prepped_.consume(hp_->size()); + n -= header_remain; + prepped_.consume(header_remain); is_header_done_ = true; } prepped_.consume(n); - if( out_ ) - { - BOOST_ASSERT(st_ != style::empty); - out_->consume(n); - } - auto is_empty = (buffers::size(prepped_) == 0); - - if( st_ == style::buffers && !filter_ && is_empty ) - more_ = false; - if( st_ == style::empty && - is_empty && - !is_expect_continue_ ) - more_ = false; + // no-op when cb0_ is not in use + cb0_.consume(n); - if( is_empty ) - is_done_ = filter_ ? filter_done_ : !more_; -} + if(!prepped_.empty()) + return; -void -serializer:: -use_deflate_encoding() -{ - // can only apply one encoding - if(filter_) - detail::throw_logic_error(); + if(needs_exp100_continue_) + return; - is_compressed_ = true; - filter_ = &ws_.emplace(ctx_, ws_, false); -} + if(more_input_) + return; -void -serializer:: -use_gzip_encoding() -{ - // can only apply one encoding - if( filter_ ) - detail::throw_logic_error(); + if(filter_ && !filter_done_) + return; - is_compressed_ = true; - filter_ = &ws_.emplace(ctx_, ws_, true); + is_done_ = true; } //------------------------------------------------ -void +detail::array_of_const_buffers serializer:: -copy( - buffers::const_buffer* dest, - buffers::const_buffer const* src, - std::size_t n) noexcept +make_array(std::size_t n) { - while(n--) - *dest++ = *src++; + if(n > std::numeric_limits::max()) + detail::throw_length_error(); + + return { + ws_.push_array(n, + buffers::const_buffer{}), + static_cast(n) }; } void @@ -500,40 +565,29 @@ serializer:: start_init( message_view_base const& m) { + reset(); + // VFALCO what do we do with // metadata error code failures? // m.ph_->md.maybe_throw(); auto const& md = m.metadata(); - - is_done_ = false; - is_header_done_ = false; - is_expect_continue_ = md.expect.is_100_continue; + needs_exp100_continue_ = md.expect.is_100_continue; // Transfer-Encoding + is_chunked_ = md.transfer_encoding.is_chunked; + + // Content-Encoding + auto const& ce = md.content_encoding; + if(ce.encoding == encoding::deflate) { - auto const& te = md.transfer_encoding; - is_chunked_ = te.is_chunked; + filter_ = &ws_.emplace< + deflator_filter>(ctx_, ws_, false); } - - if( is_chunked_) + else if(ce.encoding == encoding::gzip) { - auto* p = ws_.reserve_front(chunked_overhead_); - chunk_header_ = - buffers::mutable_buffer(p, chunk_header_len_); - chunk_close_ = - buffers::mutable_buffer( - p + chunk_header_len_, crlf_len_); - last_chunk_ = - buffers::mutable_buffer( - p + chunk_header_len_ + crlf_len_, - last_chunk_len_); - - buffers::copy( - chunk_close_, buffers::const_buffer("\r\n", 2)); - buffers::copy( - last_chunk_, - buffers::const_buffer("0\r\n\r\n", 5)); + filter_ = &ws_.emplace< + deflator_filter>(ctx_, ws_, true); } } @@ -542,12 +596,13 @@ serializer:: start_empty( message_view_base const& m) { - start_init(m); + using mutable_buffer = + buffers::mutable_buffer; + start_init(m); st_ = style::empty; - more_ = true; - if(! is_chunked_) + if(!is_chunked_) { prepped_ = make_array( 1); // header @@ -558,21 +613,16 @@ start_empty( 1 + // header 1); // final chunk - // Buffer is too small - if(ws_.size() < 5) - detail::throw_length_error(); + mutable_buffer final_chunk = { + ws_.reserve_front( + final_chunk_len), + final_chunk_len }; + write_final_chunk(final_chunk); - buffers::mutable_buffer dest( - ws_.data(), 5); - buffers::copy( - dest, - buffers::const_buffer( - "0\r\n\r\n", 5)); - prepped_[1] = dest; + prepped_[1] = final_chunk; } - hp_ = &prepped_[0]; - *hp_ = { m.ph_->cbuf, m.ph_->size }; + prepped_[0] = { m.ph_->cbuf, m.ph_->size }; } void @@ -580,131 +630,175 @@ serializer:: start_buffers( message_view_base const& m) { + using mutable_buffer = + buffers::mutable_buffer; + + // start_init() already called st_ = style::buffers; - tmp1_ = {}; - if( !filter_ && !is_chunked_ ) - { - prepped_ = make_array( - 1 + // header - buf_.size()); // user input + const auto buffers_max = (std::min)( + std::size_t{ 16 }, + buf_gen_->count()); - hp_ = &prepped_[0]; - *hp_ = { m.ph_->cbuf, m.ph_->size }; + if(!filter_) + { + if(!is_chunked_) + { + // no filter and no chunked - copy(&prepped_[1], buf_.data(), buf_.size()); + prepped_ = make_array( + 1 + // header + buffers_max ); // buffers + + prepped_[0] = { m.ph_->cbuf, m.ph_->size }; + std::generate( + prepped_.begin() + 1, + prepped_.end(), + std::ref(*buf_gen_)); + more_input_ = !buf_gen_->is_empty(); + return; + } - more_ = (buffers::size(buf_) > 0); - return; - } + // no filter and chunked - if( !filter_ && is_chunked_ ) - { - if( buffers::size(buf_) == 0 ) + if(buf_gen_->is_empty()) { prepped_ = make_array( - 1 + // header - 1); // last chunk - - hp_ = &prepped_[0]; - *hp_ = { m.ph_->cbuf, m.ph_->size }; - prepped_[1] = last_chunk_; - more_ = false; + 1 + // header + 1); // final chunk + + mutable_buffer final_chunk = { + ws_.reserve_front( + final_chunk_len), + final_chunk_len }; + write_final_chunk( + final_chunk); + + prepped_[0] = { m.ph_->cbuf, m.ph_->size }; + prepped_[1] = final_chunk; return; } + // Write entire buffers as a single chunk + // since total size is known + + mutable_buffer chunk_header = { + ws_.reserve_front( + chunk_header_len), + chunk_header_len }; + write_chunk_header( - chunk_header_, buffers::size(buf_)); + chunk_header, + buf_gen_->size()); + + mutable_buffer crlf_and_final_chunk = { + ws_.reserve_front( + crlf_len + final_chunk_len), + crlf_len + final_chunk_len }; + + write_crlf( + buffers::prefix( + crlf_and_final_chunk, + crlf_len)); + + write_final_chunk( + buffers::sans_prefix( + crlf_and_final_chunk, + crlf_len)); prepped_ = make_array( - 1 + // header - 1 + // chunk header - buf_.size() + // user input - 1 + // chunk close - 1); // last chunk - - hp_ = &prepped_[0]; - *hp_ = { m.ph_->cbuf, m.ph_->size }; - prepped_[1] = chunk_header_; - copy(&prepped_[2], buf_.data(), buf_.size()); - - prepped_[prepped_.size() - 2] = chunk_close_; - prepped_[prepped_.size() - 1] = last_chunk_; - more_ = true; + 1 + // header + 1 + // chunk header + buffers_max + // buffers + 1); // buffer or (crlf and final chunk) + + prepped_[0] = { m.ph_->cbuf, m.ph_->size }; + prepped_[1] = chunk_header; + std::generate( + prepped_.begin() + 2, + prepped_.end() - 1, + std::ref(*buf_gen_)); + + more_input_ = !buf_gen_->is_empty(); + // assigning the last slot + if(more_input_) + { + prepped_[prepped_.size() - 1] = + buf_gen_->operator()(); + + // deferred until buf_gen_ is drained + tmp_ = crlf_and_final_chunk; + } + else + { + prepped_[prepped_.size() - 1] = + crlf_and_final_chunk; + } return; } - if( is_chunked_ ) + // filter + + prepped_ = make_array( + 1 + // header + 2); // circular buffer + + const auto n = ws_.size() - 1; + cb0_ = { ws_.reserve_front(n), n }; + + if(is_chunked_) { - prepped_ = make_array( - 1 + // header - 1 + // chunk header - 2 + // tmp - 1 + // chunk close - 1); // last chunk + if(cb0_.capacity() <= chunked_overhead_) + detail::throw_length_error(); } else - prepped_ = make_array( - 1 + // header - 2); // tmp - - hp_ = &prepped_[0]; - *hp_ = { m.ph_->cbuf, m.ph_->size }; - tmp0_ = { ws_.data(), ws_.size() }; - out_ = &tmp0_; - in_ = out_; - more_ = true; + { + if(cb0_.capacity() == 0) + detail::throw_length_error(); + } + + prepped_[0] = { m.ph_->cbuf, m.ph_->size }; + more_input_ = !buf_gen_->is_empty(); } void serializer:: start_source( - message_view_base const& m, - source* src) + message_view_base const& m) { + // start_init() already called st_ = style::source; - src_ = src; - if( is_chunked_ ) + prepped_ = make_array( + 1 + // header + 2); // circular buffer + + if(filter_) { - prepped_ = make_array( - 1 + // header - 1 + // chunk header - 2 + // tmp - 1 + // chunk close - 1); // last chunk + // TODO: Optimize buffer distribution + const auto n = (ws_.size() - 1) / 2; + cb0_ = { ws_.reserve_front(n), n }; + cb1_ = { ws_.reserve_front(n), n }; } else - prepped_ = make_array( - 1 + // header - 2); // tmp + { + const auto n = ws_.size() - 1; + cb0_ = { ws_.reserve_front(n), n }; + } - if( !filter_ ) + if(is_chunked_) { - tmp0_ = { ws_.data(), ws_.size() }; - if( tmp0_.capacity() < 1 ) + if(cb0_.capacity() <= chunked_overhead_) detail::throw_length_error(); - - in_ = &tmp0_; - out_ = &tmp0_; } else { - auto n = ws_.size() / 2; - auto* p = ws_.reserve_front(n); - tmp1_ = buffers::circular_buffer(p, n); - - tmp0_ = { ws_.data(), ws_.size() }; - if( tmp0_.capacity() < 1 ) + if(cb0_.capacity() == 0) detail::throw_length_error(); - - in_ = &tmp1_; - out_ = &tmp0_; } - hp_ = &prepped_[0]; - *hp_ = { m.ph_->cbuf, m.ph_->size }; - more_ = true; + prepped_[0] = { m.ph_->cbuf, m.ph_->size }; + more_input_ = true; } auto @@ -714,49 +808,39 @@ start_stream( stream { start_init(m); - st_ = style::stream; - if( is_chunked_ ) + + prepped_ = make_array( + 1 + // header + 2); // circular buffer + + if(filter_) { - prepped_ = make_array( - 1 + // header - 1 + // chunk header - 2 + // tmp - 1 + // chunk close - 1); // last chunk + // TODO: Optimize buffer distribution + const auto n = (ws_.size() - 1) / 2; + cb0_ = { ws_.reserve_front(n), n }; + cb1_ = { ws_.reserve_front(n), n }; } else - prepped_ = make_array( - 1 + // header - 2); // tmp + { + const auto n = ws_.size() - 1; + cb0_ = { ws_.reserve_front(n), n }; + } - if( !filter_ ) + if(is_chunked_) { - tmp0_ = { ws_.data(), ws_.size() }; - if( tmp0_.capacity() < 1 ) + if(cb0_.capacity() <= chunked_overhead_) detail::throw_length_error(); - - in_ = &tmp0_; - out_ = &tmp0_; } else { - auto n = ws_.size() / 2; - auto* p = ws_.reserve_front(n); - tmp1_ = buffers::circular_buffer(p, n); - - tmp0_ = { ws_.data(), ws_.size() }; - if( tmp0_.capacity() < 1 ) + if(cb0_.capacity() == 0) detail::throw_length_error(); - - in_ = &tmp1_; - out_ = &tmp0_; } - hp_ = &prepped_[0]; - *hp_ = { m.ph_->cbuf, m.ph_->size }; - more_ = true; - return stream{*this}; + prepped_[0] = { m.ph_->cbuf, m.ph_->size }; + more_input_ = true; + return stream{ *this }; } //------------------------------------------------ @@ -766,15 +850,18 @@ serializer:: stream:: capacity() const noexcept { - return sr_->in_->capacity(); -} + if(sr_->filter_) + return sr_->cb1_.capacity(); -std::size_t -serializer:: -stream:: -size() const noexcept -{ - return sr_->in_->size(); + if(!sr_->is_chunked_) + return sr_->cb0_.capacity(); + + // chunked with no filter + const auto cap = sr_->cb0_.capacity(); + if(cap > chunked_overhead_) + return cap - chunked_overhead_; + + return 0; } bool @@ -791,7 +878,23 @@ stream:: prepare() const -> buffers_type { - return sr_->in_->prepare(sr_->in_->capacity()); + if(sr_->filter_) + return sr_->cb1_.prepare( + sr_->cb1_.capacity()); + + if(!sr_->is_chunked_) + return sr_->cb0_.prepare( + sr_->cb0_.capacity()); + + // chunked with no filter + const auto cap = sr_->cb0_.capacity(); + if(cap <= chunked_overhead_) + detail::throw_length_error(); + + return buffers::sans_prefix( + sr_->cb0_.prepare( + cap - crlf_len - final_chunk_len), + chunk_header_len); } void @@ -799,12 +902,29 @@ serializer:: stream:: commit(std::size_t n) const { - // the stream must make a non-zero amount of bytes - // available to the serializer - if( n == 0 ) - detail::throw_logic_error(); + if(sr_->filter_) + return sr_->cb1_.commit(n); + + if(!sr_->is_chunked_) + return sr_->cb0_.commit(n); - sr_->in_->commit(n); + // chunked with no filter + if(n != 0) + { + write_chunk_header( + sr_->cb0_.prepare( + chunk_header_len), + n); + sr_->cb0_.commit( + chunk_header_len); + + sr_->cb0_.prepare(n); + sr_->cb0_.commit(n); + + write_crlf( + sr_->cb0_.prepare(crlf_len)); + sr_->cb0_.commit(crlf_len); + } } void @@ -813,9 +933,22 @@ stream:: close() const { // Precondition violation - if(! sr_->more_ ) + if(!sr_->more_input_) detail::throw_logic_error(); - sr_->more_ = false; + + sr_->more_input_ = false; + + if(sr_->filter_) + return; + + if(!sr_->is_chunked_) + return; + + // chunked with no filter + write_final_chunk( + sr_->cb0_.prepare( + final_chunk_len)); + sr_->cb0_.commit(final_chunk_len); } //------------------------------------------------ diff --git a/test/unit/serializer.cpp b/test/unit/serializer.cpp index f23bc272..e2098df7 100644 --- a/test/unit/serializer.cpp +++ b/test/unit/serializer.cpp @@ -257,20 +257,45 @@ struct serializer_test //-------------------------------------------- void - check( + check_buffers( core::string_view headers, core::string_view body, - core::string_view expected) + core::string_view expected_header, + core::string_view expected_body) { response res(headers); - std::string sb = body; + std::array< + buffers::const_buffer, 23> buf; + + const auto buf_size = + (body.size() / buf.size()) + 1; + for(auto& cb : buf) + { + if(body.size() < buf_size) + { + cb = { body.data(), body.size() }; + body.remove_prefix(body.size()); + break; + } + cb = { body.data(), buf_size }; + body.remove_prefix(buf_size); + } context ctx; serializer sr(ctx); - sr.start(res, - string_body(std::move(sb))); + buffers::const_buffer_span cbs( + buf.data(), buf.size()); + sr.start(res, cbs); std::string s = read(sr); - BOOST_TEST_EQ(s, expected); + core::string_view sv(s); + + BOOST_TEST( + sv.substr(0, expected_header.size()) + == expected_header); + + BOOST_TEST( + sv.substr(expected_header.size()) + == expected_body); }; template @@ -311,17 +336,9 @@ struct serializer_test context ctx; serializer sr(ctx, sr_capacity); auto stream = sr.start_stream(res); - BOOST_TEST_EQ(stream.size(), 0); BOOST_TEST_GT(stream.capacity(), 0); BOOST_TEST_LE(stream.capacity(), sr_capacity); - auto const N = stream.size() + stream.capacity(); - auto check_N = [&] - { - BOOST_TEST_EQ( - stream.capacity() + stream.size(), N); - }; - std::vector s; // stores complete output auto prepare_chunk = [&] @@ -345,13 +362,6 @@ struct serializer_test BOOST_TEST(!stream.is_full()); body.remove_prefix(bs); - // if(! res.chunked() ) - // BOOST_TEST_EQ(stream.size(), bs); - // else - // // chunk overhead: header + \r\n - // BOOST_TEST_EQ(stream.size(), bs + 18 + 2); - - check_N(); }; auto consume_body_buffer = [&]( @@ -403,8 +413,6 @@ struct serializer_test for(auto pos = cbs.begin(); pos != end; ++pos) consume_body_buffer(*pos); - // BOOST_TEST_EQ(stream.size(), 0); - check_N(); } BOOST_TEST_THROWS(stream.close(), std::logic_error); @@ -416,7 +424,7 @@ struct serializer_test testOutput() { // buffers (0 size) - check( + check_buffers( "HTTP/1.1 200 OK\r\n" "Server: test\r\n" "Content-Length: 0\r\n" @@ -426,39 +434,38 @@ struct serializer_test "HTTP/1.1 200 OK\r\n" "Server: test\r\n" "Content-Length: 0\r\n" - "\r\n"); + "\r\n", + ""); // buffers - check( + check_buffers( "HTTP/1.1 200 OK\r\n" "Server: test\r\n" - "Content-Length: 5\r\n" + "Content-Length: 2048\r\n" "\r\n", - "12345", + std::string(2048, '*'), //-------------------------- "HTTP/1.1 200 OK\r\n" "Server: test\r\n" - "Content-Length: 5\r\n" - "\r\n" - "12345"); + "Content-Length: 2048\r\n" + "\r\n", + std::string(2048, '*')); // buffers chunked - check( + check_buffers( "HTTP/1.1 200 OK\r\n" "Server: test\r\n" "Transfer-Encoding: chunked\r\n" "\r\n", - "12345", + std::string(2048, '*'), //-------------------------- "HTTP/1.1 200 OK\r\n" "Server: test\r\n" "Transfer-Encoding: chunked\r\n" - "\r\n" - "0000000000000005\r\n" - "12345" - "\r\n" - "0\r\n" - "\r\n"); + "\r\n", + std::string("0000000000000800\r\n") + + std::string(2048, '*') + + std::string("\r\n0\r\n\r\n")); // source check_src( @@ -663,9 +670,6 @@ struct serializer_test std::string s; system::result< serializer::const_buffers_type> rv; - BOOST_TEST_THROWS( - sr.consume(req.buffer().size() + 1), - std::invalid_argument); for(;;) { rv = sr.prepare(); @@ -766,52 +770,44 @@ struct serializer_test "HTTP/1.1 200 OK\r\n" "Transfer-Encoding: chunked\r\n" "\r\n"; - - core::string_view serialized = - "HTTP/1.1 200 OK\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "0\r\n\r\n"; - response res(sv); - context ctx; serializer sr(ctx); auto stream = sr.start_stream(res); + auto mbs = stream.prepare(); BOOST_TEST_GT( buffers::size(mbs), 0); BOOST_TEST(!stream.is_full()); - BOOST_TEST_THROWS( - stream.commit(0), std::logic_error); - auto mcbs = sr.prepare(); - BOOST_TEST(mcbs.has_error()); - BOOST_TEST(mcbs.error() == error::need_data); - - stream.close(); + // commit 0 bytes must be possible + stream.commit(0); - mcbs = sr.prepare(); + auto mcbs = sr.prepare(); auto cbs = mcbs.value(); BOOST_TEST_EQ( buffers::size(cbs), - serialized.size()); + sv.size()); + sr.consume(sv.size()); - std::vector s( - buffers::size(cbs), 'a'); - buffers::copy( - buffers::make_buffer( - s.data(), s.size()), - cbs); + mcbs = sr.prepare(); + BOOST_TEST(mcbs.has_error()); + BOOST_TEST( + mcbs.error() == error::need_data); - core::string_view octets(s.data(),s.size()); + stream.close(); + + mcbs = sr.prepare(); + std::string body; + append(body, *mcbs); BOOST_TEST_EQ( - octets, serialized); + "0\r\n\r\n", + body); + sr.consume(5); - sr.consume(s.size()); BOOST_TEST(sr.is_done()); BOOST_TEST_THROWS( - sr.consume(s.size()), + sr.consume(1), std::logic_error); } } diff --git a/test/unit/zlib.cpp b/test/unit/zlib.cpp index 3262d635..5e9baa72 100644 --- a/test/unit/zlib.cpp +++ b/test/unit/zlib.cpp @@ -404,23 +404,16 @@ struct zlib_test sr.reset(); response res; - res.set("Transfer-Encoding", c); + res.set("Content-Encoding", c); res.set_chunked(chunked_encoding); std::string header; { header += "HTTP/1.1 200 OK\r\n"; if( c == "deflate" ) - { - header += "Transfer-Encoding: deflate\r\n"; - sr.use_deflate_encoding(); - } + header += "Content-Encoding: deflate\r\n"; if( c == "gzip" ) - { - header += "Transfer-Encoding: gzip\r\n"; - sr.use_gzip_encoding(); - } - + header += "Content-Encoding: gzip\r\n"; if( chunked_encoding ) header += "Transfer-Encoding: chunked\r\n"; @@ -763,9 +756,9 @@ struct zlib_test response res{ "HTTP/1.1 200 OK\r\n" + "Content-Encoding: gzip\r\n" "\r\n" }; std::string buf(1024, '*'); - sr.use_gzip_encoding(); sr.start(res, buffers::const_buffer{ buf.data(), buf.size() }); auto rs = sr.prepare();