beast/test/bench/parser/nodejs_parser.hpp
2019-08-29 10:52:07 -07:00

641 lines
15 KiB
C++

//
// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_HTTP_NODEJS_PARSER_HPP
#define BOOST_BEAST_HTTP_NODEJS_PARSER_HPP
#include "nodejs-parser/http_parser.h"
#include <boost/beast/http/message.hpp>
#include <boost/beast/http/rfc7230.hpp>
#include <boost/beast/core/buffers_range.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/system/error_code.hpp>
#include <boost/throw_exception.hpp>
#include <cstdint>
#include <string>
#include <type_traits>
#include <utility>
namespace boost {
namespace beast {
namespace http {
namespace detail {
class nodejs_message_category final
: public boost::system::error_category
{
public:
const char*
name() const noexcept override
{
return "nodejs-http-error";
}
std::string
message(int ev) const override
{
return http_errno_description(
static_cast<http_errno>(ev));
}
boost::system::error_condition
default_error_condition(int ev) const noexcept override
{
return boost::system::error_condition{ev, *this};
}
bool
equivalent(int ev,
boost::system::error_condition const& condition
) const noexcept override
{
return condition.value() == ev &&
&condition.category() == this;
}
bool
equivalent(boost::system::error_code const& error,
int ev) const noexcept override
{
return error.value() == ev &&
&error.category() == this;
}
};
template<class = void>
boost::system::error_code
make_nodejs_error(int http_errno)
{
static nodejs_message_category const mc{};
return boost::system::error_code{http_errno, mc};
}
inline
char const*
method_to_string(unsigned method)
{
using namespace beast;
switch(static_cast<http_method>(method))
{
case HTTP_DELETE: return "DELETE";
case HTTP_GET: return "GET";
case HTTP_HEAD: return "HEAD";
case HTTP_POST: return "POST";
case HTTP_PUT: return "PUT";
// pathological
case HTTP_CONNECT: return "CONNECT";
case HTTP_OPTIONS: return "OPTIONS";
case HTTP_TRACE: return "TRACE";
// webdav
case HTTP_COPY: return "COPY";
case HTTP_LOCK: return "LOCK";
case HTTP_MKCOL: return "MKCOL";
case HTTP_MOVE: return "MOVE";
case HTTP_PROPFIND: return "PROPFIND";
case HTTP_PROPPATCH: return "PROPPATCH";
case HTTP_SEARCH: return "SEARCH";
case HTTP_UNLOCK: return "UNLOCK";
case HTTP_BIND: return "BIND";
case HTTP_REBIND: return "REBIND";
case HTTP_UNBIND: return "UNBIND";
case HTTP_ACL: return "ACL";
// subversion
case HTTP_REPORT: return "REPORT";
case HTTP_MKACTIVITY: return "MKACTIVITY";
case HTTP_CHECKOUT: return "CHECKOUT";
case HTTP_MERGE: return "MERGE";
// upnp
case HTTP_MSEARCH: return "MSEARCH";
case HTTP_NOTIFY: return "NOTIFY";
case HTTP_SUBSCRIBE: return "SUBSCRIBE";
case HTTP_UNSUBSCRIBE: return "UNSUBSCRIBE";
// RFC-5789
case HTTP_PATCH: return "PATCH";
case HTTP_PURGE: return "PURGE";
// CalDav
case HTTP_MKCALENDAR: return "MKCALENDAR";
// RFC-2068, section 19.6.1.2
case HTTP_LINK: return "LINK";
case HTTP_UNLINK: return "UNLINK";
};
return "<unknown>";
}
} // detail
template<class Derived>
class nodejs_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;
nodejs_basic_parser(nodejs_basic_parser&& other);
nodejs_basic_parser&
operator=(nodejs_basic_parser&& other);
nodejs_basic_parser(nodejs_basic_parser const& other);
nodejs_basic_parser& operator=(nodejs_basic_parser const& other);
explicit
nodejs_basic_parser(bool request) noexcept;
bool
complete() const noexcept
{
return complete_;
}
std::size_t
write(void const* data, std::size_t size)
{
error_code ec;
auto const used = write(data, size, ec);
if(ec)
BOOST_THROW_EXCEPTION(system_error{ec});
return used;
}
std::size_t
write(void const* data, std::size_t size,
error_code& ec);
template<class ConstBufferSequence>
std::size_t
write(ConstBufferSequence const& buffers)
{
error_code ec;
auto const used = write(buffers, ec);
if(ec)
BOOST_THROW_EXCEPTION(system_error{ec});
return used;
}
template<class ConstBufferSequence>
std::size_t
write(ConstBufferSequence const& buffers,
error_code& ec);
void
write_eof()
{
error_code ec;
write_eof(ec);
if(ec)
BOOST_THROW_EXCEPTION(system_error{ec});
}
void
write_eof(error_code& ec);
void
check_header();
private:
Derived&
impl()
{
return *static_cast<Derived*>(this);
}
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 = &nodejs_basic_parser::cb_message_start;
on_url = &nodejs_basic_parser::cb_url;
on_status = &nodejs_basic_parser::cb_status;
on_header_field = &nodejs_basic_parser::cb_header_field;
on_header_value = &nodejs_basic_parser::cb_header_value;
on_headers_complete = &nodejs_basic_parser::cb_headers_complete;
on_body = &nodejs_basic_parser::cb_body;
on_message_complete = &nodejs_basic_parser::cb_message_complete;
on_chunk_header = &nodejs_basic_parser::cb_chunk_header;
on_chunk_complete = &nodejs_basic_parser::cb_chunk_complete;
}
};
static
http_parser_settings const*
hooks();
};
template<class Derived>
template<class ConstBufferSequence>
std::size_t
nodejs_basic_parser<Derived>::write(
ConstBufferSequence const& buffers, error_code& ec)
{
static_assert(net::is_const_buffer_sequence<
ConstBufferSequence>::value,
"ConstBufferSequence type requirements not met");
std::size_t bytes_used = 0;
for(auto buffer : beast::buffers_range_ref(buffers))
{
auto const n = write(
static_cast<void const*>(buffer.data()),
buffer.size(), ec);
if(ec)
return 0;
bytes_used += n;
if(complete())
break;
}
return bytes_used;
}
template<class Derived>
http_parser_settings const*
nodejs_basic_parser<Derived>::hooks()
{
static hooks_t const h;
return &h;
}
template<class Derived>
nodejs_basic_parser<Derived>::
nodejs_basic_parser(nodejs_basic_parser&& other)
{
state_ = other.state_;
state_.data = this;
complete_ = other.complete_;
url_ = std::move(other.url_);
status_ = std::move(other.status_);
field_ = std::move(other.field_);
value_ = std::move(other.value_);
}
template<class Derived>
auto
nodejs_basic_parser<Derived>::
operator=(nodejs_basic_parser&& other) ->
nodejs_basic_parser&
{
state_ = other.state_;
state_.data = this;
complete_ = other.complete_;
url_ = std::move(other.url_);
status_ = std::move(other.status_);
field_ = std::move(other.field_);
value_ = std::move(other.value_);
return *this;
}
template<class Derived>
nodejs_basic_parser<Derived>::
nodejs_basic_parser(nodejs_basic_parser const& other)
{
state_ = other.state_;
state_.data = this;
complete_ = other.complete_;
url_ = other.url_;
status_ = other.status_;
field_ = other.field_;
value_ = other.value_;
}
template<class Derived>
auto
nodejs_basic_parser<Derived>::
operator=(nodejs_basic_parser const& other) ->
nodejs_basic_parser&
{
state_ = other.state_;
state_.data = this;
complete_ = other.complete_;
url_ = other.url_;
status_ = other.status_;
field_ = other.field_;
value_ = other.value_;
return *this;
}
template<class Derived>
nodejs_basic_parser<Derived>::
nodejs_basic_parser(bool request) noexcept
{
state_.data = this;
http_parser_init(&state_, request
? http_parser_type::HTTP_REQUEST
: http_parser_type::HTTP_RESPONSE);
}
template<class Derived>
std::size_t
nodejs_basic_parser<Derived>::
write(void const* data,
std::size_t size, error_code& ec)
{
ec_ = &ec;
auto const n = http_parser_execute(
&state_, hooks(),
static_cast<const char*>(data), size);
if(! ec)
ec = detail::make_nodejs_error(
static_cast<int>(state_.http_errno));
if(ec)
return 0;
return n;
}
template<class Derived>
void
nodejs_basic_parser<Derived>::
write_eof(error_code& ec)
{
ec_ = &ec;
http_parser_execute(&state_, hooks(), nullptr, 0);
if(! ec)
ec = detail::make_nodejs_error(
static_cast<int>(state_.http_errno));
}
template<class Derived>
void
nodejs_basic_parser<Derived>::
check_header()
{
if(! value_.empty())
{
impl().on_field(field_, value_);
field_.clear();
value_.clear();
}
}
template<class Derived>
int
nodejs_basic_parser<Derived>::
cb_message_start(http_parser* p)
{
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
t.complete_ = false;
t.url_.clear();
t.status_.clear();
t.field_.clear();
t.value_.clear();
t.impl().on_start();
return 0;
}
template<class Derived>
int
nodejs_basic_parser<Derived>::
cb_url(http_parser* p,
char const* in, std::size_t bytes)
{
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
t.url_.append(in, bytes);
return 0;
}
template<class Derived>
int
nodejs_basic_parser<Derived>::
cb_status(http_parser* p,
char const* in, std::size_t bytes)
{
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
t.status_.append(in, bytes);
return 0;
}
template<class Derived>
int
nodejs_basic_parser<Derived>::
cb_header_field(http_parser* p,
char const* in, std::size_t bytes)
{
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
t.check_header();
t.field_.append(in, bytes);
return 0;
}
template<class Derived>
int
nodejs_basic_parser<Derived>::
cb_header_value(http_parser* p,
char const* in, std::size_t bytes)
{
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
t.value_.append(in, bytes);
return 0;
}
template<class Derived>
int
nodejs_basic_parser<Derived>::
cb_headers_complete(http_parser* p)
{
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
t.check_header();
t.impl().on_headers_complete(*t.ec_);
if(*t.ec_)
return 1;
bool const keep_alive =
http_should_keep_alive(p) != 0;
if(p->type == http_parser_type::HTTP_REQUEST)
{
t.impl().on_request(p->method, t.url_,
p->http_major, p->http_minor, keep_alive,
p->upgrade);
return 0;
}
return t.impl().on_response(p->status_code, t.status_,
p->http_major, p->http_minor, keep_alive,
p->upgrade) ? 0 : 1;
}
template<class Derived>
int
nodejs_basic_parser<Derived>::
cb_body(http_parser* p,
char const* in, std::size_t bytes)
{
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
t.impl().on_body(in, bytes, *t.ec_);
return *t.ec_ ? 1 : 0;
}
template<class Derived>
int
nodejs_basic_parser<Derived>::
cb_message_complete(http_parser* p)
{
auto& t = *reinterpret_cast<nodejs_basic_parser*>(p->data);
t.complete_ = true;
t.impl().on_complete();
return 0;
}
template<class Derived>
int
nodejs_basic_parser<Derived>::
cb_chunk_header(http_parser*)
{
return 0;
}
template<class Derived>
int
nodejs_basic_parser<Derived>::
cb_chunk_complete(http_parser*)
{
return 0;
}
//------------------------------------------------------------------------------
/** A HTTP parser.
The parser may only be used once.
*/
template<bool isRequest, class Body, class Fields>
class nodejs_parser
: public nodejs_basic_parser<nodejs_parser<isRequest, Body, Fields>>
{
bool started_ = false;
public:
nodejs_parser(nodejs_parser&&) = default;
nodejs_parser()
: http::nodejs_basic_parser<nodejs_parser>(isRequest)
{
}
/// Returns `true` if at least one byte has been processed
bool
started()
{
return started_;
}
private:
friend class http::nodejs_basic_parser<nodejs_parser>;
void
on_start()
{
started_ = true;
}
void
on_field(std::string const&, std::string const&)
{
}
void
on_headers_complete(error_code& ec)
{
// vFALCO TODO Decode the Content-Length and
// Transfer-Encoding, see if we can reserve the buffer.
//
// r_.reserve(content_length)
ec = {};
}
bool
on_request(unsigned, std::string const&,
int, int, bool, bool, std::true_type)
{
return true;
}
bool
on_request(unsigned, std::string const&,
int, int, bool, bool, std::false_type)
{
return true;
}
bool
on_request(unsigned method, std::string const& url,
int major, int minor, bool keep_alive, bool upgrade)
{
return on_request(method, url,
major, minor, keep_alive, upgrade,
std::integral_constant<
bool, isRequest>{});
}
bool
on_response(int, std::string const&,
int, int, bool, bool, std::true_type)
{
return true;
}
bool
on_response(int, std::string const&, int, int, bool, bool,
std::false_type)
{
return true;
}
bool
on_response(int status, std::string const& reason,
int major, int minor, bool keep_alive, bool upgrade)
{
return on_response(
status, reason, major, minor, keep_alive, upgrade,
std::integral_constant<bool, ! isRequest>{});
}
void
on_body(void const*, std::size_t, error_code& ec)
{
ec = {};
}
void
on_complete()
{
}
};
} // http
} // beast
} // boost
#endif