1042 lines
29 KiB
C++
1042 lines
29 KiB
C++
//
|
|
// Copyright (c) 2016-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)
|
|
//
|
|
// Official repository: https://github.com/boostorg/beast
|
|
//
|
|
|
|
#include <boost/beast.hpp>
|
|
#include <iostream>
|
|
|
|
/* This file contains the functions and classes found in the documentation
|
|
|
|
They are compiled and run as part of the unit tests, so you can copy
|
|
the code and use it in your own projects as a starting point for
|
|
building a network application.
|
|
*/
|
|
|
|
// The documentation assumes the boost::beast::http namespace
|
|
namespace boost {
|
|
namespace beast {
|
|
namespace http {
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Example: Expect 100-continue
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
//[example_http_send_expect_100_continue
|
|
|
|
/** Send a request with Expect: 100-continue
|
|
|
|
This function will send a request with the Expect: 100-continue
|
|
field by first sending the header, then waiting for a successful
|
|
response from the server before continuing to send the body. If
|
|
a non-successful server response is received, the function
|
|
returns immediately.
|
|
|
|
@param stream The remote HTTP server stream.
|
|
|
|
@param buffer The buffer used for reading.
|
|
|
|
@param req The request to send. This function modifies the object:
|
|
the Expect header field is inserted into the message if it does
|
|
not already exist, and set to "100-continue".
|
|
|
|
@param ec Set to the error, if any occurred.
|
|
*/
|
|
template<
|
|
class SyncStream,
|
|
class DynamicBuffer,
|
|
class Body, class Allocator>
|
|
void
|
|
send_expect_100_continue(
|
|
SyncStream& stream,
|
|
DynamicBuffer& buffer,
|
|
request<Body, basic_fields<Allocator>>& req,
|
|
error_code& ec)
|
|
{
|
|
static_assert(is_sync_stream<SyncStream>::value,
|
|
"SyncStream requirements not met");
|
|
|
|
static_assert(
|
|
boost::asio::is_dynamic_buffer<DynamicBuffer>::value,
|
|
"DynamicBuffer requirements not met");
|
|
|
|
// Insert or replace the Expect field
|
|
req.set(field::expect, "100-continue");
|
|
|
|
// Create the serializer
|
|
request_serializer<Body, basic_fields<Allocator>> sr{req};
|
|
|
|
// Send just the header
|
|
write_header(stream, sr, ec);
|
|
if(ec)
|
|
return;
|
|
|
|
// Read the response from the server.
|
|
// A robust client could set a timeout here.
|
|
{
|
|
response<string_body> res;
|
|
read(stream, buffer, res, ec);
|
|
if(ec)
|
|
return;
|
|
if(res.result() != status::continue_)
|
|
{
|
|
// The server indicated that it will not
|
|
// accept the request, so skip sending the body.
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Server is OK with the request, send the body
|
|
write(stream, sr, ec);
|
|
}
|
|
|
|
//]
|
|
|
|
//[example_http_receive_expect_100_continue
|
|
|
|
/** Receive a request, handling Expect: 100-continue if present.
|
|
|
|
This function will read a request from the specified stream.
|
|
If the request contains the Expect: 100-continue field, a
|
|
status response will be delivered.
|
|
|
|
@param stream The remote HTTP client stream.
|
|
|
|
@param buffer The buffer used for reading.
|
|
|
|
@param ec Set to the error, if any occurred.
|
|
*/
|
|
template<
|
|
class SyncStream,
|
|
class DynamicBuffer>
|
|
void
|
|
receive_expect_100_continue(
|
|
SyncStream& stream,
|
|
DynamicBuffer& buffer,
|
|
error_code& ec)
|
|
{
|
|
static_assert(is_sync_stream<SyncStream>::value,
|
|
"SyncStream requirements not met");
|
|
|
|
static_assert(
|
|
boost::asio::is_dynamic_buffer<DynamicBuffer>::value,
|
|
"DynamicBuffer requirements not met");
|
|
|
|
// Declare a parser for a request with a string body
|
|
request_parser<string_body> parser;
|
|
|
|
// Read the header
|
|
read_header(stream, buffer, parser, ec);
|
|
if(ec)
|
|
return;
|
|
|
|
// Check for the Expect field value
|
|
if(parser.get()[field::expect] == "100-continue")
|
|
{
|
|
// send 100 response
|
|
response<empty_body> res;
|
|
res.version(11);
|
|
res.result(status::continue_);
|
|
res.set(field::server, "test");
|
|
write(stream, res, ec);
|
|
if(ec)
|
|
return;
|
|
}
|
|
|
|
// Read the rest of the message.
|
|
//
|
|
read(stream, buffer, parser, ec);
|
|
}
|
|
|
|
//]
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Example: Send Child Process Output
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
//[example_http_send_cgi_response
|
|
|
|
/** Send the output of a child process as an HTTP response.
|
|
|
|
The output of the child process comes from a @b SyncReadStream. Data
|
|
will be sent continuously as it is produced, without the requirement
|
|
that the entire process output is buffered before being sent. The
|
|
response will use the chunked transfer encoding.
|
|
|
|
@param input A stream to read the child process output from.
|
|
|
|
@param output A stream to write the HTTP response to.
|
|
|
|
@param ec Set to the error, if any occurred.
|
|
*/
|
|
template<
|
|
class SyncReadStream,
|
|
class SyncWriteStream>
|
|
void
|
|
send_cgi_response(
|
|
SyncReadStream& input,
|
|
SyncWriteStream& output,
|
|
error_code& ec)
|
|
{
|
|
static_assert(is_sync_read_stream<SyncReadStream>::value,
|
|
"SyncReadStream requirements not met");
|
|
|
|
static_assert(is_sync_write_stream<SyncWriteStream>::value,
|
|
"SyncWriteStream requirements not met");
|
|
|
|
// Set up the response. We use the buffer_body type,
|
|
// allowing serialization to use manually provided buffers.
|
|
response<buffer_body> res;
|
|
|
|
res.result(status::ok);
|
|
res.version(11);
|
|
res.set(field::server, "Beast");
|
|
res.set(field::transfer_encoding, "chunked");
|
|
|
|
// No data yet, but we set more = true to indicate
|
|
// that it might be coming later. Otherwise the
|
|
// serializer::is_done would return true right after
|
|
// sending the header.
|
|
res.body().data = nullptr;
|
|
res.body().more = true;
|
|
|
|
// Create the serializer.
|
|
response_serializer<buffer_body, fields> sr{res};
|
|
|
|
// Send the header immediately.
|
|
write_header(output, sr, ec);
|
|
if(ec)
|
|
return;
|
|
|
|
// Alternate between reading from the child process
|
|
// and sending all the process output until there
|
|
// is no more output.
|
|
do
|
|
{
|
|
// Read a buffer from the child process
|
|
char buffer[2048];
|
|
auto bytes_transferred = input.read_some(
|
|
boost::asio::buffer(buffer, sizeof(buffer)), ec);
|
|
if(ec == boost::asio::error::eof)
|
|
{
|
|
ec = {};
|
|
|
|
// `nullptr` indicates there is no buffer
|
|
res.body().data = nullptr;
|
|
|
|
// `false` means no more data is coming
|
|
res.body().more = false;
|
|
}
|
|
else
|
|
{
|
|
if(ec)
|
|
return;
|
|
|
|
// Point to our buffer with the bytes that
|
|
// we received, and indicate that there may
|
|
// be some more data coming
|
|
res.body().data = buffer;
|
|
res.body().size = bytes_transferred;
|
|
res.body().more = true;
|
|
}
|
|
|
|
// Write everything in the body buffer
|
|
write(output, sr, ec);
|
|
|
|
// This error is returned by body_buffer during
|
|
// serialization when it is done sending the data
|
|
// provided and needs another buffer.
|
|
if(ec == error::need_buffer)
|
|
{
|
|
ec = {};
|
|
continue;
|
|
}
|
|
if(ec)
|
|
return;
|
|
}
|
|
while(! sr.is_done());
|
|
}
|
|
|
|
//]
|
|
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
// Example: HEAD Request
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
//[example_http_do_head_response
|
|
|
|
/** Handle a HEAD request for a resource.
|
|
*/
|
|
template<
|
|
class SyncStream,
|
|
class DynamicBuffer
|
|
>
|
|
void do_server_head(
|
|
SyncStream& stream,
|
|
DynamicBuffer& buffer,
|
|
error_code& ec)
|
|
{
|
|
static_assert(is_sync_stream<SyncStream>::value,
|
|
"SyncStream requirements not met");
|
|
static_assert(
|
|
boost::asio::is_dynamic_buffer<DynamicBuffer>::value,
|
|
"DynamicBuffer requirements not met");
|
|
|
|
// We deliver this payload for all GET requests
|
|
static std::string const payload = "Hello, world!";
|
|
|
|
// Read the request
|
|
request<string_body> req;
|
|
read(stream, buffer, req, ec);
|
|
if(ec)
|
|
return;
|
|
|
|
// Set up the response, starting with the common fields
|
|
response<string_body> res;
|
|
res.version(11);
|
|
res.set(field::server, "test");
|
|
|
|
// Now handle request-specific fields
|
|
switch(req.method())
|
|
{
|
|
case verb::head:
|
|
case verb::get:
|
|
{
|
|
// A HEAD request is handled by delivering the same
|
|
// set of headers that would be sent for a GET request,
|
|
// including the Content-Length, except for the body.
|
|
res.result(status::ok);
|
|
res.set(field::content_length, payload.size());
|
|
|
|
// For GET requests, we include the body
|
|
if(req.method() == verb::get)
|
|
{
|
|
// We deliver the same payload for GET requests
|
|
// regardless of the target. A real server might
|
|
// deliver a file based on the target.
|
|
res.body() = payload;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
// We return responses indicating an error if
|
|
// we do not recognize the request method.
|
|
res.result(status::bad_request);
|
|
res.set(field::content_type, "text/plain");
|
|
res.body() = "Invalid request-method '" + std::string(req.method_string()) + "'";
|
|
res.prepare_payload();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Send the response
|
|
write(stream, res, ec);
|
|
if(ec)
|
|
return;
|
|
}
|
|
|
|
//]
|
|
|
|
//[example_http_do_head_request
|
|
|
|
/** Send a HEAD request for a resource.
|
|
|
|
This function submits a HEAD request for the specified resource
|
|
and returns the response.
|
|
|
|
@param res The response. This is an output parameter.
|
|
|
|
@param stream The synchronous stream to use.
|
|
|
|
@param buffer The buffer to use.
|
|
|
|
@param target The request target.
|
|
|
|
@param ec Set to the error, if any occurred.
|
|
|
|
@throws std::invalid_argument if target is empty.
|
|
*/
|
|
template<
|
|
class SyncStream,
|
|
class DynamicBuffer
|
|
>
|
|
response<empty_body>
|
|
do_head_request(
|
|
SyncStream& stream,
|
|
DynamicBuffer& buffer,
|
|
string_view target,
|
|
error_code& ec)
|
|
{
|
|
// Do some type checking to be a good citizen
|
|
static_assert(is_sync_stream<SyncStream>::value,
|
|
"SyncStream requirements not met");
|
|
static_assert(
|
|
boost::asio::is_dynamic_buffer<DynamicBuffer>::value,
|
|
"DynamicBuffer requirements not met");
|
|
|
|
// The interfaces we are using are low level and do not
|
|
// perform any checking of arguments; so we do it here.
|
|
if(target.empty())
|
|
throw std::invalid_argument("target may not be empty");
|
|
|
|
// Build the HEAD request for the target
|
|
request<empty_body> req;
|
|
req.version(11);
|
|
req.method(verb::head);
|
|
req.target(target);
|
|
req.set(field::user_agent, "test");
|
|
|
|
// A client MUST send a Host header field in all HTTP/1.1 request messages.
|
|
// https://tools.ietf.org/html/rfc7230#section-5.4
|
|
req.set(field::host, "localhost");
|
|
|
|
// Now send it
|
|
write(stream, req, ec);
|
|
if(ec)
|
|
return {};
|
|
|
|
// Create a parser to read the response.
|
|
// We use the `empty_body` type since
|
|
// a response to a HEAD request MUST NOT
|
|
// include a body.
|
|
response_parser<empty_body> p;
|
|
|
|
// Inform the parser that there will be no body.
|
|
p.skip(true);
|
|
|
|
// Read the message. Even though fields like
|
|
// Content-Length or Transfer-Encoding may be
|
|
// set, the message will not contain a body.
|
|
read(stream, buffer, p, ec);
|
|
if(ec)
|
|
return {};
|
|
|
|
// Transfer ownership of the response to the caller.
|
|
return p.release();
|
|
}
|
|
|
|
//]
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Example: HTTP Relay
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
//[example_http_relay
|
|
|
|
/** Relay an HTTP message.
|
|
|
|
This function efficiently relays an HTTP message from a downstream
|
|
client to an upstream server, or from an upstream server to a
|
|
downstream client. After the message header is read from the input,
|
|
a user provided transformation function is invoked which may change
|
|
the contents of the header before forwarding to the output. This may
|
|
be used to adjust fields such as Server, or proxy fields.
|
|
|
|
@param output The stream to write to.
|
|
|
|
@param input The stream to read from.
|
|
|
|
@param buffer The buffer to use for the input.
|
|
|
|
@param transform The header transformation to apply. The function will
|
|
be called with this signature:
|
|
@code
|
|
template<class Body>
|
|
void transform(message<
|
|
isRequest, Body, Fields>&, // The message to transform
|
|
error_code&); // Set to the error, if any
|
|
@endcode
|
|
|
|
@param ec Set to the error if any occurred.
|
|
|
|
@tparam isRequest `true` to relay a request.
|
|
|
|
@tparam Fields The type of fields to use for the message.
|
|
*/
|
|
template<
|
|
bool isRequest,
|
|
class SyncWriteStream,
|
|
class SyncReadStream,
|
|
class DynamicBuffer,
|
|
class Transform>
|
|
void
|
|
relay(
|
|
SyncWriteStream& output,
|
|
SyncReadStream& input,
|
|
DynamicBuffer& buffer,
|
|
error_code& ec,
|
|
Transform&& transform)
|
|
{
|
|
static_assert(is_sync_write_stream<SyncWriteStream>::value,
|
|
"SyncWriteStream requirements not met");
|
|
|
|
static_assert(is_sync_read_stream<SyncReadStream>::value,
|
|
"SyncReadStream requirements not met");
|
|
|
|
// A small buffer for relaying the body piece by piece
|
|
char buf[2048];
|
|
|
|
// Create a parser with a buffer body to read from the input.
|
|
parser<isRequest, buffer_body> p;
|
|
|
|
// Create a serializer from the message contained in the parser.
|
|
serializer<isRequest, buffer_body, fields> sr{p.get()};
|
|
|
|
// Read just the header from the input
|
|
read_header(input, buffer, p, ec);
|
|
if(ec)
|
|
return;
|
|
|
|
// Apply the caller's header transformation
|
|
transform(p.get(), ec);
|
|
if(ec)
|
|
return;
|
|
|
|
// Send the transformed message to the output
|
|
write_header(output, sr, ec);
|
|
if(ec)
|
|
return;
|
|
|
|
// Loop over the input and transfer it to the output
|
|
do
|
|
{
|
|
if(! p.is_done())
|
|
{
|
|
// Set up the body for writing into our small buffer
|
|
p.get().body().data = buf;
|
|
p.get().body().size = sizeof(buf);
|
|
|
|
// Read as much as we can
|
|
read(input, buffer, p, ec);
|
|
|
|
// This error is returned when buffer_body uses up the buffer
|
|
if(ec == error::need_buffer)
|
|
ec = {};
|
|
if(ec)
|
|
return;
|
|
|
|
// Set up the body for reading.
|
|
// This is how much was parsed:
|
|
p.get().body().size = sizeof(buf) - p.get().body().size;
|
|
p.get().body().data = buf;
|
|
p.get().body().more = ! p.is_done();
|
|
}
|
|
else
|
|
{
|
|
p.get().body().data = nullptr;
|
|
p.get().body().size = 0;
|
|
}
|
|
|
|
// Write everything in the buffer (which might be empty)
|
|
write(output, sr, ec);
|
|
|
|
// This error is returned when buffer_body uses up the buffer
|
|
if(ec == error::need_buffer)
|
|
ec = {};
|
|
if(ec)
|
|
return;
|
|
}
|
|
while(! p.is_done() && ! sr.is_done());
|
|
}
|
|
|
|
//]
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Example: Serialize to std::ostream
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
//[example_http_write_ostream
|
|
|
|
// The detail namespace means "not public"
|
|
namespace detail {
|
|
|
|
// This helper is needed for C++11.
|
|
// When invoked with a buffer sequence, writes the buffers `to the std::ostream`.
|
|
template<class Serializer>
|
|
class write_ostream_helper
|
|
{
|
|
Serializer& sr_;
|
|
std::ostream& os_;
|
|
|
|
public:
|
|
write_ostream_helper(Serializer& sr, std::ostream& os)
|
|
: sr_(sr)
|
|
, os_(os)
|
|
{
|
|
}
|
|
|
|
// This function is called by the serializer
|
|
template<class ConstBufferSequence>
|
|
void
|
|
operator()(error_code& ec, ConstBufferSequence const& buffers) const
|
|
{
|
|
// Error codes must be cleared on success
|
|
ec = {};
|
|
|
|
// Keep a running total of how much we wrote
|
|
std::size_t bytes_transferred = 0;
|
|
|
|
// Loop over the buffer sequence
|
|
for(auto it = boost::asio::buffer_sequence_begin(buffers);
|
|
it != boost::asio::buffer_sequence_end(buffers); ++it)
|
|
{
|
|
// This is the next buffer in the sequence
|
|
boost::asio::const_buffer const buffer = *it;
|
|
|
|
// Write it to the std::ostream
|
|
os_.write(
|
|
reinterpret_cast<char const*>(buffer.data()),
|
|
buffer.size());
|
|
|
|
// If the std::ostream fails, convert it to an error code
|
|
if(os_.fail())
|
|
{
|
|
ec = make_error_code(errc::io_error);
|
|
return;
|
|
}
|
|
|
|
// Adjust our running total
|
|
bytes_transferred += buffer_size(buffer);
|
|
}
|
|
|
|
// Inform the serializer of the amount we consumed
|
|
sr_.consume(bytes_transferred);
|
|
}
|
|
};
|
|
|
|
} // detail
|
|
|
|
/** Write a message to a `std::ostream`.
|
|
|
|
This function writes the serialized representation of the
|
|
HTTP/1 message to the sream.
|
|
|
|
@param os The `std::ostream` to write to.
|
|
|
|
@param msg The message to serialize.
|
|
|
|
@param ec Set to the error, if any occurred.
|
|
*/
|
|
template<
|
|
bool isRequest,
|
|
class Body,
|
|
class Fields>
|
|
void
|
|
write_ostream(
|
|
std::ostream& os,
|
|
message<isRequest, Body, Fields>& msg,
|
|
error_code& ec)
|
|
{
|
|
// Create the serializer instance
|
|
serializer<isRequest, Body, Fields> sr{msg};
|
|
|
|
// This lambda is used as the "visit" function
|
|
detail::write_ostream_helper<decltype(sr)> lambda{sr, os};
|
|
do
|
|
{
|
|
// In C++14 we could use a generic lambda but since we want
|
|
// to require only C++11, the lambda is written out by hand.
|
|
// This function call retrieves the next serialized buffers.
|
|
sr.next(ec, lambda);
|
|
if(ec)
|
|
return;
|
|
}
|
|
while(! sr.is_done());
|
|
}
|
|
|
|
//]
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Example: Parse from std::istream
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
//[example_http_read_istream
|
|
|
|
/** Read a message from a `std::istream`.
|
|
|
|
This function attempts to parse a complete HTTP/1 message from the stream.
|
|
|
|
@param is The `std::istream` to read from.
|
|
|
|
@param buffer The buffer to use.
|
|
|
|
@param msg The message to store the result.
|
|
|
|
@param ec Set to the error, if any occurred.
|
|
*/
|
|
template<
|
|
class Allocator,
|
|
bool isRequest,
|
|
class Body>
|
|
void
|
|
read_istream(
|
|
std::istream& is,
|
|
basic_flat_buffer<Allocator>& buffer,
|
|
message<isRequest, Body, fields>& msg,
|
|
error_code& ec)
|
|
{
|
|
// Create the message parser
|
|
//
|
|
// Arguments passed to the parser's constructor are
|
|
// forwarded to the message constructor. Here, we use
|
|
// a move construction in case the caller has constructed
|
|
// their message in a non-default way.
|
|
//
|
|
parser<isRequest, Body> p{std::move(msg)};
|
|
|
|
do
|
|
{
|
|
// Extract whatever characters are presently available in the istream
|
|
if(is.rdbuf()->in_avail() > 0)
|
|
{
|
|
// Get a mutable buffer sequence for writing
|
|
auto const b = buffer.prepare(
|
|
static_cast<std::size_t>(is.rdbuf()->in_avail()));
|
|
|
|
// Now get everything we can from the istream
|
|
buffer.commit(static_cast<std::size_t>(is.readsome(
|
|
reinterpret_cast<char*>(b.data()), b.size())));
|
|
}
|
|
else if(buffer.size() == 0)
|
|
{
|
|
// Our buffer is empty and we need more characters,
|
|
// see if we've reached the end of file on the istream
|
|
if(! is.eof())
|
|
{
|
|
// Get a mutable buffer sequence for writing
|
|
auto const b = buffer.prepare(1024);
|
|
|
|
// Try to get more from the istream. This might block.
|
|
is.read(reinterpret_cast<char*>(b.data()), b.size());
|
|
|
|
// If an error occurs on the istream then return it to the caller.
|
|
if(is.fail() && ! is.eof())
|
|
{
|
|
// We'll just re-use io_error since std::istream has no error_code interface.
|
|
ec = make_error_code(errc::io_error);
|
|
return;
|
|
}
|
|
|
|
// Commit the characters we got to the buffer.
|
|
buffer.commit(static_cast<std::size_t>(is.gcount()));
|
|
}
|
|
else
|
|
{
|
|
// Inform the parser that we've reached the end of the istream.
|
|
p.put_eof(ec);
|
|
if(ec)
|
|
return;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Write the data to the parser
|
|
auto const bytes_used = p.put(buffer.data(), ec);
|
|
|
|
// This error means that the parser needs additional octets.
|
|
if(ec == error::need_more)
|
|
ec = {};
|
|
if(ec)
|
|
return;
|
|
|
|
// Consume the buffer octets that were actually parsed.
|
|
buffer.consume(bytes_used);
|
|
}
|
|
while(! p.is_done());
|
|
|
|
// Transfer ownership of the message container in the parser to the caller.
|
|
msg = p.release();
|
|
}
|
|
|
|
//]
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Example: Deferred Body Type
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
//[example_http_defer_body
|
|
|
|
/** Handle a form POST request, choosing a body type depending on the Content-Type.
|
|
|
|
This reads a request from the input stream. If the method is POST, and
|
|
the Content-Type is "application/x-www-form-urlencoded " or
|
|
"multipart/form-data", a `string_body` is used to receive and store
|
|
the message body. Otherwise, a `dynamic_body` is used to store the message
|
|
body. After the request is received, the handler will be invoked with the
|
|
request.
|
|
|
|
@param stream The stream to read from.
|
|
|
|
@param buffer The buffer to use for reading.
|
|
|
|
@param handler The handler to invoke when the request is complete.
|
|
The handler must be invokable with this signature:
|
|
@code
|
|
template<class Body>
|
|
void handler(request<Body>&& req);
|
|
@endcode
|
|
|
|
@throws system_error Thrown on failure.
|
|
*/
|
|
template<
|
|
class SyncReadStream,
|
|
class DynamicBuffer,
|
|
class Handler>
|
|
void
|
|
do_form_request(
|
|
SyncReadStream& stream,
|
|
DynamicBuffer& buffer,
|
|
Handler&& handler)
|
|
{
|
|
// Start with an empty_body parser
|
|
request_parser<empty_body> req0;
|
|
|
|
// Read just the header. Otherwise, the empty_body
|
|
// would generate an error if body octets were received.
|
|
read_header(stream, buffer, req0);
|
|
|
|
// Choose a body depending on the method verb
|
|
switch(req0.get().method())
|
|
{
|
|
case verb::post:
|
|
{
|
|
// If this is not a form upload then use a string_body
|
|
if( req0.get()[field::content_type] != "application/x-www-form-urlencoded" &&
|
|
req0.get()[field::content_type] != "multipart/form-data")
|
|
goto do_dynamic_body;
|
|
|
|
// Commit to string_body as the body type.
|
|
// As long as there are no body octets in the parser
|
|
// we are constructing from, no exception is thrown.
|
|
request_parser<string_body> req{std::move(req0)};
|
|
|
|
// Finish reading the message
|
|
read(stream, buffer, req);
|
|
|
|
// Call the handler. It can take ownership
|
|
// if desired, since we are calling release()
|
|
handler(req.release());
|
|
break;
|
|
}
|
|
|
|
do_dynamic_body:
|
|
default:
|
|
{
|
|
// Commit to dynamic_body as the body type.
|
|
// As long as there are no body octets in the parser
|
|
// we are constructing from, no exception is thrown.
|
|
request_parser<dynamic_body> req{std::move(req0)};
|
|
|
|
// Finish reading the message
|
|
read(stream, buffer, req);
|
|
|
|
// Call the handler. It can take ownership
|
|
// if desired, since we are calling release()
|
|
handler(req.release());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//]
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Example: Incremental Read
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
//[example_incremental_read
|
|
|
|
/* This function reads a message using a fixed size buffer to hold
|
|
portions of the body, and prints the body contents to a `std::ostream`.
|
|
*/
|
|
template<
|
|
bool isRequest,
|
|
class SyncReadStream,
|
|
class DynamicBuffer>
|
|
void
|
|
read_and_print_body(
|
|
std::ostream& os,
|
|
SyncReadStream& stream,
|
|
DynamicBuffer& buffer,
|
|
error_code& ec)
|
|
{
|
|
parser<isRequest, buffer_body> p;
|
|
read_header(stream, buffer, p, ec);
|
|
if(ec)
|
|
return;
|
|
while(! p.is_done())
|
|
{
|
|
char buf[512];
|
|
p.get().body().data = buf;
|
|
p.get().body().size = sizeof(buf);
|
|
read(stream, buffer, p, ec);
|
|
if(ec == error::need_buffer)
|
|
ec = {};
|
|
if(ec)
|
|
return;
|
|
os.write(buf, sizeof(buf) - p.get().body().size);
|
|
}
|
|
}
|
|
|
|
//]
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Example: Expect 100-continue
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
//[example_chunk_parsing
|
|
|
|
/** Read a message with a chunked body and print the chunks and extensions
|
|
*/
|
|
template<
|
|
bool isRequest,
|
|
class SyncReadStream,
|
|
class DynamicBuffer>
|
|
void
|
|
print_chunked_body(
|
|
std::ostream& os,
|
|
SyncReadStream& stream,
|
|
DynamicBuffer& buffer,
|
|
error_code& ec)
|
|
{
|
|
// Declare the parser with an empty body since
|
|
// we plan on capturing the chunks ourselves.
|
|
parser<isRequest, empty_body> p;
|
|
|
|
// First read the complete header
|
|
read_header(stream, buffer, p, ec);
|
|
if(ec)
|
|
return;
|
|
|
|
// This container will hold the extensions for each chunk
|
|
chunk_extensions ce;
|
|
|
|
// This string will hold the body of each chunk
|
|
std::string chunk;
|
|
|
|
// Declare our chunk header callback This is invoked
|
|
// after each chunk header and also after the last chunk.
|
|
auto header_cb =
|
|
[&](std::uint64_t size, // Size of the chunk, or zero for the last chunk
|
|
string_view extensions, // The raw chunk-extensions string. Already validated.
|
|
error_code& ev) // We can set this to indicate an error
|
|
{
|
|
// Parse the chunk extensions so we can access them easily
|
|
ce.parse(extensions, ev);
|
|
if(ev)
|
|
return;
|
|
|
|
// See if the chunk is too big
|
|
if(size > (std::numeric_limits<std::size_t>::max)())
|
|
{
|
|
ev = error::body_limit;
|
|
return;
|
|
}
|
|
|
|
// Make sure we have enough storage, and
|
|
// reset the container for the upcoming chunk
|
|
chunk.reserve(static_cast<std::size_t>(size));
|
|
chunk.clear();
|
|
};
|
|
|
|
// Set the callback. The function requires a non-const reference so we
|
|
// use a local variable, since temporaries can only bind to const refs.
|
|
p.on_chunk_header(header_cb);
|
|
|
|
// Declare the chunk body callback. This is called one or
|
|
// more times for each piece of a chunk body.
|
|
auto body_cb =
|
|
[&](std::uint64_t remain, // The number of bytes left in this chunk
|
|
string_view body, // A buffer holding chunk body data
|
|
error_code& ec) // We can set this to indicate an error
|
|
{
|
|
// If this is the last piece of the chunk body,
|
|
// set the error so that the call to `read` returns
|
|
// and we can process the chunk.
|
|
if(remain == body.size())
|
|
ec = error::end_of_chunk;
|
|
|
|
// Append this piece to our container
|
|
chunk.append(body.data(), body.size());
|
|
|
|
// The return value informs the parser of how much of the body we
|
|
// consumed. We will indicate that we consumed everything passed in.
|
|
return body.size();
|
|
};
|
|
p.on_chunk_body(body_cb);
|
|
|
|
while(! p.is_done())
|
|
{
|
|
// Read as much as we can. When we reach the end of the chunk, the chunk
|
|
// body callback will make the read return with the end_of_chunk error.
|
|
read(stream, buffer, p, ec);
|
|
if(! ec)
|
|
continue;
|
|
else if(ec != error::end_of_chunk)
|
|
return;
|
|
else
|
|
ec = {};
|
|
|
|
// We got a whole chunk, print the extensions:
|
|
for(auto const& extension : ce)
|
|
{
|
|
os << "Extension: " << extension.first;
|
|
if(! extension.second.empty())
|
|
os << " = " << extension.second << std::endl;
|
|
else
|
|
os << std::endl;
|
|
}
|
|
|
|
// Now print the chunk body
|
|
os << "Chunk Body: " << chunk << std::endl;
|
|
}
|
|
|
|
// Get a reference to the parsed message, this is for convenience
|
|
auto const& msg = p.get();
|
|
|
|
// Check each field promised in the "Trailer" header and output it
|
|
for(auto const& name : token_list{msg[field::trailer]})
|
|
{
|
|
// Find the trailer field
|
|
auto it = msg.find(name);
|
|
if(it == msg.end())
|
|
{
|
|
// Oops! They promised the field but failed to deliver it
|
|
os << "Missing Trailer: " << name << std::endl;
|
|
continue;
|
|
}
|
|
os << it->name() << ": " << it->value() << std::endl;
|
|
}
|
|
}
|
|
|
|
//]
|
|
|
|
} // http
|
|
} // beast
|
|
} // boost
|