diff --git a/beast/http/get.h b/beast/asio/streambuf.h similarity index 65% rename from beast/http/get.h rename to beast/asio/streambuf.h index b4d4cf0d5a..885b603a0f 100644 --- a/beast/http/get.h +++ b/beast/asio/streambuf.h @@ -17,22 +17,33 @@ */ //============================================================================== -#ifndef BEAST_HTTP_GET_H_INCLUDED -#define BEAST_HTTP_GET_H_INCLUDED +#ifndef BEAST_ASIO_BASIC_STREAMBUF_H_INCLUDED +#define BEAST_ASIO_BASIC_STREAMBUF_H_INCLUDED -#include - -#include -#include +#include +#include +#include namespace beast { -namespace http { +namespace asio { -/** Perform simple HTTP GET to retrieve a resource as a string. */ -std::pair -get (std::string const& url_string); +template > +class basic_streambuf : private Alloc +{ +private: + typedef std::allocator_traits alloc_traits; + std::vector bufs_; +public: + ~basic_streambuf() + { + for (auto const& buf : bufs_) + alloc_traits::deallocate ( + boost::asio::buffer_cast(buf)); + } } -} + +} // asio +} // beast #endif diff --git a/beast/http/HTTP.unity.cpp b/beast/http/HTTP.unity.cpp index 0e748e61c1..92072fe3ae 100644 --- a/beast/http/HTTP.unity.cpp +++ b/beast/http/HTTP.unity.cpp @@ -21,18 +21,17 @@ #include #endif +#include #include -#include #include #include #include -#include #include #include -#include #include #include +#include #include #include #include diff --git a/beast/http/basic_message.h b/beast/http/basic_message.h deleted file mode 100644 index 249b87e4d1..0000000000 --- a/beast/http/basic_message.h +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 - bool - operator() (String const& lhs, element const& rhs) const - { - return beast::ci_less::operator() (lhs, rhs.field); - } - - template - 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 - >::type list_t; - - typedef boost::intrusive::make_set - >::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(*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(*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 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 - 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 -void -xwrite (AsioStreamBuf& stream, std::string const& s) -{ - stream.commit (boost::asio::buffer_copy ( - stream.prepare (s.size()), boost::asio::buffer(s))); -} - -template -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 -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 -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 \ No newline at end of file diff --git a/beast/http/basic_parser.h b/beast/http/basic_parser.h new file mode 100644 index 0000000000..208ed3c09f --- /dev/null +++ b/beast/http/basic_parser.h @@ -0,0 +1,259 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + 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 +#include +#include +#include +#include +#include +#include +#include + +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 + 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 + std::pair + write (ConstBufferSequence const& buffers) + { + std::pair result (true, 0); + for (auto const& buffer : buffers) + { + std::size_t bytes_consumed; + std::tie (result.first, bytes_consumed) = + write (boost::asio::buffer_cast (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 diff --git a/beast/http/body.h b/beast/http/body.h new file mode 100644 index 0000000000..16bf77db08 --- /dev/null +++ b/beast/http/body.h @@ -0,0 +1,133 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + 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 +#include +#include +#include +#include +#include // +#include + +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 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 + void + write (ConstBufferSequence const& buffers) + { + for (auto const& buffer : buffers) + write (boost::asio::buffer_cast (buffer), + boost::asio::buffer_size (buffer)); + } + + std::size_t + size() const; + + const_buffers_type + data() const; +}; + +template +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 ()) +{ +} + +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 diff --git a/beast/http/headers.h b/beast/http/headers.h new file mode 100644 index 0000000000..e37f338a23 --- /dev/null +++ b/beast/http/headers.h @@ -0,0 +1,350 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + 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 +#include +#include +#include +#include +#include +#include +#include +#include + +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 + element (std::string const& f, std::string const& v); + + std::string field; + std::string value; +}; + +struct less : private beast::ci_less +{ + template + bool + operator() (String const& lhs, element const& rhs) const; + + template + 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 + >::type list_t; + + typedef boost::intrusive::make_set + >::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 + headers (headers&& other); + + template + headers (headers const& other); + + template + headers& + operator= (headers&& other); + + template + 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 + iterator + find (std::string const& field) const; + + /** Returns the value for a case-insensitive matching header, or "" */ + template + std::string const& + operator[] (std::string const& field) const; + + /** Clear the contents of the headers. */ + template + 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 + void + append (std::string const& field, std::string const& value); +}; + +template +std::string +to_string (headers const& h); + +// HACK! +template +std::map +build_map (headers const& h); + +//------------------------------------------------------------------------------ + +namespace detail { + +template +element::element ( + std::string const& f, std::string const& v) + : field (f) + , value (v) +{ +} + +template +bool +less::operator() ( + String const& lhs, element const& rhs) const +{ + return beast::ci_less::operator() (lhs, rhs.field); +} + +template +bool +less::operator() ( + element const& lhs, String const& rhs) const +{ + return beast::ci_less::operator() (lhs.field, rhs); +} + +} // detail + +//------------------------------------------------------------------------------ + +template +headers::headers (headers&& other) + : list_ (std::move(other.list_)) + , set_ (std::move(other.set_)) +{ + +} + +template +headers::headers (headers const& other) +{ + for (auto const& e : other.list_) + append (e.field, e.value); +} + +template +headers& +headers::operator= (headers&& other) +{ + list_ = std::move(other.list_); + set_ = std::move(other.set_); + return *this; +} + +template +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 +headers::iterator +headers::find (std::string const& field) const +{ + auto const iter (set_.find (field, + std::cref(static_cast(*this)))); + if (iter == set_.end()) + return list_.end(); + return list_.iterator_to (*iter); +} + +template +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 +void +headers::clear() noexcept +{ + for (auto iter (list_.begin()); iter != list_.end();) + delete &(*iter++); +} + +template +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(*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 +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 +std::map +build_map (headers const& h) +{ + std::map 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 \ No newline at end of file diff --git a/beast/http/impl/parser.cpp b/beast/http/impl/basic_parser.cpp similarity index 53% rename from beast/http/impl/parser.cpp rename to beast/http/impl/basic_parser.cpp index d34709c1b8..e34992bc5b 100644 --- a/beast/http/impl/parser.cpp +++ b/beast/http/impl/basic_parser.cpp @@ -17,14 +17,64 @@ */ //============================================================================== -#include +#include #include #include +#include +#include namespace beast { 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(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), "state_t size must match http_parser size"); @@ -36,45 +86,66 @@ parser::parser (bool request) s->data = this; auto h (reinterpret_cast (&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; - + h->on_message_begin = &basic_parser::cb_message_start; + h->on_url = &basic_parser::cb_url; + h->on_status = &basic_parser::cb_status; + h->on_header_field = &basic_parser::cb_header_field; + h->on_header_value = &basic_parser::cb_header_value; + h->on_headers_complete = &basic_parser::cb_headers_complete; + h->on_body = &basic_parser::cb_body; + h->on_message_complete = &basic_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::write (void const* data, std::size_t bytes) +basic_parser& +basic_parser::operator= (basic_parser&& other) { - std::pair result (error_code(), 0); + *reinterpret_cast(&state_) = + *reinterpret_cast(&other.state_); + reinterpret_cast(&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 (&state_)); + return error_code{static_cast(s->http_errno), message_category()}; +} + +std::pair +basic_parser::write (void const* data, std::size_t bytes) +{ + std::pair result (false, 0); auto s (reinterpret_cast (&state_)); auto h (reinterpret_cast (&hooks_)); result.second = joyent::http_parser_execute (s, h, static_cast (data), bytes); - result.first = ec_; + result.first = s->http_errno == 0; return result; } -parser::error_code -parser::eof() +bool +basic_parser::write_eof() { auto s (reinterpret_cast (&state_)); auto h (reinterpret_cast (&hooks_)); joyent::http_parser_execute (s, h, nullptr, 0); - return ec_; + return s->http_errno == 0; } //------------------------------------------------------------------------------ void -parser::check_header() +basic_parser::check_header() { if (! value_.empty()) { @@ -86,7 +157,7 @@ parser::check_header() } int -parser::do_message_start () +basic_parser::do_message_start () { complete_ = false; url_.clear(); @@ -98,21 +169,21 @@ parser::do_message_start () } 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 (in), bytes); return 0; } 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 (in), bytes); return 0; } 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(); field_.append (static_cast (in), bytes); @@ -120,7 +191,7 @@ parser::do_header_field (char const* in, std::size_t bytes) } 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 (in), bytes); 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). */ int -parser::do_headers_complete() +basic_parser::do_headers_complete() { check_header(); auto const p (reinterpret_cast (&state_)); @@ -149,7 +220,7 @@ parser::do_headers_complete() has already had the transfer-encoding removed. */ 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); 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. */ int -parser::do_message_complete () +basic_parser::do_message_complete () { complete_ = true; on_complete(); @@ -167,64 +238,64 @@ parser::do_message_complete () //------------------------------------------------------------------------------ int -parser::cb_message_start (joyent::http_parser* p) +basic_parser::cb_message_start (joyent::http_parser* p) { - return reinterpret_cast ( + return reinterpret_cast ( p->data)->do_message_start(); } int -parser::cb_url (joyent::http_parser* p, +basic_parser::cb_url (joyent::http_parser* p, char const* in, std::size_t bytes) { - return reinterpret_cast ( + return reinterpret_cast ( p->data)->do_url (in, bytes); } int -parser::cb_status (joyent::http_parser* p, +basic_parser::cb_status (joyent::http_parser* p, char const* in, std::size_t bytes) { - return reinterpret_cast ( + return reinterpret_cast ( p->data)->do_status (in, bytes); } 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) { - return reinterpret_cast ( + return reinterpret_cast ( p->data)->do_header_field (in, bytes); } 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) { - return reinterpret_cast ( + return reinterpret_cast ( p->data)->do_header_value (in, bytes); } int -parser::cb_headers_complete (joyent::http_parser* p) +basic_parser::cb_headers_complete (joyent::http_parser* p) { - return reinterpret_cast ( + return reinterpret_cast ( p->data)->do_headers_complete(); } int -parser::cb_body (joyent::http_parser* p, +basic_parser::cb_body (joyent::http_parser* p, char const* in, std::size_t bytes) { - return reinterpret_cast ( + return reinterpret_cast ( p->data)->do_body ( in, bytes); } int -parser::cb_message_complete (joyent::http_parser* p) +basic_parser::cb_message_complete (joyent::http_parser* p) { - return reinterpret_cast ( + return reinterpret_cast ( p->data)->do_message_complete(); } diff --git a/beast/http/impl/get.cpp b/beast/http/impl/get.cpp deleted file mode 100644 index 3079742190..0000000000 --- a/beast/http/impl/get.cpp +++ /dev/null @@ -1,42 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of Beast: https://github.com/vinniefalco/Beast - Copyright 2013, Vinnie Falco - - 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 - -#include - -namespace beast { -namespace http { - -std::pair -get (std::string const& url_string) -{ - std::pair result; - - url u; - u.parse (url_string, result.second); - if (result.second) - return result; - - return result; -} - - -} -} diff --git a/beast/http/message.h b/beast/http/message.h new file mode 100644 index 0000000000..2af1e18e6e --- /dev/null +++ b/beast/http/message.h @@ -0,0 +1,309 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 version_; + bool keep_alive_; + bool upgrade_; + +public: + ~message() = default; + message (message const&) = delete; + message& operator= (message const&) = delete; + + template + message(); + + template + message (message&& other); + + template + 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 + version() const + { + return version_; + } +}; + +//------------------------------------------------------------------------------ + +template +message::message() + : request_ (true) + , method_ (beast::http::method_t::http_get) + , url_ ("/") + , status_ (200) + , version_ (1, 1) + , keep_alive_ (false) + , upgrade_ (false) +{ +} + +template +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 +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 +void +write (AsioStreamBuf& stream, std::string const& s) +{ + stream.commit (boost::asio::buffer_copy ( + stream.prepare (s.size()), boost::asio::buffer(s))); +} + +template +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 +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 +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 \ No newline at end of file diff --git a/beast/http/parser.h b/beast/http/parser.h index dba185e24a..aac178aa38 100644 --- a/beast/http/parser.h +++ b/beast/http/parser.h @@ -20,230 +20,166 @@ #ifndef BEAST_HTTP_PARSER_H_INCLUDED #define BEAST_HTTP_PARSER_H_INCLUDED -#include -#include -#include -#include -#include -#include +#include #include +#include namespace beast { - -namespace joyent { -struct http_parser; -}; - 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_; + 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 + parser& + operator= (parser&& other); 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; - }; + template + void + do_start (); - 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; - }; - - 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. - */ + template bool - complete() const + do_request (method_t method, std::string const& url, + int major, int minor, bool keep_alive, bool upgrade); + + template + bool + do_response (int status, std::string const& text, + int major, int minor, bool keep_alive, bool upgrade); + + template + void + do_field (std::string const& field, std::string const& value); + + template + void + do_body (void const* data, std::size_t bytes); + + template + 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 - 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 - std::pair - write (ConstBufferSequence const& buffers) - { - std::pair 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 (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 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 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 - 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 - on_complete() = 0; + on_body (void const* data, std::size_t bytes) override + { + do_body (data, bytes); + } -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*); + void + on_complete() override + { + do_complete(); + } }; +//------------------------------------------------------------------------------ + +template +parser& +parser::operator= (parser&& other) +{ + basic_parser::operator= (std::move(other)); + message_ = std::move (other.message_); + return *this; +} + +template +void +parser::do_start () +{ +} + +template +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 +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 +void +parser::do_field (std::string const& field, std::string const& value) +{ + message_.get().headers.append (field, value); +} + +template +void +parser::do_body (void const* data, std::size_t bytes) +{ + message_.get().body.write (data, bytes); +} + +template +void +parser::do_complete() +{ +} + } // http } // beast -#endif +#endif \ No newline at end of file diff --git a/beast/http/tests/client_session.test.cpp b/beast/http/tests/client_session.test.cpp index 3b45eef069..85bab859c6 100644 --- a/beast/http/tests/client_session.test.cpp +++ b/beast/http/tests/client_session.test.cpp @@ -28,7 +28,6 @@ #include #include -#include #include #include #include @@ -365,14 +364,8 @@ std::advance (last, 3000); test_concurrent_get (std::begin (sequence), last); } - void test_get() - { - get ("http://www.google.com"); - } - void run() { - //test_get(); test_concurrent_get (urls_large_data()); } }; diff --git a/beast/http/tests/basic_message.test.cpp b/beast/http/tests/parser.test.cpp similarity index 57% rename from beast/http/tests/basic_message.test.cpp rename to beast/http/tests/parser.test.cpp index 9ca7c84636..d9e53e4639 100644 --- a/beast/http/tests/basic_message.test.cpp +++ b/beast/http/tests/parser.test.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,27 +17,28 @@ */ //============================================================================== -#include +#include +#include #include namespace beast { namespace http { -class basic_message_test : public beast::unit_test::suite +class message_test : public beast::unit_test::suite { public: - std::pair + std::pair request (std::string const& text) { - basic_message m; - basic_message::parser p (m, true); + message m; + parser p (m, true); auto result (p.write (boost::asio::buffer(text))); - p.eof(); + p.write_eof(); return std::make_pair (std::move(m), result.first); } void - run() + dump() { auto const result = request ( "GET / HTTP/1.1\r\n" @@ -51,14 +52,43 @@ public: "\r\n" "x" ); - + log << result.first.headers; 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 } // beast