// // 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_WRITE_IPP #define BEAST_HTTP_IMPL_WRITE_IPP #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(DynamicBuffer& dynabuf, header const& msg) { BOOST_ASSERT(msg.version == 10 || msg.version == 11); write(dynabuf, msg.method); write(dynabuf, " "); write(dynabuf, msg.url); switch(msg.version) { case 10: write(dynabuf, " HTTP/1.0\r\n"); break; case 11: write(dynabuf, " HTTP/1.1\r\n"); break; } } template void write_start_line(DynamicBuffer& dynabuf, header const& msg) { BOOST_ASSERT(msg.version == 10 || msg.version == 11); switch(msg.version) { case 10: write(dynabuf, "HTTP/1.0 "); break; case 11: write(dynabuf, "HTTP/1.1 "); break; } write(dynabuf, msg.status); write(dynabuf, " "); write(dynabuf, msg.reason); write(dynabuf, "\r\n"); } template void write_fields(DynamicBuffer& dynabuf, FieldSequence const& fields) { static_assert(is_DynamicBuffer::value, "DynamicBuffer requirements not met"); //static_assert(is_FieldSequence::value, // "FieldSequence requirements not met"); for(auto const& field : fields) { write(dynabuf, field.name()); write(dynabuf, ": "); write(dynabuf, field.value()); write(dynabuf, "\r\n"); } } } // detail //------------------------------------------------------------------------------ namespace detail { template class write_streambuf_op { struct data { bool cont; Stream& s; streambuf sb; int state = 0; data(Handler& handler, Stream& s_, streambuf&& sb_) : cont(beast_asio_helpers:: is_continuation(handler)) , s(s_) , sb(std::move(sb_)) { } }; handler_ptr d_; public: write_streambuf_op(write_streambuf_op&&) = default; write_streambuf_op(write_streambuf_op const&) = default; template write_streambuf_op(DeducedHandler&& h, Stream& s, Args&&... args) : d_(std::forward(h), s, std::forward(args)...) { (*this)(error_code{}, 0, false); } void operator()(error_code ec, std::size_t bytes_transferred, bool again = true); friend void* asio_handler_allocate( std::size_t size, write_streambuf_op* op) { return beast_asio_helpers:: allocate(size, op->d_.handler()); } friend void asio_handler_deallocate( void* p, std::size_t size, write_streambuf_op* op) { return beast_asio_helpers:: deallocate(p, size, op->d_.handler()); } friend bool asio_handler_is_continuation(write_streambuf_op* op) { return op->d_->cont; } template friend void asio_handler_invoke(Function&& f, write_streambuf_op* op) { return beast_asio_helpers:: invoke(f, op->d_.handler()); } }; template void write_streambuf_op:: operator()(error_code ec, std::size_t, bool again) { auto& d = *d_; d.cont = d.cont || again; while(! ec && d.state != 99) { switch(d.state) { case 0: { d.state = 99; boost::asio::async_write(d.s, d.sb.data(), std::move(*this)); return; } } } d_.invoke(ec); } } // detail template void write(SyncWriteStream& stream, header const& msg) { static_assert(is_SyncWriteStream::value, "SyncWriteStream requirements not met"); error_code ec; write(stream, msg, ec); if(ec) throw system_error{ec}; } template void write(SyncWriteStream& stream, header const& msg, error_code& ec) { static_assert(is_SyncWriteStream::value, "SyncWriteStream requirements not met"); streambuf sb; detail::write_start_line(sb, msg); detail::write_fields(sb, msg.fields); beast::write(sb, "\r\n"); boost::asio::write(stream, sb.data(), ec); } template typename async_completion< WriteHandler, void(error_code)>::result_type async_write(AsyncWriteStream& stream, header const& msg, WriteHandler&& handler) { static_assert(is_AsyncWriteStream::value, "AsyncWriteStream requirements not met"); beast::async_completion completion{handler}; streambuf sb; detail::write_start_line(sb, msg); detail::write_fields(sb, msg.fields); beast::write(sb, "\r\n"); detail::write_streambuf_op{ completion.handler, stream, std::move(sb)}; return completion.result.get(); } //------------------------------------------------------------------------------ namespace detail { template struct write_preparation { message const& msg; typename Body::writer w; streambuf sb; bool chunked; bool close; explicit write_preparation( message const& msg_) : msg(msg_) , w(msg) , chunked(token_list{ msg.fields["Transfer-Encoding"]}.exists("chunked")) , close(token_list{ msg.fields["Connection"]}.exists("close") || (msg.version < 11 && ! msg.fields.exists( "Content-Length"))) { } void init(error_code& ec) { w.init(ec); if(ec) return; write_start_line(sb, msg); write_fields(sb, msg.fields); beast::write(sb, "\r\n"); } }; template class write_op { struct data { bool cont; Stream& s; // VFALCO How do we use handler_alloc in write_preparation? write_preparation< isRequest, Body, Fields> wp; int state = 0; data(Handler& handler, Stream& s_, message const& m_) : cont(beast_asio_helpers:: is_continuation(handler)) , s(s_) , wp(m_) { } }; class writef0_lambda { write_op& self_; public: explicit writef0_lambda(write_op& self) : self_(self) { } template void operator()(ConstBufferSequence const& buffers) const { auto& d = *self_.d_; // write header and body if(d.wp.chunked) boost::asio::async_write(d.s, buffer_cat(d.wp.sb.data(), chunk_encode(false, buffers)), std::move(self_)); else boost::asio::async_write(d.s, buffer_cat(d.wp.sb.data(), buffers), std::move(self_)); } }; class writef_lambda { write_op& self_; public: explicit writef_lambda(write_op& self) : self_(self) { } template void operator()(ConstBufferSequence const& buffers) const { auto& d = *self_.d_; // write body if(d.wp.chunked) boost::asio::async_write(d.s, chunk_encode(false, buffers), std::move(self_)); else boost::asio::async_write(d.s, buffers, std::move(self_)); } }; handler_ptr d_; public: write_op(write_op&&) = default; write_op(write_op const&) = default; template write_op(DeducedHandler&& h, Stream& s, Args&&... args) : d_(std::forward(h), s, std::forward(args)...) { (*this)(error_code{}, 0, false); } void operator()(error_code ec, std::size_t bytes_transferred, bool again = true); friend void* asio_handler_allocate( std::size_t size, write_op* op) { return beast_asio_helpers:: allocate(size, op->d_.handler()); } friend void asio_handler_deallocate( void* p, std::size_t size, write_op* op) { return beast_asio_helpers:: deallocate(p, size, op->d_.handler()); } friend bool asio_handler_is_continuation(write_op* op) { return op->d_->cont; } template friend void asio_handler_invoke(Function&& f, write_op* op) { return beast_asio_helpers:: invoke(f, op->d_.handler()); } }; template void write_op:: operator()(error_code ec, std::size_t, bool again) { auto& d = *d_; d.cont = d.cont || again; while(! ec && d.state != 99) { switch(d.state) { case 0: { d.wp.init(ec); if(ec) { // call handler d.state = 99; d.s.get_io_service().post(bind_handler( std::move(*this), ec, 0, false)); return; } d.state = 1; break; } case 1: { auto const result = d.wp.w.write(ec, writef0_lambda{*this}); if(ec) { // call handler d.state = 99; d.s.get_io_service().post(bind_handler( std::move(*this), ec, false)); return; } if(result) d.state = d.wp.chunked ? 4 : 5; else d.state = 2; return; } // sent header and body case 2: d.wp.sb.consume(d.wp.sb.size()); d.state = 3; break; case 3: { auto const result = d.wp.w.write(ec, writef_lambda{*this}); if(ec) { // call handler d.state = 99; break; } if(result) d.state = d.wp.chunked ? 4 : 5; else d.state = 2; return; } case 4: // VFALCO Unfortunately the current interface to the // Writer concept prevents us from coalescing the // final body chunk with the final chunk delimiter. // // write final chunk d.state = 5; boost::asio::async_write(d.s, chunk_encode_final(), std::move(*this)); return; case 5: if(d.wp.close) { // VFALCO TODO Decide on an error code ec = boost::asio::error::eof; } d.state = 99; break; } } d_.invoke(ec); } template class writef0_lambda { DynamicBuffer const& sb_; SyncWriteStream& stream_; bool chunked_; error_code& ec_; public: writef0_lambda(SyncWriteStream& stream, DynamicBuffer const& sb, bool chunked, error_code& ec) : sb_(sb) , stream_(stream) , chunked_(chunked) , ec_(ec) { } template void operator()(ConstBufferSequence const& buffers) const { // write header and body if(chunked_) boost::asio::write(stream_, buffer_cat( sb_.data(), chunk_encode(false, buffers)), ec_); else boost::asio::write(stream_, buffer_cat( sb_.data(), buffers), ec_); } }; template class writef_lambda { SyncWriteStream& stream_; bool chunked_; error_code& ec_; public: writef_lambda(SyncWriteStream& stream, bool chunked, error_code& ec) : stream_(stream) , chunked_(chunked) , ec_(ec) { } template void operator()(ConstBufferSequence const& buffers) const { // write body if(chunked_) boost::asio::write(stream_, chunk_encode(false, buffers), ec_); else boost::asio::write(stream_, buffers, ec_); } }; } // detail template void write(SyncWriteStream& stream, message const& msg) { static_assert(is_SyncWriteStream::value, "SyncWriteStream requirements not met"); static_assert(is_Body::value, "Body requirements not met"); static_assert(has_writer::value, "Body has no writer"); static_assert(is_Writer>::value, "Writer requirements not met"); error_code ec; write(stream, msg, ec); if(ec) throw system_error{ec}; } template void write(SyncWriteStream& stream, message const& msg, error_code& ec) { static_assert(is_SyncWriteStream::value, "SyncWriteStream requirements not met"); static_assert(is_Body::value, "Body requirements not met"); static_assert(has_writer::value, "Body has no writer"); static_assert(is_Writer>::value, "Writer requirements not met"); detail::write_preparation wp(msg); wp.init(ec); if(ec) return; auto result = wp.w.write( ec, detail::writef0_lambda< SyncWriteStream, decltype(wp.sb)>{ stream, wp.sb, wp.chunked, ec}); if(ec) return; wp.sb.consume(wp.sb.size()); if(! result) { detail::writef_lambda wf{ stream, wp.chunked, ec}; for(;;) { result = wp.w.write(ec, wf); if(ec) return; if(result) break; } } if(wp.chunked) { // VFALCO Unfortunately the current interface to the // Writer concept prevents us from using coalescing the // final body chunk with the final chunk delimiter. // // write final chunk boost::asio::write(stream, chunk_encode_final(), ec); if(ec) return; } if(wp.close) { // VFALCO TODO Decide on an error code ec = boost::asio::error::eof; } } template typename async_completion< WriteHandler, void(error_code)>::result_type async_write(AsyncWriteStream& stream, message const& msg, WriteHandler&& handler) { static_assert(is_AsyncWriteStream::value, "AsyncWriteStream requirements not met"); static_assert(is_Body::value, "Body requirements not met"); static_assert(has_writer::value, "Body has no writer"); static_assert(is_Writer>::value, "Writer requirements not met"); beast::async_completion completion{handler}; detail::write_op{completion.handler, stream, msg}; return completion.result.get(); } //------------------------------------------------------------------------------ template std::ostream& operator<<(std::ostream& os, header const& msg) { beast::detail::sync_ostream oss{os}; error_code ec; write(oss, msg, ec); if(ec) throw system_error{ec}; return os; } template std::ostream& operator<<(std::ostream& os, message const& msg) { static_assert(is_Body::value, "Body requirements not met"); static_assert(has_writer::value, "Body has no writer"); static_assert(is_Writer>::value, "Writer requirements not met"); beast::detail::sync_ostream oss{os}; error_code ec; write(oss, msg, ec); if(ec && ec != boost::asio::error::eof) throw system_error{ec}; return os; } } // http } // beast #endif