From 060f4a007b51c55603d87197aa66acd2bc47522b Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 28 May 2017 17:04:39 -0700 Subject: [PATCH] Refactor serialization algorithms: serializer interface is changed to be buffer-only, no streams, and placed in its own header file. Operations on serializers are moved to free functions as part of the HTTP write family of synchronous and asynchronous algorithms. --- doc/quickref.xml | 2 + include/beast/http.hpp | 1 + include/beast/http/impl/serializer.ipp | 407 ++++++++++ include/beast/http/impl/write.ipp | 988 +++++-------------------- include/beast/http/serializer.hpp | 350 +++++++++ include/beast/http/write.hpp | 452 ++--------- test/Jamfile | 1 + test/http/CMakeLists.txt | 1 + test/http/design.cpp | 18 +- test/http/serializer.cpp | 9 + test/http/write.cpp | 52 +- 11 files changed, 1064 insertions(+), 1217 deletions(-) create mode 100644 include/beast/http/impl/serializer.ipp create mode 100644 include/beast/http/serializer.hpp create mode 100644 test/http/serializer.cpp diff --git a/doc/quickref.xml b/doc/quickref.xml index 1949e3d1..55b89655 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -61,6 +61,7 @@ async_read async_read_some async_write + async_write_some is_keep_alive is_upgrade make_serializer @@ -71,6 +72,7 @@ reason_string swap write + write_some Type Traits diff --git a/include/beast/http.hpp b/include/beast/http.hpp index 5d67a23b..ce3044e8 100644 --- a/include/beast/http.hpp +++ b/include/beast/http.hpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include diff --git a/include/beast/http/impl/serializer.ipp b/include/beast/http/impl/serializer.ipp new file mode 100644 index 00000000..7b3600f4 --- /dev/null +++ b/include/beast/http/impl/serializer.ipp @@ -0,0 +1,407 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot 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) +// + +#ifndef BEAST_HTTP_IMPL_SERIALIZER_IPP +#define BEAST_HTTP_IMPL_SERIALIZER_IPP + +#include +#include +#include + +namespace beast { +namespace http { +namespace detail { + +template +void +write_start_line(std::ostream& os, + header const& msg) +{ + BOOST_ASSERT(msg.version == 10 || msg.version == 11); + os << msg.method() << " " << msg.target(); + switch(msg.version) + { + case 10: os << " HTTP/1.0\r\n"; break; + case 11: os << " HTTP/1.1\r\n"; break; + } +} + +template +void +write_start_line(std::ostream& os, + header const& msg) +{ + BOOST_ASSERT(msg.version == 10 || msg.version == 11); + switch(msg.version) + { + case 10: os << "HTTP/1.0 "; break; + case 11: os << "HTTP/1.1 "; break; + } + os << msg.status << " " << msg.reason() << "\r\n"; +} + +template +void +write_fields(std::ostream& os, + FieldSequence const& fields) +{ + //static_assert(is_FieldSequence::value, + // "FieldSequence requirements not met"); + for(auto const& field : fields) + { + auto const name = field.name(); + BOOST_ASSERT(! name.empty()); + if(name[0] == ':') + continue; + os << field.name() << ": " << field.value() << "\r\n"; + } +} + +} // detail + +//------------------------------------------------------------------------------ + +template +serializer:: +serializer(message const& m, + Decorator const& d, Allocator const& alloc) + : m_(m) + , d_(d) + , b_(1024, alloc) + , chunked_(token_list{ + m.fields["Transfer-Encoding"]}.exists("chunked")) + , close_(token_list{ + m.fields["Connection"]}.exists("close") || + (m.version < 11 && ! m.fields.exists( + "Content-Length"))) +{ + s_ = chunked_ ? do_init_c : do_init; + // VFALCO Move this stuff to the switch? + auto os = ostream(b_); + detail::write_start_line(os, m_); + detail::write_fields(os, m_.fields); + os << "\r\n"; +} + +template +template +void +serializer:: +get(error_code& ec, Visit&& visit) +{ + using boost::asio::buffer_size; + switch(s_) + { + case do_init: + { + if(split_) + goto go_header_only; + rd_.emplace(m_); + rd_->init(ec); + if(ec) + return; + auto result = rd_->get(ec); + if(ec) + { + // Can't use need_more when ! is_deferred + BOOST_ASSERT(ec != error::need_more); + return; + } + if(! result) + goto go_header_only; + more_ = result->second; + v_ = cb0_t{ + boost::in_place_init, + b_.data(), + result->first}; + s_ = do_header; + // [[fallthrough]] + } + + case do_header: + visit(ec, boost::get(v_)); + break; + + go_header_only: + s_ = do_header_only; + case do_header_only: + visit(ec, b_.data()); + break; + + case do_body: + BOOST_ASSERT(! rd_); + rd_.emplace(m_); + rd_->init(ec); + if(ec) + return; + s_ = do_body + 1; + // [[fallthrough]] + + case do_body + 1: + { + auto result = rd_->get(ec); + if(ec) + return; + if(! result) + goto go_complete; + more_ = result->second; + v_ = cb1_t{result->first}; + s_ = do_body + 2; + // [[fallthrough]] + } + + case do_body + 2: + visit(ec, boost::get(v_)); + break; + + //---------------------------------------------------------------------- + + case do_init_c: + { + if(split_) + goto go_header_only_c; + rd_.emplace(m_); + rd_->init(ec); + if(ec) + return; + auto result = rd_->get(ec); + if(ec) + { + // Can't use need_more when ! is_deferred + BOOST_ASSERT(ec != error::need_more); + return; + } + if(! result) + goto go_header_only_c; + more_ = result->second; + v_ = ch0_t{ + boost::in_place_init, + b_.data(), + detail::chunk_header{ + buffer_size(result->first)}, + [&]() + { + auto sv = d_(result->first); + return boost::asio::const_buffers_1{ + sv.data(), sv.size()}; + + }(), + result->first, + detail::chunk_crlf()}; + s_ = do_header_c; + // [[fallthrough]] + } + + case do_header_c: + visit(ec, boost::get(v_)); + break; + + go_header_only_c: + s_ = do_header_only_c; + case do_header_only_c: + visit(ec, b_.data()); + break; + + case do_body_c: + BOOST_ASSERT(! rd_); + rd_.emplace(m_); + rd_->init(ec); + if(ec) + return; + s_ = do_body_c + 1; + // [[fallthrough]] + + case do_body_c + 1: + { + auto result = rd_->get(ec); + if(ec) + return; + if(! result) + goto go_final_c; + more_ = result->second; + v_ = ch1_t{ + boost::in_place_init, + detail::chunk_header{ + buffer_size(result->first)}, + [&]() + { + auto sv = d_(result->first); + return boost::asio::const_buffers_1{ + sv.data(), sv.size()}; + + }(), + result->first, + detail::chunk_crlf()}; + s_ = do_body_c + 2; + // [[fallthrough]] + } + + case do_body_c + 2: + visit(ec, boost::get(v_)); + break; + + go_final_c: + case do_final_c: + v_ = ch2_t{ + boost::in_place_init, + detail::chunk_final(), + [&]() + { + auto sv = d_( + boost::asio::null_buffers{}); + return boost::asio::const_buffers_1{ + sv.data(), sv.size()}; + + }(), + detail::chunk_crlf()}; + s_ = do_final_c + 1; + // [[fallthrough]] + + case do_final_c + 1: + visit(ec, boost::get(v_)); + break; + + //---------------------------------------------------------------------- + + default: + case do_complete: + BOOST_ASSERT(false); + break; + + go_complete: + s_ = do_complete; + break; + } +} + +template +void +serializer:: +consume(std::size_t n) +{ + using boost::asio::buffer_size; + switch(s_) + { + case do_header: + BOOST_ASSERT(n <= buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + header_done_ = true; + v_ = boost::blank{}; + b_.consume(b_.size()); // VFALCO delete b_? + if(! more_) + goto go_complete; + s_ = do_body + 1; + break; + + case do_header_only: + BOOST_ASSERT(n <= b_.size()); + b_.consume(n); + if(buffer_size(b_.data()) > 0) + break; + // VFALCO delete b_? + header_done_ = true; + if(! is_deferred::value) + goto go_complete; + s_ = do_body; + break; + + case do_body + 2: + { + BOOST_ASSERT(n <= buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + v_ = boost::blank{}; + if(! more_) + goto go_complete; + s_ = do_body + 1; + break; + } + + //---------------------------------------------------------------------- + + case do_header_c: + BOOST_ASSERT(n <= buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + header_done_ = true; + v_ = boost::blank{}; + b_.consume(b_.size()); // VFALCO delete b_? + if(more_) + s_ = do_body_c + 1; + else + s_ = do_final_c; + break; + + case do_header_only_c: + { + BOOST_ASSERT(n <= buffer_size(b_.data())); + b_.consume(n); + if(buffer_size(b_.data()) > 0) + break; + // VFALCO delete b_? + header_done_ = true; + if(! is_deferred::value) + { + s_ = do_final_c; + break; + } + s_ = do_body_c; + break; + } + + case do_body_c + 2: + BOOST_ASSERT(n <= buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + v_ = boost::blank{}; + if(more_) + s_ = do_body_c + 1; + else + s_ = do_final_c; + break; + + case do_final_c + 1: + BOOST_ASSERT(buffer_size( + boost::get(v_))); + boost::get(v_).consume(n); + if(buffer_size(boost::get(v_)) > 0) + break; + v_ = boost::blank{}; + goto go_complete; + + //---------------------------------------------------------------------- + + default: + BOOST_ASSERT(false); + case do_complete: + break; + + go_complete: + s_ = do_complete; + break; + } +} + +} // http +} // beast + +#endif diff --git a/include/beast/http/impl/write.ipp b/include/beast/http/impl/write.ipp index 7b76bbf4..ecf3641a 100644 --- a/include/beast/http/impl/write.ipp +++ b/include/beast/http/impl/write.ipp @@ -9,128 +9,79 @@ #define BEAST_HTTP_IMPL_WRITE_IPP #include -#include -#include -#include #include #include #include #include -#include #include #include #include #include #include -#include +#include #include -#include -#include #include #include -#include namespace beast { namespace http { - namespace detail { -template -void -write_start_line(std::ostream& os, - header const& msg) +template< + class Stream, class Serializer, class Handler> +class write_some_op { - BOOST_ASSERT(msg.version == 10 || msg.version == 11); - os << msg.method() << " " << msg.target(); - switch(msg.version) - { - case 10: os << " HTTP/1.0\r\n"; break; - case 11: os << " HTTP/1.1\r\n"; break; - } -} - -template -void -write_start_line(std::ostream& os, - header const& msg) -{ - BOOST_ASSERT(msg.version == 10 || msg.version == 11); - switch(msg.version) - { - case 10: os << "HTTP/1.0 "; break; - case 11: os << "HTTP/1.1 "; break; - } - os << msg.status << " " << msg.reason() << "\r\n"; -} - -template -void -write_fields(std::ostream& os, - FieldSequence const& fields) -{ - //static_assert(is_FieldSequence::value, - // "FieldSequence requirements not met"); - for(auto const& field : fields) - { - auto const name = field.name(); - BOOST_ASSERT(! name.empty()); - if(name[0] == ':') - continue; - os << field.name() << ": " << field.value() << "\r\n"; - } -} - -} // detail - -//------------------------------------------------------------------------------ - -template -template -class serializer::async_op -{ - serializer& w_; Stream& s_; + Serializer& sr_; Handler h_; - bool cont_; + + class lambda + { + write_some_op& op_; + + public: + bool empty = true; + + explicit + lambda(write_some_op& op) + : op_(op) + { + } + + template + void + operator()(error_code& ec, + ConstBufferSequence const& buffer) + { + empty = false; + return op_.s_.async_write_some( + buffer, std::move(op_)); + } + }; public: - async_op(async_op&&) = default; - async_op(async_op const&) = default; + write_some_op(write_some_op&&) = default; + write_some_op(write_some_op const&) = default; - async_op(Handler&& h, Stream& s, - serializer& w) - : w_(w) - , s_(s) - , h_(std::move(h)) + template + write_some_op(DeducedHandler&& h, + Stream& s, Serializer& sr) + : s_(s) + , sr_(sr) + , h_(std::forward(h)) { - using boost::asio::asio_handler_is_continuation; - cont_ = asio_handler_is_continuation( - std::addressof(h_)); - } - - async_op(Handler const& h, Stream& s, - serializer& w) - : w_(w) - , s_(s) - , h_(h) - { - using boost::asio::asio_handler_is_continuation; - cont_ = asio_handler_is_continuation( - std::addressof(h_)); } void - operator()(error_code ec, std::size_t - bytes_transferred, bool again = true); + operator()(); + + void + operator()(error_code ec, + std::size_t bytes_transferred); friend void* asio_handler_allocate( - std::size_t size, async_op* op) + std::size_t size, write_some_op* op) { using boost::asio::asio_handler_allocate; return asio_handler_allocate( @@ -139,7 +90,7 @@ public: friend void asio_handler_deallocate( - void* p, std::size_t size, async_op* op) + void* p, std::size_t size, write_some_op* op) { using boost::asio::asio_handler_deallocate; asio_handler_deallocate( @@ -147,729 +98,50 @@ public: } friend - bool asio_handler_is_continuation(async_op* op) + bool asio_handler_is_continuation(write_some_op* op) { - return op->cont_; + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->h_)); } template friend - void asio_handler_invoke( - Function&& f, async_op* op) + void asio_handler_invoke(Function&& f, write_some_op* op) { using boost::asio::asio_handler_invoke; - asio_handler_invoke( - f, std::addressof(op->h_)); + asio_handler_invoke(f, std::addressof(op->h_)); } }; -template -template +template< + class Stream, class Serializer, class Handler> void -serializer:: -async_op:: -operator()(error_code ec, - std::size_t bytes_transferred, bool again) +write_some_op:: +operator()() { - cont_ = again || cont_; - using boost::asio::buffer_size; - if(ec) - goto upcall; - switch(w_.s_) - { - case do_init: - { - if(w_.split_) - goto go_header_only; - w_.rd_.emplace(w_.m_); - w_.rd_->init(ec); - if(ec) - return s_.get_io_service().post( - bind_handler(std::move(*this), ec, 0)); - auto result = w_.rd_->get(ec); - if(ec) - { - // Can't use need_more when ! is_deferred - BOOST_ASSERT(ec != error::need_more); - return s_.get_io_service().post( - bind_handler(std::move(*this), ec, 0)); - } - if(! result) - goto go_header_only; - w_.more_ = result->second; - w_.v_ = cb0_t{ - boost::in_place_init, - w_.b_.data(), - result->first}; - // [[fallthrough]] - } + lambda f{*this}; + error_code ec; + sr_.get(ec, f); + if(! ec && ! f.empty) + return; + return s_.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); +} - case do_header: - w_.s_ = do_header + 1; - return s_.async_write_some( - buffer_prefix(w_.limit_, - boost::get(w_.v_)), - std::move(*this)); - - case do_header + 1: - boost::get(w_.v_).consume( - bytes_transferred); - if(buffer_size( - boost::get(w_.v_)) > 0) - { - w_.s_ = do_header; - break; - } - w_.header_done_ = true; - w_.v_ = boost::blank{}; - w_.b_.consume(w_.b_.size()); // VFALCO delete b_? - if(! w_.more_) - goto go_complete; - w_.s_ = do_body; - break; - - go_header_only: - case do_header_only: - w_.s_ = do_header_only + 1; - return s_.async_write_some( - buffer_prefix(w_.limit_, - w_.b_.data()), std::move(*this)); - - case do_header_only + 1: - w_.b_.consume(bytes_transferred); - if(buffer_size(w_.b_.data()) > 0) - { - w_.s_ = do_header_only; - break; - } - // VFALCO delete b_? - w_.header_done_ = true; - if(! is_deferred::value) - goto go_complete; - BOOST_ASSERT(! w_.rd_); - w_.rd_.emplace(w_.m_); - w_.rd_->init(ec); - if(ec) - goto upcall; - w_.s_ = do_body; - break; - - case do_body: - { - auto result = w_.rd_->get(ec); - if(ec) - return s_.get_io_service().post( - bind_handler(std::move(*this), ec, 0)); - if(! result) - - { - w_.s_ = do_complete; - return s_.get_io_service().post( - bind_handler(std::move(*this), - error_code{}, 0)); - } - w_.more_ = result->second; - w_.v_ = cb1_t{result->first}; - // [[fallthrough]] - } - - case do_body + 1: - w_.s_ = do_body + 2; - return s_.async_write_some( - buffer_prefix(w_.limit_, - boost::get(w_.v_)), - std::move(*this)); - - case do_body + 2: - boost::get(w_.v_).consume( - bytes_transferred); - if(buffer_size( - boost::get(w_.v_)) > 0) - { - w_.s_ = do_body + 1; - break; - } - w_.v_ = boost::blank{}; - if(! w_.more_) - goto go_complete; - w_.s_ = do_body; - break; - - //---------------------------------------------------------------------- - - case do_init_c: - { - if(w_.split_) - goto go_header_only_c; - w_.rd_.emplace(w_.m_); - w_.rd_->init(ec); - if(ec) - return s_.get_io_service().post( - bind_handler(std::move(*this), ec, 0)); - auto result = w_.rd_->get(ec); - if(ec) - { - // Can't use need_more when ! is_deferred - BOOST_ASSERT(ec != error::need_more); - return s_.get_io_service().post( - bind_handler(std::move(*this), ec, 0)); - } - if(! result) - goto go_header_only_c; - w_.more_ = result->second; - w_.v_ = ch0_t{ - boost::in_place_init, - w_.b_.data(), - detail::chunk_header{ - buffer_size(result->first)}, - [&]() - { - auto sv = w_.d_(result->first); - return boost::asio::const_buffers_1{ - sv.data(), sv.size()}; - - }(), - result->first, - detail::chunk_crlf()}; - // [[fallthrough]] - } - - case do_header_c: - w_.s_ = do_header_c + 1; - return s_.async_write_some( - buffer_prefix(w_.limit_, - boost::get(w_.v_)), - std::move(*this)); - - case do_header_c + 1: - boost::get(w_.v_).consume( - bytes_transferred); - if(buffer_size( - boost::get(w_.v_)) > 0) - { - w_.s_ = do_header_c; - break; - } - w_.header_done_ = true; - w_.v_ = boost::blank{}; - w_.b_.consume(w_.b_.size()); // VFALCO delete b_? - if(! w_.more_) - w_.s_ = do_final_c; - else - w_.s_ = do_body_c; - break; - - go_header_only_c: - case do_header_only_c: - w_.s_ = do_header_only_c + 1; - return s_.async_write_some( - buffer_prefix(w_.limit_, - w_.b_.data()), std::move(*this)); - - case do_header_only_c + 1: - w_.b_.consume(bytes_transferred); - if(buffer_size(w_.b_.data()) > 0) - { - w_.s_ = do_header_only_c; - break; - } - // VFALCO delete b_? - w_.header_done_ = true; - if(! is_deferred::value) - { - w_.s_ = do_final_c; - break; - } - BOOST_ASSERT(! w_.rd_); - w_.rd_.emplace(w_.m_); - w_.rd_->init(ec); - if(ec) - goto upcall; - w_.s_ = do_body_c; - break; - - case do_body_c: - { - auto result = w_.rd_->get(ec); - if(ec) - return s_.get_io_service().post( - bind_handler(std::move(*this), - ec, 0)); - if(! result) - goto go_final_c; - w_.more_ = result->second; - w_.v_ = ch1_t{ - boost::in_place_init, - detail::chunk_header{ - buffer_size(result->first)}, - [&]() - { - auto sv = w_.d_(result->first); - return boost::asio::const_buffers_1{ - sv.data(), sv.size()}; - - }(), - result->first, - detail::chunk_crlf()}; - // [[fallthrough]] - } - - case do_body_c + 1: - w_.s_ = do_body_c + 2; - return s_.async_write_some( - buffer_prefix(w_.limit_, - boost::get(w_.v_)), - std::move(*this)); - - case do_body_c + 2: - boost::get(w_.v_).consume( - bytes_transferred); - if(buffer_size( - boost::get(w_.v_)) > 0) - { - w_.s_ = do_body_c + 1; - break; - } - w_.v_ = boost::blank{}; - if(w_.more_) - w_.s_ = do_body_c; - else - w_.s_ = do_final_c; - break; - - go_final_c: - case do_final_c: - w_.v_ = ch2_t{ - boost::in_place_init, - detail::chunk_final(), - [&]() - { - auto sv = w_.d_( - boost::asio::null_buffers{}); - return boost::asio::const_buffers_1{ - sv.data(), sv.size()}; - - }(), - detail::chunk_crlf()}; - // [[fallthrough]] - - case do_final_c + 1: - w_.s_ = do_final_c + 2; - return s_.async_write_some( - buffer_prefix(w_.limit_, - boost::get(w_.v_)), - std::move(*this)); - - case do_final_c + 2: - boost::get(w_.v_).consume( - bytes_transferred); - if(buffer_size(boost::get(w_.v_)) > 0) - { - w_.s_ = do_final_c + 1; - break; - } - w_.v_ = boost::blank{}; - goto go_complete; - - //---------------------------------------------------------------------- - - default: - BOOST_ASSERT(false); - - go_complete: - w_.s_ = do_complete; - case do_complete: - if(w_.close_) - // VFALCO TODO Decide on an error code - ec = boost::asio::error::eof; - break; - } - -upcall: +template< + class Stream, class Serializer, class Handler> +void +write_some_op:: +operator()(error_code ec, std::size_t bytes_transferred) +{ + if(! ec) + sr_.consume(bytes_transferred); h_(ec); } //------------------------------------------------------------------------------ -template -serializer:: -serializer(message const& m, - Decorator const& d, - Allocator const& alloc) - : m_(m) - , d_(d) - , b_(1024, alloc) - , chunked_(token_list{ - m.fields["Transfer-Encoding"]}.exists("chunked")) - , close_(token_list{ - m.fields["Connection"]}.exists("close") || - (m.version < 11 && ! m.fields.exists( - "Content-Length"))) -{ - s_ = chunked_ ? do_init_c : do_init; - auto os = ostream(b_); - detail::write_start_line(os, m_); - detail::write_fields(os, m_.fields); - os << "\r\n"; -} - -template -template -void -serializer:: -write_some(SyncWriteStream& stream) -{ - static_assert( - is_sync_write_stream::value, - "SyncWriteStream requirements not met"); - static_assert(is_body::value, - "Body requirements not met"); - static_assert(is_body_reader::value, - "BodyReader requirements not met"); - error_code ec; - write_some(stream, ec); - if(ec) - BOOST_THROW_EXCEPTION(system_error{ec}); -} - -template -template -void -serializer:: -write_some(SyncWriteStream& stream, error_code &ec) -{ - static_assert( - is_sync_write_stream::value, - "SyncWriteStream requirements not met"); - static_assert(is_body::value, - "Body requirements not met"); - static_assert(is_body_reader::value, - "BodyReader requirements not met"); - - using boost::asio::buffer_size; - switch(s_) - { - case do_init: - { - if(split_) - goto go_header_only; - rd_.emplace(m_); - rd_->init(ec); - if(ec) - return; - auto result = rd_->get(ec); - if(ec) - { - // Can't use need_more when ! is_deferred - BOOST_ASSERT(ec != error::need_more); - return; - } - if(! result) - goto go_header_only; - more_ = result->second; - v_ = cb0_t{ - boost::in_place_init, - b_.data(), - result->first}; - s_ = do_header; - // [[fallthrough]] - } - - case do_header: - { - auto bytes_transferred = - stream.write_some( - buffer_prefix(limit_, - boost::get(v_)), ec); - if(ec) - return; - boost::get(v_).consume( - bytes_transferred); - if(buffer_size(boost::get(v_)) > 0) - break; - header_done_ = true; - v_ = boost::blank{}; - b_.consume(b_.size()); // VFALCO delete b_? - if(! more_) - goto go_complete; - s_ = do_body; - break; - } - - go_header_only: - s_ = do_header_only; - case do_header_only: - { - auto bytes_transferred = - stream.write_some(buffer_prefix( - limit_, b_.data()), ec); - if(ec) - return; - b_.consume(bytes_transferred); - if(buffer_size(b_.data()) > 0) - break; - // VFALCO delete b_? - header_done_ = true; - if(! is_deferred::value) - goto go_complete; - BOOST_ASSERT(! rd_); - rd_.emplace(m_); - rd_->init(ec); - if(ec) - return; - s_ = do_body; - break; - } - - case do_body: - { - auto result = rd_->get(ec); - if(ec) - return; - if(! result) - goto go_complete; - more_ = result->second; - v_ = cb1_t{result->first}; - s_ = do_body + 1; - // [[fallthrough]] - } - - case do_body + 1: - { - auto bytes_transferred = - stream.write_some(buffer_prefix( - limit_, boost::get(v_)), ec); - if(ec) - return; - boost::get(v_).consume( - bytes_transferred); - if(buffer_size(boost::get(v_)) > 0) - break; - v_ = boost::blank{}; - if(! more_) - goto go_complete; - s_ = do_body; - break; - } - - //---------------------------------------------------------------------- - - case do_init_c: - { - if(split_) - goto go_header_only_c; - rd_.emplace(m_); - rd_->init(ec); - if(ec) - return; - auto result = rd_->get(ec); - if(ec) - { - // Can't use need_more when ! is_deferred - BOOST_ASSERT(ec != error::need_more); - return; - } - if(! result) - goto go_header_only_c; - more_ = result->second; - v_ = ch0_t{ - boost::in_place_init, - b_.data(), - detail::chunk_header{ - buffer_size(result->first)}, - [&]() - { - auto sv = d_(result->first); - return boost::asio::const_buffers_1{ - sv.data(), sv.size()}; - - }(), - result->first, - detail::chunk_crlf()}; - s_ = do_header_c; - // [[fallthrough]] - } - - case do_header_c: - { - auto bytes_transferred = - stream.write_some(buffer_prefix( - limit_, boost::get(v_)), ec); - if(ec) - return; - boost::get(v_).consume( - bytes_transferred); - if(buffer_size(boost::get(v_)) > 0) - break; - header_done_ = true; - v_ = boost::blank{}; - b_.consume(b_.size()); // VFALCO delete b_? - if(more_) - s_ = do_body_c; - else - s_ = do_final_c; - break; - } - - go_header_only_c: - s_ = do_header_only_c; - case do_header_only_c: - { - auto bytes_transferred = - stream.write_some(buffer_prefix( - limit_, b_.data()), ec); - if(ec) - return; - b_.consume(bytes_transferred); - if(buffer_size(b_.data()) > 0) - break; - // VFALCO delete b_? - header_done_ = true; - if(! is_deferred::value) - { - s_ = do_final_c; - break; - } - BOOST_ASSERT(! rd_); - rd_.emplace(m_); - rd_->init(ec); - if(ec) - return; - s_ = do_body_c; - break; - } - - case do_body_c: - { - auto result = rd_->get(ec); - if(ec) - return; - if(! result) - goto go_final_c; - more_ = result->second; - v_ = ch1_t{ - boost::in_place_init, - detail::chunk_header{ - buffer_size(result->first)}, - [&]() - { - auto sv = d_(result->first); - return boost::asio::const_buffers_1{ - sv.data(), sv.size()}; - - }(), - result->first, - detail::chunk_crlf()}; - s_ = do_body_c + 1; - // [[fallthrough]] - } - - case do_body_c + 1: - { - auto bytes_transferred = - stream.write_some(buffer_prefix( - limit_, boost::get(v_)), ec); - if(ec) - return; - boost::get(v_).consume( - bytes_transferred); - if(buffer_size(boost::get(v_)) > 0) - break; - v_ = boost::blank{}; - if(more_) - s_ = do_body_c; - else - s_ = do_final_c; - break; - } - - go_final_c: - case do_final_c: - v_ = ch2_t{ - boost::in_place_init, - detail::chunk_final(), - [&]() - { - auto sv = d_( - boost::asio::null_buffers{}); - return boost::asio::const_buffers_1{ - sv.data(), sv.size()}; - - }(), - detail::chunk_crlf()}; - s_ = do_final_c + 1; - // [[fallthrough]] - - case do_final_c + 1: - { - auto bytes_transferred = - stream.write_some(buffer_prefix( - limit_, boost::get(v_)), ec); - if(ec) - return; - boost::get(v_).consume( - bytes_transferred); - if(buffer_size(boost::get(v_)) > 0) - break; - v_ = boost::blank{}; - goto go_complete; - } - - default: - case do_complete: - BOOST_ASSERT(false); - - //---------------------------------------------------------------------- - - go_complete: - s_ = do_complete; - if(close_) - { - // VFALCO TODO Decide on an error code - ec = boost::asio::error::eof; - return; - } - break; - } -} - -template -template -async_return_type -serializer:: -async_write_some(AsyncWriteStream& stream, - WriteHandler&& handler) -{ - static_assert(is_async_write_stream::value, - "AsyncWriteStream requirements not met"); - static_assert(is_body::value, - "Body requirements not met"); - static_assert(is_body_reader::value, - "BodyReader requirements not met"); - async_completion init{handler}; - async_op>{ - init.completion_handler, stream, *this}( - error_code{}, 0, false); - - return init.result.get(); -} - -//------------------------------------------------------------------------------ - -namespace detail { - template class write_op @@ -879,12 +151,12 @@ class write_op int state = 0; Stream& s; serializer> ws; + empty_decorator, handler_alloc> sr; data(Handler& h, Stream& s_, message< isRequest, Body, Fields> const& m_) : s(s_) - , ws(m_, empty_decorator{}, + , sr(m_, empty_decorator{}, handler_alloc{h}) { } @@ -956,20 +228,120 @@ operator()(error_code ec) { case 0: d.state = 1; - return d.ws.async_write_some(d.s, std::move(*this)); + write_some_op{std::move(*this), d.s, d.sr}(); + return; case 1: d.state = 2; + // [[fallthrough]] case 2: - if(d.ws.is_done()) + if(d.sr.is_done()) + { + if(d.sr.needs_close()) + // VFALCO Choose an error code + ec = boost::asio::error::eof; break; - return d.ws.async_write_some(d.s, std::move(*this)); + } + write_some_op{std::move(*this), d.s, d.sr}(); + return; } upcall: d_.invoke(ec); } +//------------------------------------------------------------------------------ + +template +class write_some_lambda +{ + Stream& stream_; + +public: + boost::optional bytes_transferred; + + explicit + write_some_lambda(Stream& stream) + : stream_(stream) + { + } + + template + void + operator()(error_code& ec, + ConstBufferSequence const& buffer) + { + bytes_transferred = + stream_.write_some(buffer, ec); + } +}; + } // detail +//------------------------------------------------------------------------------ + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields, + class Decorator, class Allocator> +void +write_some(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator, Allocator>& sr) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + error_code ec; + write_some(stream, sr, ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ec}); +} + +template< + class SyncWriteStream, + bool isRequest, class Body, class Fields, + class Decorator, class Allocator> +void +write_some(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator, Allocator>& sr, + error_code& ec) +{ + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + detail::write_some_lambda f{stream}; + sr.get(ec, f); + if(! ec) + { + if(f.bytes_transferred) + sr.consume(*f.bytes_transferred); + if(sr.is_done() && sr.needs_close()) + // VFALCO decide on an error code + ec = boost::asio::error::eof; + } +} + +template +async_return_type +async_write_some(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator, Allocator>& sr, + WriteHandler&& handler) +{ + static_assert(is_async_write_stream< + AsyncWriteStream>::value, + "AsyncWriteStream requirements not met"); + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + async_completion init{handler}; + detail::write_some_op>{ + init.completion_handler, stream, sr}(); + return init.result.get(); +} + template void @@ -1001,13 +373,17 @@ write(SyncWriteStream& stream, "Body requirements not met"); static_assert(is_body_reader::value, "BodyReader requirements not met"); - auto ws = make_serializer(msg); + auto sr = make_serializer(msg); for(;;) { - ws.write_some(stream, ec); +#if 0 + sr.write_some(stream, ec); +#else + write_some(stream, sr, ec); +#endif if(ec) return; - if(ws.is_done()) + if(sr.is_done()) break; } } diff --git a/include/beast/http/serializer.hpp b/include/beast/http/serializer.hpp new file mode 100644 index 00000000..ec6a4550 --- /dev/null +++ b/include/beast/http/serializer.hpp @@ -0,0 +1,350 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot 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) +// + +#ifndef BEAST_HTTP_SERIALIZER_HPP +#define BEAST_HTTP_SERIALIZER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A chunk decorator which does nothing. + + When selected as a chunk decorator, objects of this type + affect the output of messages specifying chunked + transfer encodings as follows: + + @li chunk headers will have empty chunk extensions, and + + @li final chunks will have an empty set of trailers. + + @see @ref serializer +*/ +struct empty_decorator +{ + template + string_view + operator()(ConstBufferSequence const&) const + { + return {"\r\n"}; + } + + string_view + operator()(boost::asio::null_buffers) const + { + return {}; + } +}; + +/** Provides buffer oriented HTTP message serialization functionality. + + An object of this type is used to serialize a complete + HTTP message into a seriest of octets. To use this class, + construct an instance with the message to be serialized. + To make it easier to declare the type, the helper function + @ref make_serializer is provided. + + The implementation will automatically perform chunk encoding + if the contents of the message indicate that chunk encoding + is required. If the semantics of the message indicate that + the connection should be closed after the message is sent, the + function @ref needs_close will return `true`. + + Upon construction, an optional chunk decorator may be + specified. This decorator is a function object called with + each buffer sequence of the body when the chunked transfer + encoding is indicate in the message header. The decorator + will be called with an empty buffer sequence (actually + the type `boost::asio::null_buffers`) to indicate the + final chunk. The decorator may return a string which forms + the chunk extension for chunks, and the field trailers + for the final chunk. + + In C++11 the decorator must be declared as a class or + struct with a templated operator() thusly: + + @code + // The implementation guarantees that operator() + // will be called only after the view returned by + // any previous calls to operator() are no longer + // needed. The decorator instance is intended to + // manage the lifetime of the storage for all returned + // views. + // + struct decorator + { + // Returns the chunk-extension for each chunk. + // The buffer returned must include a trailing "\r\n", + // and the leading semicolon (";") if one or more + // chunk extensions are specified. + // + template + string_view + operator()(ConstBufferSequence const&) const; + + // Returns a set of field trailers for the final chunk. + // Each field should be formatted according to rfc7230 + // including the trailing "\r\n" for each field. If + // no trailers are indicated, an empty string is returned. + // + string_view + operator()(boost::asio::null_buffers) const; + }; + @endcode + + @tparam isRequest `true` if the message is a request. + + @tparam Body The body type of the message. + + @tparam Fields The type of fields in the message. + + @tparam Decorator The type of chunk decorator to use. + + @tparam Allocator The type of allocator to use. + + @see @ref make_serializer +*/ +template< + bool isRequest, class Body, class Fields, + class Decorator = empty_decorator, + class Allocator = std::allocator +> +class serializer +{ + static_assert(is_body::value, + "Body requirements not met"); + static_assert(is_body_reader::value, + "BodyReader requirements not met"); + + enum + { + do_init = 0, + do_header_only = 10, + do_header = 20, + do_body = 40, + + do_init_c = 50, + do_header_only_c = 60, + do_header_c = 70, + do_body_c = 90, + do_final_c = 100, + + do_complete = 110 + }; + + void split(bool, std::true_type) {} + void split(bool v, std::false_type) { split_ = v; } + + using buffer_type = + basic_multi_buffer; + + using reader = typename Body::reader; + + using is_deferred = + typename reader::is_deferred; + + using cb0_t = consuming_buffers>; // body + + using cb1_t = consuming_buffers< + typename reader::const_buffers_type>; // body + + using ch0_t = consuming_buffers>; // crlf + + using ch1_t = consuming_buffers>; // crlf + + using ch2_t = consuming_buffers>; // crlf + + message const& m_; + Decorator d_; + boost::optional rd_; + buffer_type b_; + boost::variant v_; + int s_; + bool split_ = is_deferred::value; + bool header_done_ = false; + bool chunked_; + bool close_; + bool more_; + +public: + /** Constructor + + @param msg The message to serialize. The message object + must remain valid for the lifetime of the write stream. + + @param decorator An optional decorator to use. + + @param alloc An optional allocator to use. + */ + explicit + serializer(message const& msg, + Decorator const& decorator = Decorator{}, + Allocator const& alloc = Allocator{}); + + /** Returns `true` if we will pause after writing the header. + */ + bool + split() const + { + return split_; + } + + /** Set whether the header and body are written separately. + + When the split feature is enabled, the implementation will + write only the octets corresponding to the serialized header + first. If the header has already been written, this function + will have no effect on output. This function should be called + before any writes take place, otherwise the behavior is + undefined. + */ + void + split(bool v) + { + split(v, is_deferred{}); + } + + /** Return `true` if serialization of the header is complete. + + This function indicates whether or not all octets + corresponding to the serialized representation of the + header have been successfully delivered to the stream. + */ + bool + is_header_done() const + { + return header_done_; + } + + /** Return `true` if serialization is complete + + The operation is complete when all octets corresponding + to the serialized representation of the message have been + successfully delivered to the stream. + */ + bool + is_done() const + { + return s_ == do_complete; + } + + /** Return `true` if Connection: close semantic is indicated. + + After serialization is complete, if there is an + underlying network connection then it should be closed if + this function returns `true`. + */ + bool + needs_close() const + { + return close_; + } + + /** Returns the next set of buffers in the serialization. + + This function will attempt to call the `visit` function + object with a @b ConstBufferSequence of unspecified type + representing the next set of buffers in the serialization + of the message represented by this object. + + If there are no more buffers in the serialization, the + visit function will not be called. In this case, no error + will be indicated, and the function @ref is_done will + return `true`. + + @param ec Set to the error, if any occurred. + + @param visit The function to call. The equivalent function + signature of this object must be: + @code template + void visit(error_code&, ConstBufferSequence const&); + @endcode + The function is not copied, if no error occurs it will be + invoked before the call to @ref get returns. + + */ + template + void + get(error_code& ec, Visit&& visit); + + /** Consume buffer octets in the serialization. + + This function should be called after one or more octets + contained in the buffers provided in the prior call + to @ref get have been used. + + After a call to @ref consume, callers should check the + return value of @ref is_done to determine if the entire + message has been serialized. + + @param n The number of octets to consume. This number must + be greater than zero and no greater than the number of + octets in the buffers provided in the prior call to @ref get. + */ + void + consume(std::size_t n); +}; + +/** Return a stateful object to serialize an HTTP message. + + This convenience function makes it easier to declare + the variable for a given message. + + @see @ref serializer +*/ +template< + bool isRequest, class Body, class Fields, + class Decorator = empty_decorator, + class Allocator = std::allocator> +inline +serializer::type, + typename std::decay::type> +make_serializer(message const& m, + Decorator const& decorator = Decorator{}, + Allocator const& allocator = Allocator{}) +{ + return serializer::type, + typename std::decay::type>{ + m, decorator, allocator}; +} + +} // http +} // beast + +#include + +#endif diff --git a/include/beast/http/write.hpp b/include/beast/http/write.hpp index 9e58290c..969d090e 100644 --- a/include/beast/http/write.hpp +++ b/include/beast/http/write.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -27,410 +28,109 @@ namespace beast { namespace http { -/** A chunk decorator which does nothing. +/** Write some serialized message data to a stream. - When selected as a chunk decorator, objects of this type - affect the output of messages specifying chunked - transfer encodings as follows: + This function is used to write serialized message data to the + stream. The function call will block until one of the following + conditions is true: + + @li One or more bytes have been transferred. - @li chunk headers will have empty chunk extensions, and + @li An error occurs on the stream. - @li final chunks will have an empty set of trailers. + In order to completely serialize a message, this function + should be called until `sr.is_done()` returns `true`. + + @param stream The stream to write to. This type must + satisfy the requirements of @b SyncWriteStream. - @see @ref serializer + @param sr The serializer to use. + + @throws system_error Thrown on failure. + + @see @ref async_write_some, @ref serializer */ -struct empty_decorator -{ - template - string_view - operator()(ConstBufferSequence const&) const - { - return {"\r\n"}; - } - - string_view - operator()(boost::asio::null_buffers) const - { - return {}; - } -}; - -/** Provides stream-oriented HTTP message serialization functionality. - - Objects of this type may be used to perform synchronous or - asynchronous serialization of an HTTP message on a stream. - Unlike functions such as @ref write or @ref async_write, - the stream operations provided here guarantee that bounded - work will be performed. This is accomplished by making one - or more calls to the underlying stream's `write_some` or - `async_write_some` member functions. In order to fully - serialize the message, multiple calls are required. - - The ability to incrementally serialize a message, peforming - bounded work at each iteration is useful in many scenarios, - such as: - - @li Setting consistent, per-call timeouts - - @li Efficiently relaying body content from another stream - - @li Performing application-layer flow control - - To use this class, construct an instance with the message - to be sent. To make it easier to declare the type, the - helper function @ref make_serializer is provided: - - The implementation will automatically perform chunk encoding - if the contents of the message indicate that chunk encoding - is required. If the semantics of the message indicate that - the connection should be closed after the message is sent, - the error delivered from stream operations will be - `boost::asio::error::eof`. - - @code - template - void send(Stream& stream, request const& msg) - { - serializer w{msg}; - do - { - w.write_some(); - } - while(! w.is_done()); - } - @endcode - - Upon construction, an optional chunk decorator may be - specified. This decorator is a function object called with - each buffer sequence of the body when the chunked transfer - encoding is indicate in the message header. The decorator - will be called with an empty buffer sequence (actually - the type `boost::asio::null_buffers`) to indicate the - final chunk. The decorator may return a string which forms - the chunk extension for chunks, and the field trailers - for the final chunk. - - In C++11 the decorator must be declared as a class or - struct with a templated operator() thusly: - - @code - // The implementation guarantees that operator() - // will be called only after the view returned by - // any previous calls to operator() are no longer - // needed. The decorator instance is intended to - // manage the lifetime of the storage for all returned - // views. - // - struct decorator - { - // Returns the chunk-extension for each chunk. - // The buffer returned must include a trailing "\r\n", - // and the leading semicolon (";") if one or more - // chunk extensions are specified. - // - template - string_view - operator()(ConstBufferSequence const&) const; - - // Returns a set of field trailers for the final chunk. - // Each field should be formatted according to rfc7230 - // including the trailing "\r\n" for each field. If - // no trailers are indicated, an empty string is returned. - // - string_view - operator()(boost::asio::null_buffers) const; - }; - @endcode - - @par Thread Safety - @e Distinct @e objects: Safe.@n - @e Shared @e objects: Unsafe. - - @tparam isRequest `true` if the message is a request. - - @tparam Body The body type of the message. - - @tparam Fields The type of fields in the message. - - @tparam Decorator The type of chunk decorator to use. - - @tparam Allocator The type of allocator to use. - - @see @ref make_serializer -*/ -template< +template -> -class serializer -{ - template - class async_op; + class Decorator, class Allocator> +void +write_some(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator, Allocator>& sr); - enum - { - do_init = 0, - do_header_only = 10, - do_header = 20, - do_body = 40, + +/** Write some serialized message data to a stream. + + This function is used to write serialized message data to the + stream. The function call will block until one of the following + conditions is true: - do_init_c = 50, - do_header_only_c = 60, - do_header_c = 70, - do_body_c = 90, - do_final_c = 100, + @li One or more bytes have been transferred. - do_complete = 110 - }; + @li An error occurs on the stream. - void split(bool, std::true_type) {} - void split(bool v, std::false_type) { split_ = v; } - - using buffer_type = - basic_multi_buffer; - - using reader = typename Body::reader; - - using is_deferred = - typename reader::is_deferred; - - using cb0_t = consuming_buffers>; // body - - using cb1_t = consuming_buffers< - typename reader::const_buffers_type>; // body - - using ch0_t = consuming_buffers>; // crlf + In order to completely serialize a message, this function + should be called until `sr.is_done()` returns `true`. - using ch1_t = consuming_buffers>; // crlf + @param stream The stream to write to. This type must + satisfy the requirements of @b SyncWriteStream. - using ch2_t = consuming_buffers>; // crlf + @param sr The serializer to use. - message const& m_; - Decorator d_; - std::size_t limit_ = - (std::numeric_limits::max)(); - boost::optional rd_; - buffer_type b_; - boost::variant v_; - int s_; - bool split_ = is_deferred::value; - bool header_done_ = false; - bool chunked_; - bool close_; - bool more_; + @param ec Set to indicate what error occurred, if any. -public: - /** Constructor + @see @ref async_write_some, @ref serializer +*/ +template +void +write_some(SyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator, Allocator>& sr, + error_code& ec); - @param msg The message to serialize. The message object - must remain valid for the lifetime of the write stream. +/** Start an asynchronous write of some serialized message data to a stream. - @param decorator An optional decorator to use. + This function is used to asynchronously write serialized + message data to the stream. The function call always returns + immediately. The asynchronous operation will continue until + one of the following conditions is true: - @param alloc An optional allocator to use. - */ - explicit - serializer(message const& msg, - Decorator const& decorator = Decorator{}, - Allocator const& alloc = Allocator{}); + @li One or more bytes have been transferred. - /// Returns the maximum number of bytes that will be written in each operation - std::size_t - limit() const - { - return limit_; - } + @li An error occurs on the stream. - /** Returns `true` if we will pause after writing the header. - */ - bool - split() const - { - return split_; - } + In order to completely serialize a message, this function + should be called until `sr.is_done()` returns `true`. - /** Set whether the header and body are written separately. + @param stream The stream to write to. This type must + satisfy the requirements of @b SyncWriteStream. - When the split feature is enabled, the implementation will - write only the octets corresponding to the serialized header - first. If the header has already been written, this function - will have no effect on output. This function should be called - before any writes take place, otherwise the behavior is - undefined. - */ - void - split(bool v) - { - split(v, is_deferred{}); - } + @param sr The serializer to use for writing. - /** Set the maximum number of bytes that will be written in each operation. + @param handler The handler to be called when the request + completes. Copies will be made of the handler as required. The + equivalent function signature of the handler must be: + @code void handler( + error_code const& ec // Result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using `boost::asio::io_service::post`. - By default, there is no limit on the size of writes. - - @param n The byte limit. This must be greater than zero. - */ - void - limit(std::size_t n) - { - limit_ = n; - } - - /** Return `true` if serialization of the header is complete. - - This function indicates whether or not all octets - corresponding to the serialized representation of the - header have been successfully delivered to the stream. - */ - bool - is_header_done() const - { - return header_done_; - } - - /** Return `true` if serialization is complete - - The operation is complete when all octets corresponding - to the serialized representation of the message have been - successfully delivered to the stream. - */ - bool - is_done() const - { - return s_ == do_complete; - } - - /** Write some serialized message data to the stream. - - This function is used to write serialized message data to the - stream. The function call will block until one of the following - conditions is true: - - @li One or more bytes have been transferred. - - @li An error occurs on the stream. - - In order to completely serialize a message, this function - should be called until @ref is_done returns `true`. If the - semantics of the message indicate that the connection should - be closed after the message is sent, the error delivered from - this call will be `boost::asio::error::eof`. - - @param stream The stream to write to. This type must - satisfy the requirements of @b SyncWriteStream. - - @throws system_error Thrown on failure. - */ - template - void - write_some(SyncWriteStream& stream); - - /** Write some serialized message data to the stream. - - This function is used to write serialized message data to the - stream. The function call will block until one of the following - conditions is true: - - @li One or more bytes have been transferred. - - @li An error occurs on the stream. - - In order to completely serialize a message, this function - should be called until @ref is_done returns `true`. If the - semantics of the message indicate that the connection should - be closed after the message is sent, the error delivered from - this call will be `boost::asio::error::eof`. - - @param stream The stream to write to. This type must - satisfy the requirements of @b SyncWriteStream. - - @param ec Set to indicate what error occurred, if any. - */ - template - void - write_some(SyncWriteStream& stream, error_code &ec); - - /** Start an asynchronous write of some serialized message data. - - This function is used to asynchronously write serialized - message data to the stream. The function call always returns - immediately. The asynchronous operation will continue until - one of the following conditions is true: - - @li One or more bytes have been transferred. - - @li An error occurs on the stream. - - In order to completely serialize a message, this function - should be called until @ref is_done returns `true`. If the - semantics of the message indicate that the connection should - be closed after the message is sent, the error delivered from - this call will be `boost::asio::error::eof`. - - @param stream The stream to write to. This type must - satisfy the requirements of @b SyncWriteStream. - - @param handler The handler to be called when the request - completes. Copies will be made of the handler as required. The - equivalent function signature of the handler must be: - @code void handler( - error_code const& ec // Result of operation - ); @endcode - Regardless of whether the asynchronous operation completes - immediately or not, the handler will not be invoked from within - this function. Invocation of the handler will be performed in a - manner equivalent to using `boost::asio::io_service::post`. - */ - template + @see @ref write_some, @ref serializer +*/ +template #if BEAST_DOXYGEN void_or_deduced #else - async_return_type +async_return_type #endif - async_write_some(AsyncWriteStream& stream, +async_write_some(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator, Allocator>& sr, WriteHandler&& handler); -}; - -/** Return a stateful object to serialize an HTTP message. - - This convenience function makes it easier to declare - the variable for a given message. -*/ -template< - bool isRequest, class Body, class Fields, - class Decorator = empty_decorator, - class Allocator = std::allocator> -inline -serializer::type, - typename std::decay::type> -make_serializer(message const& m, - Decorator const& decorator = Decorator{}, - Allocator const& allocator = Allocator{}) -{ - return serializer::type, - typename std::decay::type>{ - m, decorator, allocator}; -} - -//------------------------------------------------------------------------------ /** Write a HTTP/1 message to a stream. diff --git a/test/Jamfile b/test/Jamfile index 38995912..2bb0b928 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -52,6 +52,7 @@ unit-test http-tests : http/message_parser.cpp http/read.cpp http/rfc7230.cpp + http/serializer.cpp http/string_body.cpp http/type_traits.cpp http/write.cpp diff --git a/test/http/CMakeLists.txt b/test/http/CMakeLists.txt index ea3255ec..b95f3b69 100644 --- a/test/http/CMakeLists.txt +++ b/test/http/CMakeLists.txt @@ -22,6 +22,7 @@ add_executable (http-tests message_parser.cpp read.cpp rfc7230.cpp + serializer.cpp string_body.cpp type_traits.cpp write.cpp diff --git a/test/http/design.cpp b/test/http/design.cpp index 7c07fcff..29145fd7 100644 --- a/test/http/design.cpp +++ b/test/http/design.cpp @@ -77,11 +77,11 @@ public: req.body = "Hello, world!"; // send header - auto ws = make_serializer(req); + auto sr = make_serializer(req); for(;;) { - ws.async_write_some(stream, yield); - if(ws.is_header_done()) + async_write_some(stream, sr, yield); + if(sr.is_header_done()) break; } @@ -92,8 +92,8 @@ public: } // send body - while(! ws.is_done()) - ws.async_write_some(stream, yield); + while(! sr.is_done()) + async_write_some(stream, sr, yield); } void @@ -162,7 +162,7 @@ public: m.target("/"); m.fields.insert("User-Agent", "test"); m.fields.insert("Content-Length", s.size()); - auto ws = make_serializer(m); + auto sr = make_serializer(m); error_code ec; for(;;) { @@ -171,7 +171,7 @@ public: m.body.second = s.size() > 3; for(;;) { - ws.write_some(p.client, ec); + write_some(p.client, sr, ec); if(ec == error::need_more) { ec = {}; @@ -179,12 +179,12 @@ public: } if(! BEAST_EXPECTS(! ec, ec.message())) return; - if(ws.is_done()) + if(sr.is_done()) break; } s.erase(s.begin(), s.begin() + boost::asio::buffer_size(*m.body.first)); - if(ws.is_done()) + if(sr.is_done()) break; } BEAST_EXPECT(p.server.str() == diff --git a/test/http/serializer.cpp b/test/http/serializer.cpp new file mode 100644 index 00000000..3fb95423 --- /dev/null +++ b/test/http/serializer.cpp @@ -0,0 +1,9 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot 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) +// + +// Test that header file is self-contained. +#include diff --git a/test/http/write.cpp b/test/http/write.cpp index b0ca6fac..e46045e6 100644 --- a/test/http/write.cpp +++ b/test/http/write.cpp @@ -765,15 +765,15 @@ public: isRequest, Body, Fields> const& m, error_code& ec, Decorator const& decorator = Decorator{}) { - auto ws = make_serializer(m, decorator); + auto sr = make_serializer(m, decorator); for(;;) { stream.nwrite = 0; - ws.write_some(stream, ec); + write_some(stream, sr, ec); if(ec) return; BEAST_EXPECT(stream.nwrite <= 1); - if(ws.is_done()) + if(sr.is_done()) break; } } @@ -787,15 +787,15 @@ public: error_code& ec, yield_context yield, Decorator const& decorator = Decorator{}) { - auto ws = make_serializer(m); + auto sr = make_serializer(m); for(;;) { stream.nwrite = 0; - ws.async_write_some(stream, yield[ec]); + async_write_some(stream, sr, yield[ec]); if(ec) return; BEAST_EXPECT(stream.nwrite <= 1); - if(ws.is_done()) + if(sr.is_done()) break; } } @@ -857,12 +857,12 @@ public: { auto m = m0; error_code ec; - serializer w{m}; - w.split(true); + serializer sr{m}; + sr.split(true); for(;;) { - w.write_some(p.client); - if(w.is_header_done()) + write_some(p.client, sr); + if(sr.is_header_done()) break; } BEAST_EXPECT(! m.body.read); @@ -871,12 +871,12 @@ public: { auto m = m0; error_code ec; - serializer w{m}; - w.split(true); + serializer sr{m}; + sr.split(true); for(;;) { - w.async_write_some(p.client, yield); - if(w.is_header_done()) + async_write_some(p.client, sr, yield); + if(sr.is_header_done()) break; } BEAST_EXPECT(! m.body.read); @@ -921,12 +921,12 @@ public: auto m = m0; error_code ec; test::string_ostream so{get_io_service(), 3}; - serializer w{m}; - w.split(true); + serializer sr{m}; + sr.split(true); for(;;) { - w.write_some(p.client); - if(w.is_header_done()) + write_some(p.client, sr); + if(sr.is_header_done()) break; } BEAST_EXPECT(! m.body.read); @@ -935,12 +935,12 @@ public: { auto m = m0; error_code ec; - serializer w{m}; - w.split(true); + serializer sr{m}; + sr.split(true); for(;;) { - w.async_write_some(p.client, yield); - if(w.is_header_done()) + async_write_some(p.client, sr, yield); + if(sr.is_header_done()) break; } BEAST_EXPECT(! m.body.read); @@ -969,13 +969,13 @@ public: m.body.first = boost::none; m.body.second = true; - auto w = make_serializer(m); + auto sr = make_serializer(m); // send the header first, so the // other end gets it right away for(;;) { - w.write_some(output, ec); + write_some(output, sr, ec); if(ec == error::need_more) { ec = {}; @@ -1008,7 +1008,7 @@ public: // write to output for(;;) { - w.write_some(output, ec); + write_some(output, sr, ec); if(ec == error::need_more) { ec = {}; @@ -1016,7 +1016,7 @@ public: } if(ec) return; - if(w.is_done()) + if(sr.is_done()) goto is_done; } b.consume(b.size());