From 8a40df3f1a1dfd5e09ae995607e01cdf3439ff59 Mon Sep 17 00:00:00 2001 From: Mungo Gill Date: Mon, 22 Dec 2025 13:18:30 +0000 Subject: [PATCH 1/2] feat: separate set_headers from start functions in serializer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add new API to set message headers separately from starting serialization. This allows setting headers once and then choosing the body style (empty, buffers, source, or stream) in a second step. New public methods: - set_headers(message_base const&) - start() - empty body - start(ConstBufferSequence&&) - buffer sequence body - start(Args&&...) - source body - start_stream() - stream body The original start functions that take message_base remain for backward compatibility and are now implemented in terms of the new set_headers + start pattern. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- include/boost/http_proto/impl/serializer.hpp | 71 ++++- include/boost/http_proto/serializer.hpp | 286 ++++++++++++++++++- src/serializer.cpp | 137 +++++++-- 3 files changed, 458 insertions(+), 36 deletions(-) diff --git a/include/boost/http_proto/impl/serializer.hpp b/include/boost/http_proto/impl/serializer.hpp index 1613dc6b..ee255415 100644 --- a/include/boost/http_proto/impl/serializer.hpp +++ b/include/boost/http_proto/impl/serializer.hpp @@ -124,9 +124,26 @@ start( ConstBufferSequence>::value, "ConstBufferSequence type requirements not met"); - start_init(m); - start_buffers( - m, + set_headers(m); + start_buffers_impl( + ws().emplace::type>>( + std::forward(cbs))); +} + +template< + class ConstBufferSequence, + class> +void +serializer:: +start( + ConstBufferSequence&& cbs) +{ + static_assert(buffers::is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence type requirements not met"); + + start_buffers_impl( ws().emplace::type>>( std::forward(cbs))); @@ -149,10 +166,54 @@ start( std::is_constructible::value, "The Source cannot be constructed with the given arguments"); - start_init(m); + set_headers(m); auto& source = ws().emplace( std::forward(args)...); - start_source(m, source); + start_source_impl(source); + return source; +} + +template< + class Source, + class Arg0, + class... Args, + class> +Source& +serializer:: +start( + Arg0&& arg0, + Args&&... args) +{ + static_assert( + !std::is_abstract::value, ""); + static_assert( + std::is_constructible::value || + std::is_constructible::value, + "The Source cannot be constructed with the given arguments"); + + auto& source = ws().emplace( + std::forward(arg0), + std::forward(args)...); + start_source_impl(source); + return source; +} + +template< + class Source, + class> +Source& +serializer:: +start() +{ + static_assert( + !std::is_abstract::value, ""); + static_assert( + std::is_default_constructible::value || + std::is_constructible::value, + "The Source cannot be default-constructed"); + + auto& source = ws().emplace(); + start_source_impl(source); return source; } diff --git a/include/boost/http_proto/serializer.hpp b/include/boost/http_proto/serializer.hpp index e0e65d08..5a4f71a0 100644 --- a/include/boost/http_proto/serializer.hpp +++ b/include/boost/http_proto/serializer.hpp @@ -534,6 +534,279 @@ class serializer bool is_done() const noexcept; + /** Set the message headers for serialization. + + This function prepares the serializer with the + HTTP start-line and headers from `m`. After + calling this function, one of the @ref start + or @ref start_stream overloads that do not + take a message parameter must be called to + begin serialization. + + Changing the contents of the message after + calling this function and before @ref is_done + returns `true` results in undefined behavior. + + @par Preconditions + @code + this->is_done() == true + @endcode + + @par Exception Safety + Strong guarantee. + + @throw std::logic_error `this->is_done() == false`. + + @param m The request or response headers to serialize. + + @see + @ref message_base. + */ + BOOST_HTTP_PROTO_DECL + void + set_headers(message_base const& m); + + /** Start serializing a message with an empty body. + + This overload requires @ref set_headers to have + been called first. + + @par Preconditions + @ref set_headers has been called and no @ref start + or @ref start_stream function has been called since. + + @par Postconditions + @code + this->is_done() == false + @endcode + + @par Exception Safety + Strong guarantee. + + @throw std::logic_error if @ref set_headers was not called. + + @throw std::length_error if there is insufficient internal buffer + space to start the operation. + + @see + @ref set_headers. + */ + BOOST_HTTP_PROTO_DECL + void + start(); + + /** Start serializing a message with a buffer sequence body. + + This overload requires @ref set_headers to have + been called first. + + At least one copy of the specified buffer sequence is maintained until + the serializer is done, gets reset, or is destroyed, after which all + of its copies are destroyed. Ownership of the underlying memory is not + transferred; the caller must ensure the memory remains valid until the + serializer's copies are destroyed. + + @par Preconditions + @ref set_headers has been called and no @ref start + or @ref start_stream function has been called since. + + @par Postconditions + @code + this->is_done() == false + @endcode + + @par Constraints + @code + buffers::is_const_buffer_sequence_v == true + @endcode + + @par Exception Safety + Strong guarantee. + + @throw std::logic_error if @ref set_headers was not called. + + @throw std::length_error If there is insufficient internal buffer + space to start the operation. + + @param buffers A buffer sequence containing the message body. + + @see + @ref set_headers. + */ + template< + class ConstBufferSequence, + class = typename std::enable_if< + buffers::is_const_buffer_sequence< + ConstBufferSequence>::value>::type + > + void + start( + ConstBufferSequence&& buffers); + + /** Start serializing a message with a @em Source body. + + This overload requires @ref set_headers to have + been called first. + + The serializer destroys the Source object when: + @li `this->is_done() == true` + @li An unrecoverable serialization error occurs + @li The serializer is destroyed + + @par Example + @code + file f("example.zip", file_mode::scan); + response.set_payload_size(f.size()); + serializer.set_headers(response); + serializer.start(std::move(f)); + @endcode + + @par Preconditions + @ref set_headers has been called and no @ref start + or @ref start_stream function has been called since. + + @par Postconditions + @code + this->is_done() == false + @endcode + + @par Constraints + @code + is_source::value == true + @endcode + + @par Exception Safety + Strong guarantee. + + @throw std::logic_error if @ref set_headers was not called. + + @throw std::length_error if there is insufficient + internal buffer space to start the operation. + + @param args Arguments to be passed to the + `Source` constructor. + + @return A reference to the constructed Source object. + + @see + @ref set_headers, + @ref source, + @ref file_source. + */ + template< + class Source, + class Arg0, + class... Args, + class = typename std::enable_if< + is_source::value && + !std::is_convertible< + Arg0, + message_base const&>::value>::type> + Source& + start(Arg0&& arg0, Args&&... args); + + /** Start serializing a message with a @em Source body. + + This overload requires @ref set_headers to have + been called first. This version takes no arguments + and default-constructs the Source. + + @par Preconditions + @ref set_headers has been called and no @ref start + or @ref start_stream function has been called since. + + @par Postconditions + @code + this->is_done() == false + @endcode + + @par Constraints + @code + is_source::value == true + @endcode + + @par Exception Safety + Strong guarantee. + + @throw std::logic_error if @ref set_headers was not called. + + @throw std::length_error if there is insufficient + internal buffer space to start the operation. + + @return A reference to the constructed Source object. + + @see + @ref set_headers, + @ref source. + */ + template< + class Source, + class = typename std::enable_if< + is_source::value>::type> + Source& + start(); + + /** Start serializing a message using a stream interface. + + This overload requires @ref set_headers to have + been called first. + + Returns a @ref stream object for writing the body + data into the serializer's internal buffer. + + Once the serializer is destroyed, @ref reset + is called, or @ref is_done returns true, the + only valid operation on the stream is destruction. + + @par Example + @code + serializer.set_headers(response); + serializer::stream st = serializer.start_stream(); + do + { + if(st.is_open()) + { + std::size_t n = source.read_some(st.prepare()); + + if(ec == error::eof) + st.close(); + else + st.commit(n); + } + + write_some(client, serializer); + + } while(!serializer.is_done()); + @endcode + + @par Preconditions + @ref set_headers has been called and no @ref start + or @ref start_stream function has been called since. + + @par Postconditions + @code + this->is_done() == false + @endcode + + @par Exception Safety + Strong guarantee. + + @throw std::logic_error if @ref set_headers was not called. + + @throw std::length_error if there is insufficient + internal buffer space to start the operation. + + @return A @ref stream object for writing the body + data into the serializer's internal buffer. + + @see + @ref set_headers, + @ref stream. + */ + BOOST_HTTP_PROTO_DECL + stream + start_stream(); + private: class impl; class cbs_gen; @@ -546,21 +819,22 @@ class serializer BOOST_HTTP_PROTO_DECL void - start_init( - message_base const&); + start_empty_impl(); BOOST_HTTP_PROTO_DECL void - start_buffers( - message_base const&, + start_buffers_impl( cbs_gen&); BOOST_HTTP_PROTO_DECL void - start_source( - message_base const&, + start_source_impl( source&); + BOOST_HTTP_PROTO_DECL + stream + start_stream_impl(); + impl* impl_ = nullptr; }; diff --git a/src/serializer.cpp b/src/serializer.cpp index 0e1450e9..1389ad87 100644 --- a/src/serializer.cpp +++ b/src/serializer.cpp @@ -283,6 +283,7 @@ class serializer::impl { reset, start, + headers_set, header, body }; @@ -316,6 +317,10 @@ class serializer::impl bool needs_exp100_continue_ = false; bool filter_done_ = false; + // Stored header buffer from set_headers + char const* header_cbuf_ = nullptr; + std::size_t header_size_ = 0; + public: impl(const capy::polystore& ctx) : ctx_(ctx) @@ -630,7 +635,7 @@ class serializer::impl } void - start_init( + set_headers( message_base const& m) { // Precondition violation @@ -640,7 +645,7 @@ class serializer::impl // TODO: To uphold the strong exception guarantee, // `state_` must be reset to `state::start` if an // exception is thrown during the start operation. - state_ = state::header; + state_ = state::headers_set; // VFALCO what do we do with // metadata error code failures? @@ -695,13 +700,55 @@ class serializer::impl filter_ = nullptr; break; } + + // Store header buffer for later use + header_cbuf_ = m.h_.cbuf; + header_size_ = m.h_.size; } void start_empty( message_base const& m) { - start_init(m); + set_headers(m); + start_empty_impl(); + } + + void + start_buffers( + message_base const& m, + cbs_gen& cbs_gen) + { + set_headers(m); + start_buffers_impl(cbs_gen); + } + + void + start_source( + message_base const& m, + source& source) + { + set_headers(m); + start_source_impl(source); + } + + stream + start_stream(message_base const& m) + { + set_headers(m); + return start_stream_impl(); + } + + // New methods that use stored header from set_headers() + + void + start_empty_impl() + { + // Precondition violation + if(state_ != state::headers_set) + detail::throw_logic_error(); + + state_ = state::header; style_ = style::empty; prepped_ = make_array( @@ -713,16 +760,19 @@ class serializer::impl if(!filter_) out_finish(); - prepped_.append({ m.h_.cbuf, m.h_.size }); + prepped_.append({ header_cbuf_, header_size_ }); more_input_ = false; } void - start_buffers( - message_base const& m, + start_buffers_impl( cbs_gen& cbs_gen) { - // start_init() already called + // Precondition violation + if(state_ != state::headers_set) + detail::throw_logic_error(); + + state_ = state::header; style_ = style::buffers; cbs_gen_ = &cbs_gen; @@ -736,7 +786,7 @@ class serializer::impl batch_size + // buffers (is_chunked_ ? 2 : 0)); // chunk header + final chunk - prepped_.append({ m.h_.cbuf, m.h_.size }); + prepped_.append({ header_cbuf_, header_size_ }); more_input_ = (batch_size != 0); if(is_chunked_) @@ -750,7 +800,7 @@ class serializer::impl auto h_len = chunk_header_len(stats.size); buffers::mutable_buffer mb( ws_.reserve_front(h_len), h_len); - write_chunk_header({{ {mb}, {} }}, stats.size); + write_chunk_header({{ {mb}, {} }}, stats.size); prepped_.append(mb); } } @@ -765,17 +815,20 @@ class serializer::impl out_init(); - prepped_.append({ m.h_.cbuf, m.h_.size }); + prepped_.append({ header_cbuf_, header_size_ }); tmp_ = {}; more_input_ = true; } void - start_source( - message_base const& m, + start_source_impl( source& source) { - // start_init() already called + // Precondition violation + if(state_ != state::headers_set) + detail::throw_logic_error(); + + state_ = state::header; style_ = style::source; source_ = &source; @@ -792,14 +845,18 @@ class serializer::impl out_init(); - prepped_.append({ m.h_.cbuf, m.h_.size }); + prepped_.append({ header_cbuf_, header_size_ }); more_input_ = true; } stream - start_stream(message_base const& m) + start_stream_impl() { - start_init(m); + // Precondition violation + if(state_ != state::headers_set) + detail::throw_logic_error(); + + state_ = state::header; style_ = style::stream; prepped_ = make_array( @@ -815,7 +872,7 @@ class serializer::impl out_init(); - prepped_.append({ m.h_.cbuf, m.h_.size }); + prepped_.append({ header_cbuf_, header_size_ }); more_input_ = true; return stream{ this }; } @@ -978,6 +1035,22 @@ start(message_base const& m) impl_->start_empty(m); } +void +serializer:: +set_headers(message_base const& m) +{ + BOOST_ASSERT(impl_); + impl_->set_headers(m); +} + +void +serializer:: +start() +{ + BOOST_ASSERT(impl_); + impl_->start_empty_impl(); +} + auto serializer:: start_stream( @@ -987,6 +1060,14 @@ start_stream( return impl_->start_stream(m); } +auto +serializer:: +start_stream() -> stream +{ + BOOST_ASSERT(impl_); + return impl_->start_stream_impl(); +} + auto serializer:: prepare() -> @@ -1024,30 +1105,36 @@ ws() void serializer:: -start_init(message_base const& m) +start_empty_impl() { BOOST_ASSERT(impl_); - impl_->start_init(m); + impl_->start_empty_impl(); } void serializer:: -start_buffers( - message_base const& m, +start_buffers_impl( cbs_gen& cbs_gen) { BOOST_ASSERT(impl_); - impl_->start_buffers(m, cbs_gen); + impl_->start_buffers_impl(cbs_gen); } void serializer:: -start_source( - message_base const& m, +start_source_impl( source& source) { BOOST_ASSERT(impl_); - impl_->start_source(m, source); + impl_->start_source_impl(source); +} + +auto +serializer:: +start_stream_impl() -> stream +{ + BOOST_ASSERT(impl_); + return impl_->start_stream_impl(); } //------------------------------------------------ From e26027aaf8d1b2476206f490d25be739f5126f5b Mon Sep 17 00:00:00 2001 From: Mungo Gill Date: Mon, 22 Dec 2025 14:36:25 +0000 Subject: [PATCH 2/2] rename enum --- include/boost/http_proto/serializer.hpp | 24 +++++++++----- src/serializer.cpp | 42 ++++++++++++------------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/include/boost/http_proto/serializer.hpp b/include/boost/http_proto/serializer.hpp index 5a4f71a0..42fc0e55 100644 --- a/include/boost/http_proto/serializer.hpp +++ b/include/boost/http_proto/serializer.hpp @@ -534,6 +534,22 @@ class serializer bool is_done() const noexcept; + /** Return true if headers have been set. + + Returns `true` if @ref set_headers has been + called and no @ref start or @ref start_stream + function has been called since. This indicates + that the serializer is ready to begin + serialization with a body style. + + @see + @ref set_headers, + @ref is_done. + */ + BOOST_HTTP_PROTO_DECL + bool + is_headers_set() const noexcept; + /** Set the message headers for serialization. This function prepares the serializer with the @@ -817,10 +833,6 @@ class serializer detail::workspace& ws(); - BOOST_HTTP_PROTO_DECL - void - start_empty_impl(); - BOOST_HTTP_PROTO_DECL void start_buffers_impl( @@ -831,10 +843,6 @@ class serializer start_source_impl( source&); - BOOST_HTTP_PROTO_DECL - stream - start_stream_impl(); - impl* impl_ = nullptr; }; diff --git a/src/serializer.cpp b/src/serializer.cpp index 1389ad87..55b5b2fd 100644 --- a/src/serializer.cpp +++ b/src/serializer.cpp @@ -282,7 +282,7 @@ class serializer::impl enum class state { reset, - start, + initialized, headers_set, header, body @@ -309,7 +309,7 @@ class serializer::impl detail::array_of_const_buffers prepped_; buffers::const_buffer tmp_; - state state_ = state::start; + state state_ = state::initialized; style style_ = style::empty; uint8_t chunk_header_len_ = 0; bool more_input_ = false; @@ -333,7 +333,7 @@ class serializer::impl reset() noexcept { ws_.clear(); - state_ = state::start; + state_ = state::initialized; } auto @@ -639,11 +639,11 @@ class serializer::impl message_base const& m) { // Precondition violation - if(state_ != state::start) + if(state_ != state::initialized) detail::throw_logic_error(); // TODO: To uphold the strong exception guarantee, - // `state_` must be reset to `state::start` if an + // `state_` must be reset to `state::initialized` if an // exception is thrown during the start operation. state_ = state::headers_set; @@ -880,7 +880,13 @@ class serializer::impl bool is_done() const noexcept { - return state_ == state::start; + return state_ == state::initialized; + } + + bool + is_headers_set() const noexcept + { + return state_ == state::headers_set; } detail::workspace& @@ -1093,22 +1099,22 @@ is_done() const noexcept return impl_->is_done(); } -//------------------------------------------------ - -detail::workspace& +bool serializer:: -ws() +is_headers_set() const noexcept { BOOST_ASSERT(impl_); - return impl_->ws(); + return impl_->is_headers_set(); } -void +//------------------------------------------------ + +detail::workspace& serializer:: -start_empty_impl() +ws() { BOOST_ASSERT(impl_); - impl_->start_empty_impl(); + return impl_->ws(); } void @@ -1129,14 +1135,6 @@ start_source_impl( impl_->start_source_impl(source); } -auto -serializer:: -start_stream_impl() -> stream -{ - BOOST_ASSERT(impl_); - return impl_->start_stream_impl(); -} - //------------------------------------------------ std::size_t