mirror of
https://github.com/boostorg/beast.git
synced 2025-07-31 21:34:46 +02:00
Add basic_file_body.hpp
This commit is contained in:
@@ -4,6 +4,7 @@ Version 80:
|
|||||||
* Add basic_dynamic_body.hpp
|
* Add basic_dynamic_body.hpp
|
||||||
* Shrink buffer_prefix_view
|
* Shrink buffer_prefix_view
|
||||||
* Remove unused file_path
|
* Remove unused file_path
|
||||||
|
* Add basic_file_body.hpp
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@@ -84,7 +84,7 @@
|
|||||||
[import ../example/http-client/http_client.cpp]
|
[import ../example/http-client/http_client.cpp]
|
||||||
[import ../example/websocket-client/websocket_client.cpp]
|
[import ../example/websocket-client/websocket_client.cpp]
|
||||||
|
|
||||||
[import ../include/beast/http/file_body.hpp]
|
[import ../include/beast/http/basic_file_body.hpp]
|
||||||
|
|
||||||
[import ../test/exemplars.cpp]
|
[import ../test/exemplars.cpp]
|
||||||
[import ../test/core/doc_snippets.cpp]
|
[import ../test/core/doc_snippets.cpp]
|
||||||
|
@@ -146,6 +146,8 @@ meet the requirements, or use the ones that come with the library:
|
|||||||
]]
|
]]
|
||||||
[[
|
[[
|
||||||
[link beast.ref.beast__http__file_body `file_body`]
|
[link beast.ref.beast__http__file_body `file_body`]
|
||||||
|
|
||||||
|
[link beast.ref.beast__http__basic_file_body `basic_file_body`]
|
||||||
][
|
][
|
||||||
This body is represented by a file opened for either reading or
|
This body is represented by a file opened for either reading or
|
||||||
writing. Messages with this body may be serialized and parsed.
|
writing. Messages with this body may be serialized and parsed.
|
||||||
|
@@ -30,14 +30,15 @@
|
|||||||
<bridgehead renderas="sect3">Classes</bridgehead>
|
<bridgehead renderas="sect3">Classes</bridgehead>
|
||||||
<simplelist type="vert" columns="1">
|
<simplelist type="vert" columns="1">
|
||||||
<member><link linkend="beast.ref.beast__http__basic_dynamic_body">basic_dynamic_body</link></member>
|
<member><link linkend="beast.ref.beast__http__basic_dynamic_body">basic_dynamic_body</link></member>
|
||||||
<member><link linkend="beast.ref.beast__http__basic_file_body">basic_file_body</link></member>
|
|
||||||
<member><link linkend="beast.ref.beast__http__basic_fields">basic_fields</link></member>
|
<member><link linkend="beast.ref.beast__http__basic_fields">basic_fields</link></member>
|
||||||
|
<member><link linkend="beast.ref.beast__http__basic_file_body">basic_file_body</link></member>
|
||||||
<member><link linkend="beast.ref.beast__http__basic_parser">basic_parser</link></member>
|
<member><link linkend="beast.ref.beast__http__basic_parser">basic_parser</link></member>
|
||||||
<member><link linkend="beast.ref.beast__http__basic_string_body">basic_string_body</link></member>
|
<member><link linkend="beast.ref.beast__http__basic_string_body">basic_string_body</link></member>
|
||||||
<member><link linkend="beast.ref.beast__http__buffer_body">buffer_body</link></member>
|
<member><link linkend="beast.ref.beast__http__buffer_body">buffer_body</link></member>
|
||||||
<member><link linkend="beast.ref.beast__http__dynamic_body">dynamic_body</link></member>
|
<member><link linkend="beast.ref.beast__http__dynamic_body">dynamic_body</link></member>
|
||||||
<member><link linkend="beast.ref.beast__http__empty_body">empty_body</link></member>
|
<member><link linkend="beast.ref.beast__http__empty_body">empty_body</link></member>
|
||||||
<member><link linkend="beast.ref.beast__http__fields">fields</link></member>
|
<member><link linkend="beast.ref.beast__http__fields">fields</link></member>
|
||||||
|
<member><link linkend="beast.ref.beast__http__file_body">file_body</link></member>
|
||||||
<member><link linkend="beast.ref.beast__http__header">header</link></member>
|
<member><link linkend="beast.ref.beast__http__header">header</link></member>
|
||||||
<member><link linkend="beast.ref.beast__http__message">message</link></member>
|
<member><link linkend="beast.ref.beast__http__message">message</link></member>
|
||||||
<member><link linkend="beast.ref.beast__http__parser">parser</link></member>
|
<member><link linkend="beast.ref.beast__http__parser">parser</link></member>
|
||||||
|
528
include/beast/http/basic_file_body.hpp
Normal file
528
include/beast/http/basic_file_body.hpp
Normal file
@@ -0,0 +1,528 @@
|
|||||||
|
//
|
||||||
|
// 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_BASIC_FILE_BODY_HPP
|
||||||
|
#define BEAST_HTTP_BASIC_FILE_BODY_HPP
|
||||||
|
|
||||||
|
#include <beast/config.hpp>
|
||||||
|
#include <beast/core/error.hpp>
|
||||||
|
#include <beast/core/file_base.hpp>
|
||||||
|
#include <beast/core/type_traits.hpp>
|
||||||
|
#include <beast/http/message.hpp>
|
||||||
|
#include <boost/assert.hpp>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace beast {
|
||||||
|
namespace http {
|
||||||
|
|
||||||
|
//[example_http_file_body_1
|
||||||
|
|
||||||
|
/** A message body represented by a file on the filesystem.
|
||||||
|
|
||||||
|
Messages with this type have bodies represented by a
|
||||||
|
file on the file system. When parsing a message using
|
||||||
|
this body type, the data is stored in the file pointed
|
||||||
|
to by the path, which must be writable. When serializing,
|
||||||
|
the implementation will read the file and present those
|
||||||
|
octets as the body content. This may be used to serve
|
||||||
|
content from a directory as part of a web service.
|
||||||
|
|
||||||
|
@tparam File The implementation to use for accessing files.
|
||||||
|
This type must meet the requirements of @b File.
|
||||||
|
*/
|
||||||
|
template<class File>
|
||||||
|
struct basic_file_body
|
||||||
|
{
|
||||||
|
// Make sure the type meets the requirements
|
||||||
|
static_assert(is_file<File>::value,
|
||||||
|
"File requirements not met");
|
||||||
|
|
||||||
|
/// The type of File this body uses
|
||||||
|
using file_type = File;
|
||||||
|
|
||||||
|
// Algorithm for retrieving buffers when serializing.
|
||||||
|
class reader;
|
||||||
|
|
||||||
|
// Algorithm for storing buffers when parsing.
|
||||||
|
class writer;
|
||||||
|
|
||||||
|
// The type of the @ref message::body member.
|
||||||
|
class value_type;
|
||||||
|
|
||||||
|
/** Returns the size of the body
|
||||||
|
|
||||||
|
@param body The file body to use
|
||||||
|
*/
|
||||||
|
static
|
||||||
|
std::uint64_t
|
||||||
|
size(value_type const& body);
|
||||||
|
};
|
||||||
|
|
||||||
|
//]
|
||||||
|
|
||||||
|
//[example_http_file_body_2
|
||||||
|
|
||||||
|
/** The type of the @ref message::body member.
|
||||||
|
|
||||||
|
Messages declared using `basic_file_body` will have this type for
|
||||||
|
the body member. This rich class interface allow the file to be
|
||||||
|
opened with the file handle maintained directly in the object,
|
||||||
|
which is attached to the message.
|
||||||
|
*/
|
||||||
|
template<class File>
|
||||||
|
class basic_file_body<File>::value_type
|
||||||
|
{
|
||||||
|
// This body container holds a handle to the file
|
||||||
|
// when it is open, and also caches the size when set.
|
||||||
|
|
||||||
|
friend class reader;
|
||||||
|
friend class writer;
|
||||||
|
friend struct basic_file_body;
|
||||||
|
|
||||||
|
// This represents the open file
|
||||||
|
File file_;
|
||||||
|
|
||||||
|
// The cached file size
|
||||||
|
std::uint64_t file_size_ = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Destructor.
|
||||||
|
|
||||||
|
If the file is open, it is closed first.
|
||||||
|
*/
|
||||||
|
~value_type() = default;
|
||||||
|
|
||||||
|
/// Constructor
|
||||||
|
value_type() = default;
|
||||||
|
|
||||||
|
/// Constructor
|
||||||
|
value_type(value_type&& other) = default;
|
||||||
|
|
||||||
|
/// Move assignment
|
||||||
|
value_type& operator=(value_type&& other) = default;
|
||||||
|
|
||||||
|
/// Returns `true` if the file is open
|
||||||
|
bool
|
||||||
|
is_open() const
|
||||||
|
{
|
||||||
|
return file_.is_open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the size of the file if open
|
||||||
|
std::uint64_t
|
||||||
|
size() const
|
||||||
|
{
|
||||||
|
return file_size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Close the file if open
|
||||||
|
void
|
||||||
|
close();
|
||||||
|
|
||||||
|
/** Open a file at the given path with the specified mode
|
||||||
|
|
||||||
|
@param path The utf-8 encoded path to the file
|
||||||
|
|
||||||
|
@param mode The file mode to use
|
||||||
|
|
||||||
|
@param ec Set to the error, if any occurred
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
open(char const* path, file_mode mode, error_code& ec);
|
||||||
|
|
||||||
|
/** Set the open file
|
||||||
|
|
||||||
|
This function is used to set the open
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
reset(File&& file, error_code& ec);
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class File>
|
||||||
|
void
|
||||||
|
basic_file_body<File>::
|
||||||
|
value_type::
|
||||||
|
close()
|
||||||
|
{
|
||||||
|
error_code ignored;
|
||||||
|
file_.close(ignored);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class File>
|
||||||
|
void
|
||||||
|
basic_file_body<File>::
|
||||||
|
value_type::
|
||||||
|
open(char const* path, file_mode mode, error_code& ec)
|
||||||
|
{
|
||||||
|
// Open the file
|
||||||
|
file_.open(path, mode, ec);
|
||||||
|
if(ec)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Cache the size
|
||||||
|
file_size_ = file_.size(ec);
|
||||||
|
if(ec)
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class File>
|
||||||
|
void
|
||||||
|
basic_file_body<File>::
|
||||||
|
value_type::
|
||||||
|
reset(File&& file, error_code& ec)
|
||||||
|
{
|
||||||
|
// First close the file if open
|
||||||
|
if(file_.is_open())
|
||||||
|
{
|
||||||
|
error_code ignored;
|
||||||
|
file_.close(ignored);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take ownership of the new file
|
||||||
|
file_ = std::move(file);
|
||||||
|
|
||||||
|
// Cache the size
|
||||||
|
file_size_ = file_.size(ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is called from message::payload_size
|
||||||
|
template<class File>
|
||||||
|
std::uint64_t
|
||||||
|
basic_file_body<File>::
|
||||||
|
size(value_type const& body)
|
||||||
|
{
|
||||||
|
// Forward the call to the body
|
||||||
|
return body.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
//]
|
||||||
|
|
||||||
|
//[example_http_file_body_3
|
||||||
|
|
||||||
|
/** Algorithm for retrieving buffers when serializing.
|
||||||
|
|
||||||
|
Objects of this type are created during serialization
|
||||||
|
to extract the buffers representing the body.
|
||||||
|
*/
|
||||||
|
template<class File>
|
||||||
|
class basic_file_body<File>::reader
|
||||||
|
{
|
||||||
|
value_type& body_; // The body we are reading from
|
||||||
|
std::uint64_t remain_; // The number of unread bytes
|
||||||
|
char buf_[4096]; // Small buffer for reading
|
||||||
|
|
||||||
|
public:
|
||||||
|
// The type of buffer sequence returned by `get`.
|
||||||
|
//
|
||||||
|
using const_buffers_type =
|
||||||
|
boost::asio::const_buffers_1;
|
||||||
|
|
||||||
|
// Constructor.
|
||||||
|
//
|
||||||
|
// `m` holds the message we are sending, which will
|
||||||
|
// always have the `file_body` as the body type.
|
||||||
|
//
|
||||||
|
// Note that the message is passed by non-const reference.
|
||||||
|
// This is intentional, because reading from the file
|
||||||
|
// changes its "current position" which counts makes the
|
||||||
|
// operation logically not-const (although it is bitwise
|
||||||
|
// const).
|
||||||
|
//
|
||||||
|
// The BodyReader concept allows the reader to choose
|
||||||
|
// whether to take the message by const reference or
|
||||||
|
// non-const reference. Depending on the choice, a
|
||||||
|
// serializer constructed using that body type will
|
||||||
|
// require the same const or non-const reference to
|
||||||
|
// construct.
|
||||||
|
//
|
||||||
|
// Readers which accept const messages usually allow
|
||||||
|
// the same body to be serialized by multiple threads
|
||||||
|
// concurrently, while readers accepting non-const
|
||||||
|
// messages may only be serialized by one thread at
|
||||||
|
// a time.
|
||||||
|
//
|
||||||
|
template<bool isRequest, class Fields>
|
||||||
|
reader(message<
|
||||||
|
isRequest, basic_file_body, Fields>& m);
|
||||||
|
|
||||||
|
// Initializer
|
||||||
|
//
|
||||||
|
// This is called before the body is serialized and
|
||||||
|
// gives the reader a chance to do something that might
|
||||||
|
// need to return an error code.
|
||||||
|
//
|
||||||
|
void
|
||||||
|
init(error_code& ec);
|
||||||
|
|
||||||
|
// This function is called zero or more times to
|
||||||
|
// retrieve buffers. A return value of `boost::none`
|
||||||
|
// means there are no more buffers. Otherwise,
|
||||||
|
// the contained pair will have the next buffer
|
||||||
|
// to serialize, and a `bool` indicating whether
|
||||||
|
// or not there may be additional buffers.
|
||||||
|
boost::optional<std::pair<const_buffers_type, bool>>
|
||||||
|
get(error_code& ec);
|
||||||
|
};
|
||||||
|
|
||||||
|
//]
|
||||||
|
|
||||||
|
//[example_http_file_body_4
|
||||||
|
|
||||||
|
// Here we just stash a reference to the path for later.
|
||||||
|
// Rather than dealing with messy constructor exceptions,
|
||||||
|
// we save the things that might fail for the call to `init`.
|
||||||
|
//
|
||||||
|
template<class File>
|
||||||
|
template<bool isRequest, class Fields>
|
||||||
|
basic_file_body<File>::
|
||||||
|
reader::
|
||||||
|
reader(message<isRequest, basic_file_body, Fields>& m)
|
||||||
|
: body_(m.body)
|
||||||
|
{
|
||||||
|
// The file must already be open
|
||||||
|
BOOST_ASSERT(body_.file_.is_open());
|
||||||
|
|
||||||
|
// Get the size of the file
|
||||||
|
remain_ = body_.file_size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializer
|
||||||
|
template<class File>
|
||||||
|
void
|
||||||
|
basic_file_body<File>::
|
||||||
|
reader::
|
||||||
|
init(error_code& ec)
|
||||||
|
{
|
||||||
|
// The error_code specification requires that we
|
||||||
|
// either set the error to some value, or set it
|
||||||
|
// to indicate no error.
|
||||||
|
//
|
||||||
|
// We don't do anything fancy so set "no error"
|
||||||
|
ec.assign(0, ec.category());
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is called repeatedly by the serializer to
|
||||||
|
// retrieve the buffers representing the body. Our strategy
|
||||||
|
// is to read into our buffer and return it until we have
|
||||||
|
// read through the whole file.
|
||||||
|
//
|
||||||
|
template<class File>
|
||||||
|
auto
|
||||||
|
basic_file_body<File>::
|
||||||
|
reader::
|
||||||
|
get(error_code& ec) ->
|
||||||
|
boost::optional<std::pair<const_buffers_type, bool>>
|
||||||
|
{
|
||||||
|
// Calculate the smaller of our buffer size,
|
||||||
|
// or the amount of unread data in the file.
|
||||||
|
auto const amount = remain_ > sizeof(buf_) ?
|
||||||
|
sizeof(buf_) : static_cast<std::size_t>(remain_);
|
||||||
|
|
||||||
|
// Handle the case where the file is zero length
|
||||||
|
if(amount == 0)
|
||||||
|
{
|
||||||
|
// Modify the error code to indicate success
|
||||||
|
// This is required by the error_code specification.
|
||||||
|
//
|
||||||
|
// NOTE We use the existing category instead of calling
|
||||||
|
// into the library to get the generic category because
|
||||||
|
// that saves us a possibly expensive atomic operation.
|
||||||
|
//
|
||||||
|
ec.assign(0, ec.category());
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now read the next buffer
|
||||||
|
auto const nread = body_.file_.read(buf_, amount, ec);
|
||||||
|
if(ec)
|
||||||
|
return boost::none;
|
||||||
|
|
||||||
|
// Make sure there is forward progress
|
||||||
|
BOOST_ASSERT(nread != 0);
|
||||||
|
BOOST_ASSERT(nread <= remain_);
|
||||||
|
|
||||||
|
// Update the amount remaining based on what we got
|
||||||
|
remain_ -= nread;
|
||||||
|
|
||||||
|
// Return the buffer to the caller.
|
||||||
|
//
|
||||||
|
// The second element of the pair indicates whether or
|
||||||
|
// not there is more data. As long as there is some
|
||||||
|
// unread bytes, there will be more data. Otherwise,
|
||||||
|
// we set this bool to `false` so we will not be called
|
||||||
|
// again.
|
||||||
|
//
|
||||||
|
ec.assign(0, ec.category());
|
||||||
|
return {{
|
||||||
|
const_buffers_type{buf_, nread}, // buffer to return.
|
||||||
|
remain_ > 0 // `true` if there are more buffers.
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
//]
|
||||||
|
|
||||||
|
//[example_http_file_body_5
|
||||||
|
|
||||||
|
/** Algorithm for storing buffers when parsing.
|
||||||
|
|
||||||
|
Objects of this type are created during parsing
|
||||||
|
to store incoming buffers representing the body.
|
||||||
|
*/
|
||||||
|
template<class File>
|
||||||
|
class basic_file_body<File>::writer
|
||||||
|
{
|
||||||
|
value_type& body_; // The body we are writing to
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Constructor.
|
||||||
|
//
|
||||||
|
// This is called after the header is parsed and
|
||||||
|
// indicates that a non-zero sized body may be present.
|
||||||
|
// `m` holds the message we are receiving, which will
|
||||||
|
// always have the `file_body` as the body type.
|
||||||
|
//
|
||||||
|
template<bool isRequest, class Fields>
|
||||||
|
explicit
|
||||||
|
writer(
|
||||||
|
message<isRequest, basic_file_body, Fields>& m);
|
||||||
|
|
||||||
|
// Initializer
|
||||||
|
//
|
||||||
|
// This is called before the body is parsed and
|
||||||
|
// gives the writer a chance to do something that might
|
||||||
|
// need to return an error code. It informs us of
|
||||||
|
// the payload size (`content_length`) which we can
|
||||||
|
// optionally use for optimization.
|
||||||
|
//
|
||||||
|
void
|
||||||
|
init(boost::optional<std::uint64_t> const&, error_code& ec);
|
||||||
|
|
||||||
|
// This function is called one or more times to store
|
||||||
|
// buffer sequences corresponding to the incoming body.
|
||||||
|
//
|
||||||
|
template<class ConstBufferSequence>
|
||||||
|
std::size_t
|
||||||
|
put(ConstBufferSequence const& buffers,
|
||||||
|
error_code& ec);
|
||||||
|
|
||||||
|
// This function is called when writing is complete.
|
||||||
|
// It is an opportunity to perform any final actions
|
||||||
|
// which might fail, in order to return an error code.
|
||||||
|
// Operations that might fail should not be attemped in
|
||||||
|
// destructors, since an exception thrown from there
|
||||||
|
// would terminate the program.
|
||||||
|
//
|
||||||
|
void
|
||||||
|
finish(error_code& ec);
|
||||||
|
};
|
||||||
|
|
||||||
|
//]
|
||||||
|
|
||||||
|
//[example_http_file_body_6
|
||||||
|
|
||||||
|
// We don't do much in the writer constructor since the
|
||||||
|
// file is already open.
|
||||||
|
//
|
||||||
|
template<class File>
|
||||||
|
template<bool isRequest, class Fields>
|
||||||
|
basic_file_body<File>::
|
||||||
|
writer::
|
||||||
|
writer(message<isRequest, basic_file_body, Fields>& m)
|
||||||
|
: body_(m.body)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class File>
|
||||||
|
void
|
||||||
|
basic_file_body<File>::
|
||||||
|
writer::
|
||||||
|
init(
|
||||||
|
boost::optional<std::uint64_t> const& content_length,
|
||||||
|
error_code& ec)
|
||||||
|
{
|
||||||
|
// The file must already be open for writing
|
||||||
|
BOOST_ASSERT(body_.file_.is_open());
|
||||||
|
|
||||||
|
// We don't do anything with this but a sophisticated
|
||||||
|
// application might check available space on the device
|
||||||
|
// to see if there is enough room to store the body.
|
||||||
|
boost::ignore_unused(content_length);
|
||||||
|
|
||||||
|
// The error_code specification requires that we
|
||||||
|
// either set the error to some value, or set it
|
||||||
|
// to indicate no error.
|
||||||
|
//
|
||||||
|
// We don't do anything fancy so set "no error"
|
||||||
|
ec.assign(0, ec.category());
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will get called one or more times with body buffers
|
||||||
|
//
|
||||||
|
template<class File>
|
||||||
|
template<class ConstBufferSequence>
|
||||||
|
std::size_t
|
||||||
|
basic_file_body<File>::
|
||||||
|
writer::
|
||||||
|
put(ConstBufferSequence const& buffers, error_code& ec)
|
||||||
|
{
|
||||||
|
// This function must return the total number of
|
||||||
|
// bytes transferred from the input buffers.
|
||||||
|
std::size_t nwritten = 0;
|
||||||
|
|
||||||
|
// Loop over all the buffers in the sequence,
|
||||||
|
// and write each one to the file.
|
||||||
|
for(boost::asio::const_buffer buffer : buffers)
|
||||||
|
{
|
||||||
|
// Write this buffer to the file
|
||||||
|
nwritten += body_.file_.write(
|
||||||
|
boost::asio::buffer_cast<void const*>(buffer),
|
||||||
|
boost::asio::buffer_size(buffer),
|
||||||
|
ec);
|
||||||
|
if(ec)
|
||||||
|
return nwritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indicate success
|
||||||
|
// This is required by the error_code specification
|
||||||
|
ec.assign(0, ec.category());
|
||||||
|
|
||||||
|
return nwritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called after writing is done when there's no error.
|
||||||
|
template<class File>
|
||||||
|
void
|
||||||
|
basic_file_body<File>::
|
||||||
|
writer::
|
||||||
|
finish(error_code& ec)
|
||||||
|
{
|
||||||
|
// This has to be cleared before returning, to
|
||||||
|
// indicate no error. The specification requires it.
|
||||||
|
ec.assign(0, ec.category());
|
||||||
|
}
|
||||||
|
|
||||||
|
//]
|
||||||
|
|
||||||
|
#if ! BEAST_DOXYGEN
|
||||||
|
// operator<< is not supported for file_body
|
||||||
|
template<bool isRequest, class File, class Fields>
|
||||||
|
std::ostream&
|
||||||
|
operator<<(std::ostream& os, message<
|
||||||
|
isRequest, basic_file_body<File>, Fields> const& msg) = delete;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // http
|
||||||
|
} // beast
|
||||||
|
|
||||||
|
#endif
|
@@ -8,11 +8,8 @@
|
|||||||
#ifndef BEAST_HTTP_FILE_BODY_HPP
|
#ifndef BEAST_HTTP_FILE_BODY_HPP
|
||||||
#define BEAST_HTTP_FILE_BODY_HPP
|
#define BEAST_HTTP_FILE_BODY_HPP
|
||||||
|
|
||||||
#include <beast/config.hpp>
|
|
||||||
#include <beast/core/error.hpp>
|
|
||||||
#include <beast/core/file.hpp>
|
#include <beast/core/file.hpp>
|
||||||
#include <beast/core/type_traits.hpp>
|
#include <beast/http/basic_file_body.hpp>
|
||||||
#include <beast/http/message.hpp>
|
|
||||||
#include <boost/assert.hpp>
|
#include <boost/assert.hpp>
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -23,505 +20,9 @@
|
|||||||
namespace beast {
|
namespace beast {
|
||||||
namespace http {
|
namespace http {
|
||||||
|
|
||||||
//[example_http_file_body_1
|
|
||||||
|
|
||||||
/** A message body represented by a file on the filesystem.
|
|
||||||
|
|
||||||
Messages with this type have bodies represented by a
|
|
||||||
file on the file system. When parsing a message using
|
|
||||||
this body type, the data is stored in the file pointed
|
|
||||||
to by the path, which must be writable. When serializing,
|
|
||||||
the implementation will read the file and present those
|
|
||||||
octets as the body content. This may be used to serve
|
|
||||||
content from a directory as part of a web service.
|
|
||||||
|
|
||||||
@tparam File The implementation to use for accessing files.
|
|
||||||
This type must meet the requirements of @b File.
|
|
||||||
*/
|
|
||||||
template<class File>
|
|
||||||
struct basic_file_body
|
|
||||||
{
|
|
||||||
static_assert(is_file<File>::value,
|
|
||||||
"File requirements not met");
|
|
||||||
|
|
||||||
/// The type of File this body uses
|
|
||||||
using file_type = File;
|
|
||||||
|
|
||||||
/** Algorithm for retrieving buffers when serializing.
|
|
||||||
|
|
||||||
Objects of this type are created during serialization
|
|
||||||
to extract the buffers representing the body.
|
|
||||||
*/
|
|
||||||
class reader;
|
|
||||||
|
|
||||||
/** Algorithm for storing buffers when parsing.
|
|
||||||
|
|
||||||
Objects of this type are created during parsing
|
|
||||||
to store incoming buffers representing the body.
|
|
||||||
*/
|
|
||||||
class writer;
|
|
||||||
|
|
||||||
/** The type of the @ref message::body member.
|
|
||||||
|
|
||||||
Messages declared using `basic_file_body` will have this
|
|
||||||
type for the body member. This rich class interface
|
|
||||||
allow the file to be opened with the file handle
|
|
||||||
maintained directly in the object, which is attached
|
|
||||||
to the message.
|
|
||||||
*/
|
|
||||||
class value_type;
|
|
||||||
|
|
||||||
/** Returns the size of the body
|
|
||||||
|
|
||||||
@param v The file body to use
|
|
||||||
*/
|
|
||||||
static
|
|
||||||
std::uint64_t
|
|
||||||
size(value_type const& v);
|
|
||||||
};
|
|
||||||
|
|
||||||
//]
|
|
||||||
|
|
||||||
//[example_http_file_body_2
|
|
||||||
|
|
||||||
// The body container holds a handle to the file when
|
|
||||||
// it is open, and also caches the size when set.
|
|
||||||
//
|
|
||||||
template<class File>
|
|
||||||
class basic_file_body<File>::value_type
|
|
||||||
{
|
|
||||||
friend class reader;
|
|
||||||
friend class writer;
|
|
||||||
friend struct basic_file_body;
|
|
||||||
|
|
||||||
// This represents the open file
|
|
||||||
File file_;
|
|
||||||
|
|
||||||
// The cached file size
|
|
||||||
std::uint64_t file_size_ = 0;
|
|
||||||
|
|
||||||
public:
|
|
||||||
/** Destructor.
|
|
||||||
|
|
||||||
If the file is open, it is closed first.
|
|
||||||
*/
|
|
||||||
~value_type() = default;
|
|
||||||
|
|
||||||
/// Constructor
|
|
||||||
value_type() = default;
|
|
||||||
|
|
||||||
/// Constructor
|
|
||||||
value_type(value_type&& other) = default;
|
|
||||||
|
|
||||||
/// Move assignment
|
|
||||||
value_type& operator=(value_type&& other) = default;
|
|
||||||
|
|
||||||
/// Returns `true` if the file is open
|
|
||||||
bool
|
|
||||||
is_open() const
|
|
||||||
{
|
|
||||||
return file_.is_open();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the size of the file if open
|
|
||||||
std::uint64_t
|
|
||||||
size() const
|
|
||||||
{
|
|
||||||
return file_size_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Close the file if open
|
|
||||||
void
|
|
||||||
close();
|
|
||||||
|
|
||||||
/** Open a file at the given path with the specified mode
|
|
||||||
|
|
||||||
@param path The utf-8 encoded path to the file
|
|
||||||
|
|
||||||
@param mode The file mode to use
|
|
||||||
|
|
||||||
@param ec Set to the error, if any occurred
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
open(char const* path, file_mode mode, error_code& ec);
|
|
||||||
|
|
||||||
/** Set the open file
|
|
||||||
|
|
||||||
This function is used to set the open
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
reset(File&& file, error_code& ec);
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class File>
|
|
||||||
void
|
|
||||||
basic_file_body<File>::
|
|
||||||
value_type::
|
|
||||||
close()
|
|
||||||
{
|
|
||||||
error_code ignored;
|
|
||||||
file_.close(ignored);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class File>
|
|
||||||
void
|
|
||||||
basic_file_body<File>::
|
|
||||||
value_type::
|
|
||||||
open(char const* path, file_mode mode, error_code& ec)
|
|
||||||
{
|
|
||||||
// Open the file
|
|
||||||
file_.open(path, mode, ec);
|
|
||||||
if(ec)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Cache the size
|
|
||||||
file_size_ = file_.size(ec);
|
|
||||||
if(ec)
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class File>
|
|
||||||
void
|
|
||||||
basic_file_body<File>::
|
|
||||||
value_type::
|
|
||||||
reset(File&& file, error_code& ec)
|
|
||||||
{
|
|
||||||
// First close the file if open
|
|
||||||
if(file_.is_open())
|
|
||||||
{
|
|
||||||
error_code ignored;
|
|
||||||
file_.close(ignored);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take ownership of the new file
|
|
||||||
file_ = std::move(file);
|
|
||||||
|
|
||||||
// Cache the size
|
|
||||||
file_size_ = file_.size(ec);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is called from message::payload_size
|
|
||||||
template<class File>
|
|
||||||
std::uint64_t
|
|
||||||
basic_file_body<File>::
|
|
||||||
size(value_type const& v)
|
|
||||||
{
|
|
||||||
// Forward the call to the body
|
|
||||||
return v.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
//]
|
|
||||||
|
|
||||||
//[example_http_file_body_3
|
|
||||||
|
|
||||||
template<class File>
|
|
||||||
class basic_file_body<File>::reader
|
|
||||||
{
|
|
||||||
value_type& body_; // The body we are reading from
|
|
||||||
std::uint64_t remain_; // The number of unread bytes
|
|
||||||
char buf_[4096]; // Small buffer for reading
|
|
||||||
|
|
||||||
public:
|
|
||||||
// The type of buffer sequence returned by `get`.
|
|
||||||
//
|
|
||||||
using const_buffers_type =
|
|
||||||
boost::asio::const_buffers_1;
|
|
||||||
|
|
||||||
// Constructor.
|
|
||||||
//
|
|
||||||
// `m` holds the message we are sending, which will
|
|
||||||
// always have the `file_body` as the body type.
|
|
||||||
//
|
|
||||||
// Note that the message is passed by non-const reference.
|
|
||||||
// This is intentional, because reading from the file
|
|
||||||
// changes its "current position" which counts makes the
|
|
||||||
// operation logically not-const (although it is bitwise
|
|
||||||
// const).
|
|
||||||
//
|
|
||||||
// The BodyReader concept allows the reader to choose
|
|
||||||
// whether to take the message by const reference or
|
|
||||||
// non-const reference. Depending on the choice, a
|
|
||||||
// serializer constructed using that body type will
|
|
||||||
// require the same const or non-const reference to
|
|
||||||
// construct.
|
|
||||||
//
|
|
||||||
// Readers which accept const messages usually allow
|
|
||||||
// the same body to be serialized by multiple threads
|
|
||||||
// concurrently, while readers accepting non-const
|
|
||||||
// messages may only be serialized by one thread at
|
|
||||||
// a time.
|
|
||||||
//
|
|
||||||
template<bool isRequest, class Fields>
|
|
||||||
reader(message<
|
|
||||||
isRequest, basic_file_body, Fields>& m);
|
|
||||||
|
|
||||||
// Initializer
|
|
||||||
//
|
|
||||||
// This is called before the body is serialized and
|
|
||||||
// gives the reader a chance to do something that might
|
|
||||||
// need to return an error code.
|
|
||||||
//
|
|
||||||
void
|
|
||||||
init(error_code& ec);
|
|
||||||
|
|
||||||
// This function is called zero or more times to
|
|
||||||
// retrieve buffers. A return value of `boost::none`
|
|
||||||
// means there are no more buffers. Otherwise,
|
|
||||||
// the contained pair will have the next buffer
|
|
||||||
// to serialize, and a `bool` indicating whether
|
|
||||||
// or not there may be additional buffers.
|
|
||||||
boost::optional<std::pair<const_buffers_type, bool>>
|
|
||||||
get(error_code& ec);
|
|
||||||
};
|
|
||||||
|
|
||||||
//]
|
|
||||||
|
|
||||||
//[example_http_file_body_4
|
|
||||||
|
|
||||||
// Here we just stash a reference to the path for later.
|
|
||||||
// Rather than dealing with messy constructor exceptions,
|
|
||||||
// we save the things that might fail for the call to `init`.
|
|
||||||
//
|
|
||||||
template<class File>
|
|
||||||
template<bool isRequest, class Fields>
|
|
||||||
basic_file_body<File>::
|
|
||||||
reader::
|
|
||||||
reader(message<isRequest, basic_file_body, Fields>& m)
|
|
||||||
: body_(m.body)
|
|
||||||
{
|
|
||||||
// The file must already be open
|
|
||||||
BOOST_ASSERT(body_.file_.is_open());
|
|
||||||
|
|
||||||
// Get the size of the file
|
|
||||||
remain_ = body_.file_size_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initializer
|
|
||||||
template<class File>
|
|
||||||
void
|
|
||||||
basic_file_body<File>::
|
|
||||||
reader::
|
|
||||||
init(error_code& ec)
|
|
||||||
{
|
|
||||||
// The error_code specification requires that we
|
|
||||||
// either set the error to some value, or set it
|
|
||||||
// to indicate no error.
|
|
||||||
//
|
|
||||||
// We don't do anything fancy so set "no error"
|
|
||||||
ec.assign(0, ec.category());
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function is called repeatedly by the serializer to
|
|
||||||
// retrieve the buffers representing the body. Our strategy
|
|
||||||
// is to read into our buffer and return it until we have
|
|
||||||
// read through the whole file.
|
|
||||||
//
|
|
||||||
template<class File>
|
|
||||||
auto
|
|
||||||
basic_file_body<File>::
|
|
||||||
reader::
|
|
||||||
get(error_code& ec) ->
|
|
||||||
boost::optional<std::pair<const_buffers_type, bool>>
|
|
||||||
{
|
|
||||||
// Calculate the smaller of our buffer size,
|
|
||||||
// or the amount of unread data in the file.
|
|
||||||
auto const amount = remain_ > sizeof(buf_) ?
|
|
||||||
sizeof(buf_) : static_cast<std::size_t>(remain_);
|
|
||||||
|
|
||||||
// Handle the case where the file is zero length
|
|
||||||
if(amount == 0)
|
|
||||||
{
|
|
||||||
// Modify the error code to indicate success
|
|
||||||
// This is required by the error_code specification.
|
|
||||||
//
|
|
||||||
// NOTE We use the existing category instead of calling
|
|
||||||
// into the library to get the generic category because
|
|
||||||
// that saves us a possibly expensive atomic operation.
|
|
||||||
//
|
|
||||||
ec.assign(0, ec.category());
|
|
||||||
return boost::none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now read the next buffer
|
|
||||||
auto const nread = body_.file_.read(buf_, amount, ec);
|
|
||||||
if(ec)
|
|
||||||
return boost::none;
|
|
||||||
|
|
||||||
// Make sure there is forward progress
|
|
||||||
BOOST_ASSERT(nread != 0);
|
|
||||||
BOOST_ASSERT(nread <= remain_);
|
|
||||||
|
|
||||||
// Update the amount remaining based on what we got
|
|
||||||
remain_ -= nread;
|
|
||||||
|
|
||||||
// Return the buffer to the caller.
|
|
||||||
//
|
|
||||||
// The second element of the pair indicates whether or
|
|
||||||
// not there is more data. As long as there is some
|
|
||||||
// unread bytes, there will be more data. Otherwise,
|
|
||||||
// we set this bool to `false` so we will not be called
|
|
||||||
// again.
|
|
||||||
//
|
|
||||||
ec.assign(0, ec.category());
|
|
||||||
return {{
|
|
||||||
const_buffers_type{buf_, nread}, // buffer to return.
|
|
||||||
remain_ > 0 // `true` if there are more buffers.
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
//]
|
|
||||||
|
|
||||||
//[example_http_file_body_5
|
|
||||||
|
|
||||||
template<class File>
|
|
||||||
class basic_file_body<File>::writer
|
|
||||||
{
|
|
||||||
value_type& body_; // The body we are writing to
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Constructor.
|
|
||||||
//
|
|
||||||
// This is called after the header is parsed and
|
|
||||||
// indicates that a non-zero sized body may be present.
|
|
||||||
// `m` holds the message we are receiving, which will
|
|
||||||
// always have the `file_body` as the body type.
|
|
||||||
//
|
|
||||||
template<bool isRequest, class Fields>
|
|
||||||
explicit
|
|
||||||
writer(
|
|
||||||
message<isRequest, basic_file_body, Fields>& m);
|
|
||||||
|
|
||||||
// Initializer
|
|
||||||
//
|
|
||||||
// This is called before the body is parsed and
|
|
||||||
// gives the writer a chance to do something that might
|
|
||||||
// need to return an error code. It informs us of
|
|
||||||
// the payload size (`content_length`) which we can
|
|
||||||
// optionally use for optimization.
|
|
||||||
//
|
|
||||||
void
|
|
||||||
init(boost::optional<std::uint64_t> const&, error_code& ec);
|
|
||||||
|
|
||||||
// This function is called one or more times to store
|
|
||||||
// buffer sequences corresponding to the incoming body.
|
|
||||||
//
|
|
||||||
template<class ConstBufferSequence>
|
|
||||||
std::size_t
|
|
||||||
put(ConstBufferSequence const& buffers,
|
|
||||||
error_code& ec);
|
|
||||||
|
|
||||||
// This function is called when writing is complete.
|
|
||||||
// It is an opportunity to perform any final actions
|
|
||||||
// which might fail, in order to return an error code.
|
|
||||||
// Operations that might fail should not be attemped in
|
|
||||||
// destructors, since an exception thrown from there
|
|
||||||
// would terminate the program.
|
|
||||||
//
|
|
||||||
void
|
|
||||||
finish(error_code& ec);
|
|
||||||
};
|
|
||||||
|
|
||||||
//]
|
|
||||||
|
|
||||||
//[example_http_file_body_6
|
|
||||||
|
|
||||||
// We don't do much in the writer constructor since the
|
|
||||||
// file is already open.
|
|
||||||
//
|
|
||||||
template<class File>
|
|
||||||
template<bool isRequest, class Fields>
|
|
||||||
basic_file_body<File>::
|
|
||||||
writer::
|
|
||||||
writer(message<isRequest, basic_file_body, Fields>& m)
|
|
||||||
: body_(m.body)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class File>
|
|
||||||
void
|
|
||||||
basic_file_body<File>::
|
|
||||||
writer::
|
|
||||||
init(
|
|
||||||
boost::optional<std::uint64_t> const& content_length,
|
|
||||||
error_code& ec)
|
|
||||||
{
|
|
||||||
// The file must already be open for writing
|
|
||||||
BOOST_ASSERT(body_.file_.is_open());
|
|
||||||
|
|
||||||
// We don't do anything with this but a sophisticated
|
|
||||||
// application might check available space on the device
|
|
||||||
// to see if there is enough room to store the body.
|
|
||||||
boost::ignore_unused(content_length);
|
|
||||||
|
|
||||||
// The error_code specification requires that we
|
|
||||||
// either set the error to some value, or set it
|
|
||||||
// to indicate no error.
|
|
||||||
//
|
|
||||||
// We don't do anything fancy so set "no error"
|
|
||||||
ec.assign(0, ec.category());
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will get called one or more times with body buffers
|
|
||||||
//
|
|
||||||
template<class File>
|
|
||||||
template<class ConstBufferSequence>
|
|
||||||
std::size_t
|
|
||||||
basic_file_body<File>::
|
|
||||||
writer::
|
|
||||||
put(ConstBufferSequence const& buffers, error_code& ec)
|
|
||||||
{
|
|
||||||
// This function must return the total number of
|
|
||||||
// bytes transferred from the input buffers.
|
|
||||||
std::size_t nwritten = 0;
|
|
||||||
|
|
||||||
// Loop over all the buffers in the sequence,
|
|
||||||
// and write each one to the file.
|
|
||||||
for(boost::asio::const_buffer buffer : buffers)
|
|
||||||
{
|
|
||||||
// Write this buffer to the file
|
|
||||||
nwritten += body_.file_.write(
|
|
||||||
boost::asio::buffer_cast<void const*>(buffer),
|
|
||||||
boost::asio::buffer_size(buffer),
|
|
||||||
ec);
|
|
||||||
if(ec)
|
|
||||||
return nwritten;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Indicate success
|
|
||||||
// This is required by the error_code specification
|
|
||||||
ec.assign(0, ec.category());
|
|
||||||
|
|
||||||
return nwritten;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called after writing is done when there's no error.
|
|
||||||
template<class File>
|
|
||||||
void
|
|
||||||
basic_file_body<File>::
|
|
||||||
writer::
|
|
||||||
finish(error_code& ec)
|
|
||||||
{
|
|
||||||
// This has to be cleared before returning, to
|
|
||||||
// indicate no error. The specification requires it.
|
|
||||||
ec.assign(0, ec.category());
|
|
||||||
}
|
|
||||||
|
|
||||||
//]
|
|
||||||
|
|
||||||
/// A message body represented by a file on the filesystem.
|
/// A message body represented by a file on the filesystem.
|
||||||
using file_body = basic_file_body<file>;
|
using file_body = basic_file_body<file>;
|
||||||
|
|
||||||
#if ! BEAST_DOXYGEN
|
|
||||||
// operator<< is not supported for file_body
|
|
||||||
template<bool isRequest, class File, class Fields>
|
|
||||||
std::ostream&
|
|
||||||
operator<<(std::ostream& os, message<
|
|
||||||
isRequest, basic_file_body<File>, Fields> const& msg) = delete;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // http
|
} // http
|
||||||
} // beast
|
} // beast
|
||||||
|
|
||||||
|
@@ -14,6 +14,7 @@ add_executable (http-tests
|
|||||||
test_parser.hpp
|
test_parser.hpp
|
||||||
../../extras/beast/unit_test/main.cpp
|
../../extras/beast/unit_test/main.cpp
|
||||||
basic_dynamic_body.cpp
|
basic_dynamic_body.cpp
|
||||||
|
basic_file_body.cpp
|
||||||
basic_parser.cpp
|
basic_parser.cpp
|
||||||
buffer_body.cpp
|
buffer_body.cpp
|
||||||
doc_examples.cpp
|
doc_examples.cpp
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
unit-test http-tests :
|
unit-test http-tests :
|
||||||
../../extras/beast/unit_test/main.cpp
|
../../extras/beast/unit_test/main.cpp
|
||||||
basic_dynamic_body.cpp
|
basic_dynamic_body.cpp
|
||||||
|
basic_file_body.cpp
|
||||||
basic_parser.cpp
|
basic_parser.cpp
|
||||||
buffer_body.cpp
|
buffer_body.cpp
|
||||||
doc_examples.cpp
|
doc_examples.cpp
|
||||||
|
9
test/http/basic_file_body.cpp
Normal file
9
test/http/basic_file_body.cpp
Normal file
@@ -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 <beast/http/basic_file_body.hpp>
|
Reference in New Issue
Block a user