diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b8f9127..99c1fa9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ Version 50 +* parser is constructible from other body types + -------------------------------------------------------------------------------- Version 49 diff --git a/doc/5_http_examples.qbk b/doc/5_http_examples.qbk index 32ea2411..f7dc75ff 100644 --- a/doc/5_http_examples.qbk +++ b/doc/5_http_examples.qbk @@ -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 diff --git a/examples/doc_http_samples.hpp b/examples/doc_http_samples.hpp index e717877e..4e31dcef 100644 --- a/examples/doc_http_samples.hpp +++ b/examples/doc_http_samples.hpp @@ -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 + void handler(request&& 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 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 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 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 diff --git a/include/beast/http/impl/parser.ipp b/include/beast/http/impl/parser.ipp index 67dc4d92..9124edd4 100644 --- a/include/beast/http/impl/parser.ipp +++ b/include/beast/http/impl/parser.ipp @@ -8,6 +8,9 @@ #ifndef BEAST_HTTP_IMPL_PARSER_IPP #define BEAST_HTTP_IMPL_PARSER_IPP +#include +#include + namespace beast { namespace http { @@ -29,6 +32,20 @@ parser(Arg1&& arg1, ArgN&&... argn) { } +template +template +parser:: +parser(parser&& parser, + Args&&... args) + : base_type(std::move(parser)) + , m_(parser.release().base(), + std::forward(args)...) +{ + if(parser.wr_) + BOOST_THROW_EXCEPTION(std::invalid_argument{ + "moved-from parser has a body"}); +} + template template parser:: diff --git a/include/beast/http/parser.hpp b/include/beast/http/parser.hpp index 1f770602..3adab28f 100644 --- a/include/beast/http/parser.hpp +++ b/include/beast/http/parser.hpp @@ -199,6 +199,20 @@ private: } }; +template< + bool isRequest,class Body, class Fields = fields> +class parser; + +namespace detail { + +template +struct is_parser : std::false_type {}; + +template +struct is_parser> : 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 +template class parser : public basic_parser> @@ -224,6 +238,9 @@ class parser static_assert(is_body_writer::value, "BodyWriter requirements not met"); + template + friend class parser; + using base_type = basic_parser>; @@ -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 @@ -266,17 +283,50 @@ public: #else template::type, - header_parser>::value && - ! std::is_same::type, parser>::value - >::type> + ! detail::is_parser::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 req0; + ... + request_parser 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 +#else + template +#endif + explicit + parser(parser&& 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. diff --git a/test/http/doc_http_samples.cpp b/test/http/doc_http_samples.cpp index f266bcc0..888895ec 100644 --- a/test/http/doc_http_samples.cpp +++ b/test/http/doc_http_samples.cpp @@ -220,11 +220,22 @@ public: }); } - //-------------------------------------------------------------------------- - // - // Deferred Body type commitment - // - //-------------------------------------------------------------------------- + struct handler + { + std::string body; + + template + void + operator()(request&&) + { + } + + void + operator()(request&& 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 parser; - auto bytes_used = - read_some(p.server, buffer, parser); - buffer.consume(bytes_used); - - request_parser 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!"); } //--------------------------------------------------------------------------