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
|
Version 50
|
||||||
|
|
||||||
|
* parser is constructible from other body types
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
Version 49
|
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)]
|
[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
|
||||||
|
@@ -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
|
||||||
|
@@ -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>::
|
||||||
|
@@ -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.
|
||||||
|
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
Reference in New Issue
Block a user