parser is constructible from other body types

This commit is contained in:
Vinnie Falco
2017-06-05 06:44:31 -07:00
parent 17eaae9ce0
commit fa28cba515
6 changed files with 237 additions and 50 deletions

View File

@@ -1,5 +1,7 @@
Version 50 Version 50
* parser is constructible from other body types
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
Version 49 Version 49

View File

@@ -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)] [section HEAD request (Client)]
The 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] [section HTTP Relay]
An HTTP proxy acts as a relay between client and server. The proxy reads a An HTTP proxy acts as a relay between client and server. The proxy reads a

View File

@@ -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 // Example: Custom Parser

View File

@@ -8,6 +8,9 @@
#ifndef BEAST_HTTP_IMPL_PARSER_IPP #ifndef BEAST_HTTP_IMPL_PARSER_IPP
#define BEAST_HTTP_IMPL_PARSER_IPP #define BEAST_HTTP_IMPL_PARSER_IPP
#include <boost/throw_exception.hpp>
#include <stdexcept>
namespace beast { namespace beast {
namespace http { 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<bool isRequest, class Body, class Fields>
template<class... Args> template<class... Args>
parser<isRequest, Body, Fields>:: parser<isRequest, Body, Fields>::

View File

@@ -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. /** An HTTP/1 parser for producing a message.
This class uses the basic HTTP/1 wire format parser to convert 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. @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 class parser
: public basic_parser<isRequest, : public basic_parser<isRequest,
parser<isRequest, Body, Fields>> parser<isRequest, Body, Fields>>
@@ -224,6 +238,9 @@ class parser
static_assert(is_body_writer<Body>::value, static_assert(is_body_writer<Body>::value,
"BodyWriter requirements not met"); "BodyWriter requirements not met");
template<bool, class, class>
friend class parser;
using base_type = basic_parser<isRequest, using base_type = basic_parser<isRequest,
parser<isRequest, Body, Fields>>; parser<isRequest, Body, Fields>>;
@@ -257,7 +274,7 @@ public:
@note This function participates in overload @note This function participates in overload
resolution only if the first argument is not a resolution only if the first argument is not a
@ref http::header_parser or @ref parser. @ref parser.
*/ */
#if BEAST_DOXYGEN #if BEAST_DOXYGEN
template<class... Args> template<class... Args>
@@ -266,17 +283,50 @@ public:
#else #else
template<class Arg1, class... ArgN, template<class Arg1, class... ArgN,
class = typename std::enable_if< class = typename std::enable_if<
! std::is_same<typename ! detail::is_parser<typename
std::decay<Arg1>::type, std::decay<Arg1>::type>::value>::type>
header_parser<isRequest, Fields>>::value &&
! std::is_same<typename
std::decay<Arg1>::type, parser>::value
>::type>
explicit explicit
parser(Arg1&& arg1, ArgN&&... argn); parser(Arg1&& arg1, ArgN&&... argn);
#endif #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 parser The header parser to construct from.
@param args Optional arguments forwarded to the message @param args Optional arguments forwarded to the message
constructor. constructor.

View File

@@ -220,11 +220,22 @@ public:
}); });
} }
//-------------------------------------------------------------------------- struct handler
// {
// Deferred Body type commitment std::string body;
//
//-------------------------------------------------------------------------- template<class Body>
void
operator()(request<Body>&&)
{
}
void
operator()(request<string_body>&& req)
{
body = req.body;
}
};
void void
doDeferredBody() doDeferredBody()
@@ -233,24 +244,15 @@ public:
ostream(p.server.buffer) << ostream(p.server.buffer) <<
"POST / HTTP/1.1\r\n" "POST / HTTP/1.1\r\n"
"User-Agent: test\r\n" "User-Agent: test\r\n"
"Content-Type: multipart/form-data\r\n"
"Content-Length: 13\r\n" "Content-Length: 13\r\n"
"\r\n" "\r\n"
"Hello, world!"; "Hello, world!";
handler h;
flat_buffer buffer; flat_buffer buffer;
header_parser<true, fields> parser; do_form_request(p.server, buffer, h);
auto bytes_used = BEAST_EXPECT(h.body == "Hello, world!");
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);
}
} }
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------