forked from boostorg/beast
New buffer sequence classes are provided to allow full
control over the serialization of chunk-encoded message
payloads:
* chunk_header
A ConstBufferSequence representing the chunk header.
It includes a hexadecimal-encoded size, an optional
set of chunk extensions, and the trailing CRLF
required to denote the end of the chunk header.
This allows the caller to manually serialize the chunk
body in one or more calls to a stream output function.
The caller must also output an object of type `chunk_crlf`
to the stream to indicate the end of the chunk body.
* chunk_crlf
A small ConstBufferSequence that simply represents
the two character sequence "\r\n" (CRLF). This is needed
for the case where the caller wants to output a chunk
body as a series of buffers (i.e. "chunking a chunk").
* chunk_body
A ConstBufferSequence representing a complete chunk.
This includes the size, an optional set of chunk extensions,
a caller provided buffer containing the body, and the
required CRLF that follows.
* chunk_final
A ConstBufferSequence representing a final chunk. It
includes an optional set of caller-provided field trailers
* chunk_extensions
A container for building a set of chunk extensions to use
during serialization. The use of the container is optional,
callers may provide their own buffer containing a correctly
formatted set of chunk extensions, or they may use their
own convenience container which meets the requirements.
The basic_fields container is modified to allow construction
outside the context of a message. The container can be used
to provide trailers to `chunk_final`.
Actions Required:
* Remove references to ChunkDecorators. Use the new chunk-encoding
buffer sequences to manually produce a chunked payload body in
the case where control over the chunk-extensions and/or trailers
is required.
582 lines
14 KiB
C++
582 lines
14 KiB
C++
//
|
|
// 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 <beast/core/async_result.hpp>
|
|
#include <beast/core/bind_handler.hpp>
|
|
#include <beast/core/type_traits.hpp>
|
|
#include <beast/core/detail/clamp.hpp>
|
|
#include <beast/http/serializer.hpp>
|
|
#include <boost/asio/basic_stream_socket.hpp>
|
|
#include <boost/asio/handler_alloc_hook.hpp>
|
|
#include <boost/asio/handler_continuation_hook.hpp>
|
|
#include <boost/asio/handler_invoke_hook.hpp>
|
|
#include <boost/asio/windows/overlapped_ptr.hpp>
|
|
#include <boost/make_unique.hpp>
|
|
#include <boost/smart_ptr/make_shared_array.hpp>
|
|
#include <boost/detail/winapi/basic_types.hpp>
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
|
|
namespace beast {
|
|
namespace http {
|
|
|
|
namespace detail {
|
|
template<class, class, bool, class>
|
|
class write_some_win32_op;
|
|
} // detail
|
|
|
|
template<>
|
|
struct basic_file_body<file_win32>
|
|
{
|
|
using file_type = file_win32;
|
|
|
|
class reader;
|
|
class writer;
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
class value_type
|
|
{
|
|
friend class reader;
|
|
friend class writer;
|
|
friend struct basic_file_body<file_win32>;
|
|
|
|
template<class, class, bool, class>
|
|
friend class detail::write_some_win32_op;
|
|
template<
|
|
class Protocol, bool isRequest, class Fields>
|
|
friend
|
|
void
|
|
write_some(
|
|
boost::asio::basic_stream_socket<Protocol>& sock,
|
|
serializer<isRequest,
|
|
basic_file_body<file_win32>, Fields>& 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<class, class, bool, class>
|
|
friend class detail::write_some_win32_op;
|
|
template<
|
|
class Protocol, bool isRequest, class Fields>
|
|
friend
|
|
void
|
|
write_some(
|
|
boost::asio::basic_stream_socket<Protocol>& sock,
|
|
serializer<isRequest,
|
|
basic_file_body<file_win32>, Fields>& 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<bool isRequest, class Fields>
|
|
reader(message<isRequest,
|
|
basic_file_body<file_win32>, Fields>& m)
|
|
: body_(m.body)
|
|
{
|
|
}
|
|
|
|
void
|
|
init(error_code&)
|
|
{
|
|
BOOST_ASSERT(body_.file_.is_open());
|
|
pos_ = body_.first_;
|
|
}
|
|
|
|
boost::optional<std::pair<const_buffers_type, bool>>
|
|
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<bool isRequest, class Fields>
|
|
explicit
|
|
writer(message<isRequest, basic_file_body, Fields>& 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<class ConstBufferSequence>
|
|
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<void const*>(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<file_win32>::
|
|
value_type::
|
|
close()
|
|
{
|
|
error_code ignored;
|
|
file_.close(ignored);
|
|
}
|
|
|
|
inline
|
|
void
|
|
basic_file_body<file_win32>::
|
|
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<file_win32>::
|
|
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<class Unsigned>
|
|
inline
|
|
boost::detail::winapi::DWORD_
|
|
lowPart(Unsigned n)
|
|
{
|
|
return static_cast<
|
|
boost::detail::winapi::DWORD_>(
|
|
n & 0xffffffff);
|
|
}
|
|
|
|
template<class Unsigned>
|
|
inline
|
|
boost::detail::winapi::DWORD_
|
|
highPart(Unsigned n, std::true_type)
|
|
{
|
|
return static_cast<
|
|
boost::detail::winapi::DWORD_>(
|
|
(n>>32)&0xffffffff);
|
|
}
|
|
|
|
template<class Unsigned>
|
|
inline
|
|
boost::detail::winapi::DWORD_
|
|
highPart(Unsigned, std::false_type)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
template<class Unsigned>
|
|
inline
|
|
boost::detail::winapi::DWORD_
|
|
highPart(Unsigned n)
|
|
{
|
|
return highPart(n, std::integral_constant<
|
|
bool, (sizeof(Unsigned)>4)>{});
|
|
}
|
|
|
|
class null_lambda
|
|
{
|
|
public:
|
|
template<class ConstBufferSequence>
|
|
void
|
|
operator()(error_code&,
|
|
ConstBufferSequence const&) const
|
|
{
|
|
BOOST_ASSERT(false);
|
|
}
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#if BOOST_ASIO_HAS_WINDOWS_OVERLAPPED_PTR
|
|
|
|
template<
|
|
class Protocol, class Handler,
|
|
bool isRequest, class Fields>
|
|
class write_some_win32_op
|
|
{
|
|
boost::asio::basic_stream_socket<Protocol>& sock_;
|
|
serializer<isRequest,
|
|
basic_file_body<file_win32>, Fields>& 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<class DeducedHandler>
|
|
write_some_win32_op(
|
|
DeducedHandler&& h,
|
|
boost::asio::basic_stream_socket<Protocol>& s,
|
|
serializer<isRequest,
|
|
basic_file_body<file_win32>,Fields>& sr)
|
|
: sock_(s)
|
|
, sr_(sr)
|
|
, h_(std::forward<DeducedHandler>(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<class Function>
|
|
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<
|
|
class Protocol, class Handler,
|
|
bool isRequest, class Fields>
|
|
void
|
|
write_some_win32_op<
|
|
Protocol, Handler, isRequest, Fields>::
|
|
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<boost::detail::winapi::DWORD_>(
|
|
beast::detail::clamp(std::min<std::uint64_t>(
|
|
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<int>(
|
|
boost::detail::winapi::GetLastError()),
|
|
system_category()}, 0);
|
|
return;
|
|
}
|
|
overlapped.release();
|
|
}
|
|
|
|
template<
|
|
class Protocol, class Handler,
|
|
bool isRequest, class Fields>
|
|
void
|
|
write_some_win32_op<
|
|
Protocol, Handler, isRequest, Fields>::
|
|
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<class Protocol, bool isRequest, class Fields>
|
|
void
|
|
write_some(
|
|
boost::asio::basic_stream_socket<Protocol>& sock,
|
|
serializer<isRequest,
|
|
basic_file_body<file_win32>, Fields>& 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<boost::detail::winapi::DWORD_>(
|
|
beast::detail::clamp(std::min<std::uint64_t>(
|
|
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<int>(
|
|
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 WriteHandler>
|
|
async_return_type<WriteHandler, void(error_code)>
|
|
async_write_some(
|
|
boost::asio::basic_stream_socket<Protocol>& sock,
|
|
serializer<isRequest,
|
|
basic_file_body<file_win32>, Fields>& sr,
|
|
WriteHandler&& handler)
|
|
{
|
|
async_completion<WriteHandler,
|
|
void(error_code)> init{handler};
|
|
detail::write_some_win32_op<Protocol, handler_type<
|
|
WriteHandler, void(error_code)>, isRequest, Fields>{
|
|
init.completion_handler, sock, sr}();
|
|
return init.result.get();
|
|
}
|
|
|
|
#endif
|
|
|
|
} // http
|
|
} // beast
|
|
|
|
#endif
|
|
|
|
#endif
|