mirror of
https://github.com/boostorg/beast.git
synced 2025-08-03 06:44:39 +02:00
parser is constructible from other body types
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
Version 50
|
||||
|
||||
* parser is constructible from other body types
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Version 49
|
||||
|
@@ -44,29 +44,6 @@ synchronous version of this server action looks like this:
|
||||
|
||||
|
||||
|
||||
[section Send Child Process Output]
|
||||
|
||||
Sometimes it is necessary to send a message whose body is not conveniently
|
||||
described by a single container. For example, when implementing an HTTP relay
|
||||
function a robust implementation needs to present body buffers individually
|
||||
as they become available from the downstream host. These buffers should be
|
||||
fixed in size, otherwise creating the unnecessary and inefficient burden of
|
||||
reading the complete message body before forwarding it to the upstream host.
|
||||
|
||||
To enable these use-cases, the body type __buffer_body__ is provided. This
|
||||
body uses a caller-provided pointer and size instead of an owned container.
|
||||
To use this body, instantiate an instance of the serializer and fill in
|
||||
the pointer and size fields before calling a stream write function.
|
||||
|
||||
This example reads from a child process and sends the output back in an
|
||||
HTTP response. The output of the process is sent as it becomes available:
|
||||
|
||||
[http_sample_send_cgi_response]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
[section HEAD request (Client)]
|
||||
|
||||
The
|
||||
@@ -134,6 +111,51 @@ parses an HTTP message from a `std::istream`:
|
||||
|
||||
|
||||
|
||||
[section Send Child Process Output]
|
||||
|
||||
Sometimes it is necessary to send a message whose body is not conveniently
|
||||
described by a single container. For example, when implementing an HTTP relay
|
||||
function a robust implementation needs to present body buffers individually
|
||||
as they become available from the downstream host. These buffers should be
|
||||
fixed in size, otherwise creating the unnecessary and inefficient burden of
|
||||
reading the complete message body before forwarding it to the upstream host.
|
||||
|
||||
To enable these use-cases, the body type __buffer_body__ is provided. This
|
||||
body uses a caller-provided pointer and size instead of an owned container.
|
||||
To use this body, instantiate an instance of the serializer and fill in
|
||||
the pointer and size fields before calling a stream write function.
|
||||
|
||||
This example reads from a child process and sends the output back in an
|
||||
HTTP response. The output of the process is sent as it becomes available:
|
||||
|
||||
[http_sample_send_cgi_response]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
[section Defer Body Type]
|
||||
|
||||
Sophisticated servers may wish to defer the choice of the Body template type
|
||||
until after the header is available. Then, a body type may be chosen
|
||||
depending on the header contents. For example, depending on the verb,
|
||||
target path, or target query parameters. To accomplish this, a parser
|
||||
is declared to read in the header only, using a trivial body type such as
|
||||
[link beast.ref.http__empty_body `empty_body`]. Then, a new parser is constructed
|
||||
from this existing parser where the body type is conditionally determined
|
||||
by information from the header or elsewhere.
|
||||
|
||||
This example illustrates how a server may make the commitment of a body
|
||||
type depending on the method verb:
|
||||
|
||||
[http_sample_defer_body]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[section HTTP Relay]
|
||||
|
||||
An HTTP proxy acts as a relay between client and server. The proxy reads a
|
||||
|
@@ -760,6 +760,100 @@ read_istream(
|
||||
|
||||
//]
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Example: Deferred Body Type
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//[http_sample_defer_body
|
||||
|
||||
/** Handle a form PUT 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().fields["Content-Type"] != "application/x-www-form-urlencoded" &&
|
||||
req0.get().fields["Content-Type"] != "multipart/form-data")
|
||||
goto do_string_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_string_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: Custom Parser
|
||||
|
@@ -8,6 +8,9 @@
|
||||
#ifndef BEAST_HTTP_IMPL_PARSER_IPP
|
||||
#define BEAST_HTTP_IMPL_PARSER_IPP
|
||||
|
||||
#include <boost/throw_exception.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
@@ -29,6 +32,20 @@ parser(Arg1&& arg1, ArgN&&... argn)
|
||||
{
|
||||
}
|
||||
|
||||
template<bool isRequest, class Body, class Fields>
|
||||
template<class OtherBody, class... Args>
|
||||
parser<isRequest, Body, Fields>::
|
||||
parser(parser<isRequest, OtherBody, Fields>&& parser,
|
||||
Args&&... args)
|
||||
: base_type(std::move(parser))
|
||||
, m_(parser.release().base(),
|
||||
std::forward<Args>(args)...)
|
||||
{
|
||||
if(parser.wr_)
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument{
|
||||
"moved-from parser has a body"});
|
||||
}
|
||||
|
||||
template<bool isRequest, class Body, class Fields>
|
||||
template<class... Args>
|
||||
parser<isRequest, Body, Fields>::
|
||||
|
@@ -199,6 +199,20 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
template<
|
||||
bool isRequest,class Body, class Fields = fields>
|
||||
class parser;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<class T>
|
||||
struct is_parser : std::false_type {};
|
||||
|
||||
template<bool isRequest, class Body, class Fields>
|
||||
struct is_parser<parser<isRequest, Body, Fields>> : std::true_type {};
|
||||
|
||||
} // detail
|
||||
|
||||
/** An HTTP/1 parser for producing a message.
|
||||
|
||||
This class uses the basic HTTP/1 wire format parser to convert
|
||||
@@ -213,7 +227,7 @@ private:
|
||||
|
||||
@note A new instance of the parser is required for each message.
|
||||
*/
|
||||
template<bool isRequest, class Body, class Fields = fields>
|
||||
template<bool isRequest, class Body, class Fields>
|
||||
class parser
|
||||
: public basic_parser<isRequest,
|
||||
parser<isRequest, Body, Fields>>
|
||||
@@ -224,6 +238,9 @@ class parser
|
||||
static_assert(is_body_writer<Body>::value,
|
||||
"BodyWriter requirements not met");
|
||||
|
||||
template<bool, class, class>
|
||||
friend class parser;
|
||||
|
||||
using base_type = basic_parser<isRequest,
|
||||
parser<isRequest, Body, Fields>>;
|
||||
|
||||
@@ -257,7 +274,7 @@ public:
|
||||
|
||||
@note This function participates in overload
|
||||
resolution only if the first argument is not a
|
||||
@ref http::header_parser or @ref parser.
|
||||
@ref parser.
|
||||
*/
|
||||
#if BEAST_DOXYGEN
|
||||
template<class... Args>
|
||||
@@ -266,17 +283,50 @@ public:
|
||||
#else
|
||||
template<class Arg1, class... ArgN,
|
||||
class = typename std::enable_if<
|
||||
! std::is_same<typename
|
||||
std::decay<Arg1>::type,
|
||||
header_parser<isRequest, Fields>>::value &&
|
||||
! std::is_same<typename
|
||||
std::decay<Arg1>::type, parser>::value
|
||||
>::type>
|
||||
! detail::is_parser<typename
|
||||
std::decay<Arg1>::type>::value>::type>
|
||||
explicit
|
||||
parser(Arg1&& arg1, ArgN&&... argn);
|
||||
#endif
|
||||
|
||||
/** Construct a message parser from a @ref header_parser.
|
||||
/** Construct a parser from another parser, changing the Body type.
|
||||
|
||||
This constructs a new parser by move constructing the
|
||||
header from another parser with a different body type. The
|
||||
constructed-from parser must not have any parsed body octets or
|
||||
initialized @b BodyWriter, otherwise an exception is generated.
|
||||
|
||||
@par Example
|
||||
@code
|
||||
// Deferred body type commitment
|
||||
request_parser<empty_body> req0;
|
||||
...
|
||||
request_parser<string_body> req{std::move(req0)};
|
||||
@endcode
|
||||
|
||||
If an exception is thrown, the state of the constructed-from
|
||||
parser is undefined.
|
||||
|
||||
@param parser The other parser to construct from. After
|
||||
this call returns, the constructed-from parser may only
|
||||
be destroyed.
|
||||
|
||||
@param args Optional arguments forwarded to the message
|
||||
constructor.
|
||||
|
||||
@throws std::invalid_argument Thrown when the constructed-from
|
||||
parser has already initialized a body writer.
|
||||
*/
|
||||
#if BEAST_DOXYGEN
|
||||
template<class OtherBody, class... Args>
|
||||
#else
|
||||
template<class OtherBody, class... Args>
|
||||
#endif
|
||||
explicit
|
||||
parser(parser<isRequest, OtherBody, Fields>&& parser,
|
||||
Args&&... args);
|
||||
|
||||
/** Construct a parser from a @ref header_parser.
|
||||
@param parser The header parser to construct from.
|
||||
@param args Optional arguments forwarded to the message
|
||||
constructor.
|
||||
|
@@ -220,11 +220,22 @@ public:
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// Deferred Body type commitment
|
||||
//
|
||||
//--------------------------------------------------------------------------
|
||||
struct handler
|
||||
{
|
||||
std::string body;
|
||||
|
||||
template<class Body>
|
||||
void
|
||||
operator()(request<Body>&&)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(request<string_body>&& req)
|
||||
{
|
||||
body = req.body;
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
doDeferredBody()
|
||||
@@ -233,24 +244,15 @@ public:
|
||||
ostream(p.server.buffer) <<
|
||||
"POST / HTTP/1.1\r\n"
|
||||
"User-Agent: test\r\n"
|
||||
"Content-Type: multipart/form-data\r\n"
|
||||
"Content-Length: 13\r\n"
|
||||
"\r\n"
|
||||
"Hello, world!";
|
||||
|
||||
handler h;
|
||||
flat_buffer buffer;
|
||||
header_parser<true, fields> parser;
|
||||
auto bytes_used =
|
||||
read_some(p.server, buffer, parser);
|
||||
buffer.consume(bytes_used);
|
||||
|
||||
request_parser<string_body> parser2(
|
||||
std::move(parser));
|
||||
|
||||
while(! parser2.is_done())
|
||||
{
|
||||
bytes_used = read_some(p.server, buffer, parser2);
|
||||
buffer.consume(bytes_used);
|
||||
}
|
||||
do_form_request(p.server, buffer, h);
|
||||
BEAST_EXPECT(h.body == "Hello, world!");
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
Reference in New Issue
Block a user