HTTP support improvements:

* RFC2616 compliance
* Case insensitive equality, inequality operators for strings
* Improvements to http::parser
* Tidy up HTTP method enumeration
This commit is contained in:
Vinnie Falco
2014-07-31 15:45:27 -07:00
parent df32f27762
commit 5b8bb822ba
11 changed files with 1389 additions and 262 deletions

View File

@@ -24,13 +24,16 @@
#include <beast/http/impl/basic_url.cpp> #include <beast/http/impl/basic_url.cpp>
#include <beast/http/impl/get.cpp> #include <beast/http/impl/get.cpp>
#include <beast/http/impl/joyent_parser.cpp> #include <beast/http/impl/joyent_parser.cpp>
#include <beast/http/impl/message_parser.cpp> #include <beast/http/impl/method.cpp>
#include <beast/http/impl/ParsedURL.cpp> #include <beast/http/impl/ParsedURL.cpp>
#include <beast/http/impl/parser.cpp>
#include <beast/http/impl/raw_parser.cpp> #include <beast/http/impl/raw_parser.cpp>
#include <beast/http/impl/URL.cpp> #include <beast/http/impl/URL.cpp>
#include <beast/http/tests/basic_message.test.cpp>
#include <beast/http/tests/basic_url.test.cpp> #include <beast/http/tests/basic_url.test.cpp>
#include <beast/http/tests/client_session.test.cpp> #include <beast/http/tests/client_session.test.cpp>
#include <beast/http/tests/ParsedURL.cpp> #include <beast/http/tests/ParsedURL.cpp>
#include <beast/http/tests/rfc2616.test.cpp>
#include <beast/http/tests/urls_large_data.cpp> #include <beast/http/tests/urls_large_data.cpp>

509
beast/http/basic_message.h Normal file
View File

@@ -0,0 +1,509 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
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_MESSAGE_H_INCLUDED
#define BEAST_HTTP_MESSAGE_H_INCLUDED
#include <beast/http/method.h>
#include <beast/http/parser.h>
#include <beast/utility/ci_char_traits.h>
#include <boost/intrusive/list.hpp>
#include <boost/intrusive/set.hpp>
#include <boost/system/error_code.hpp>
#include <algorithm>
#include <cassert>
#include <cctype>
#include <string>
#include <utility>
#include <vector>
namespace beast {
namespace http {
class basic_message
{
public:
class parser;
typedef boost::system::error_code error_code;
private:
class element
: public boost::intrusive::set_base_hook <
boost::intrusive::link_mode <
boost::intrusive::normal_link>
>
, public boost::intrusive::list_base_hook <
boost::intrusive::link_mode <
boost::intrusive::normal_link>
>
{
public:
element (std::string const& f, std::string const& v)
: field (f)
, value (v)
{
}
std::string field;
std::string value;
};
struct less : private beast::ci_less
{
template <class String>
bool
operator() (String const& lhs, element const& rhs) const
{
return beast::ci_less::operator() (lhs, rhs.field);
}
template <class String>
bool
operator() (element const& lhs, String const& rhs) const
{
return beast::ci_less::operator() (lhs.field, rhs);
}
};
class headers_t : private less
{
private:
typedef boost::intrusive::make_list <element,
boost::intrusive::constant_time_size <false>
>::type list_t;
typedef boost::intrusive::make_set <element,
boost::intrusive::constant_time_size <true>
>::type set_t;
list_t list_;
set_t set_;
public:
typedef list_t::const_iterator iterator;
~headers_t()
{
clear();
}
headers_t() = default;
headers_t (headers_t&& other)
: list_ (std::move(other.list_))
, set_ (std::move(other.set_))
{
}
headers_t (headers_t const& other)
{
for (auto const& e : other.list_)
append (e.field, e.value);
}
headers_t&
operator= (headers_t&& other)
{
list_ = std::move(other.list_);
set_ = std::move(other.set_);
}
headers_t&
operator= (headers_t const& other)
{
clear();
for (auto const& e : other.list_)
append (e.field, e.value);
}
void
clear()
{
for (auto iter (list_.begin()); iter != list_.end();)
{
element* const p (&*iter);
++iter;
delete p;
}
}
iterator
begin() const
{
return list_.cbegin();
}
iterator
end() const
{
return list_.cend();
}
iterator
cbegin() const
{
return list_.cbegin();
}
iterator
cend() const
{
return list_.cend();
}
iterator
find (std::string const& field) const
{
auto const iter (set_.find (field,
std::cref(static_cast<less const&>(*this))));
if (iter == set_.end())
return list_.end();
return list_.iterator_to (*iter);
}
std::string const&
operator[] (std::string const& field) const
{
static std::string none;
auto const found (find (field));
if (found == end())
return none;
return found->value;
}
void
append (std::string const& field, std::string const& value)
{
set_t::insert_commit_data d;
auto const result (set_.insert_check (field,
std::cref(static_cast<less const&>(*this)), d));
if (result.second)
{
element* const p (new element (field, value));
list_.push_back (*p);
auto const iter (set_.insert_commit (*p, d));
return;
}
// If field already exists, append comma
// separated value as per RFC2616 section 4.2
auto& cur (result.first->value);
cur.reserve (cur.size() + 1 + value.size());
cur.append (1, ',');
cur.append (value);
}
};
bool request_;
// request
beast::http::method_t method_;
std::string url_;
// response
int status_;
std::string reason_;
// message
std::pair<int, int> version_;
bool keep_alive_;
bool upgrade_;
public:
~basic_message() = default;
basic_message()
: request_ (true)
, method_ (beast::http::method_t::http_get)
, url_ ("/")
, status_ (200)
, version_ (1, 1)
, keep_alive_ (false)
, upgrade_ (false)
{
}
basic_message (basic_message&& other)
: request_ (true)
, method_ (std::move(other.method_))
, url_ (std::move(other.url_))
, status_ (other.status_)
, reason_ (std::move(other.reason_))
, version_ (other.version_)
, keep_alive_ (other.keep_alive_)
, upgrade_ (other.upgrade_)
, headers (std::move(other.headers))
{
}
bool
request() const
{
return request_;
}
void
request (bool value)
{
request_ = value;
}
// Request
void
method (beast::http::method_t http_method)
{
method_ = http_method;
}
beast::http::method_t
method() const
{
return method_;
}
void
url (std::string const& s)
{
url_ = s;
}
std::string const&
url() const
{
return url_;
}
/** Returns `false` if this is not the last message.
When keep_alive returns `false`:
* Server roles respond with a "Connection: close" header.
* Client roles close the connection.
*/
bool
keep_alive() const
{
return keep_alive_;
}
/** Set the keep_alive setting. */
void
keep_alive (bool value)
{
keep_alive_ = value;
}
/** Returns `true` if this is an HTTP Upgrade message.
@note Upgrade messages have no content body.
*/
bool
upgrade() const
{
return upgrade_;
}
/** Set the upgrade setting. */
void
upgrade (bool value)
{
upgrade_ = value;
}
int
status() const
{
return status_;
}
void
status (int code)
{
status_ = code;
}
std::string const&
reason() const
{
return reason_;
}
void
reason (std::string const& text)
{
reason_ = text;
}
// Message
void
version (int major, int minor)
{
version_ = std::make_pair (major, minor);
}
std::pair<int, int>
version() const
{
return version_;
}
// Memberspace
headers_t headers;
};
//------------------------------------------------------------------------------
class basic_message::parser : public beast::http::parser
{
private:
basic_message& message_;
public:
parser (basic_message& message, bool request)
: beast::http::parser (request)
, message_ (message)
{
message_.request(request);
}
private:
void
on_start () override
{
}
bool
on_request (method_t method, std::string const& url,
int major, int minor, bool keep_alive, bool upgrade) override
{
message_.method (method);
message_.url (url);
message_.version (major, minor);
message_.keep_alive(keep_alive);
message_.upgrade(upgrade);
return upgrade ? false : false;
}
bool
on_response (int status, std::string const& text,
int major, int minor, bool keep_alive, bool upgrade) override
{
message_.status (status);
message_.reason (text);
message_.version (major, minor);
message_.keep_alive(keep_alive);
message_.upgrade(upgrade);
return upgrade ? false : false;
}
void
on_field (std::string const& field, std::string const& value) override
{
message_.headers.append (field, value);
}
void
on_body (void const* data, std::size_t bytes) override
{
}
void
on_complete() override
{
}
};
//------------------------------------------------------------------------------
template <class AsioStreamBuf>
void
xwrite (AsioStreamBuf& stream, std::string const& s)
{
stream.commit (boost::asio::buffer_copy (
stream.prepare (s.size()), boost::asio::buffer(s)));
}
template <class AsioStreamBuf>
void
xwrite (AsioStreamBuf& stream, char const* s)
{
auto const len (::strlen(s));
stream.commit (boost::asio::buffer_copy (
stream.prepare (len), boost::asio::buffer (s, len)));
}
template <class AsioStreamBuf>
void
xwrite (AsioStreamBuf& stream, basic_message const& m)
{
if (m.request())
{
xwrite (stream, to_string(m.method()));
xwrite (stream, " ");
xwrite (stream, m.url());
xwrite (stream, " HTTP/");
xwrite (stream, std::to_string(m.version().first));
xwrite (stream, ".");
xwrite (stream, std::to_string(m.version().second));
}
else
{
xwrite (stream, "HTTP/");
xwrite (stream, std::to_string(m.version().first));
xwrite (stream, ".");
xwrite (stream, std::to_string(m.version().second));
xwrite (stream, " ");
xwrite (stream, std::to_string(m.status()));
xwrite (stream, " ");
xwrite (stream, m.reason());
}
xwrite (stream, "\r\n");
for (auto const& header : m.headers)
{
xwrite (stream, header.field);
xwrite (stream, ": ");
xwrite (stream, header.value);
xwrite (stream, "\r\n");
}
xwrite (stream, "\r\n");
}
template <class = void>
std::string
to_string (basic_message const& m)
{
std::stringstream ss;
if (m.request())
ss << to_string(m.method()) << " " << m.url() << " HTTP/" <<
std::to_string(m.version().first) << "." <<
std::to_string(m.version().second) << "\r\n";
else
ss << "HTTP/" << std::to_string(m.version().first) << "." <<
std::to_string(m.version().second) << " " <<
std::to_string(m.status()) << " " << m.reason() << "\r\n";
for (auto const& header : m.headers)
ss << header.field << ": " << header.value << "\r\n";
ss << "\r\n";
return ss.str();
}
} // http
} // beast
#endif

View File

@@ -1,224 +0,0 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#include <beast/http/message_parser.h>
#include <beast/http/impl/joyent_parser.h>
namespace beast {
namespace http {
message_parser::message_parser (bool request)
: complete_ (false)
, checked_url_ (false)
{
static_assert (sizeof(joyent::http_parser) == sizeof(state_t),
"state_t size must match http_parser size");
static_assert (sizeof(joyent::http_parser_settings) == sizeof(hooks_t),
"hooks_t size must match http_parser_settings size");
auto s (reinterpret_cast <joyent::http_parser*> (&state_));
s->data = this;
auto h (reinterpret_cast <joyent::http_parser_settings*> (&hooks_));
h->on_message_begin = &message_parser::cb_message_start;
h->on_url = &message_parser::cb_url;
h->on_status = &message_parser::cb_status;
h->on_header_field = &message_parser::cb_header_field;
h->on_header_value = &message_parser::cb_header_value;
h->on_headers_complete = &message_parser::cb_headers_done;
h->on_body = &message_parser::cb_body;
h->on_message_complete = &message_parser::cb_message_complete;
joyent::http_parser_init (s, request
? joyent::http_parser_type::HTTP_REQUEST
: joyent::http_parser_type::HTTP_RESPONSE);
}
std::pair <message_parser::error_code, std::size_t>
message_parser::write_one (void const* in, std::size_t bytes)
{
std::pair <error_code, std::size_t> result (error_code(), 0);
auto s (reinterpret_cast <joyent::http_parser*> (&state_));
auto h (reinterpret_cast <joyent::http_parser_settings const*> (&hooks_));
result.second = joyent::http_parser_execute (s, h,
static_cast <const char*> (in), bytes);
result.first = ec_;
return result;
}
//------------------------------------------------------------------------------
int
message_parser::check_url()
{
if (! checked_url_)
{
checked_url_ = true;
auto const p (reinterpret_cast <joyent::http_parser const*> (&state_));
ec_ = on_request (
joyent::convert_http_method (joyent::http_method(p->method)),
p->http_major, p->http_minor, url_);
if (ec_)
return 1;
}
return 0;
}
int
message_parser::do_message_start ()
{
return ec_ ? 1 : 0;
}
int
message_parser::do_url (char const* in, std::size_t bytes)
{
url_.append (static_cast <char const*> (in), bytes);
return 0;
}
int
message_parser::do_status (char const* in, std::size_t bytes)
{
return ec_ ? 1 : 0;
}
int
message_parser::do_header_field (char const* in, std::size_t bytes)
{
if (check_url())
return 1;
if (! value_.empty())
{
ec_ = on_field (field_, value_);
if (ec_)
return 1;
field_.clear();
value_.clear();
}
field_.append (static_cast <char const*> (in), bytes);
return 0;
}
int
message_parser::do_header_value (char const* in, std::size_t bytes)
{
value_.append (static_cast <char const*> (in), bytes);
return 0;
}
// Returning 1 from here tells the joyent parser
// that the message has no body (e.g. a HEAD request).
//
int
message_parser::do_headers_done ()
{
if (check_url())
return 1;
if (! value_.empty())
{
ec_ = on_field (field_, value_);
if (ec_)
return 1;
field_.clear();
value_.clear();
}
return ec_ ? 1 : 0;
}
int
message_parser::do_body (char const* in, std::size_t bytes)
{
return ec_ ? 1 : 0;
}
int
message_parser::do_message_complete ()
{
complete_ = true;
return 0;
}
//------------------------------------------------------------------------------
int
message_parser::cb_message_start (joyent::http_parser* p)
{
return reinterpret_cast <message_parser*> (
p->data)->do_message_start();
}
int
message_parser::cb_url (joyent::http_parser* p,
char const* in, std::size_t bytes)
{
return reinterpret_cast <message_parser*> (
p->data)->do_url (in, bytes);
}
int
message_parser::cb_status (joyent::http_parser* p,
char const* in, std::size_t bytes)
{
return reinterpret_cast <message_parser*> (
p->data)->do_status (in, bytes);
}
int
message_parser::cb_header_field (joyent::http_parser* p,
char const* in, std::size_t bytes)
{
return reinterpret_cast <message_parser*> (
p->data)->do_header_field (in, bytes);
}
int
message_parser::cb_header_value (joyent::http_parser* p,
char const* in, std::size_t bytes)
{
return reinterpret_cast <message_parser*> (
p->data)->do_header_value (in, bytes);
}
int
message_parser::cb_headers_done (joyent::http_parser* p)
{
return reinterpret_cast <message_parser*> (
p->data)->do_headers_done();
}
int
message_parser::cb_body (joyent::http_parser* p,
char const* in, std::size_t bytes)
{
return reinterpret_cast <message_parser*> (
p->data)->do_body (
in, bytes);
}
int
message_parser::cb_message_complete (joyent::http_parser* p)
{
return reinterpret_cast <message_parser*> (
p->data)->do_message_complete();
}
} // http
} // beast

124
beast/http/impl/method.cpp Normal file
View File

@@ -0,0 +1,124 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#include <beast/http/method.h>
#include <cassert>
namespace beast {
namespace http {
std::string
to_string (method_t m)
{
switch(m)
{
case method_t::http_delete: return "DELETE";
case method_t::http_get: return "GET";
case method_t::http_head: return "HEAD";
case method_t::http_post: return "POST";
case method_t::http_put: return "PUT";
case method_t::http_connect: return "CONNECT";
case method_t::http_options: return "OPTIONS";
case method_t::http_trace: return "TRACE";
case method_t::http_copy: return "COPY";
case method_t::http_lock: return "LOCK";
case method_t::http_mkcol: return "MKCOL";
case method_t::http_move: return "MOVE";
case method_t::http_propfind: return "PROPFIND";
case method_t::http_proppatch: return "PROPPATCH";
case method_t::http_search: return "SEARCH";
case method_t::http_unlock: return "UNLOCK";
case method_t::http_report: return "REPORT";
case method_t::http_mkactivity: return "MKACTIVITY";
case method_t::http_checkout: return "CHECKOUT";
case method_t::http_merge: return "MERGE";
case method_t::http_msearch: return "MSEARCH";
case method_t::http_notify: return "NOTIFY";
case method_t::http_subscribe: return "SUBSCRIBE";
case method_t::http_unsubscribe: return "UNSUBSCRIBE";
case method_t::http_patch: return "PATCH";
case method_t::http_purge: return "PURGE";
default:
assert(false);
break;
};
return "GET";
}
std::string
status_text (int status)
{
switch(status)
{
case 100: return "Continue";
case 101: return "Switching Protocols";
case 200: return "OK";
case 201: return "Created";
case 202: return "Accepted";
case 203: return "Non-Authoritative Information";
case 204: return "No Content";
case 205: return "Reset Content";
case 206: return "Partial Content";
case 300: return "Multiple Choices";
case 301: return "Moved Permanently";
case 302: return "Found";
case 303: return "See Other";
case 304: return "Not Modified";
case 305: return "Use Proxy";
//case 306: return "<reserved>";
case 307: return "Temporary Redirect";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 402: return "Payment Required";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 406: return "Not Acceptable";
case 407: return "Proxy Authentication Required";
case 408: return "Request Timeout";
case 409: return "Conflict";
case 410: return "Gone";
case 411: return "Length Required";
case 412: return "Precondition Failed";
case 413: return "Request Entity Too Large";
case 414: return "Request-URI Too Long";
case 415: return "Unsupported Media Type";
case 416: return "Requested Range Not Satisfiable";
case 417: return "Expectation Failed";
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Timeout";
case 505: return "HTTP Version Not Supported";
default:
break;
}
return "Unknown HTTP status";
}
}
}

232
beast/http/impl/parser.cpp Normal file
View File

@@ -0,0 +1,232 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#include <beast/http/parser.h>
#include <beast/http/impl/joyent_parser.h>
#include <beast/http/rfc2616.h>
namespace beast {
namespace http {
parser::parser (bool request)
{
static_assert (sizeof(joyent::http_parser) == sizeof(state_t),
"state_t size must match http_parser size");
static_assert (sizeof(joyent::http_parser_settings) == sizeof(hooks_t),
"hooks_t size must match http_parser_settings size");
auto s (reinterpret_cast <joyent::http_parser*> (&state_));
s->data = this;
auto h (reinterpret_cast <joyent::http_parser_settings*> (&hooks_));
h->on_message_begin = &parser::cb_message_start;
h->on_url = &parser::cb_url;
h->on_status = &parser::cb_status;
h->on_header_field = &parser::cb_header_field;
h->on_header_value = &parser::cb_header_value;
h->on_headers_complete = &parser::cb_headers_complete;
h->on_body = &parser::cb_body;
h->on_message_complete = &parser::cb_message_complete;
joyent::http_parser_init (s, request
? joyent::http_parser_type::HTTP_REQUEST
: joyent::http_parser_type::HTTP_RESPONSE);
}
std::pair <parser::error_code, std::size_t>
parser::write (void const* data, std::size_t bytes)
{
std::pair <error_code, std::size_t> result (error_code(), 0);
auto s (reinterpret_cast <joyent::http_parser*> (&state_));
auto h (reinterpret_cast <joyent::http_parser_settings const*> (&hooks_));
result.second = joyent::http_parser_execute (s, h,
static_cast <const char*> (data), bytes);
result.first = ec_;
return result;
}
parser::error_code
parser::eof()
{
auto s (reinterpret_cast <joyent::http_parser*> (&state_));
auto h (reinterpret_cast <joyent::http_parser_settings const*> (&hooks_));
joyent::http_parser_execute (s, h, nullptr, 0);
return ec_;
}
//------------------------------------------------------------------------------
void
parser::check_header()
{
if (! value_.empty())
{
rfc2616::trim_right_in_place (value_);
on_field (field_, value_);
field_.clear();
value_.clear();
}
}
int
parser::do_message_start ()
{
complete_ = false;
url_.clear();
status_.clear();
field_.clear();
value_.clear();
on_start();
return 0;
}
int
parser::do_url (char const* in, std::size_t bytes)
{
url_.append (static_cast <char const*> (in), bytes);
return 0;
}
int
parser::do_status (char const* in, std::size_t bytes)
{
status_.append (static_cast <char const*> (in), bytes);
return 0;
}
int
parser::do_header_field (char const* in, std::size_t bytes)
{
check_header();
field_.append (static_cast <char const*> (in), bytes);
return 0;
}
int
parser::do_header_value (char const* in, std::size_t bytes)
{
value_.append (static_cast <char const*> (in), bytes);
return 0;
}
/* Called when all the headers are complete but before
the content body, if present.
Returning 1 from here tells the joyent parser
that the message has no body (e.g. a HEAD request).
*/
int
parser::do_headers_complete()
{
check_header();
auto const p (reinterpret_cast <joyent::http_parser const*> (&state_));
bool const keep_alive (joyent::http_should_keep_alive (p) != 0);
if (p->type == joyent::http_parser_type::HTTP_REQUEST)
return on_request (joyent::convert_http_method (
joyent::http_method(p->method)), url_,
p->http_major, p->http_minor, keep_alive, p->upgrade) ? 0 : 1;
return on_response (p->status_code, status_,
p->http_major, p->http_minor, keep_alive, p->upgrade) ? 0 : 1;
}
/* Called repeatedly for the content body. The passed buffer
has already had the transfer-encoding removed.
*/
int
parser::do_body (char const* in, std::size_t bytes)
{
on_body (in, bytes);
return 0;
}
/* Called when the both the headers and content body (if any) are complete. */
int
parser::do_message_complete ()
{
complete_ = true;
on_complete();
return 0;
}
//------------------------------------------------------------------------------
int
parser::cb_message_start (joyent::http_parser* p)
{
return reinterpret_cast <parser*> (
p->data)->do_message_start();
}
int
parser::cb_url (joyent::http_parser* p,
char const* in, std::size_t bytes)
{
return reinterpret_cast <parser*> (
p->data)->do_url (in, bytes);
}
int
parser::cb_status (joyent::http_parser* p,
char const* in, std::size_t bytes)
{
return reinterpret_cast <parser*> (
p->data)->do_status (in, bytes);
}
int
parser::cb_header_field (joyent::http_parser* p,
char const* in, std::size_t bytes)
{
return reinterpret_cast <parser*> (
p->data)->do_header_field (in, bytes);
}
int
parser::cb_header_value (joyent::http_parser* p,
char const* in, std::size_t bytes)
{
return reinterpret_cast <parser*> (
p->data)->do_header_value (in, bytes);
}
int
parser::cb_headers_complete (joyent::http_parser* p)
{
return reinterpret_cast <parser*> (
p->data)->do_headers_complete();
}
int
parser::cb_body (joyent::http_parser* p,
char const* in, std::size_t bytes)
{
return reinterpret_cast <parser*> (
p->data)->do_body (
in, bytes);
}
int
parser::cb_message_complete (joyent::http_parser* p)
{
return reinterpret_cast <parser*> (
p->data)->do_message_complete();
}
} // http
} // beast

View File

@@ -21,6 +21,7 @@
#define BEAST_HTTP_METHOD_H_INCLUDED #define BEAST_HTTP_METHOD_H_INCLUDED
#include <memory> #include <memory>
#include <string>
namespace beast { namespace beast {
namespace http { namespace http {
@@ -65,6 +66,20 @@ enum class method_t
http_purge http_purge
}; };
std::string
to_string (method_t m);
template <class Stream>
Stream&
operator<< (Stream& s, method_t m)
{
return s << to_string(m);
}
/** Returns the string corresponding to the numeric HTTP status code. */
std::string
status_text (int status);
} }
} }

View File

@@ -17,10 +17,11 @@
*/ */
//============================================================================== //==============================================================================
#ifndef BEAST_HTTP_MESSAGE_PARSER_H_INCLUDED #ifndef BEAST_HTTP_PARSER_H_INCLUDED
#define BEAST_HTTP_MESSAGE_PARSER_H_INCLUDED #define BEAST_HTTP_PARSER_H_INCLUDED
#include <beast/http/method.h> #include <beast/http/method.h>
#include <boost/asio/buffer.hpp>
#include <boost/system/error_code.hpp> #include <boost/system/error_code.hpp>
#include <array> #include <array>
#include <cstdint> #include <cstdint>
@@ -35,7 +36,7 @@ struct http_parser;
namespace http { namespace http {
class message_parser class parser
{ {
public: public:
typedef boost::system::error_code error_code; typedef boost::system::error_code error_code;
@@ -82,9 +83,9 @@ private:
char state_ [sizeof(state_t)]; char state_ [sizeof(state_t)];
char hooks_ [sizeof(hooks_t)]; char hooks_ [sizeof(hooks_t)];
bool complete_; bool complete_ = false;
std::string url_; std::string url_;
bool checked_url_; std::string status_;
std::string field_; std::string field_;
std::string value_; std::string value_;
@@ -94,7 +95,7 @@ protected:
process an HTTP request. process an HTTP request.
*/ */
explicit explicit
message_parser (bool request); parser (bool request);
public: public:
/** Returns `true` if parsing is complete. /** Returns `true` if parsing is complete.
@@ -109,18 +110,18 @@ public:
/** Write data to the parser. /** Write data to the parser.
The return value includes the error code if any, The return value includes the error code if any,
and the number of bytes consumed in the input sequence. and the number of bytes consumed in the input sequence.
@param data A buffer containing the data to write
@param bytes The size of the buffer pointed to by data.
*/ */
std::pair <error_code, std::size_t> std::pair <error_code, std::size_t>
write_one (void const* in, std::size_t bytes); write (void const* data, std::size_t bytes);
template <class ConstBuffer>
std::pair <error_code, std::size_t>
write_one (ConstBuffer const& buffer)
{
return write_one (boost::asio::buffer_cast <void const*> (buffer),
boost::asio::buffer_size (buffer));
}
/** Write a set of buffer data to the parser.
The return value includes the error code if any,
and the number of bytes consumed in the input sequence.
@param buffers The buffers to write. These must meet the
requirements of ConstBufferSequence.
*/
template <class ConstBufferSequence> template <class ConstBufferSequence>
std::pair <error_code, std::size_t> std::pair <error_code, std::size_t>
write (ConstBufferSequence const& buffers) write (ConstBufferSequence const& buffers)
@@ -129,7 +130,9 @@ public:
for (auto const& buffer : buffers) for (auto const& buffer : buffers)
{ {
std::size_t bytes_consumed; std::size_t bytes_consumed;
std::tie (result.first, bytes_consumed) = write_one (buffer); std::tie (result.first, bytes_consumed) =
write (boost::asio::buffer_cast <void const*> (buffer),
boost::asio::buffer_size (buffer));
if (result.first) if (result.first)
break; break;
result.second += bytes_consumed; result.second += bytes_consumed;
@@ -137,25 +140,96 @@ public:
return result; return result;
} }
protected: /** Called to indicate the end of file.
virtual 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 Typically this should be called when the
socket indicates a closure.
*/
error_code error_code
on_request (method_t method, int http_major, eof();
int http_minor, std::string const& url) = 0;
protected:
/** Called once when a new message begins. */
virtual virtual
error_code void
on_start() = 0;
/** Called for each header field. */
virtual
void
on_field (std::string const& field, std::string const& value) = 0; on_field (std::string const& field, std::string const& value) = 0;
/** 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.
When upgrade is true, no content-body is expected, and the
return value is ignored.
@param method The HTTP method specified in the request line
@param major The HTTP major version number
@param minor The HTTP minor version number
@param url The URL specified in the request line
@param keep_alive `false` if this is the last message.
@param upgrade `true` if the Upgrade header is specified
@return `true` If upgrade is false and a content body is expected.
*/
virtual
bool
on_request (method_t method, std::string const& url,
int major, int minor, bool keep_alive, bool upgrade) = 0;
/** 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.
When upgrade is true, no content-body is expected, and the
return value is ignored.
@param status The numerical HTTP status code in the response line
@param text The status text in the response line
@param major The HTTP major version number
@param minor The HTTP minor version number
@param keep_alive `false` if this is the last message.
@param upgrade `true` if the Upgrade header is specified
@return `true` If upgrade is false and a content body is expected.
*/
virtual
bool
on_response (int status, std::string const& text,
int major, int minor, bool keep_alive, bool upgrade) = 0;
/** Called zero or more times for the content body.
Any transfer encoding is already decoded in the
memory pointed to by data.
@param data A memory block containing the next decoded
chunk of the content body.
@param bytes The number of bytes pointed to by data.
*/
virtual
void
on_body (void const* data, std::size_t bytes) = 0;
/** Called once when the message is complete. */
virtual
void
on_complete() = 0;
private: private:
int check_url(); void check_header();
int do_message_start (); int do_message_start ();
int do_url (char const* in, std::size_t bytes); int do_url (char const* in, std::size_t bytes);
int do_status (char const* in, std::size_t bytes); int do_status (char const* in, std::size_t bytes);
int do_header_field (char const* in, std::size_t bytes); int do_header_field (char const* in, std::size_t bytes);
int do_header_value (char const* in, std::size_t bytes); int do_header_value (char const* in, std::size_t bytes);
int do_headers_done (); int do_headers_complete ();
int do_body (char const* in, std::size_t bytes); int do_body (char const* in, std::size_t bytes);
int do_message_complete (); int do_message_complete ();
@@ -164,7 +238,7 @@ private:
static int cb_status (joyent::http_parser*, char const*, std::size_t); static int cb_status (joyent::http_parser*, char const*, std::size_t);
static int cb_header_field (joyent::http_parser*, char const*, std::size_t); static int cb_header_field (joyent::http_parser*, char const*, std::size_t);
static int cb_header_value (joyent::http_parser*, char const*, std::size_t); static int cb_header_value (joyent::http_parser*, char const*, std::size_t);
static int cb_headers_done (joyent::http_parser*); static int cb_headers_complete (joyent::http_parser*);
static int cb_body (joyent::http_parser*, char const*, std::size_t); static int cb_body (joyent::http_parser*, char const*, std::size_t);
static int cb_message_complete (joyent::http_parser*); static int cb_message_complete (joyent::http_parser*);
}; };

239
beast/http/rfc2616.h Normal file
View File

@@ -0,0 +1,239 @@
//------------------------------------------------------------------------------
/*
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_RFC2616_H_INCLUDED
#define BEAST_HTTP_RFC2616_H_INCLUDED
#include <algorithm>
#include <string>
#include <utility>
namespace beast {
namespace http {
/** Routines for performing RFC2616 compliance.
RFC2616:
Hypertext Transfer Protocol -- HTTP/1.1
http://www.w3.org/Protocols/rfc2616/rfc2616
*/
namespace rfc2616 {
/** Returns `true` if `c` is linear white space.
This excludes the CRLF sequence allowed for line continuations.
*/
template <class CharT>
bool
is_lws (CharT c)
{
return c == ' ' || c == '\t';
}
/** Returns `true` if `c` is any whitespace character. */
template <class CharT>
bool
is_white (CharT c)
{
switch (c)
{
case ' ': case '\f': case '\n':
case '\r': case '\t': case '\v':
return true;
};
return false;
}
/** Returns `true` if `c` is a control character. */
template <class CharT>
bool
is_ctl (CharT c)
{
return c <= 31 || c >= 127;
}
/** Returns `true` if `c` is a separator. */
template <class CharT>
bool
is_sep (CharT c)
{
switch (c)
{
case '(': case ')': case '<': case '>': case '@':
case ',': case ';': case ':': case '\\': case '"':
case '{': case '}': case ' ': case '\t':
return true;
};
return false;
}
template <class FwdIter>
FwdIter
trim_left (FwdIter first, FwdIter last)
{
return std::find_if_not (first, last,
&is_white <typename FwdIter::value_type>);
}
template <class FwdIter>
FwdIter
trim_right (FwdIter first, FwdIter last)
{
if (first == last)
return last;
do
{
--last;
if (! is_white (*last))
return ++last;
}
while (last != first);
return first;
}
template <class CharT, class Traits, class Allocator>
void
trim_right_in_place (std::basic_string <
CharT, Traits, Allocator>& s)
{
s.resize (std::distance (s.begin(),
trim_right (s.begin(), s.end())));
}
template <class FwdIter>
std::pair <FwdIter, FwdIter>
trim (FwdIter first, FwdIter last)
{
first = trim_left (first, last);
last = trim_right (first, last);
return std::make_pair (first, last);
}
template <class String>
String
trim (String const& s)
{
using std::begin;
using std::end;
auto first (begin(s));
auto last (end(s));
std::tie (first, last) = trim (first, last);
return { first, last };
}
template <class String>
String
trim_right (String const& s)
{
using std::begin;
using std::end;
auto first (begin(s));
auto last (end(s));
last = trim_right (first, last);
return { first, last };
}
inline
std::string
trim (std::string const& s)
{
return trim <std::string> (s);
}
/** Call a functor for each comma delimited element.
Quotes and escape sequences will be parsed and converted appropriately.
Excess white space, commas, double quotes, and empty elements are not
passed to func.
Format:
#(token|quoted-string)
Reference:
http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2
*/
template <class FwdIter, class Function>
void
for_each_element (FwdIter first, FwdIter last, Function func)
{
FwdIter iter (first);
std::string e;
while (iter != last)
{
if (*iter == '"')
{
// quoted-string
++iter;
while (iter != last)
{
if (*iter == '"')
{
++iter;
break;
}
if (*iter == '\\')
{
// quoted-pair
++iter;
if (iter != last)
e.append (1, *iter++);
}
else
{
// qdtext
e.append (1, *iter++);
}
}
if (! e.empty())
{
func (e);
e.clear();
}
}
else if (*iter == ',')
{
e = trim_right (e);
if (! e.empty())
{
func (e);
e.clear();
}
++iter;
}
else if (is_lws (*iter))
{
++iter;
}
else
{
e.append (1, *iter++);
}
}
if (! e.empty())
{
e = trim_right (e);
if (! e.empty())
func (e);
}
}
} // rfc2616
} // http
} // beast
#endif

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of Beast: https://github.com/vinniefalco/Beast This file is part of rippled: https://github.com/ripple/rippled
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com> Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -17,29 +17,48 @@
*/ */
//============================================================================== //==============================================================================
#if BEAST_INCLUDE_BEASTCONFIG #include <beast/http/basic_message.h>
#include "../../BeastConfig.h"
#endif
#include <beast/unit_test/suite.h> #include <beast/unit_test/suite.h>
namespace beast { namespace beast {
namespace http { namespace http {
class basic_message_tests : public unit_test::suite class basic_message_test : public beast::unit_test::suite
{ {
public: public:
void testCreate() std::pair <basic_message, bool>
request (std::string const& text)
{ {
pass(); basic_message m;
basic_message::parser p (m, true);
auto result (p.write (boost::asio::buffer(text)));
auto result2 (p.eof());
return std::make_pair (std::move(m), result.first);
} }
void run() void
run()
{ {
testCreate(); auto const result = request (
"GET / HTTP/1.1\r\n"
//"Connection: Upgrade\r\n"
//"Upgrade: Ripple\r\n"
"Field: \t Value \t \r\n"
"Blib: Continu\r\n"
" ation\r\n"
"Field: Hey\r\n"
"Content-Length: 1\r\n"
"\r\n"
"x"
);
log << "|" << result.first.headers["Field"] << "|";
pass();
} }
}; };
BEAST_DEFINE_TESTSUITE(basic_message,http,beast); BEAST_DEFINE_TESTSUITE(basic_message,http,beast);
}
} } // http
} // beast

View File

@@ -0,0 +1,93 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#if BEAST_INCLUDE_BEASTCONFIG
#include "../../BeastConfig.h"
#endif
#include <beast/http/rfc2616.h>
#include <beast/unit_test/suite.h>
#include <string>
#include <vector>
namespace beast {
namespace http {
namespace rfc2616 {
class rfc2616_test : public beast::unit_test::suite
{
public:
void
check (std::string const& value,
std::vector <std::string> const& expected)
{
std::vector <std::string> parsed;
for_each_element (value.begin(), value.end(),
[&](std::string const& element)
{
parsed.push_back (element);
});
expect (parsed == expected);
}
void
run()
{
check ("", {});
check (" ", {});
check (" ", {});
check ("\t", {});
check (" \t ", {});
check (",", {});
check (",,", {});
check (" ,", {});
check (" , ,", {});
check ("x", {"x"});
check (" x", {"x"});
check (" \t x", {"x"});
check ("x ", {"x"});
check ("x \t", {"x"});
check (" \t x \t ", {"x"});
check ("\"\"", {});
check (" \"\"", {});
check ("\"\" ", {});
check ("\"x\"", {"x"});
check ("\" \"", {" "});
check ("\" x\"", {" x"});
check ("\"x \"", {"x "});
check ("\" x \"", {" x "});
check ("\"\tx \"", {"\tx "});
check ("x,y", { "x", "y" });
check ("x ,\ty ", { "x", "y" });
check ("x, y, z", {"x","y","z"});
check ("x, \"y\", z", {"x","y","z"});
}
};
BEAST_DEFINE_TESTSUITE(rfc2616,http,beast);
}
}
}

View File

@@ -20,11 +20,54 @@
#ifndef BEAST_UTILITY_CI_CHAR_TRAITS_H_INCLUDED #ifndef BEAST_UTILITY_CI_CHAR_TRAITS_H_INCLUDED
#define BEAST_UTILITY_CI_CHAR_TRAITS_H_INCLUDED #define BEAST_UTILITY_CI_CHAR_TRAITS_H_INCLUDED
#include <beast/cxx14/algorithm.h> // <algorithm>
#include <locale> #include <locale>
#include <string> #include <string>
namespace beast { namespace beast {
/** Case-insensitive function object for performing less than comparisons. */
struct ci_less
{
static bool const is_transparent = true;
template <class String>
bool
operator() (String const& lhs, String const& rhs) const
{
typedef typename String::value_type char_type;
return std::lexicographical_compare (std::begin(lhs), std::end(lhs),
std::begin(rhs), std::end(rhs),
[] (char_type lhs, char_type rhs)
{
return std::tolower(lhs) < std::tolower(rhs);
}
);
}
};
/** Case-insensitive function object for performing equal to comparisons. */
struct ci_equal_to
{
static bool const is_transparent = true;
template <class String>
bool
operator() (String const& lhs, String const& rhs) const
{
typedef typename String::value_type char_type;
return std::equal (lhs.begin(), lhs.end(), rhs.begin(), rhs.end(),
[] (char_type lhs, char_type rhs)
{
return std::tolower(lhs) == std::tolower(rhs);
}
);
}
};
// DEPRECATED VFALCO This causes far more problems than it solves!
//
/** Case insensitive character traits. */
struct ci_char_traits : std::char_traits <char> struct ci_char_traits : std::char_traits <char>
{ {
static static