From f657057c49a9ea0d1cc63df9c58fbe0485ac9f46 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 7 Jul 2017 09:03:06 -0700 Subject: [PATCH] Add file_body_win32: basic_file_body is specialized for file_win32: When serializing file bodies to stream sockets on Windows, the native platform function ::TransmitFile is used, improving performance. --- CHANGELOG.md | 1 + include/beast/http/file_body.hpp | 2 + include/beast/http/impl/file_body_win32.ipp | 579 ++++++++++++++++++++ include/beast/http/impl/write.ipp | 32 +- include/beast/http/serializer.hpp | 18 +- 5 files changed, 622 insertions(+), 10 deletions(-) create mode 100644 include/beast/http/impl/file_body_win32.ipp diff --git a/CHANGELOG.md b/CHANGELOG.md index a4859fef..d71095f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Version 76: * Add serializer::chunked * Serializer members are not const * serializing file_body is not const +* Add file_body_win32 API Changes: diff --git a/include/beast/http/file_body.hpp b/include/beast/http/file_body.hpp index bd2753b7..50e7d966 100644 --- a/include/beast/http/file_body.hpp +++ b/include/beast/http/file_body.hpp @@ -516,4 +516,6 @@ using file_body = basic_file_body; } // http } // beast +#include + #endif diff --git a/include/beast/http/impl/file_body_win32.ipp b/include/beast/http/impl/file_body_win32.ipp new file mode 100644 index 00000000..03734339 --- /dev/null +++ b/include/beast/http/impl/file_body_win32.ipp @@ -0,0 +1,579 @@ +// +// 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_FILE_BODY_WIN32_IPP +#define BEAST_HTTP_IMPL_FILE_BODY_WIN32_IPP + +#if BEAST_USE_WIN32_FILE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { +template +class write_some_win32_op; +} // detail + +template<> +struct basic_file_body +{ + using file_type = file_win32; + + class reader; + class writer; + + //-------------------------------------------------------------------------- + + class value_type + { + friend class reader; + friend class writer; + friend struct basic_file_body; + + template + friend class detail::write_some_win32_op; + template + friend + void + write_some( + boost::asio::basic_stream_socket& sock, + serializer, + Fields, Decorator>& sr, + error_code& ec); + + file_win32 file_; + std::uint64_t size_ = 0; // cached file size + std::uint64_t first_; // starting offset of the range + std::uint64_t last_; // ending offset of the range + + public: + ~value_type() = default; + value_type() = default; + value_type(value_type&& other) = default; + value_type& operator=(value_type&& other) = default; + + bool + is_open() const + { + return file_.is_open(); + } + + std::uint64_t + size() const + { + return size_; + } + + void + close(); + + void + open(char const* path, file_mode mode, error_code& ec); + + void + reset(file_win32&& file, error_code& ec); + }; + + //-------------------------------------------------------------------------- + + class reader + { + template + friend class detail::write_some_win32_op; + template + friend + void + write_some( + boost::asio::basic_stream_socket& sock, + serializer, + Fields, Decorator>& sr, + error_code& ec); + + value_type& body_; // The body we are reading from + std::uint64_t pos_; // The current position in the file + char buf_[4096]; // Small buffer for reading + + public: + using const_buffers_type = + boost::asio::const_buffers_1; + + template + reader(message, Fields>& m) + : body_(m.body) + { + } + + void + init(error_code&) + { + BOOST_ASSERT(body_.file_.is_open()); + pos_ = body_.first_; + } + + boost::optional> + get(error_code& ec) + { + std::size_t const n = (std::min)(sizeof(buf_), + beast::detail::clamp(body_.last_ - pos_)); + if(n == 0) + { + ec.assign(0, ec.category()); + return boost::none; + } + auto const nread = body_.file_.read(buf_, n, ec); + if(ec) + return boost::none; + BOOST_ASSERT(nread != 0); + pos_ += nread; + ec.assign(0, ec.category()); + return {{ + {buf_, nread}, // buffer to return. + pos_ < body_.last_}}; // `true` if there are more buffers. + } + }; + + //-------------------------------------------------------------------------- + + class writer + { + value_type& body_; + + public: + template + explicit + writer(message& m) + : body_(m.body) + { + } + + void + init(boost::optional< + std::uint64_t> const& content_length, + error_code& ec) + { + // VFALCO We could reserve space in the file + boost::ignore_unused(content_length); + BOOST_ASSERT(body_.file_.is_open()); + ec.assign(0, ec.category()); + } + + template + std::size_t + put(ConstBufferSequence const& buffers, + error_code& ec) + { + std::size_t nwritten = 0; + for(boost::asio::const_buffer buffer : buffers) + { + nwritten += body_.file_.write( + boost::asio::buffer_cast(buffer), + boost::asio::buffer_size(buffer), + ec); + if(ec) + return nwritten; + } + ec.assign(0, ec.category()); + return nwritten; + } + + void + finish(error_code& ec) + { + ec.assign(0, ec.category()); + } + }; + + //-------------------------------------------------------------------------- + + static + std::uint64_t + size(value_type const& body) + { + return body.size(); + } +}; + +//------------------------------------------------------------------------------ + +inline +void +basic_file_body:: +value_type:: +close() +{ + error_code ignored; + file_.close(ignored); +} + +inline +void +basic_file_body:: +value_type:: +open(char const* path, file_mode mode, error_code& ec) +{ + file_.open(path, mode, ec); + if(ec) + return; + size_ = file_.size(ec); + if(ec) + { + close(); + return; + } + first_ = 0; + last_ = size_; +} + +inline +void +basic_file_body:: +value_type:: +reset(file_win32&& file, error_code& ec) +{ + if(file_.is_open()) + { + error_code ignored; + file_.close(ignored); + } + file_ = std::move(file); + if(file_.is_open()) + { + size_ = file_.size(ec); + if(ec) + { + close(); + return; + } + first_ = 0; + last_ = size_; + } +} + +//------------------------------------------------------------------------------ + +namespace detail { + +template +inline +boost::detail::winapi::DWORD_ +lowPart(Unsigned n) +{ + return static_cast< + boost::detail::winapi::DWORD_>( + n & 0xffffffff); +} + +template +inline +boost::detail::winapi::DWORD_ +highPart(Unsigned n, std::true_type) +{ + return static_cast< + boost::detail::winapi::DWORD_>( + (n>>32)&0xffffffff); +} + +template +inline +boost::detail::winapi::DWORD_ +highPart(Unsigned, std::false_type) +{ + return 0; +} + +template +inline +boost::detail::winapi::DWORD_ +highPart(Unsigned n) +{ + return highPart(n, std::integral_constant< + bool, (sizeof(Unsigned)>4)>{}); +} + +class null_lambda +{ +public: + template + void + operator()(error_code&, + ConstBufferSequence const&) const + { + BOOST_ASSERT(false); + } +}; + +//------------------------------------------------------------------------------ + +#if BOOST_ASIO_HAS_WINDOWS_OVERLAPPED_PTR + +template +class write_some_win32_op +{ + boost::asio::basic_stream_socket& sock_; + serializer, + Fields, Decorator>& sr_; + bool header_ = false; + Handler h_; + +public: + write_some_win32_op(write_some_win32_op&&) = default; + write_some_win32_op(write_some_win32_op const&) = default; + + template + write_some_win32_op( + DeducedHandler&& h, + boost::asio::basic_stream_socket& s, + serializer, + Fields, Decorator>& sr) + : sock_(s) + , sr_(sr) + , h_(std::forward(h)) + { + } + + void + operator()(); + + void + operator()(error_code ec, + std::size_t bytes_transferred = 0); + + friend + void* asio_handler_allocate( + std::size_t size, write_some_win32_op* op) + { + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->h_)); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, write_some_win32_op* op) + { + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->h_)); + } + + friend + bool asio_handler_is_continuation(write_some_win32_op* op) + { + using boost::asio::asio_handler_is_continuation; + return asio_handler_is_continuation( + std::addressof(op->h_)); + } + + template + friend + void asio_handler_invoke(Function&& f, write_some_win32_op* op) + { + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->h_)); + } +}; + +template +void +write_some_win32_op< + Protocol, Handler, isRequest, Fields, Decorator>:: +operator()() +{ + if(! sr_.is_header_done()) + { + header_ = true; + sr_.split(true); + return detail::async_write_some( + sock_, sr_, std::move(*this)); + } + if(sr_.chunked()) + { + return detail::async_write_some( + sock_, sr_, std::move(*this)); + } + auto& r = sr_.reader_impl(); + boost::detail::winapi::DWORD_ const nNumberOfBytesToWrite = + std::min( + beast::detail::clamp(std::min( + r.body_.last_ - r.pos_, sr_.limit())), + 2147483646); + boost::asio::windows::overlapped_ptr overlapped{ + sock_.get_io_service(), *this}; + auto& ov = *overlapped.get(); + ov.Offset = lowPart(r.pos_); + ov.OffsetHigh = highPart(r.pos_); + auto const bSuccess = ::TransmitFile( + sock_.native_handle(), + sr_.get().body.file_.native_handle(), + nNumberOfBytesToWrite, + 0, + overlapped.get(), + nullptr, + 0); + auto const dwError = ::GetLastError(); + if(! bSuccess && dwError != + boost::detail::winapi::ERROR_IO_PENDING_) + { + // completed immediately + overlapped.complete(error_code{static_cast( + boost::detail::winapi::GetLastError()), + system_category()}, 0); + return; + } + overlapped.release(); +} + +template +void +write_some_win32_op< + Protocol, Handler,isRequest, Fields, Decorator>:: +operator()(error_code ec, std::size_t bytes_transferred) +{ + if(! ec) + { + if(header_) + { + header_ = false; + return (*this)(); + } + auto& r = sr_.reader_impl(); + r.pos_ += bytes_transferred; + BOOST_ASSERT(r.pos_ <= r.body_.last_); + if(r.pos_ >= r.body_.last_) + { + sr_.next(ec, null_lambda{}); + BOOST_ASSERT(! ec); + BOOST_ASSERT(sr_.is_done()); + if(! sr_.keep_alive()) + ec = error::end_of_stream; + } + } + h_(ec); +} + +#endif + +} // detail + +//------------------------------------------------------------------------------ + +template +void +write_some( + boost::asio::basic_stream_socket& sock, + serializer, + Fields, Decorator>& sr, + error_code& ec) +{ + if(! sr.is_header_done()) + { + sr.split(true); + detail::write_some(sock, sr, ec); + if(ec) + return; + return; + } + if(sr.chunked()) + { + detail::write_some(sock, sr, ec); + if(ec) + return; + return; + } + auto& r = sr.reader_impl(); + r.body_.file_.seek(r.pos_, ec); + if(ec) + return; + boost::detail::winapi::DWORD_ const nNumberOfBytesToWrite = + std::min( + beast::detail::clamp(std::min( + r.body_.last_ - r.pos_, sr.limit())), + 2147483646); + auto const bSuccess = ::TransmitFile( + sock.native_handle(), + r.body_.file_.native_handle(), + nNumberOfBytesToWrite, + 0, + nullptr, + nullptr, + 0); + if(! bSuccess) + { + ec.assign(static_cast( + boost::detail::winapi::GetLastError()), + system_category()); + return; + } + r.pos_ += nNumberOfBytesToWrite; + BOOST_ASSERT(r.pos_ <= r.body_.last_); + if(r.pos_ < r.body_.last_) + { + ec.assign(0, ec.category()); + } + else + { + sr.next(ec, detail::null_lambda{}); + BOOST_ASSERT(! ec); + BOOST_ASSERT(sr.is_done()); + if(! sr.keep_alive()) + ec = error::end_of_stream; + } +} + +#if BOOST_ASIO_HAS_WINDOWS_OVERLAPPED_PTR + +template< + class Protocol, + bool isRequest, class Fields, class Decorator, + class WriteHandler> +async_return_type +async_write_some( + boost::asio::basic_stream_socket& sock, + serializer, + Fields, Decorator>& sr, + WriteHandler&& handler) +{ + async_completion init{handler}; + detail::write_some_win32_op, isRequest, Fields, + Decorator>{init.completion_handler, sock, sr}(); + return init.result.get(); +} + +#endif + +} // http +} // beast + +#endif + +#endif diff --git a/include/beast/http/impl/write.ipp b/include/beast/http/impl/write.ipp index 66da2d17..1060a4f0 100644 --- a/include/beast/http/impl/write.ipp +++ b/include/beast/http/impl/write.ipp @@ -285,7 +285,8 @@ operator()(error_code ec) bind_handler(std::move(*this), ec)); } state_ = 2; - return async_write_some(s_, sr_, std::move(*this)); + return beast::http::async_write_some( + s_, sr_, std::move(*this)); } case 1: @@ -299,7 +300,8 @@ operator()(error_code ec) { if(Predicate{}(sr_)) goto upcall; - return async_write_some(s_, sr_, std::move(*this)); + return beast::http::async_write_some( + s_, sr_, std::move(*this)); } } upcall: @@ -490,6 +492,23 @@ write_some( ec.assign(0, ec.category()); } +template +async_return_type +async_write_some(AsyncWriteStream& stream, serializer< + isRequest, Body, Fields, Decorator>& sr, + WriteHandler&& handler) +{ + async_completion init{handler}; + detail::write_some_op, + isRequest, Body, Fields, Decorator>{ + init.completion_handler, stream, sr}(); + return init.result.get(); +} + } // detail template::value, "BodyReader requirements not met"); - async_completion init{handler}; - detail::write_some_op, - isRequest, Body, Fields, Decorator>{ - init.completion_handler, stream, sr}(); - return init.result.get(); + return detail::async_write_some(stream, sr, + std::forward(handler)); } //------------------------------------------------------------------------------ diff --git a/include/beast/http/serializer.hpp b/include/beast/http/serializer.hpp index d69bf711..34bf1d83 100644 --- a/include/beast/http/serializer.hpp +++ b/include/beast/http/serializer.hpp @@ -314,6 +314,22 @@ public: return m_; } + /** Provides access to the associated @b BodyReader + + This function provides access to the instance of the reader + associated with the body and created by the serializer + upon construction. The behavior of accessing this object + is defined by the specification of the particular reader + and its associated body. + + @return A reference to the reader. + */ + reader& + reader_impl() + { + return rd_; + } + /// Returns the serialized buffer size limit std::size_t limit() @@ -335,7 +351,7 @@ public: void limit(std::size_t limit) { - limit_ = limit > 0 ? limit: + limit_ = limit > 0 ? limit : (std::numeric_limits::max)(); }