Files
rippled/beast/http/basic_parser.h
Vinnie Falco 8b62c23ab6 Beast.HTTP:
New classes are introduced to represent HTTP messages and their
associated bodies. The parser interface is reworked to use CRTP,
error codes, and trait checks.

New classes:

* basic_headers

  Models field/value pairs in a HTTP message.

* message

  Models a HTTP message, body behavior defined by template argument.
  Parsed message carries metadata generated during parsing.

* parser

  Produces parsed messages.

* empty_body, string_body, basic_streambuf_body

  Classes used to represent content bodies in various ways.

New functions:

* read, async_read, write, async_write

  Read and write HTTP messages on a socket.

New concepts:

* Body: Represents the HTTP Content-Body.
* Field: A HTTP header field.
* FieldSequence: A forward sequence of fields.
* Reader: Parses a Body from a stream of bytes.
* Writer: Serializes a Body to buffers.

basic_parser changes:

* add write methods which throw exceptions instead
* error_code passed via parameter instead of return value
* fold private member calls into existing callbacks
* basic_parser uses CRTP instead of virtual members
* add documentation on Derived requirements for CRTP

impl/http-parser changes:

* joyent renamed to nodejs to reflect upstream changes
2016-04-20 12:01:15 -04:00

553 lines
16 KiB
C++

//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef BEAST_HTTP_BASIC_PARSER_H_INCLUDED
#define BEAST_HTTP_BASIC_PARSER_H_INCLUDED
#include <beast/http/method.h>
#include <beast/http/src/nodejs_parser.h>
#include <beast/asio/type_check.h>
#include <boost/asio/buffer.hpp>
#include <boost/system/error_code.hpp>
#include <cstdint>
#include <string>
#include <beast/cxx17/type_traits.h> // <type_traits>
namespace beast {
namespace http {
/** Parser for producing HTTP requests and responses.
Callbacks:
If a is an object of type Derived, and the call expression is
valid then the stated effects will take place:
a.on_start()
Called once when a new message begins.
a.on_field(std::string field, std::string value)
Called for each field
a.on_headers_complete(error_code&)
Called when all the header fields have been received, but
before any part of the body if any is received.
a.on_request(method_t method, std::string url,
int major, int minor, bool keep_alive, bool upgrade)
Called for requests when all the headers have been received.
This will precede any content body.
When keep_alive is false:
* Server roles respond with a "Connection: close" header.
* Client roles close the connection.
a.on_response(int status, std::string text,
int major, int minor, bool keep_alive,
bool upgrade)
Called for responses when all the headers have been received.
This will precede any content body.
When keep_alive is `false`:
* Client roles close the connection.
* Server roles respond with a "Connection: close" header.
This function should return `true` if upgrade is false and
a content body is expected. When upgrade is true, no
content-body is expected, and the return value is ignored.
a.on_body(void const* data, std::size_t bytes, error_code&)
Called zero or more times for the content body. Any transfer
encoding is already decoded in the memory pointed to by data.
a.on_complete()
Called when parsing completes successfully.
The parser uses traits to determine if the callback is possible.
If the Derived type omits the callbacks, they are simply skipped
with no compilation error.
*/
/*
VFALCO TODO is_call_possible, enable_if_t on Derived calls
use boost::string_ref instead of std::string
*/
template<class Derived>
class basic_parser
{
http_parser state_;
boost::system::error_code* ec_;
bool complete_ = false;
std::string url_;
std::string status_;
std::string field_;
std::string value_;
public:
using error_code = boost::system::error_code;
/** Move constructor.
The state of the moved-from object is undefined,
but safe to destroy.
*/
basic_parser(basic_parser&& other);
/** Move assignment.
The state of the moved-from object is undefined,
but safe to destroy.
*/
basic_parser&
operator=(basic_parser&& other);
/** Copy constructor. */
basic_parser(basic_parser const& other);
/** Copy assignment. */
basic_parser& operator=(basic_parser const& other);
/** Construct the parser.
@param request If `true`, the parser is setup for a request.
*/
explicit
basic_parser(bool request) noexcept;
/** Returns `true` if parsing is complete.
This is only defined when no errors have been returned.
*/
bool
complete() const noexcept
{
return complete_;
}
/** Write data to the parser.
@param data A pointer to a buffer representing the input sequence.
@param size The number of bytes in the buffer pointed to by data.
@throws boost::system::system_error Thrown on failure.
@return The number of bytes consumed in the input sequence.
*/
std::size_t
write(void const* data, std::size_t size)
{
error_code ec;
auto const used = write(data, size, ec);
if(ec)
throw boost::system::system_error{ec};
return used;
}
/** Write data to the parser.
@param data A pointer to a buffer representing the input sequence.
@param size The number of bytes in the buffer pointed to by data.
@param ec Set to the error, if any error occurred.
@return The number of bytes consumed in the input sequence.
*/
std::size_t
write(void const* data, std::size_t size,
error_code& ec);
/** Write data to the parser.
@param buffers An object meeting the requirements of
ConstBufferSequence that represents the input sequence.
@throws boost::system::system_error Thrown on failure.
@return The number of bytes consumed in the input sequence.
*/
template<class ConstBufferSequence>
std::size_t
write(ConstBufferSequence const& buffers)
{
error_code ec;
auto const used = write(buffers, ec);
if(ec)
throw boost::system::system_error{ec};
return used;
}
/** Write data to the parser.
@param buffers An object meeting the requirements of
ConstBufferSequence that represents the input sequence.
@param ec Set to the error, if any error occurred.
@return The number of bytes consumed in the input sequence.
*/
template<class ConstBufferSequence>
std::size_t
write(ConstBufferSequence const& buffers,
error_code& ec);
/** Called to indicate the end of file.
HTTP needs to know where the end of the stream is. For example,
sometimes servers send responses without Content-Length and
expect the client to consume input (for the body) until EOF.
Callbacks and errors will still be processed as usual.
@note This is typically called when a socket read returns eof.
@throws boost::system::system_error Thrown on failure.
*/
void
write_eof()
{
error_code ec;
write_eof(ec);
if(ec)
throw boost::system::system_error{ec};
}
/** Called to indicate the end of file.
HTTP needs to know where the end of the stream is. For example,
sometimes servers send responses without Content-Length and
expect the client to consume input (for the body) until EOF.
Callbacks and errors will still be processed as usual.
@note This is typically called when a socket read returns eof.
@param ec Set to the error, if any error occurred.
*/
void
write_eof(error_code& ec);
private:
Derived&
impl()
{
return *static_cast<Derived*>(this);
}
template<class C>
class has_on_start_t
{
template<class T, class R =
decltype(std::declval<T>().on_start(), std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_start =
std::bool_constant<has_on_start_t<C>::value>;
void
call_on_start(std::true_type)
{
impl().on_start();
}
void
call_on_start(std::false_type)
{
}
template<class C>
class has_on_field_t
{
template<class T, class R =
decltype(std::declval<T>().on_field(
std::declval<std::string const&>(),
std::declval<std::string const&>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_field =
std::bool_constant<has_on_field_t<C>::value>;
void
call_on_field(std::string const& field,
std::string const& value, std::true_type)
{
impl().on_field(field, value);
}
void
call_on_field(std::string const&, std::string const&,
std::false_type)
{
}
template<class C>
class has_on_headers_complete_t
{
template<class T, class R =
decltype(std::declval<T>().on_headers_complete(
std::declval<error_code&>()), std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_headers_complete =
std::bool_constant<has_on_headers_complete_t<C>::value>;
void
call_on_headers_complete(error_code& ec, std::true_type)
{
impl().on_headers_complete(ec);
}
void
call_on_headers_complete(error_code&, std::false_type)
{
}
template<class C>
class has_on_request_t
{
template<class T, class R =
decltype(std::declval<T>().on_request(
std::declval<method_t>(), std::declval<std::string>(),
std::declval<int>(), std::declval<int>(),
std::declval<bool>(), std::declval<bool>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_request =
std::bool_constant<has_on_request_t<C>::value>;
void
call_on_request(method_t method, std::string url,
int major, int minor, bool keep_alive, bool upgrade,
std::true_type)
{
impl().on_request(
method, url, major, minor, keep_alive, upgrade);
}
void
call_on_request(method_t, std::string, int, int, bool, bool,
std::false_type)
{
}
template<class C>
class has_on_response_t
{
template<class T, class R =
decltype(std::declval<T>().on_response(
std::declval<int>(), std::declval<std::string>,
std::declval<int>(), std::declval<int>(),
std::declval<bool>(), std::declval<bool>()),
std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
#if 0
using type = decltype(check<C>(0));
#else
// VFALCO Trait seems broken for http::parser
using type = std::true_type;
#endif
public:
static bool const value = type::value;
};
template<class C>
using has_on_response =
std::bool_constant<has_on_response_t<C>::value>;
bool
call_on_response(int status, std::string text,
int major, int minor, bool keep_alive, bool upgrade,
std::true_type)
{
return impl().on_response(
status, text, major, minor, keep_alive, upgrade);
}
bool
call_on_response(int, std::string, int, int, bool, bool,
std::false_type)
{
// VFALCO Certainly incorrect
return true;
}
template<class C>
class has_on_body_t
{
template<class T, class R =
decltype(std::declval<T>().on_body(
std::declval<void const*>(), std::declval<std::size_t>(),
std::declval<error_code&>()), std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_body =
std::bool_constant<has_on_body_t<C>::value>;
void
call_on_body(void const* data, std::size_t bytes,
error_code& ec, std::true_type)
{
impl().on_body(data, bytes, ec);
}
void
call_on_body(void const*, std::size_t,
error_code&, std::false_type)
{
}
template<class C>
class has_on_complete_t
{
template<class T, class R =
decltype(std::declval<T>().on_complete(), std::true_type{})>
static R check(int);
template <class>
static std::false_type check(...);
using type = decltype(check<C>(0));
public:
static bool const value = type::value;
};
template<class C>
using has_on_complete =
std::bool_constant<has_on_complete_t<C>::value>;
void
call_on_complete(std::true_type)
{
impl().on_complete();
}
void
call_on_complete(std::false_type)
{
}
void
check_header();
static int cb_message_start(http_parser*);
static int cb_url(http_parser*, char const*, std::size_t);
static int cb_status(http_parser*, char const*, std::size_t);
static int cb_header_field(http_parser*, char const*, std::size_t);
static int cb_header_value(http_parser*, char const*, std::size_t);
static int cb_headers_complete(http_parser*);
static int cb_body(http_parser*, char const*, std::size_t);
static int cb_message_complete(http_parser*);
static int cb_chunk_header(http_parser*);
static int cb_chunk_complete(http_parser*);
struct hooks_t : http_parser_settings
{
hooks_t()
{
http_parser_settings_init(this);
on_message_begin = &basic_parser::cb_message_start;
on_url = &basic_parser::cb_url;
on_status = &basic_parser::cb_status;
on_header_field = &basic_parser::cb_header_field;
on_header_value = &basic_parser::cb_header_value;
on_headers_complete = &basic_parser::cb_headers_complete;
on_body = &basic_parser::cb_body;
on_message_complete = &basic_parser::cb_message_complete;
on_chunk_header = &basic_parser::cb_chunk_header;
on_chunk_complete = &basic_parser::cb_chunk_complete;
}
};
static
http_parser_settings const*
hooks();
};
template<class Derived>
template<class ConstBufferSequence>
std::size_t
basic_parser<Derived>::write(
ConstBufferSequence const& buffers, error_code& ec)
{
static_assert(beast::is_ConstBufferSequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
using boost::asio::buffer_cast;
using boost::asio::buffer_size;
std::size_t bytes_used = 0;
for (auto const& buffer : buffers)
{
auto const n = write(
buffer_cast<void const*>(buffer),
buffer_size(buffer), ec);
if(ec)
return 0;
bytes_used += n;
if(complete())
break;
}
return bytes_used;
}
template<class Derived>
http_parser_settings const*
basic_parser<Derived>::hooks()
{
static hooks_t const h;
return &h;
}
} // http
} // beast
#include <beast/http/impl/basic_parser.ipp>
#endif