HTTP message and parser improvements:

* streambuf wrapper supports rvalue move
* message class holds a complete HTTP message
* body class holds the HTTP content body
* headers class holds RFC-compliant HTTP headers
* basic_parser provides class interface to joyent's http-parser
* parser class parses into a message object
* Remove unused http get client free function
* unit test for parsing malformed messages
This commit is contained in:
Vinnie Falco
2014-08-26 09:25:19 -07:00
committed by Tom Ritchford
parent d81154bf6c
commit b968821cc1
12 changed files with 1365 additions and 827 deletions

View File

@@ -17,22 +17,33 @@
*/ */
//============================================================================== //==============================================================================
#ifndef BEAST_HTTP_GET_H_INCLUDED #ifndef BEAST_ASIO_BASIC_STREAMBUF_H_INCLUDED
#define BEAST_HTTP_GET_H_INCLUDED #define BEAST_ASIO_BASIC_STREAMBUF_H_INCLUDED
#include <boost/system/error_code.hpp> #include <boost/asio/buffer.hpp>
#include <memory>
#include <string> #include <vector>
#include <utility>
namespace beast { namespace beast {
namespace http { namespace asio {
/** Perform simple HTTP GET to retrieve a resource as a string. */ template <class Alloc = std::allocator <char>>
std::pair <std::string, boost::system::error_code> class basic_streambuf : private Alloc
get (std::string const& url_string); {
private:
typedef std::allocator_traits <Alloc> alloc_traits;
std::vector <boost::asio::mutable_buffer> bufs_;
public:
~basic_streambuf()
{
for (auto const& buf : bufs_)
alloc_traits::deallocate (
boost::asio::buffer_cast<char const*>(buf));
}
} }
}
} // asio
} // beast
#endif #endif

View File

@@ -21,18 +21,17 @@
#include <BeastConfig.h> #include <BeastConfig.h>
#endif #endif
#include <beast/http/impl/basic_parser.cpp>
#include <beast/http/impl/basic_url.cpp> #include <beast/http/impl/basic_url.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/method.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/parser.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/rfc2616.test.cpp>
#include <beast/http/tests/urls_large_data.cpp> #include <beast/http/tests/urls_large_data.cpp>

View File

@@ -1,511 +0,0 @@
//------------------------------------------------------------------------------
/*
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_);
return *this;
}
headers_t&
operator= (headers_t const& other)
{
clear();
for (auto const& e : other.list_)
append (e.field, e.value);
return *this;
}
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);
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

259
beast/http/basic_parser.h Normal file
View File

@@ -0,0 +1,259 @@
//------------------------------------------------------------------------------
/*
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 <boost/asio/buffer.hpp>
#include <boost/system/error_code.hpp>
#include <array>
#include <cstdint>
#include <memory>
#include <string>
#include <beast/utility/noexcept.h>
namespace beast {
namespace joyent {
struct http_parser;
};
namespace http {
class basic_parser
{
private:
// These structures must exactly match the
// declarations in joyent http_parser.h include
//
struct state_t
{
unsigned int type : 2;
unsigned int flags : 6;
unsigned int state : 8;
unsigned int header_state : 8;
unsigned int index : 8;
std::uint32_t nread;
std::uint64_t content_length;
unsigned short http_major;
unsigned short http_minor;
unsigned int status_code : 16;
unsigned int method : 8;
unsigned int http_errno : 7;
unsigned int upgrade : 1;
void *data;
};
typedef int (*data_cb_t) (
state_t*, const char *at, size_t length);
typedef int (*cb_t) (state_t*);
struct hooks_t
{
cb_t on_message_begin;
data_cb_t on_url;
data_cb_t on_status;
data_cb_t on_header_field;
data_cb_t on_header_value;
cb_t on_headers_complete;
data_cb_t on_body;
cb_t on_message_complete;
};
char state_ [sizeof(state_t)];
char hooks_ [sizeof(hooks_t)];
bool complete_ = false;
std::string url_;
std::string status_;
std::string field_;
std::string value_;
public:
typedef boost::system::error_code error_code;
virtual
~basic_parser() = default;
/** Construct the parser.
If `request` is `true` this sets up the parser to
process an HTTP request.
*/
explicit
basic_parser (bool request) noexcept;
basic_parser&
operator= (basic_parser&& other);
/** Returns `true` if parsing is complete.
This is only defined when no errors have been returned.
*/
bool
complete() const noexcept
{
return complete_;
}
/** Returns the error if write or write_eof returned false. */
error_code
error() const noexcept;
/** Write data to the parser.
@param data A buffer containing the data to write
@param bytes The size of the buffer pointed to by data.
@return A pair with bool success, and the number of bytes consumed.
*/
std::pair <bool, std::size_t>
write (void const* data, std::size_t bytes);
/** 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.
@return A pair with bool success, and the number of bytes consumed.
*/
template <class ConstBufferSequence>
std::pair <bool, std::size_t>
write (ConstBufferSequence const& buffers)
{
std::pair <bool, std::size_t> result (true, 0);
for (auto const& buffer : buffers)
{
std::size_t bytes_consumed;
std::tie (result.first, bytes_consumed) =
write (boost::asio::buffer_cast <void const*> (buffer),
boost::asio::buffer_size (buffer));
if (! result.first)
break;
result.second += bytes_consumed;
if (complete())
break;
}
return result;
}
/** 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.
@return `true` if the message is complete.
*/
bool
write_eof();
protected:
/** Called once when a new message begins. */
virtual
void
on_start() = 0;
/** Called for each header field. */
virtual
void
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:
void check_header();
int do_message_start ();
int do_url (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_value (char const* in, std::size_t bytes);
int do_headers_complete ();
int do_body (char const* in, std::size_t bytes);
int do_message_complete ();
static int cb_message_start (joyent::http_parser*);
static int cb_url (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_value (joyent::http_parser*, char const*, std::size_t);
static int cb_headers_complete (joyent::http_parser*);
static int cb_body (joyent::http_parser*, char const*, std::size_t);
static int cb_message_complete (joyent::http_parser*);
};
} // http
} // beast
#endif

133
beast/http/body.h Normal file
View File

@@ -0,0 +1,133 @@
//------------------------------------------------------------------------------
/*
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_BODY_H_INCLUDED
#define BEAST_HTTP_BODY_H_INCLUDED
#include <cstdint>
#include <memory>
#include <boost/asio/buffer.hpp>
#include <boost/asio/streambuf.hpp>
#include <boost/optional.hpp>
#include <beast/cxx14/memory.h> // <memory>
#include <string>
namespace beast {
namespace http {
/** Container for the HTTP content-body. */
class body
{
private:
typedef boost::asio::streambuf buffer_type;
// Hack: use unique_ptr because streambuf cant be moved
std::unique_ptr <buffer_type> buf_;
public:
typedef buffer_type::const_buffers_type const_buffers_type;
body();
body (body&& other);
body& operator= (body&& other);
body (body const&) = delete;
body& operator= (body const&) = delete;
void
write (void const* data, std::size_t bytes);
template <class ConstBufferSequence>
void
write (ConstBufferSequence const& buffers)
{
for (auto const& buffer : buffers)
write (boost::asio::buffer_cast <void const*> (buffer),
boost::asio::buffer_size (buffer));
}
std::size_t
size() const;
const_buffers_type
data() const;
};
template <class = void>
std::string
to_string (body const& b)
{
std::string s;
auto const& data (b.data());
auto const n (boost::asio::buffer_size (data));
s.resize (n);
boost::asio::buffer_copy (
boost::asio::buffer (&s[0], n), data);
return s;
}
//------------------------------------------------------------------------------
inline
body::body()
: buf_ (std::make_unique <buffer_type>())
{
}
inline
body::body (body&& other)
{
buf_ = std::move(other.buf_);
}
inline
body&
body::operator= (body&& other)
{
buf_ = std::move(other.buf_);
return *this;
}
inline
void
body::write (void const* data, std::size_t bytes)
{
buf_->commit (boost::asio::buffer_copy (buf_->prepare (bytes),
boost::asio::const_buffers_1 (data, bytes)));
}
inline
std::size_t
body::size() const
{
return buf_->size();
}
inline
auto
body::data() const
-> const_buffers_type
{
return buf_->data();
}
} // http
} // beast
#endif

350
beast/http/headers.h Normal file
View File

@@ -0,0 +1,350 @@
//------------------------------------------------------------------------------
/*
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_HEADERS_H_INCLUDED
#define BEAST_HTTP_HEADERS_H_INCLUDED
#include <beast/utility/ci_char_traits.h>
#include <boost/intrusive/list.hpp>
#include <boost/intrusive/set.hpp>
#include <algorithm>
#include <cctype>
#include <map>
#include <ostream>
#include <string>
#include <utility>
namespace beast {
namespace http {
namespace detail {
struct element
: boost::intrusive::set_base_hook <
boost::intrusive::link_mode <
boost::intrusive::normal_link>>
, boost::intrusive::list_base_hook <
boost::intrusive::link_mode <
boost::intrusive::normal_link>>
{
template <class = void>
element (std::string const& f, std::string const& 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;
template <class String>
bool
operator() (element const& lhs, String const& rhs) const;
};
} // detail
/** Holds a collection of HTTP headers. */
class headers : private detail::less
{
private:
typedef boost::intrusive::make_list <detail::element,
boost::intrusive::constant_time_size <false>
>::type list_t;
typedef boost::intrusive::make_set <detail::element,
boost::intrusive::constant_time_size <true>
>::type set_t;
list_t list_;
set_t set_;
public:
typedef list_t::const_iterator iterator;
typedef iterator const_iterator;
~headers()
{
clear();
}
headers() = default;
template <class = void>
headers (headers&& other);
template <class = void>
headers (headers const& other);
template <class = void>
headers&
operator= (headers&& other);
template <class = void>
headers&
operator= (headers const& other);
/** Returns an iterator to headers in order of appearance. */
/** @{ */
iterator
begin() const;
iterator
end() const;
iterator
cbegin() const;
iterator
cend() const;
/** @} */
/** Returns an iterator to the case-insensitive matching header. */
template <class = void>
iterator
find (std::string const& field) const;
/** Returns the value for a case-insensitive matching header, or "" */
template <class = void>
std::string const&
operator[] (std::string const& field) const;
/** Clear the contents of the headers. */
template <class = void>
void
clear() noexcept;
/** Append a field value.
If a field value already exists the new value will be
extended as per RFC2616 Section 4.2.
*/
// VFALCO TODO Consider allowing rvalue references for std::move
template <class = void>
void
append (std::string const& field, std::string const& value);
};
template <class = void>
std::string
to_string (headers const& h);
// HACK!
template <class = void>
std::map <std::string, std::string>
build_map (headers const& h);
//------------------------------------------------------------------------------
namespace detail {
template <class>
element::element (
std::string const& f, std::string const& v)
: field (f)
, value (v)
{
}
template <class String>
bool
less::operator() (
String const& lhs, element const& rhs) const
{
return beast::ci_less::operator() (lhs, rhs.field);
}
template <class String>
bool
less::operator() (
element const& lhs, String const& rhs) const
{
return beast::ci_less::operator() (lhs.field, rhs);
}
} // detail
//------------------------------------------------------------------------------
template <class>
headers::headers (headers&& other)
: list_ (std::move(other.list_))
, set_ (std::move(other.set_))
{
}
template <class>
headers::headers (headers const& other)
{
for (auto const& e : other.list_)
append (e.field, e.value);
}
template <class>
headers&
headers::operator= (headers&& other)
{
list_ = std::move(other.list_);
set_ = std::move(other.set_);
return *this;
}
template <class>
headers&
headers::operator= (headers const& other)
{
clear();
for (auto const& e : other.list_)
append (e.field, e.value);
return *this;
}
inline
headers::iterator
headers::begin() const
{
return list_.cbegin();
}
inline
headers::iterator
headers::end() const
{
return list_.cend();
}
inline
headers::iterator
headers::cbegin() const
{
return list_.cbegin();
}
inline
headers::iterator
headers::cend() const
{
return list_.cend();
}
template <class>
headers::iterator
headers::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);
}
template <class>
std::string const&
headers::operator[] (std::string const& field) const
{
static std::string none;
auto const found (find (field));
if (found == end())
return none;
return found->value;
}
template <class>
void
headers::clear() noexcept
{
for (auto iter (list_.begin()); iter != list_.end();)
delete &(*iter++);
}
template <class>
void
headers::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)
{
detail::element* const p =
new detail::element (field, value);
list_.push_back (*p);
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);
}
//------------------------------------------------------------------------------
template <class>
std::string
to_string (headers const& h)
{
std::string s;
std::size_t n (0);
for (auto const& e : h)
n += e.field.size() + 2 + e.value.size() + 2;
s.reserve (n);
for (auto const& e : h)
{
s.append (e.field);
s.append (": ");
s.append (e.value);
s.append ("\r\n");
}
return s;
}
inline
std::ostream&
operator<< (std::ostream& s, headers const& h)
{
s << to_string(h);
return s;
}
template <class>
std::map <std::string, std::string>
build_map (headers const& h)
{
std::map <std::string, std::string> c;
for (auto const& e : h)
{
auto key (e.field);
// TODO Replace with safe C++14 version
std::transform (key.begin(), key.end(), key.begin(), ::tolower);
c [key] = e.value;
}
return c;
}
} // http
} // beast
#endif

View File

@@ -17,14 +17,64 @@
*/ */
//============================================================================== //==============================================================================
#include <beast/http/parser.h> #include <beast/http/basic_parser.h>
#include <beast/http/impl/joyent_parser.h> #include <beast/http/impl/joyent_parser.h>
#include <beast/http/rfc2616.h> #include <beast/http/rfc2616.h>
#include <beast/utility/noexcept.h>
#include <boost/system/error_code.hpp>
namespace beast { namespace beast {
namespace http { namespace http {
parser::parser (bool request) boost::system::error_category const&
message_category() noexcept
{
class message_category_t : public boost::system::error_category
{
public:
const char*
name() const noexcept override
{
return "http::message";
}
std::string
message (int ev) const override
{
return joyent::http_errno_description (
static_cast<joyent::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;
}
};
static message_category_t cat;
return cat;
}
//------------------------------------------------------------------------------
basic_parser::basic_parser (bool request) noexcept
{ {
static_assert (sizeof(joyent::http_parser) == sizeof(state_t), static_assert (sizeof(joyent::http_parser) == sizeof(state_t),
"state_t size must match http_parser size"); "state_t size must match http_parser size");
@@ -36,45 +86,66 @@ parser::parser (bool request)
s->data = this; s->data = this;
auto h (reinterpret_cast <joyent::http_parser_settings*> (&hooks_)); auto h (reinterpret_cast <joyent::http_parser_settings*> (&hooks_));
h->on_message_begin = &parser::cb_message_start; h->on_message_begin = &basic_parser::cb_message_start;
h->on_url = &parser::cb_url; h->on_url = &basic_parser::cb_url;
h->on_status = &parser::cb_status; h->on_status = &basic_parser::cb_status;
h->on_header_field = &parser::cb_header_field; h->on_header_field = &basic_parser::cb_header_field;
h->on_header_value = &parser::cb_header_value; h->on_header_value = &basic_parser::cb_header_value;
h->on_headers_complete = &parser::cb_headers_complete; h->on_headers_complete = &basic_parser::cb_headers_complete;
h->on_body = &parser::cb_body; h->on_body = &basic_parser::cb_body;
h->on_message_complete = &parser::cb_message_complete; h->on_message_complete = &basic_parser::cb_message_complete;
joyent::http_parser_init (s, request joyent::http_parser_init (s, request
? joyent::http_parser_type::HTTP_REQUEST ? joyent::http_parser_type::HTTP_REQUEST
: joyent::http_parser_type::HTTP_RESPONSE); : joyent::http_parser_type::HTTP_RESPONSE);
} }
std::pair <parser::error_code, std::size_t> basic_parser&
parser::write (void const* data, std::size_t bytes) basic_parser::operator= (basic_parser&& other)
{ {
std::pair <error_code, std::size_t> result (error_code(), 0); *reinterpret_cast<joyent::http_parser*>(&state_) =
*reinterpret_cast<joyent::http_parser*>(&other.state_);
reinterpret_cast<joyent::http_parser*>(&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;
}
basic_parser::error_code
basic_parser::error() const noexcept
{
auto s (reinterpret_cast <joyent::http_parser const*> (&state_));
return error_code{static_cast<int>(s->http_errno), message_category()};
}
std::pair <bool, std::size_t>
basic_parser::write (void const* data, std::size_t bytes)
{
std::pair <bool, std::size_t> result (false, 0);
auto s (reinterpret_cast <joyent::http_parser*> (&state_)); auto s (reinterpret_cast <joyent::http_parser*> (&state_));
auto h (reinterpret_cast <joyent::http_parser_settings const*> (&hooks_)); auto h (reinterpret_cast <joyent::http_parser_settings const*> (&hooks_));
result.second = joyent::http_parser_execute (s, h, result.second = joyent::http_parser_execute (s, h,
static_cast <const char*> (data), bytes); static_cast <const char*> (data), bytes);
result.first = ec_; result.first = s->http_errno == 0;
return result; return result;
} }
parser::error_code bool
parser::eof() basic_parser::write_eof()
{ {
auto s (reinterpret_cast <joyent::http_parser*> (&state_)); auto s (reinterpret_cast <joyent::http_parser*> (&state_));
auto h (reinterpret_cast <joyent::http_parser_settings const*> (&hooks_)); auto h (reinterpret_cast <joyent::http_parser_settings const*> (&hooks_));
joyent::http_parser_execute (s, h, nullptr, 0); joyent::http_parser_execute (s, h, nullptr, 0);
return ec_; return s->http_errno == 0;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void void
parser::check_header() basic_parser::check_header()
{ {
if (! value_.empty()) if (! value_.empty())
{ {
@@ -86,7 +157,7 @@ parser::check_header()
} }
int int
parser::do_message_start () basic_parser::do_message_start ()
{ {
complete_ = false; complete_ = false;
url_.clear(); url_.clear();
@@ -98,21 +169,21 @@ parser::do_message_start ()
} }
int int
parser::do_url (char const* in, std::size_t bytes) basic_parser::do_url (char const* in, std::size_t bytes)
{ {
url_.append (static_cast <char const*> (in), bytes); url_.append (static_cast <char const*> (in), bytes);
return 0; return 0;
} }
int int
parser::do_status (char const* in, std::size_t bytes) basic_parser::do_status (char const* in, std::size_t bytes)
{ {
status_.append (static_cast <char const*> (in), bytes); status_.append (static_cast <char const*> (in), bytes);
return 0; return 0;
} }
int int
parser::do_header_field (char const* in, std::size_t bytes) basic_parser::do_header_field (char const* in, std::size_t bytes)
{ {
check_header(); check_header();
field_.append (static_cast <char const*> (in), bytes); field_.append (static_cast <char const*> (in), bytes);
@@ -120,7 +191,7 @@ parser::do_header_field (char const* in, std::size_t bytes)
} }
int int
parser::do_header_value (char const* in, std::size_t bytes) basic_parser::do_header_value (char const* in, std::size_t bytes)
{ {
value_.append (static_cast <char const*> (in), bytes); value_.append (static_cast <char const*> (in), bytes);
return 0; return 0;
@@ -132,7 +203,7 @@ parser::do_header_value (char const* in, std::size_t bytes)
that the message has no body (e.g. a HEAD request). that the message has no body (e.g. a HEAD request).
*/ */
int int
parser::do_headers_complete() basic_parser::do_headers_complete()
{ {
check_header(); check_header();
auto const p (reinterpret_cast <joyent::http_parser const*> (&state_)); auto const p (reinterpret_cast <joyent::http_parser const*> (&state_));
@@ -149,7 +220,7 @@ parser::do_headers_complete()
has already had the transfer-encoding removed. has already had the transfer-encoding removed.
*/ */
int int
parser::do_body (char const* in, std::size_t bytes) basic_parser::do_body (char const* in, std::size_t bytes)
{ {
on_body (in, bytes); on_body (in, bytes);
return 0; return 0;
@@ -157,7 +228,7 @@ parser::do_body (char const* in, std::size_t bytes)
/* Called when the both the headers and content body (if any) are complete. */ /* Called when the both the headers and content body (if any) are complete. */
int int
parser::do_message_complete () basic_parser::do_message_complete ()
{ {
complete_ = true; complete_ = true;
on_complete(); on_complete();
@@ -167,64 +238,64 @@ parser::do_message_complete ()
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
int int
parser::cb_message_start (joyent::http_parser* p) basic_parser::cb_message_start (joyent::http_parser* p)
{ {
return reinterpret_cast <parser*> ( return reinterpret_cast <basic_parser*> (
p->data)->do_message_start(); p->data)->do_message_start();
} }
int int
parser::cb_url (joyent::http_parser* p, basic_parser::cb_url (joyent::http_parser* p,
char const* in, std::size_t bytes) char const* in, std::size_t bytes)
{ {
return reinterpret_cast <parser*> ( return reinterpret_cast <basic_parser*> (
p->data)->do_url (in, bytes); p->data)->do_url (in, bytes);
} }
int int
parser::cb_status (joyent::http_parser* p, basic_parser::cb_status (joyent::http_parser* p,
char const* in, std::size_t bytes) char const* in, std::size_t bytes)
{ {
return reinterpret_cast <parser*> ( return reinterpret_cast <basic_parser*> (
p->data)->do_status (in, bytes); p->data)->do_status (in, bytes);
} }
int int
parser::cb_header_field (joyent::http_parser* p, basic_parser::cb_header_field (joyent::http_parser* p,
char const* in, std::size_t bytes) char const* in, std::size_t bytes)
{ {
return reinterpret_cast <parser*> ( return reinterpret_cast <basic_parser*> (
p->data)->do_header_field (in, bytes); p->data)->do_header_field (in, bytes);
} }
int int
parser::cb_header_value (joyent::http_parser* p, basic_parser::cb_header_value (joyent::http_parser* p,
char const* in, std::size_t bytes) char const* in, std::size_t bytes)
{ {
return reinterpret_cast <parser*> ( return reinterpret_cast <basic_parser*> (
p->data)->do_header_value (in, bytes); p->data)->do_header_value (in, bytes);
} }
int int
parser::cb_headers_complete (joyent::http_parser* p) basic_parser::cb_headers_complete (joyent::http_parser* p)
{ {
return reinterpret_cast <parser*> ( return reinterpret_cast <basic_parser*> (
p->data)->do_headers_complete(); p->data)->do_headers_complete();
} }
int int
parser::cb_body (joyent::http_parser* p, basic_parser::cb_body (joyent::http_parser* p,
char const* in, std::size_t bytes) char const* in, std::size_t bytes)
{ {
return reinterpret_cast <parser*> ( return reinterpret_cast <basic_parser*> (
p->data)->do_body ( p->data)->do_body (
in, bytes); in, bytes);
} }
int int
parser::cb_message_complete (joyent::http_parser* p) basic_parser::cb_message_complete (joyent::http_parser* p)
{ {
return reinterpret_cast <parser*> ( return reinterpret_cast <basic_parser*> (
p->data)->do_message_complete(); p->data)->do_message_complete();
} }

View File

@@ -1,42 +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/get.h>
#include <beast/http/basic_url.h>
namespace beast {
namespace http {
std::pair <std::string, boost::system::error_code>
get (std::string const& url_string)
{
std::pair <std::string, boost::system::error_code> result;
url u;
u.parse (url_string, result.second);
if (result.second)
return result;
return result;
}
}
}

309
beast/http/message.h Normal file
View File

@@ -0,0 +1,309 @@
//------------------------------------------------------------------------------
/*
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_MESSAGE_H_INCLUDED
#define BEAST_HTTP_MESSAGE_H_INCLUDED
#include <beast/http/basic_parser.h>
#include <beast/http/body.h>
#include <beast/http/method.h>
#include <beast/http/headers.h>
#include <beast/utility/ci_char_traits.h>
#include <boost/intrusive/list.hpp>
#include <boost/intrusive/set.hpp>
#include <algorithm>
#include <cctype>
#include <ostream>
#include <string>
#include <utility>
namespace beast {
namespace http {
class message
{
private:
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:
~message() = default;
message (message const&) = delete;
message& operator= (message const&) = delete;
template <class = void>
message();
template <class = void>
message (message&& other);
template <class = void>
message& operator= (message&& other);
// Memberspace
beast::http::headers headers;
beast::http::body body;
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_;
}
};
//------------------------------------------------------------------------------
template <class>
message::message()
: request_ (true)
, method_ (beast::http::method_t::http_get)
, url_ ("/")
, status_ (200)
, version_ (1, 1)
, keep_alive_ (false)
, upgrade_ (false)
{
}
template <class>
message::message (message&& other)
: request_ (other.request_)
, 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))
, body (std::move(other.body))
{
}
template <class>
message&
message::operator= (message&& other)
{
request_ = other.request_;
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);
body = std::move(other.body);
return *this;
}
//------------------------------------------------------------------------------
template <class AsioStreamBuf>
void
write (AsioStreamBuf& stream, std::string const& s)
{
stream.commit (boost::asio::buffer_copy (
stream.prepare (s.size()), boost::asio::buffer(s)));
}
template <class AsioStreamBuf>
void
write (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
write (AsioStreamBuf& stream, message const& m)
{
if (m.request())
{
write (stream, to_string(m.method()));
write (stream, " ");
write (stream, m.url());
write (stream, " HTTP/");
write (stream, std::to_string(m.version().first));
write (stream, ".");
write (stream, std::to_string(m.version().second));
}
else
{
write (stream, "HTTP/");
write (stream, std::to_string(m.version().first));
write (stream, ".");
write (stream, std::to_string(m.version().second));
write (stream, " ");
write (stream, std::to_string(m.status()));
write (stream, " ");
write (stream, m.reason());
}
write (stream, "\r\n");
for (auto const& header : m.headers)
{
write (stream, header.field);
write (stream, ": ");
write (stream, header.value);
write (stream, "\r\n");
}
write (stream, "\r\n");
}
template <class = void>
std::string
to_string (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";
ss << to_string(m.headers);
ss << "\r\n";
return ss.str();
}
} // http
} // beast
#endif

View File

@@ -20,229 +20,165 @@
#ifndef BEAST_HTTP_PARSER_H_INCLUDED #ifndef BEAST_HTTP_PARSER_H_INCLUDED
#define BEAST_HTTP_PARSER_H_INCLUDED #define BEAST_HTTP_PARSER_H_INCLUDED
#include <beast/http/method.h> #include <beast/http/message.h>
#include <boost/asio/buffer.hpp>
#include <boost/system/error_code.hpp>
#include <array>
#include <cstdint>
#include <memory>
#include <string> #include <string>
#include <utility>
namespace beast { namespace beast {
namespace joyent {
struct http_parser;
};
namespace http { namespace http {
class parser /** Parser for HTTP messages.
The result is stored in a message object.
*/
class parser : public beast::http::basic_parser
{ {
private:
std::reference_wrapper <message> message_;
public: public:
typedef boost::system::error_code error_code; /** Construct a parser for HTTP request or response.
The result is stored in the passed message.
*/
parser (message& m, bool request)
: beast::http::basic_parser (request)
, message_(m)
{
message_.get().request(request);
}
template <class = void>
parser&
operator= (parser&& other);
private: private:
// These structures must exactly match the template <class = void>
// declarations in joyent http_parser.h include void
// do_start ();
struct state_t
{
unsigned int type : 2;
unsigned int flags : 6;
unsigned int state : 8;
unsigned int header_state : 8;
unsigned int index : 8;
std::uint32_t nread;
std::uint64_t content_length;
unsigned short http_major;
unsigned short http_minor;
unsigned int status_code : 16;
unsigned int method : 8;
unsigned int http_errno : 7;
unsigned int upgrade : 1;
void *data;
};
typedef int (*data_cb_t) ( template <class = void>
state_t*, const char *at, size_t length);
typedef int (*cb_t) (state_t*);
struct hooks_t
{
cb_t on_message_begin;
data_cb_t on_url;
data_cb_t on_status;
data_cb_t on_header_field;
data_cb_t on_header_value;
cb_t on_headers_complete;
data_cb_t on_body;
cb_t on_message_complete;
};
error_code ec_;
char state_ [sizeof(state_t)];
char hooks_ [sizeof(hooks_t)];
bool complete_ = false;
std::string url_;
std::string status_;
std::string field_;
std::string value_;
protected:
/** Construct the parser.
If `request` is `true` this sets up the parser to
process an HTTP request.
*/
explicit
parser (bool request);
public:
/** Returns `true` if parsing is complete.
This is only defined when no errors have been returned.
*/
bool bool
complete() const do_request (method_t method, std::string const& url,
int major, int minor, bool keep_alive, bool upgrade);
template <class = void>
bool
do_response (int status, std::string const& text,
int major, int minor, bool keep_alive, bool upgrade);
template <class = void>
void
do_field (std::string const& field, std::string const& value);
template <class = void>
void
do_body (void const* data, std::size_t bytes);
template <class = void>
void
do_complete();
void
on_start () override
{ {
return complete_; do_start();
} }
/** Write data to the parser.
The return value includes the error code if any,
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>
write (void const* data, std::size_t bytes);
/** 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>
std::pair <error_code, std::size_t>
write (ConstBufferSequence const& buffers)
{
std::pair <error_code, std::size_t> result (error_code(), 0);
for (auto const& buffer : buffers)
{
std::size_t bytes_consumed;
std::tie (result.first, bytes_consumed) =
write (boost::asio::buffer_cast <void const*> (buffer),
boost::asio::buffer_size (buffer));
if (result.first)
break;
result.second += bytes_consumed;
}
return result;
}
/** 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 Typically this should be called when the
socket indicates a closure.
*/
error_code
eof();
protected:
/** Called once when a new message begins. */
virtual
void
on_start() = 0;
/** Called for each header field. */
virtual
void
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 bool
on_request (method_t method, std::string const& url, on_request (method_t method, std::string const& url,
int major, int minor, bool keep_alive, bool upgrade) = 0; int major, int minor, bool keep_alive, bool upgrade) override
{
return do_request (method, url, major, minor, keep_alive, 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.
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 bool
on_response (int status, std::string const& text, on_response (int status, std::string const& text,
int major, int minor, bool keep_alive, bool upgrade) = 0; int major, int minor, bool keep_alive, bool upgrade) override
{
return do_response (status, text, major, minor, keep_alive, upgrade);
}
/** 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 void
on_body (void const* data, std::size_t bytes) = 0; on_field (std::string const& field, std::string const& value) override
{
do_field (field, value);
}
/** Called once when the message is complete. */
virtual
void void
on_complete() = 0; on_body (void const* data, std::size_t bytes) override
{
do_body (data, bytes);
}
private: void
void check_header(); on_complete() override
{
int do_message_start (); do_complete();
int do_url (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_value (char const* in, std::size_t bytes);
int do_headers_complete ();
int do_body (char const* in, std::size_t bytes);
int do_message_complete ();
static int cb_message_start (joyent::http_parser*);
static int cb_url (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_value (joyent::http_parser*, char const*, std::size_t);
static int cb_headers_complete (joyent::http_parser*);
static int cb_body (joyent::http_parser*, char const*, std::size_t);
static int cb_message_complete (joyent::http_parser*);
}; };
//------------------------------------------------------------------------------
template <class>
parser&
parser::operator= (parser&& other)
{
basic_parser::operator= (std::move(other));
message_ = std::move (other.message_);
return *this;
}
template <class>
void
parser::do_start ()
{
}
template <class>
bool
parser::do_request (method_t method, std::string const& url,
int major, int minor, bool keep_alive, bool upgrade)
{
message_.get().method (method);
message_.get().url (url);
message_.get().version (major, minor);
message_.get().keep_alive (keep_alive);
message_.get().upgrade (upgrade);
return true;
}
template <class>
bool
parser::do_response (int status, std::string const& text,
int major, int minor, bool keep_alive, bool upgrade)
{
message_.get().status (status);
message_.get().reason (text);
message_.get().version (major, minor);
message_.get().keep_alive (keep_alive);
message_.get().upgrade (upgrade);
return true;
}
template <class>
void
parser::do_field (std::string const& field, std::string const& value)
{
message_.get().headers.append (field, value);
}
template <class>
void
parser::do_body (void const* data, std::size_t bytes)
{
message_.get().body.write (data, bytes);
}
template <class>
void
parser::do_complete()
{
}
} // http } // http
} // beast } // beast

View File

@@ -28,7 +28,6 @@
#include <beast/http/tests/urls_large_data.h> #include <beast/http/tests/urls_large_data.h>
#include <beast/http/client_session.h> #include <beast/http/client_session.h>
#include <beast/http/get.h>
#include <beast/asio/bind_handler.h> #include <beast/asio/bind_handler.h>
#include <beast/asio/memory_buffer.h> #include <beast/asio/memory_buffer.h>
#include <beast/utility/ci_char_traits.h> #include <beast/utility/ci_char_traits.h>
@@ -365,14 +364,8 @@ std::advance (last, 3000);
test_concurrent_get (std::begin (sequence), last); test_concurrent_get (std::begin (sequence), last);
} }
void test_get()
{
get ("http://www.google.com");
}
void run() void run()
{ {
//test_get();
test_concurrent_get (urls_large_data()); test_concurrent_get (urls_large_data());
} }
}; };

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of rippled: https://github.com/ripple/rippled This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright (c) 2012, 2013 Ripple Labs Inc. Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
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,27 +17,28 @@
*/ */
//============================================================================== //==============================================================================
#include <beast/http/basic_message.h> #include <beast/http/message.h>
#include <beast/http/parser.h>
#include <beast/unit_test/suite.h> #include <beast/unit_test/suite.h>
namespace beast { namespace beast {
namespace http { namespace http {
class basic_message_test : public beast::unit_test::suite class message_test : public beast::unit_test::suite
{ {
public: public:
std::pair <basic_message, bool> std::pair <message, bool>
request (std::string const& text) request (std::string const& text)
{ {
basic_message m; message m;
basic_message::parser p (m, true); parser p (m, true);
auto result (p.write (boost::asio::buffer(text))); auto result (p.write (boost::asio::buffer(text)));
p.eof(); p.write_eof();
return std::make_pair (std::move(m), result.first); return std::make_pair (std::move(m), result.first);
} }
void void
run() dump()
{ {
auto const result = request ( auto const result = request (
"GET / HTTP/1.1\r\n" "GET / HTTP/1.1\r\n"
@@ -51,14 +52,43 @@ public:
"\r\n" "\r\n"
"x" "x"
); );
log << result.first.headers;
log << "|" << result.first.headers["Field"] << "|"; log << "|" << result.first.headers["Field"] << "|";
}
pass(); void
run()
{
{
std::string const text =
"GET / HTTP/1.1\r\n"
"\r\n"
;
message m;
parser p (m, true);
auto result (p.write (boost::asio::buffer(text)));
expect (result.first);
auto result2 (p.write_eof());
expect (result2);
expect (p.complete());
}
{
// malformed
std::string const text =
"GET\r\n"
"\r\n"
;
message m;
parser p (m, true);
auto result (p.write (boost::asio::buffer(text)));
if (expect (! result.first))
expect (p.error().message() == "invalid HTTP method");
}
} }
}; };
BEAST_DEFINE_TESTSUITE(basic_message,http,beast); BEAST_DEFINE_TESTSUITE(message,http,beast);
} // http } // http
} // beast } // beast