// // Copyright (c) 2013-2016 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 #include namespace beast { namespace http { namespace detail { template void write_firstline(DynamicBuffer& dynabuf, message_v1 const& msg) { write(dynabuf, msg.method); write(dynabuf, " "); write(dynabuf, msg.url); write(dynabuf, " HTTP/"); write(dynabuf, msg.version / 10); write(dynabuf, "."); write(dynabuf, msg.version % 10); write(dynabuf, "\r\n"); } template void write_firstline(DynamicBuffer& dynabuf, message_v1 const& msg) { write(dynabuf, "HTTP/"); write(dynabuf, msg.version / 10); write(dynabuf, "."); write(dynabuf, msg.version % 10); write(dynabuf, " "); 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"); } } template struct write_preparation { using headers_type = basic_headers>; message_v1 const& msg; typename Body::writer w; streambuf sb; bool chunked; bool close; explicit write_preparation( message_v1 const& msg_) : msg(msg_) , w(msg) , chunked(token_list{ msg.headers["Transfer-Encoding"]}.exists("chunked")) , close(token_list{ msg.headers["Connection"]}.exists("close") || (msg.version < 11 && ! msg.headers.exists( "Content-Length"))) { } void init(error_code& ec) { w.init(ec); if(ec) return; write_firstline(sb, msg); write_fields(sb, msg.headers); beast::write(sb, "\r\n"); } }; template class write_op { using alloc_type = handler_alloc; struct data { Stream& s; // VFALCO How do we use handler_alloc in write_preparation? write_preparation< isRequest, Body, Headers> wp; Handler h; resume_context resume; resume_context copy; bool cont; int state = 0; template data(DeducedHandler&& h_, Stream& s_, message_v1 const& m_) : s(s_) , wp(m_) , h(std::forward(h_)) , cont(boost_asio_handler_cont_helpers:: is_continuation(h)) { } }; class writef0_lambda { write_op& self_; public: explicit writef0_lambda(write_op& self) : self_(self) { } template void operator()(ConstBufferSequence const& buffers) { auto& d = *self_.d_; // write headers and body if(d.wp.chunked) boost::asio::async_write(d.s, buffer_cat(d.wp.sb.data(), detail::chunk_encode(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) { auto& d = *self_.d_; // write body if(d.wp.chunked) boost::asio::async_write(d.s, detail::chunk_encode(buffers), std::move(self_)); else boost::asio::async_write(d.s, buffers, std::move(self_)); } }; std::shared_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::allocate_shared(alloc_type{h}, std::forward(h), s, std::forward(args)...)) { auto& d = *d_; auto sp = d_; d.resume = { [sp]() mutable { write_op self(std::move(sp)); self.d_->cont = false; auto& ios = self.d_->s.get_io_service(); ios.dispatch(bind_handler(std::move(self), error_code{}, 0, false)); }}; d.copy = d.resume; (*this)(error_code{}, 0, false); } explicit write_op(std::shared_ptr d) : d_(std::move(d)) { } 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 boost_asio_handler_alloc_helpers:: allocate(size, op->d_->h); } friend void asio_handler_deallocate( void* p, std::size_t size, write_op* op) { return boost_asio_handler_alloc_helpers:: deallocate(p, size, op->d_->h); } 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 boost_asio_handler_invoke_helpers:: invoke(f, op->d_->h); } }; 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( std::move(d.copy), 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(boost::indeterminate(result)) { // suspend d.copy = d.resume; return; } if(result) d.state = d.wp.chunked ? 4 : 5; else d.state = 2; return; } // sent headers and body case 2: d.wp.sb.consume(d.wp.sb.size()); d.state = 3; break; case 3: { auto const result = d.wp.w( std::move(d.copy), ec, writef_lambda{*this}); if(ec) { // call handler d.state = 99; break; } if(boost::indeterminate(result)) { // suspend d.copy = d.resume; return; } 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, detail::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.h(ec); d.resume = {}; d.copy = {}; } 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) { // write headers and body if(chunked_) boost::asio::write(stream_, buffer_cat( sb_.data(), detail::chunk_encode(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) { // write body if(chunked_) boost::asio::write(stream_, detail::chunk_encode(buffers), ec_); else boost::asio::write(stream_, buffers, ec_); } }; } // detail //------------------------------------------------------------------------------ template void write(SyncWriteStream& stream, message_v1 const& msg) { static_assert(is_SyncWriteStream::value, "SyncWriteStream requirements not met"); static_assert(is_WritableBody::value, "WritableBody requirements not met"); error_code ec; write(stream, msg, ec); if(ec) throw system_error{ec}; } template void write(SyncWriteStream& stream, message_v1 const& msg, boost::system::error_code& ec) { static_assert(is_SyncWriteStream::value, "SyncWriteStream requirements not met"); static_assert(is_WritableBody::value, "WritableBody requirements not met"); detail::write_preparation wp(msg); wp.init(ec); if(ec) return; std::mutex m; std::condition_variable cv; bool ready = false; resume_context resume{ [&] { std::lock_guard lock(m); ready = true; cv.notify_one(); }}; auto copy = resume; boost::tribool result = wp.w(std::move(copy), ec, detail::writef0_lambda{stream, wp.sb, wp.chunked, ec}); if(ec) return; if(boost::indeterminate(result)) { copy = resume; { std::unique_lock lock(m); cv.wait(lock, [&]{ return ready; }); ready = false; } boost::asio::write(stream, wp.sb.data(), ec); if(ec) return; result = false; } wp.sb.consume(wp.sb.size()); if(! result) { for(;;) { result = wp.w(std::move(copy), ec, detail::writef_lambda{ stream, wp.chunked, ec}); if(ec) return; if(result) break; if(! result) continue; copy = resume; std::unique_lock lock(m); cv.wait(lock, [&]{ return ready; }); ready = false; } } 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, detail::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_v1 const& msg, WriteHandler&& handler) { static_assert(is_AsyncWriteStream::value, "AsyncWriteStream requirements not met"); static_assert(is_WritableBody::value, "WritableBody requirements not met"); beast::async_completion completion(handler); detail::write_op{completion.handler, stream, msg}; return completion.result.get(); } namespace detail { class ostream_SyncStream { std::ostream& os_; public: ostream_SyncStream(std::ostream& os) : os_(os) { } template std::size_t write_some(ConstBufferSequence const& buffers) { error_code ec; auto const n = write_some(buffers, ec); if(ec) throw system_error{ec}; return n; } template std::size_t write_some(ConstBufferSequence const& buffers, error_code& ec) { std::size_t n = 0; using boost::asio::buffer_cast; using boost::asio::buffer_size; for(auto const& buffer : buffers) { os_.write(buffer_cast(buffer), buffer_size(buffer)); if(os_.fail()) { ec = boost::system::errc::make_error_code( boost::system::errc::no_stream_resources); break; } n += buffer_size(buffer); } return n; } }; } // detail template std::ostream& operator<<(std::ostream& os, message_v1 const& msg) { static_assert(is_WritableBody::value, "WritableBody requirements not met"); detail::ostream_SyncStream 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