diff --git a/Jamroot b/Jamroot index f7198a97c8..e18ad6b7d6 100644 --- a/Jamroot +++ b/Jamroot @@ -41,6 +41,10 @@ project beast . #/boost//headers /boost/system//boost_system + /boost/filesystem//boost_filesystem + /boost/program_options//boost_program_options +# ssl +# crypto BOOST_ALL_NO_LIB=1 multi static @@ -61,6 +65,8 @@ project beast HPUX:ipv6 QNXNTO:socket HAIKU:network + msvc:_SCL_SECURE_NO_WARNINGS=1 + msvc:_CRT_SECURE_NO_WARNINGS=1 : usage-requirements . : diff --git a/README.md b/README.md new file mode 100644 index 0000000000..32e2029926 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Beast: A C++ Library diff --git a/ReadMe0.md b/ReadMe0.md deleted file mode 100644 index 2e24a3a037..0000000000 --- a/ReadMe0.md +++ /dev/null @@ -1 +0,0 @@ -# Beast: An amazing cross platform library diff --git a/beast/ci_char_traits.h b/beast/ci_char_traits.h index 39d784ba6c..9d958a8ebe 100644 --- a/beast/ci_char_traits.h +++ b/beast/ci_char_traits.h @@ -20,6 +20,7 @@ #ifndef BEAST_UTILITY_CI_CHAR_TRAITS_H_INCLUDED #define BEAST_UTILITY_CI_CHAR_TRAITS_H_INCLUDED +#include #include #include #include @@ -34,16 +35,15 @@ struct ci_less { static bool const is_transparent = true; - template bool - operator() (String const& lhs, String const& rhs) const + operator()(boost::string_ref const& lhs, + boost::string_ref const& rhs) const noexcept { using std::begin; using std::end; - using char_type = typename String::value_type; - return std::lexicographical_compare ( + return std::lexicographical_compare( begin(lhs), end(lhs), begin(rhs), end(rhs), - [] (char_type lhs, char_type rhs) + [](char lhs, char rhs) { return std::tolower(lhs) < std::tolower(rhs); } diff --git a/beast/http.h b/beast/http.h new file mode 100644 index 0000000000..f9ead2b69e --- /dev/null +++ b/beast/http.h @@ -0,0 +1,39 @@ +//------------------------------------------------------------------------------ +/* + 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_H_INCLUDED +#define BEAST_HTTP_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/beast/http/URL.h b/beast/http/URL.h deleted file mode 100644 index dd7fd80722..0000000000 --- a/beast/http/URL.h +++ /dev/null @@ -1,195 +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. -*/ -//============================================================================== - -#ifndef BEAST_HTTP_URL_H_INCLUDED -#define BEAST_HTTP_URL_H_INCLUDED - -#include -#include - -namespace beast { - -/** A URL. - The accompanying robust parser is hardened against all forms of attack. -*/ -class URL -{ -public: - /** Construct a URL from it's components. */ - URL ( - std::string schema_, - std::string host_, - std::uint16_t port_, - std::string port_string_, - std::string path_, - std::string query_ = "", - std::string fragment_ = "", - std::string userinfo_ = ""); - - /** Construct an empty URL. */ - explicit URL () = default; - - /** Copy construct a URL. */ - URL (URL const& other) = default; - - /** Copy assign a URL. */ - URL& operator= (URL const& other) = default; - - /** Move construct a URL. */ - URL (URL&& other) = default; - - /** Returns `true` if this is an empty URL. */ - bool - empty () const; - - /** Returns the scheme of the URL. - If no scheme was specified, the string will be empty. - */ - std::string const& - scheme () const; - - /** Returns the host of the URL. - If no host was specified, the string will be empty. - */ - std::string const& - host () const; - - /** Returns the port number as an integer. - If no port was specified, the value will be zero. - */ - std::uint16_t - port () const; - - /** Returns the port number as a string. - If no port was specified, the string will be empty. - */ - std::string const& - port_string () const; - - /** Returns the path of the URL. - If no path was specified, the string will be empty. - */ - std::string const& - path () const; - - /** Returns the query parameters portion of the URL. - If no query parameters were present, the string will be empty. - */ - std::string const& - query () const; - - /** Returns the URL fragment, if any. */ - std::string const& - fragment () const; - - /** Returns the user information, if any. */ - std::string const& - userinfo () const; - -private: - std::string m_scheme; - std::string m_host; - std::uint16_t m_port = 0; - std::string m_port_string; - std::string m_path; - std::string m_query; - std::string m_fragment; - std::string m_userinfo; -}; - -/** Attempt to parse a string into a URL */ -std::pair -parse_URL(std::string const&); - -/** Retrieve the full URL as a single string. */ -std::string -to_string(URL const& url); - -/** Output stream conversion. */ -std::ostream& -operator<< (std::ostream& os, URL const& url); - -/** URL comparisons. */ -/** @{ */ -inline bool -operator== (URL const& lhs, URL const& rhs) -{ - return to_string (lhs) == to_string (rhs); -} - -inline bool -operator!= (URL const& lhs, URL const& rhs) -{ - return to_string (lhs) != to_string (rhs); -} - -inline bool -operator< (URL const& lhs, URL const& rhs) -{ - return to_string (lhs) < to_string (rhs); -} - -inline bool operator> (URL const& lhs, URL const& rhs) -{ - return to_string (rhs) < to_string (lhs); -} - -inline bool -operator<= (URL const& lhs, URL const& rhs) -{ - return ! (to_string (rhs) < to_string (lhs)); -} - -inline bool -operator>= (URL const& lhs, URL const& rhs) -{ - return ! (to_string (lhs) < to_string (rhs)); -} -/** @} */ - -/** boost::hash support */ -template -inline -void -hash_append (Hasher& h, URL const& url) -{ - using beast::hash_append; - hash_append (h, to_string (url)); -} - -} - -//------------------------------------------------------------------------------ - -namespace std { - -template <> -struct hash -{ - std::size_t operator() (beast::URL const& url) const - { - return std::hash{} (to_string (url)); - } -}; - -} - -//------------------------------------------------------------------------------ - -#endif diff --git a/beast/http/basic_headers.h b/beast/http/basic_headers.h new file mode 100644 index 0000000000..b10390e8eb --- /dev/null +++ b/beast/http/basic_headers.h @@ -0,0 +1,447 @@ +//------------------------------------------------------------------------------ +/* + 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_HEADERS_H_INCLUDED +#define BEAST_HTTP_BASIC_HEADERS_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // +#include + +namespace beast { +namespace http { + +template +class basic_headers; + +namespace detail { + +class basic_headers_base +{ +public: + struct value_type + { + std::string first; + std::string second; + + value_type(boost::string_ref const& name_, + boost::string_ref const& value_) + : first(name_) + , second(value_) + { + } + + boost::string_ref + name() const + { + return first; + } + + boost::string_ref + value() const + { + return second; + } + }; + +protected: + template + friend class beast::http::basic_headers; + + 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>> + { + value_type data; + + element(boost::string_ref const& name, + boost::string_ref const& value) + : data(name, value) + { + } + }; + + struct less : private ci_less + { + template + bool + operator()(String const& lhs, element const& rhs) const + { + return ci_less::operator()(lhs, rhs.data.first); + } + + template + bool + operator()(element const& lhs, String const& rhs) const + { + return ci_less::operator()(lhs.data.first, rhs); + } + + bool + operator()(element const& lhs, element const& rhs) const + { + return beast::ci_less::operator()( + lhs.data.first, rhs.data.first); + } + }; + + using list_t = typename boost::intrusive::make_list< + element, boost::intrusive::constant_time_size>::type; + + using set_t = typename boost::intrusive::make_set< + element, boost::intrusive::constant_time_size, + boost::intrusive::compare>::type; + + // data + set_t set_; + list_t list_; + + basic_headers_base(set_t&& set, list_t&& list) + : set_(std::move(set)) + , list_(std::move(list)) + { + + } + +public: + class const_iterator; + + using iterator = const_iterator; + + basic_headers_base() = default; + + /// Returns an iterator to the beginning of the field sequence. + iterator + begin() const; + + /// Returns an iterator to the end of the field sequence. + iterator + end() const; + + /// Returns an iterator to the beginning of the field sequence. + iterator + cbegin() const; + + /// Returns an iterator to the end of the field sequence. + iterator + cend() const; +}; + +//------------------------------------------------------------------------------ + +class basic_headers_base::const_iterator +{ + using iter_type = list_t::const_iterator; + + iter_type it_; + + template + friend class beast::http::basic_headers; + + friend class basic_headers_base; + + const_iterator(iter_type it) + : it_(it) + { + } + +public: + using value_type = + typename basic_headers_base::value_type; + using pointer = value_type const*; + using reference = value_type const&; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::bidirectional_iterator_tag; + + const_iterator() = default; + const_iterator(const_iterator&& other) = default; + const_iterator(const_iterator const& other) = default; + const_iterator& operator=(const_iterator&& other) = default; + const_iterator& operator=(const_iterator const& other) = default; + + bool + operator==(const_iterator const& other) const + { + return it_ == other.it_; + } + + bool + operator!=(const_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return it_->data; + } + + pointer + operator->() const + { + return &**this; + } + + const_iterator& + operator++() + { + ++it_; + return *this; + } + + const_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + const_iterator& + operator--() + { + --it_; + return *this; + } + + const_iterator + operator--(int) + { + auto temp = *this; + --(*this); + return temp; + } +}; + +} // detail + +//------------------------------------------------------------------------------ + +/** Container to store HTTP headers. + + Meets the requirements of `FieldSequence`. +*/ +template +class basic_headers +#if ! GENERATING_DOCS + : private empty_base_optimization< + typename std::allocator_traits:: + template rebind_alloc< + detail::basic_headers_base::element>> + , public detail::basic_headers_base +#endif +{ + using alloc_type = typename + std::allocator_traits:: + template rebind_alloc< + detail::basic_headers_base::element>; + + using alloc_traits = + std::allocator_traits; + + using size_type = + typename std::allocator_traits::size_type; + + void + delete_all(); + + void + move_assign(basic_headers&, std::false_type); + + void + move_assign(basic_headers&, std::true_type); + + void + copy_assign(basic_headers const&, std::false_type); + + void + copy_assign(basic_headers const&, std::true_type); + + template + void + copy_from(FieldSequence const& fs) + { + for(auto const& e : fs) + insert(e.first, e.second); + } + +public: + /// The type of allocator used. + using allocator_type = Allocator; + + /// Default constructor. + basic_headers() = default; + + /// Destructor + ~basic_headers(); + + /** Construct the headers. + + @param alloc The allocator to use. + */ + explicit + basic_headers(Allocator const& alloc); + + /** Move constructor. + + The moved-from object becomes an empty field sequence. + + @param other The object to move from. + */ + basic_headers(basic_headers&& other); + + /** Move assignment. + + The moved-from object becomes an empty field sequence. + + @param other The object to move from. + */ + basic_headers& operator=(basic_headers&& other); + + /// Copy constructor. + basic_headers(basic_headers const&); + + /// Copy assignment. + basic_headers& operator=(basic_headers const&); + + /// Copy constructor. + template + basic_headers(basic_headers const&); + + /// Copy assignment. + template + basic_headers& operator=(basic_headers const&); + + /// Construct from a field sequence. + template + basic_headers(FwdIt first, FwdIt last); + + /// Returns `true` if the field sequence contains no elements. + bool + empty() const + { + return set_.empty(); + } + + /// Returns the number of elements in the field sequence. + std::size_t + size() const + { + return set_.size(); + } + + /** Returns `true` if the specified field exists. */ + bool + exists(boost::string_ref const& name) const + { + return set_.find(name, less{}) != set_.end(); + } + + /** Returns an iterator to the case-insensitive matching header. */ + iterator + find(boost::string_ref const& name) const; + + /** Returns the value for a case-insensitive matching header, or "" */ + boost::string_ref + operator[](boost::string_ref const& name) const; + + /** Clear the contents of the basic_headers. */ + void + clear() noexcept; + + /** Remove a field. + + @return The number of fields removed. + */ + std::size_t + erase(boost::string_ref const& name); + + /** Insert 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? + void + insert(boost::string_ref const& name, + boost::string_ref const& value); + + /** Insert a field value. + + If a field value already exists the new value will be + extended as per RFC2616 Section 4.2. + */ + template::value>> + void + insert(boost::string_ref name, T const& value) + { + insert(name, + boost::lexical_cast(value)); + } + + /** Replace a field value. + + The current field value, if any, is removed. Then the + specified value is inserted as if by insert(field, value). + */ + void + replace(boost::string_ref const& name, + boost::string_ref const& value); + + /** Replace a field value. + + The current field value, if any, is removed. Then the + specified value is inserted as if by insert(field, value). + */ + template::value>> + void + replace(boost::string_ref const& name, T const& value) + { + replace(name, + boost::lexical_cast(value)); + } +}; + +} // http +} // beast + +#include + +#endif diff --git a/beast/http/basic_parser.h b/beast/http/basic_parser.h index f23a36c4ee..85301a5beb 100644 --- a/beast/http/basic_parser.h +++ b/beast/http/basic_parser.h @@ -21,66 +21,84 @@ #define BEAST_HTTP_BASIC_PARSER_H_INCLUDED #include +#include +#include #include #include -#include #include -#include #include +#include // namespace beast { - -namespace joyent { -struct http_parser; -}; - namespace http { +/** Parser for producing HTTP requests and responses. + + Callbacks: + + If a is an object of type Derived, and the call expression is + valid then the stated effects will take place: + + a.on_start() + + Called once when a new message begins. + + a.on_field(std::string field, std::string value) + + Called for each field + + a.on_headers_complete(error_code&) + + Called when all the header fields have been received, but + before any part of the body if any is received. + + a.on_request(method_t method, std::string url, + int major, int minor, bool keep_alive, bool upgrade) + + Called for requests when all the headers have been received. + This will precede any content body. + + When keep_alive is false: + * Server roles respond with a "Connection: close" header. + * Client roles close the connection. + + a.on_response(int status, std::string text, + int major, int minor, bool keep_alive, + bool upgrade) + + Called for responses when all the headers have been received. + This will precede any content body. + + When keep_alive is `false`: + * Client roles close the connection. + * Server roles respond with a "Connection: close" header. + + This function should return `true` if upgrade is false and + a content body is expected. When upgrade is true, no + content-body is expected, and the return value is ignored. + + a.on_body(void const* data, std::size_t bytes, error_code&) + + Called zero or more times for the content body. Any transfer + encoding is already decoded in the memory pointed to by data. + + a.on_complete() + + Called when parsing completes successfully. + + The parser uses traits to determine if the callback is possible. + If the Derived type omits the callbacks, they are simply skipped + with no compilation error. +*/ +/* + VFALCO TODO is_call_possible, enable_if_t on Derived calls + use boost::string_ref instead of std::string +*/ +template class 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; - }; - - using data_cb_t = int (*) ( - state_t*, const char *at, size_t length); - using cb_t = int (*) (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; - cb_t on_chunk_header; - cb_t on_chunk_complete; - }; - - char state_ [sizeof(state_t)]; - char hooks_ [sizeof(hooks_t)]; - + http_parser state_; + boost::system::error_code* ec_; bool complete_ = false; std::string url_; std::string status_; @@ -90,20 +108,36 @@ private: public: using error_code = boost::system::error_code; - virtual - ~basic_parser() = default; + /** Move constructor. + + The state of the moved-from object is undefined, + but safe to destroy. + */ + basic_parser(basic_parser&& other); + + /** Move assignment. + + The state of the moved-from object is undefined, + but safe to destroy. + */ + basic_parser& + operator=(basic_parser&& other); + + /** Copy constructor. */ + basic_parser(basic_parser const& other); + + /** Copy assignment. */ + basic_parser& operator=(basic_parser const& other); /** Construct the parser. - If `request` is `true` this sets up the parser to - process an HTTP request. + + @param request If `true`, the parser is setup for a request. */ explicit - basic_parser (bool request) noexcept; - - basic_parser& - operator= (basic_parser&& other); + basic_parser(bool request) noexcept; /** Returns `true` if parsing is complete. + This is only defined when no errors have been returned. */ bool @@ -113,153 +147,406 @@ public: } /** 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. + @param data A pointer to a buffer representing the input sequence. + @param size The number of bytes in the buffer pointed to by data. + + @throws boost::system::system_error Thrown on failure. + + @return The number of bytes consumed in the input sequence. */ - template - std::pair - write (ConstBufferSequence const& buffers); + std::size_t + write(void const* data, std::size_t size) + { + error_code ec; + auto const used = write(data, size, ec); + if(ec) + throw boost::system::system_error{ec}; + return used; + } + + /** Write data to the parser. + + @param data A pointer to a buffer representing the input sequence. + @param size The number of bytes in the buffer pointed to by data. + @param ec Set to the error, if any error occurred. + + @return The number of bytes consumed in the input sequence. + */ + std::size_t + write(void const* data, std::size_t size, + error_code& ec); + + /** Write data to the parser. + + @param buffers An object meeting the requirements of + ConstBufferSequence that represents the input sequence. + + @throws boost::system::system_error Thrown on failure. + + @return The number of bytes consumed in the input sequence. + */ + template + std::size_t + write(ConstBufferSequence const& buffers) + { + error_code ec; + auto const used = write(buffers, ec); + if(ec) + throw boost::system::system_error{ec}; + return used; + } + + /** Write data to the parser. + + @param buffers An object meeting the requirements of + ConstBufferSequence that represents the input sequence. + @param ec Set to the error, if any error occurred. + + @return The number of bytes consumed in the input sequence. + */ + template + std::size_t + write(ConstBufferSequence const& buffers, + error_code& ec); /** Called to indicate the end of file. + HTTP needs to know where the end of the stream is. For example, sometimes servers send responses without Content-Length and expect the client to consume input (for the body) until EOF. Callbacks and errors will still be processed as usual. + @note This is typically called when a socket read returns eof. - @return `true` if the message is complete. + + @throws boost::system::system_error Thrown on failure. */ - error_code - write_eof(); - -protected: - /** Called once when a new message begins. */ - virtual void - on_start() = 0; + write_eof() + { + error_code ec; + write_eof(ec); + if(ec) + throw boost::system::system_error{ec}; + } - /** Called for each header field. */ - virtual - void - on_field (std::string const& field, std::string const& value) = 0; + /** Called to indicate the end of file. - /** 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. + 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. - @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. + @note This is typically called when a socket read returns eof. + + @param ec Set to the error, if any error occurred. */ - 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; + write_eof(error_code& ec); private: - void check_header(); + Derived& + impl() + { + return *static_cast(this); + } - 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 (); - int do_chunk_header(); - int do_chunk_complete(); + template + class has_on_start_t + { + template().on_start(), std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_start = + std::bool_constant::value>; - 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*); - static int cb_chunk_header (joyent::http_parser*); - static int cb_chunk_complete (joyent::http_parser*); + void + call_on_start(std::true_type) + { + impl().on_start(); + } + + void + call_on_start(std::false_type) + { + } + + template + class has_on_field_t + { + template().on_field( + std::declval(), + std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_field = + std::bool_constant::value>; + + void + call_on_field(std::string const& field, + std::string const& value, std::true_type) + { + impl().on_field(field, value); + } + + void + call_on_field(std::string const&, std::string const&, + std::false_type) + { + } + + template + class has_on_headers_complete_t + { + template().on_headers_complete( + std::declval()), std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_headers_complete = + std::bool_constant::value>; + + void + call_on_headers_complete(error_code& ec, std::true_type) + { + impl().on_headers_complete(ec); + } + + void + call_on_headers_complete(error_code&, std::false_type) + { + } + + template + class has_on_request_t + { + template().on_request( + std::declval(), std::declval(), + std::declval(), std::declval(), + std::declval(), std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_request = + std::bool_constant::value>; + + void + call_on_request(method_t method, std::string url, + int major, int minor, bool keep_alive, bool upgrade, + std::true_type) + { + impl().on_request( + method, url, major, minor, keep_alive, upgrade); + } + + void + call_on_request(method_t, std::string, int, int, bool, bool, + std::false_type) + { + } + + template + class has_on_response_t + { + template().on_response( + std::declval(), std::declval, + std::declval(), std::declval(), + std::declval(), std::declval()), + std::true_type{})> + static R check(int); + template + static std::false_type check(...); +#if 0 + using type = decltype(check(0)); +#else + // VFALCO Trait seems broken for http::parser + using type = std::true_type; +#endif + public: + static bool const value = type::value; + }; + template + using has_on_response = + std::bool_constant::value>; + + bool + call_on_response(int status, std::string text, + int major, int minor, bool keep_alive, bool upgrade, + std::true_type) + { + return impl().on_response( + status, text, major, minor, keep_alive, upgrade); + } + + bool + call_on_response(int, std::string, int, int, bool, bool, + std::false_type) + { + // VFALCO Certainly incorrect + return true; + } + + template + class has_on_body_t + { + template().on_body( + std::declval(), std::declval(), + std::declval()), std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_body = + std::bool_constant::value>; + + void + call_on_body(void const* data, std::size_t bytes, + error_code& ec, std::true_type) + { + impl().on_body(data, bytes, ec); + } + + void + call_on_body(void const*, std::size_t, + error_code&, std::false_type) + { + } + + template + class has_on_complete_t + { + template().on_complete(), std::true_type{})> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); + public: + static bool const value = type::value; + }; + template + using has_on_complete = + std::bool_constant::value>; + + void + call_on_complete(std::true_type) + { + impl().on_complete(); + } + + void + call_on_complete(std::false_type) + { + } + + void + check_header(); + + static int cb_message_start(http_parser*); + static int cb_url(http_parser*, char const*, std::size_t); + static int cb_status(http_parser*, char const*, std::size_t); + static int cb_header_field(http_parser*, char const*, std::size_t); + static int cb_header_value(http_parser*, char const*, std::size_t); + static int cb_headers_complete(http_parser*); + static int cb_body(http_parser*, char const*, std::size_t); + static int cb_message_complete(http_parser*); + static int cb_chunk_header(http_parser*); + static int cb_chunk_complete(http_parser*); + + struct hooks_t : http_parser_settings + { + hooks_t() + { + http_parser_settings_init(this); + on_message_begin = &basic_parser::cb_message_start; + on_url = &basic_parser::cb_url; + on_status = &basic_parser::cb_status; + on_header_field = &basic_parser::cb_header_field; + on_header_value = &basic_parser::cb_header_value; + on_headers_complete = &basic_parser::cb_headers_complete; + on_body = &basic_parser::cb_body; + on_message_complete = &basic_parser::cb_message_complete; + on_chunk_header = &basic_parser::cb_chunk_header; + on_chunk_complete = &basic_parser::cb_chunk_complete; + } + }; + + static + http_parser_settings const* + hooks(); }; -template -auto -basic_parser::write (ConstBufferSequence const& buffers) -> - std::pair +template +template +std::size_t +basic_parser::write( + ConstBufferSequence const& buffers, error_code& ec) { - std::pair result ({}, 0); + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::size_t bytes_used = 0; for (auto const& buffer : buffers) { - 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()) + auto const n = write( + buffer_cast(buffer), + buffer_size(buffer), ec); + if(ec) + return 0; + bytes_used += n; + if(complete()) break; } - return result; + return bytes_used; +} + +template +http_parser_settings const* +basic_parser::hooks() +{ + static hooks_t const h; + return &h; } } // http } // beast +#include + #endif diff --git a/beast/http/body.h b/beast/http/body.h index 761b140499..90511d2d06 100644 --- a/beast/http/body.h +++ b/beast/http/body.h @@ -29,7 +29,7 @@ #include namespace beast { -namespace http { +namespace deprecated_http { /** Container for the HTTP content-body. */ class body @@ -144,7 +144,7 @@ body::data() const return buf_->data(); } -} // http +} // deprecated_http } // beast #endif diff --git a/beast/http/chunk_encode.h b/beast/http/chunk_encode.h index 1af38b0674..608dad32d2 100644 --- a/beast/http/chunk_encode.h +++ b/beast/http/chunk_encode.h @@ -148,8 +148,8 @@ chunk_encoded_buffers::to_hex( template chunk_encoded_buffers::const_iterator::const_iterator() - : where_(Where::end) - , buffers_(nullptr) + : buffers_(nullptr) + , where_(Where::end) { } @@ -193,7 +193,7 @@ chunk_encoded_buffers::const_iterator::operator--() -> const_iterator& { assert(buffers_); - assert(where_ != Where::begin); + assert(where_ != Where::head); if (where_ == Where::end) where_ = Where::input; else if (iter_ != buffers_->buffers_.begin()) @@ -247,37 +247,44 @@ chunk_encoded_buffers::const_iterator::const_iterator( { } -} +} // detail /** Returns a chunk-encoded BufferSequence. See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1 - @tparam Buffers A type meeting the requirements of BufferSequence. @param buffers The input buffer sequence. + @param final_chunk `true` If this should include a final-chunk. - @return A chunk-encoded ConstBufferSeqeunce representing the input. + + @return A chunk-encoded ConstBufferSequence representing the input. */ -/** @{ */ -template -detail::chunk_encoded_buffers -chunk_encode (Buffers const& buffers, +template +#if GENERATING_DOCS +implementation_defined +#else +detail::chunk_encoded_buffers +#endif +chunk_encode(ConstBufferSequence const& buffers, bool final_chunk = false) { return detail::chunk_encoded_buffers< - Buffers>(buffers, final_chunk); + ConstBufferSequence>{buffers, final_chunk}; } -// Returns a chunked encoding final chunk. +/// Returns a chunked encoding final chunk. inline +#if GENERATING_DOCS +implementation_defined +#else boost::asio::const_buffers_1 +#endif chunk_encode_final() { return boost::asio::const_buffers_1( "0\r\n\r\n", 5); } -/** @} */ } // http } // beast diff --git a/beast/http/detail/error.h b/beast/http/detail/error.h new file mode 100644 index 0000000000..39918f910d --- /dev/null +++ b/beast/http/detail/error.h @@ -0,0 +1,83 @@ +//------------------------------------------------------------------------------ +/* + 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_DETAIL_ERROR_H_INCLUDED +#define BEAST_HTTP_DETAIL_ERROR_H_INCLUDED + +#include +#include + +namespace beast { +namespace http { +namespace detail { + +class message_category + : public boost::system::error_category +{ +public: + const char* + name() const noexcept override + { + return "http error"; + } + + std::string + message(int ev) const override + { + return 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; + } +}; + +template +auto +make_error(int http_errno) +{ + static message_category const mc{}; + return boost::system::error_code{http_errno, mc}; +} + +} // detail +} // http +} // beast + +#endif diff --git a/beast/http/detail/write_preparation.h b/beast/http/detail/write_preparation.h new file mode 100644 index 0000000000..f7bfd2916a --- /dev/null +++ b/beast/http/detail/write_preparation.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_WRITE_PREPARATION_H_INCLUDED +#define BEAST_HTTP_WRITE_PREPARATION_H_INCLUDED + +#include + +namespace beast { +namespace http { +namespace detail { + +template +class has_content_length_value +{ + template().content_length()), + std::size_t>> + static R check(int); + template + static std::false_type check(...); + using type = decltype(check(0)); +public: + // `true` if `T` meets the requirements. + static bool const value = type::value; +}; + +// Determines if the writer can provide the content length +template +using has_content_length = + std::integral_constant::value>; + +template +struct write_preparation +{ + using headers_type = + basic_headers>; + + message const& msg; + typename Body::writer w; + streambuf sb; + bool chunked; + bool close; + + explicit + write_preparation( + message const& msg_) + : msg(msg_) + , w(msg) + { + } + + void + init(error_code& ec) + { + w.init(ec); + if(ec) + return; + // VFALCO TODO This implementation requires making a + // copy of the headers, we can do better. + // VFALCO Should we be using handler_alloc? + headers_type h(msg.headers.begin(), msg.headers.end()); + set_content_length(h, has_content_length< + typename Body::writer>{}); + + // VFALCO TODO Keep-Alive + + if(close) + { + if(msg.version >= 11) + h.insert("Connection", "close"); + } + else + { + if(msg.version < 11) + h.insert("Connection", "keep-alive"); + } + + msg.write_firstline(sb); + write_fields(sb, h); + detail::write(sb, "\r\n"); + } + +private: + void + set_content_length(headers_type& h, + std::true_type) + { + close = false; + chunked = false; + h.insert("Content-Length", w.content_length()); + } + + void + set_content_length(headers_type& h, + std::false_type) + { + if(msg.version >= 11) + { + close = false; + chunked = true; + h.insert("Transfer-Encoding", "chunked"); + } + else + { + close = true; + chunked = false; + } + } +}; + +} // detail +} // http +} // beast + +#endif diff --git a/beast/http/detail/writes.h b/beast/http/detail/writes.h new file mode 100644 index 0000000000..efeff68cd0 --- /dev/null +++ b/beast/http/detail/writes.h @@ -0,0 +1,76 @@ +//------------------------------------------------------------------------------ +/* + 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_WRITES_H_INCLUDED +#define BEAST_HTTP_WRITES_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { +namespace detail { + +template::value>> +void +write(Streambuf& streambuf, T&& t) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + auto const& s = + boost::lexical_cast( + std::forward(t)); + streambuf.commit(buffer_copy( + streambuf.prepare(s.size()), buffer(s))); +} + +template0) && + is_Streambuf::value>> +void +write(Streambuf& streambuf, char const(&s)[N]) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + streambuf.commit(buffer_copy( + streambuf.prepare(N), buffer(s, N-1))); +} + +template::value>> +void +write(Streambuf& streambuf, boost::string_ref const& s) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + streambuf.commit(buffer_copy( + streambuf.prepare(s.size()), + buffer(s.data(), s.size()))); +} + +} // detail +} // http +} // beast + +#endif diff --git a/beast/http/empty_body.h b/beast/http/empty_body.h new file mode 100644 index 0000000000..ffe74fd458 --- /dev/null +++ b/beast/http/empty_body.h @@ -0,0 +1,87 @@ +//------------------------------------------------------------------------------ +/* + 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_EMPTY_BODY_H_INCLUDED +#define BEAST_HTTP_EMPTY_BODY_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** An empty content-body. +*/ +struct empty_body +{ + struct value_type + { + }; + + struct reader + { + template + explicit + reader(message&) + { + } + + void + write(void const*, std::size_t, error_code&) + { + } + }; + + struct writer + { + template + explicit + writer(message const& m) + { + } + + void + init(error_code& ec) + { + } + + std::size_t + content_length() const + { + return 0; + } + + template + boost::tribool + operator()(resume_context&&, error_code&, Write&& write) + { + write(boost::asio::null_buffers{}); + return true; + } + }; +}; + +} // http +} // beast + +#endif diff --git a/beast/http/HTTP.unity.cpp b/beast/http/error.h similarity index 70% rename from beast/http/HTTP.unity.cpp rename to beast/http/error.h index ea2c3a518b..3a101c551b 100644 --- a/beast/http/HTTP.unity.cpp +++ b/beast/http/error.h @@ -17,18 +17,17 @@ */ //============================================================================== -#if BEAST_INCLUDE_BEASTCONFIG -#include +#ifndef BEAST_HTTP_ERROR_H_INCLUDED +#define BEAST_HTTP_ERROR_H_INCLUDED + +#include + +namespace beast { +namespace http { + +using error_code = boost::system::error_code; + +} // http +} // beast + #endif - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - diff --git a/beast/http/fields.h b/beast/http/fields.h new file mode 100644 index 0000000000..e1eeb71048 --- /dev/null +++ b/beast/http/fields.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_FIELDS_H_INCLUDED +#define BEAST_HTTP_FIELDS_H_INCLUDED + +#include + +namespace beast { +namespace http { + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-braces" +#endif // defined(__clang__) + +template +auto const& +common_fields() +{ + // Must be sorted + static std::array constexpr h{ + "Accept" + ,"Accept-Charset" + ,"Accept-Datetime" + ,"Accept-Encoding" + ,"Accept-Language" + ,"Accept-Ranges" + ,"Access-Control-Allow-Credentials" + ,"Access-Control-Allow-Headers" + ,"Access-Control-Allow-Methods" + ,"Access-Control-Allow-Origin" + ,"Access-Control-Expose-Headers" + ,"Access-Control-Max-Age" + ,"Access-Control-Request-Headers" + ,"Access-Control-Request-Method" + ,"Age" + ,"Allow" + ,"Authorization" + ,"Cache-Control" + ,"Connection" + ,"Content-Disposition" + ,"Content-Encoding" + ,"Content-Language" + ,"Content-Length" + ,"Content-Location" + ,"Content-MD5" + ,"Content-Range" + ,"Content-Type" + ,"Cookie" + ,"DNT" + ,"Date" + ,"ETag" + ,"Expect" + ,"Expires" + ,"From" + ,"Front-End-Https" + ,"Host" + ,"If-Match" + ,"If-Modified-Since" + ,"If-None-Match" + ,"If-Range" + ,"If-Unmodified-Since" + ,"Keep-Alive" + ,"Last-Modified" + ,"Link" + ,"Location" + ,"Max-Forwards" + ,"Origin" + ,"P3P" + ,"Pragma" + ,"Proxy-Authenticate" + ,"Proxy-Authorization" + ,"Proxy-Connection" + ,"Range" + ,"Referer" + ,"Refresh" + ,"Retry-After" + ,"Server" + ,"Set-Cookie" + ,"Strict-Transport-Security" + ,"TE" + ,"Timestamp" + ,"Trailer" + ,"Transfer-Encoding" + ,"Upgrade" + ,"User-Agent" + ,"VIP" + ,"Vary" + ,"Via" + ,"WWW-Authenticate" + ,"Warning" + ,"X-Accel-Redirect" + ,"X-Content-Security-Policy-Report-Only" + ,"X-Content-Type-Options" + ,"X-Forwarded-For" + ,"X-Forwarded-Proto" + ,"X-Frame-Options" + ,"X-Powered-By" + ,"X-Real-IP" + ,"X-Requested-With" + ,"X-UA-Compatible" + ,"X-Wap-Profile" + ,"X-XSS-Protection" + }; + + return h; +} + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif // defined(__clang__) + +} // http +} // beast + +#endif diff --git a/beast/http/headers.h b/beast/http/headers.h index 5c9fc14e1a..7457b8111f 100644 --- a/beast/http/headers.h +++ b/beast/http/headers.h @@ -20,390 +20,17 @@ #ifndef BEAST_HTTP_HEADERS_H_INCLUDED #define BEAST_HTTP_HEADERS_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include namespace beast { namespace http { -/** Holds a collection of HTTP headers. */ -class headers -{ -public: - using value_type = std::pair; +template +using headers = basic_headers; -private: - 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); - - value_type data; - }; - - 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; - - bool - operator() (element const& lhs, element const& rhs) const; - }; - - struct transform - : public std::unary_function - { - value_type const& - operator() (element const& e) const - { - return e.data; - } - }; - - using list_t = boost::intrusive::make_list - >::type; - - using set_t = boost::intrusive::make_set , - boost::intrusive::compare - >::type; - - list_t list_; - set_t set_; - -public: - using iterator = boost::transform_iterator ; - using const_iterator = iterator; - - ~headers() - { - clear(); - } - - headers() = default; - - headers (headers&& other); - headers& operator= (headers&& other); - - headers (headers const& other); - 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; - - /** Remove a field. - @return The number of fields removed. - */ - template - std::size_t - erase (std::string const& field); - - /** 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); - -//------------------------------------------------------------------------------ - -template -headers::element::element ( - std::string const& f, std::string const& v) -{ - data.first = f; - data.second = v; -} - -template -bool -headers::less::operator() ( - String const& lhs, element const& rhs) const -{ - return beast::ci_less::operator() (lhs, rhs.data.first); -} - -template -bool -headers::less::operator() ( - element const& lhs, String const& rhs) const -{ - return beast::ci_less::operator() (lhs.data.first, rhs); -} - -inline -bool -headers::less::operator() ( - element const& lhs, element const& rhs) const -{ - return beast::ci_less::operator() (lhs.data.first, rhs.data.first); -} - -//------------------------------------------------------------------------------ - -inline -headers::headers (headers&& other) - : list_ (std::move (other.list_)) - , set_ (std::move (other.set_)) -{ - other.list_.clear(); - other.set_.clear(); -} - -inline -headers& -headers::operator= (headers&& other) -{ - list_ = std::move(other.list_); - set_ = std::move(other.set_); - other.list_.clear(); - other.set_.clear(); - return *this; -} - -inline -headers::headers (headers const& other) -{ - for (auto const& e : other.list_) - append (e.data.first, e.data.second); -} - -inline -headers& -headers::operator= (headers const& other) -{ - clear(); - for (auto const& e : other.list_) - append (e.data.first, e.data.second); - return *this; -} - -inline -headers::iterator -headers::begin() const -{ - return {list_.cbegin(), transform{}}; -} - -inline -headers::iterator -headers::end() const -{ - return {list_.cend(), transform{}}; -} - -inline -headers::iterator -headers::cbegin() const -{ - return {list_.cbegin(), transform{}}; -} - -inline -headers::iterator -headers::cend() const -{ - return {list_.cend(), transform{}}; -} - -template -headers::iterator -headers::find (std::string const& field) const -{ - auto const iter (set_.find (field, less{})); - if (iter == set_.end()) - return {list_.end(), transform{}}; - return {list_.iterator_to (*iter), transform{}}; -} - -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->second; -} - -template -void -headers::clear() noexcept -{ - for (auto iter (list_.begin()); iter != list_.end();) - delete &(*iter++); -} - -template -std::size_t -headers::erase (std::string const& field) -{ - auto const iter = set_.find(field, less{}); - if (iter == set_.end()) - return 0; - element& e = *iter; - set_.erase(set_.iterator_to(e)); - list_.erase(list_.iterator_to(e)); - delete &e; - return 1; -} - -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, less{}, 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->data.second); - cur.reserve (cur.size() + 1 + value.size()); - cur.append (1, ','); - cur.append (value); -} - -//------------------------------------------------------------------------------ - -template -void -write (Streambuf& stream, std::string const& s) -{ - stream.commit (boost::asio::buffer_copy ( - stream.prepare (s.size()), boost::asio::buffer(s))); -} - -template -void -write (Streambuf& 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 (Streambuf& stream, headers const& h) -{ - for (auto const& _ : h) - { - write (stream, _.first); - write (stream, ": "); - write (stream, _.second); - write (stream, "\r\n"); - } -} - -template -std::string -to_string (headers const& h) -{ - std::string s; - std::size_t n (0); - for (auto const& e : h) - n += e.first.size() + 2 + e.second.size() + 2; - s.reserve (n); - for (auto const& e : h) - { - s.append (e.first); - s.append (": "); - s.append (e.second); - 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.first); - // TODO Replace with safe C++14 version - std::transform (key.begin(), key.end(), key.begin(), ::tolower); - c [key] = e.second; - } - return c; -} +using http_headers = + basic_headers>; } // http } // beast diff --git a/beast/http/impl/URL.cpp b/beast/http/impl/URL.cpp deleted file mode 100644 index 1ab27479b4..0000000000 --- a/beast/http/impl/URL.cpp +++ /dev/null @@ -1,230 +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 - -namespace beast { - -URL::URL ( - std::string scheme_, - std::string host_, - std::uint16_t port_, - std::string port_string_, - std::string path_, - std::string query_, - std::string fragment_, - std::string userinfo_) - : m_scheme (scheme_) - , m_host (host_) - , m_port (port_) - , m_port_string (port_string_) - , m_path (path_) - , m_query (query_) - , m_fragment (fragment_) - , m_userinfo (userinfo_) -{ -} - -//------------------------------------------------------------------------------ - -bool -URL::empty () const -{ - return m_scheme.empty (); -} - -std::string -const& URL::scheme () const -{ - return m_scheme; -} - -std::string -const& URL::host () const -{ - return m_host; -} - -std::string -const& URL::port_string () const -{ - return m_port_string; -} - -std::uint16_t -URL::port () const -{ - return m_port; -} - -std::string -const& URL::path () const -{ - return m_path; -} - -std::string -const& URL::query () const -{ - return m_query; -} - -std::string -const& URL::fragment () const -{ - return m_fragment; -} - -std::string -const& URL::userinfo () const -{ - return m_userinfo; -} - -//------------------------------------------------------------------------------ -std::pair -parse_URL(std::string const& url) -{ - std::size_t const buflen (url.size ()); - char const* const buf (url.c_str ()); - - joyent::http_parser_url parser; - - if (joyent::http_parser_parse_url (buf, buflen, false, &parser) != 0) - return std::make_pair (false, URL{}); - - std::string scheme; - std::string host; - std::uint16_t port (0); - std::string port_string; - std::string path; - std::string query; - std::string fragment; - std::string userinfo; - - if ((parser.field_set & (1< + + 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_HEADERS_IPP_INCLUDED +#define BEAST_HTTP_BASIC_HEADERS_IPP_INCLUDED + +#include +#include + +namespace beast { +namespace http { + +namespace detail { + +inline +auto +basic_headers_base::begin() const -> + const_iterator +{ + return list_.cbegin(); +} + +inline +auto +basic_headers_base::end() const -> + const_iterator +{ + return list_.cend(); +} + +inline +auto +basic_headers_base::cbegin() const -> + const_iterator +{ + return list_.cbegin(); +} + +inline +auto +basic_headers_base::cend() const -> + const_iterator +{ + return list_.cend(); +} + +} // detail + +//------------------------------------------------------------------------------ + +template +void +basic_headers:: +delete_all() +{ + for(auto it = list_.begin(); it != list_.end();) + { + auto& e = *it++; + e.~element(); + alloc_traits::deallocate( + this->member(), &e, 1); + } +} + +template +inline +void +basic_headers:: +move_assign(basic_headers& other, std::false_type) +{ + if(this->member() != other.member()) + { + copy_from(other); + other.clear(); + } + else + { + set_ = std::move(other.set_); + list_ = std::move(other.list_); + } +} + +template +inline +void +basic_headers:: +move_assign(basic_headers& other, std::true_type) +{ + this->member() = std::move(other.member()); + set_ = std::move(other.set_); + list_ = std::move(other.list_); +} + +template +inline +void +basic_headers:: +copy_assign(basic_headers const& other, std::false_type) +{ + copy_from(other); +} + +template +inline +void +basic_headers:: +copy_assign(basic_headers const& other, std::true_type) +{ + this->member() = other.member(); + copy_from(other); +} + +//------------------------------------------------------------------------------ + +template +basic_headers:: +~basic_headers() +{ + delete_all(); +} + +template +basic_headers:: +basic_headers(Allocator const& alloc) + : empty_base_optimization< + alloc_type>(alloc) +{ +} + +template +basic_headers:: +basic_headers(basic_headers&& other) + : empty_base_optimization( + std::move(other.member())) + , detail::basic_headers_base( + std::move(other.set_), std::move(other.list_)) +{ + other.list_.clear(); + other.set_.clear(); +} + +template +auto +basic_headers:: +operator=(basic_headers&& other) -> + basic_headers& +{ + if(this == &other) + return *this; + clear(); + move_assign(other, std::integral_constant{}); + return *this; +} + +template +basic_headers:: +basic_headers(basic_headers const& other) + : basic_headers(alloc_traits:: + select_on_container_copy_construction(other.member())) +{ + copy_from(other); +} + +template +auto +basic_headers:: +operator=(basic_headers const& other) -> + basic_headers& +{ + clear(); + copy_assign(other, std::integral_constant{}); + return *this; +} + +template +template +basic_headers:: +basic_headers(basic_headers const& other) +{ + copy_from(other); +} + +template +template +auto +basic_headers:: +operator=(basic_headers const& other) -> + basic_headers& +{ + clear(); + copy_from(other); + return *this; +} + +template +template +basic_headers:: +basic_headers(FwdIt first, FwdIt last) +{ + for(;first != last; ++first) + insert(first->name(), first->value()); +} + +template +auto +basic_headers:: +find(boost::string_ref const& name) const -> + iterator +{ + auto const it = set_.find(name, less{}); + if(it == set_.end()) + return list_.end(); + return list_.iterator_to(*it); +} + +template +boost::string_ref +basic_headers:: +operator[](boost::string_ref const& name) const +{ + // VFALCO This none object looks sketchy + static boost::string_ref const none; + auto const it = find(name); + if(it == end()) + return none; + return it->second; +} + +template +void +basic_headers:: +clear() noexcept +{ + delete_all(); + list_.clear(); + set_.clear(); +} + +template +std::size_t +basic_headers:: +erase(boost::string_ref const& name) +{ + auto const it = set_.find(name, less{}); + if(it == set_.end()) + return 0; + auto& e = *it; + set_.erase(set_.iterator_to(e)); + list_.erase(list_.iterator_to(e)); + alloc_traits::deallocate(this->member(), &e, 1); + return 1; +} + +template +void +basic_headers:: +insert(boost::string_ref const& name, + boost::string_ref const& value) +{ + typename set_t::insert_commit_data d; + auto const result = + set_.insert_check(name, less{}, d); + if (result.second) + { + auto const p = alloc_traits::allocate( + this->member(), 1); + alloc_traits::construct( + this->member(), p, name, value); + list_.push_back(*p); + set_.insert_commit(*p, d); + return; + } + // If field already exists, insert comma + // separated value as per RFC2616 section 4.2 + auto& cur = result.first->data.second; + cur.reserve(cur.size() + 1 + value.size()); + cur.append(1, ','); + cur.append(value.data(), value.size()); +} + +template +void +basic_headers:: +replace(boost::string_ref const& name, + boost::string_ref const& value) +{ + erase(name); + insert(name, value); +} + +} // http +} // beast + +#endif diff --git a/beast/http/impl/basic_parser.cpp b/beast/http/impl/basic_parser.cpp deleted file mode 100644 index f49598357c..0000000000 --- a/beast/http/impl/basic_parser.cpp +++ /dev/null @@ -1,327 +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 -#include -#include - -namespace beast { -namespace http { - -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"); - - static_assert (sizeof(joyent::http_parser_settings) == sizeof(hooks_t), - "hooks_t size must match http_parser_settings size"); - - auto s (reinterpret_cast (&state_)); - s->data = this; - - auto h (reinterpret_cast (&hooks_)); - 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; - h->on_chunk_header = &basic_parser::cb_chunk_header; - h->on_chunk_complete = &basic_parser::cb_chunk_complete; - - joyent::http_parser_init (s, request - ? joyent::http_parser_type::HTTP_REQUEST - : joyent::http_parser_type::HTTP_RESPONSE); -} - -basic_parser& -basic_parser::operator= (basic_parser&& other) -{ - *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; -} - -auto -basic_parser::write (void const* data, std::size_t bytes) -> - std::pair -{ - std::pair result ({}, 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 = error_code{static_cast(s->http_errno), - message_category()}; - return result; -} - -auto -basic_parser::write_eof() -> - error_code -{ - auto s (reinterpret_cast (&state_)); - auto h (reinterpret_cast (&hooks_)); - joyent::http_parser_execute (s, h, nullptr, 0); - return error_code{static_cast(s->http_errno), - message_category()}; -} - -//------------------------------------------------------------------------------ - -void -basic_parser::check_header() -{ - if (! value_.empty()) - { - rfc2616::trim_right_in_place (value_); - on_field (field_, value_); - field_.clear(); - value_.clear(); - } -} - -int -basic_parser::do_message_start () -{ - complete_ = false; - url_.clear(); - status_.clear(); - field_.clear(); - value_.clear(); - on_start(); - return 0; -} - -int -basic_parser::do_url (char const* in, std::size_t bytes) -{ - url_.append (static_cast (in), bytes); - return 0; -} - -int -basic_parser::do_status (char const* in, std::size_t bytes) -{ - status_.append (static_cast (in), bytes); - return 0; -} - -int -basic_parser::do_header_field (char const* in, std::size_t bytes) -{ - check_header(); - field_.append (static_cast (in), bytes); - return 0; -} - -int -basic_parser::do_header_value (char const* in, std::size_t bytes) -{ - value_.append (static_cast (in), bytes); - return 0; -} - -/* Called when all the headers are complete but before - the content body, if present. - Returning 1 from here tells the joyent parser - that the message has no body (e.g. a HEAD request). -*/ -int -basic_parser::do_headers_complete() -{ - check_header(); - auto const p (reinterpret_cast (&state_)); - bool const keep_alive (joyent::http_should_keep_alive (p) != 0); - if (p->type == joyent::http_parser_type::HTTP_REQUEST) - return on_request (joyent::convert_http_method ( - joyent::http_method(p->method)), url_, - p->http_major, p->http_minor, keep_alive, p->upgrade) ? 0 : 1; - return on_response (p->status_code, status_, - p->http_major, p->http_minor, keep_alive, p->upgrade) ? 0 : 1; -} - -/* Called repeatedly for the content body. The passed buffer - has already had the transfer-encoding removed. -*/ -int -basic_parser::do_body (char const* in, std::size_t bytes) -{ - on_body (in, bytes); - return 0; -} - -/* Called when the both the headers and content body (if any) are complete. */ -int -basic_parser::do_message_complete () -{ - complete_ = true; - on_complete(); - return 0; -} - -int -basic_parser::do_chunk_header() -{ - return 0; -} - -int -basic_parser::do_chunk_complete() -{ - return 0; -} - -//------------------------------------------------------------------------------ - -int -basic_parser::cb_message_start (joyent::http_parser* p) -{ - return reinterpret_cast ( - p->data)->do_message_start(); -} - -int -basic_parser::cb_url (joyent::http_parser* p, - char const* in, std::size_t bytes) -{ - return reinterpret_cast ( - p->data)->do_url (in, bytes); -} - -int -basic_parser::cb_status (joyent::http_parser* p, - char const* in, std::size_t bytes) -{ - return reinterpret_cast ( - p->data)->do_status (in, bytes); -} - -int -basic_parser::cb_header_field (joyent::http_parser* p, - char const* in, std::size_t bytes) -{ - return reinterpret_cast ( - p->data)->do_header_field (in, bytes); -} - -int -basic_parser::cb_header_value (joyent::http_parser* p, - char const* in, std::size_t bytes) -{ - return reinterpret_cast ( - p->data)->do_header_value (in, bytes); -} - -int -basic_parser::cb_headers_complete (joyent::http_parser* p) -{ - return reinterpret_cast ( - p->data)->do_headers_complete(); -} - -int -basic_parser::cb_body (joyent::http_parser* p, - char const* in, std::size_t bytes) -{ - return reinterpret_cast ( - p->data)->do_body ( - in, bytes); -} - -int -basic_parser::cb_message_complete (joyent::http_parser* p) -{ - return reinterpret_cast ( - p->data)->do_message_complete(); -} - -int -basic_parser::cb_chunk_header (joyent::http_parser* p) -{ - return reinterpret_cast ( - p->data)->do_chunk_header(); -} - -int -basic_parser::cb_chunk_complete (joyent::http_parser* p) -{ - return reinterpret_cast ( - p->data)->do_chunk_complete(); -} - -} // http -} // beast diff --git a/beast/http/impl/basic_parser.ipp b/beast/http/impl/basic_parser.ipp new file mode 100644 index 0000000000..9642d21f5a --- /dev/null +++ b/beast/http/impl/basic_parser.ipp @@ -0,0 +1,260 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include + +namespace beast { +namespace http { + +template +basic_parser:: +basic_parser(basic_parser&& other) +{ + state_ = other.state_; + state_.data = this; + complete_ = other.complete_; + url_ = std::move(other.url_); + status_ = std::move(other.status_); + field_ = std::move(other.field_); + value_ = std::move(other.value_); +} + +template +auto +basic_parser::operator=(basic_parser&& other) -> + basic_parser& +{ + state_ = other.state_; + state_.data = this; + complete_ = other.complete_; + url_ = std::move(other.url_); + status_ = std::move(other.status_); + field_ = std::move(other.field_); + value_ = std::move(other.value_); + return *this; +} + +template +basic_parser:: +basic_parser(basic_parser const& other) +{ + state_ = other.state_; + state_.data = this; + complete_ = other.complete_; + url_ = other.url_; + status_ = other.status_; + field_ = other.field_; + value_ = other.value_; +} + +template +auto +basic_parser:: +operator=(basic_parser const& other) -> + basic_parser& +{ + state_ = other.state_; + state_.data = this; + complete_ = other.complete_; + url_ = other.url_; + status_ = other.status_; + field_ = other.field_; + value_ = other.value_; + return *this; +} + +template +basic_parser::basic_parser(bool request) noexcept +{ + state_.data = this; + http_parser_init(&state_, request + ? http_parser_type::HTTP_REQUEST + : http_parser_type::HTTP_RESPONSE); +} + +template +std::size_t +basic_parser::write(void const* data, + std::size_t size, error_code& ec) +{ + ec_ = &ec; + auto const n = http_parser_execute( + &state_, hooks(), + static_cast(data), size); + if(! ec) + ec = detail::make_error( + static_cast(state_.http_errno)); + if(ec) + return 0; + return n; +} + +template +void +basic_parser::write_eof(error_code& ec) +{ + ec_ = &ec; + http_parser_execute( + &state_, hooks(), nullptr, 0); + if(! ec) + ec = detail::make_error( + static_cast(state_.http_errno)); +} + +template +void +basic_parser::check_header() +{ + if (! value_.empty()) + { + rfc2616::trim_right_in_place(value_); + call_on_field(field_, value_, + has_on_field{}); + field_.clear(); + value_.clear(); + } +} + +template +int +basic_parser::cb_message_start(http_parser* p) +{ + auto& t = *reinterpret_cast(p->data); + t.complete_ = false; + t.url_.clear(); + t.status_.clear(); + t.field_.clear(); + t.value_.clear(); + t.call_on_start(has_on_start{}); + return 0; +} + +template +int +basic_parser::cb_url(http_parser* p, + char const* in, std::size_t bytes) +{ + auto& t = *reinterpret_cast(p->data); + t.url_.append(in, bytes); + return 0; +} + +template +int +basic_parser::cb_status(http_parser* p, + char const* in, std::size_t bytes) +{ + auto& t = *reinterpret_cast(p->data); + t.status_.append(in, bytes); + return 0; +} + +template +int +basic_parser::cb_header_field(http_parser* p, + char const* in, std::size_t bytes) +{ + auto& t = *reinterpret_cast(p->data); + t.check_header(); + t.field_.append(in, bytes); + return 0; +} + +template +int +basic_parser::cb_header_value(http_parser* p, + char const* in, std::size_t bytes) +{ + auto& t = *reinterpret_cast(p->data); + t.value_.append(in, bytes); + return 0; +} + +/* Called when all the headers are complete but before + the content body, if present. + Returning 1 from here tells the nodejs parser + that the message has no body (e.g. a HEAD request). +*/ +template +int +basic_parser::cb_headers_complete(http_parser* p) +{ + auto& t = *reinterpret_cast(p->data); + t.check_header(); + t.call_on_headers_complete(*t.ec_, + has_on_headers_complete{}); + if(*t.ec_) + return 1; + bool const keep_alive = + http_should_keep_alive(p) != 0; + if(p->type == http_parser_type::HTTP_REQUEST) + { + t.call_on_request(convert_http_method( + http_method(p->method)), t.url_, + p->http_major, p->http_minor, keep_alive, + p->upgrade, has_on_request{}); + return 0; + } + return t.call_on_response(p->status_code, t.status_, + p->http_major, p->http_minor, keep_alive, + p->upgrade, has_on_response{}) ? 0 : 1; +} + +// Called repeatedly for the content body, +// after any transfer-encoding is applied. +template +int +basic_parser::cb_body(http_parser* p, + char const* in, std::size_t bytes) +{ + auto& t = *reinterpret_cast(p->data); + t.call_on_body(in, bytes, *t.ec_, has_on_body{}); + return *t.ec_ ? 1 : 0; +} + +// Called when the both the headers +// and content body (if any) are complete. +template +int +basic_parser::cb_message_complete(http_parser* p) +{ + auto& t = *reinterpret_cast(p->data); + t.complete_ = true; + t.call_on_complete(has_on_complete{}); + return 0; +} + +template +int +basic_parser::cb_chunk_header(http_parser*) +{ + return 0; +} + +template +int +basic_parser::cb_chunk_complete(http_parser*) +{ + return 0; +} + +} // http +} // beast diff --git a/beast/http/impl/basic_url.cpp b/beast/http/impl/basic_url.cpp deleted file mode 100644 index 3ae3a96154..0000000000 --- a/beast/http/impl/basic_url.cpp +++ /dev/null @@ -1,128 +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 { -namespace detail { - -void -basic_url_base::parse_impl (string_ref s, boost::system::error_code& ec) -{ - joyent::http_parser_url p; - - value_type const* const data (s.data()); - - int const error (joyent::http_parser_parse_url ( - data, s.size(), false, &p)); - - if (error) - { - ec = boost::system::error_code ( - boost::system::errc::invalid_argument, - boost::system::generic_category()); - return; - } - - if ((p.field_set & (1< + + 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_IPP_INCLUDED +#define BEAST_HTTP_MESSAGE_IPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +template +message:: +message() +{ + static_assert(is_Body::value, + "Body requirements not met"); +} + +template +message:: +message(request_params params) +{ + static_assert(is_Body::value, + "Body requirements not met"); + static_assert(isRequest, "message is not a request"); + this->method = params.method; + this->url = std::move(params.url); + version = params.version; +} + +template +message:: +message(response_params params) +{ + static_assert(is_Body::value, + "Body requirements not met"); + static_assert(! isRequest, "message is not a response"); + this->status = params.status; + this->reason = std::move(params.reason); + version = params.version; +} + +template +template +void +message:: +write_firstline(Streambuf& streambuf, + std::true_type) const +{ + detail::write(streambuf, to_string(this->method)); + detail::write(streambuf, " "); + detail::write(streambuf, this->url); + switch(version) + { + case 10: + detail::write(streambuf, " HTTP/1.0\r\n"); + break; + case 11: + detail::write(streambuf, " HTTP/1.1\r\n"); + break; + default: + detail::write(streambuf, " HTTP/"); + detail::write(streambuf, version / 10); + detail::write(streambuf, "."); + detail::write(streambuf, version % 10); + detail::write(streambuf, "\r\n"); + break; + } +} + +template +template +void +message:: +write_firstline(Streambuf& streambuf, + std::false_type) const +{ + switch(version) + { + case 10: + detail::write(streambuf, "HTTP/1.0 "); + break; + case 11: + detail::write(streambuf, "HTTP/1.1 "); + break; + default: + detail::write(streambuf, " HTTP/"); + detail::write(streambuf, version / 10); + detail::write(streambuf, "."); + detail::write(streambuf, version % 10); + detail::write(streambuf, " "); + break; + } + detail::write(streambuf, this->status); + detail::write(streambuf, " "); + detail::write(streambuf, this->reason); + detail::write(streambuf, "\r\n"); +} + +namespace detail { + +template +std::string +buffers_to_string(ConstBufferSequence const& buffers) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string s; + s.reserve(buffer_size(buffers)); + for(auto const& b : buffers) + s.append(buffer_cast(b), + buffer_size(b)); + return s; +} + +} // detail + +// Diagnostic output only +template +std::ostream& +operator<<(std::ostream& os, + message const& msg) +{ + static_assert(is_WritableBody::value, + "WritableBody requirements not met"); + error_code ec; + detail::write_preparation wp(msg); + wp.init(ec); + if(ec) + return os; + std::mutex m; + std::condition_variable cv; + bool ready = false; + resume_context resume{ + [&] + { + std::lock_guard lock(m); + ready = true; + cv.notify_one(); + }}; + auto copy = resume; + os << detail::buffers_to_string(wp.sb.data()); + wp.sb.consume(wp.sb.size()); + auto writef = + [&os, &wp](auto const& buffers) + { + if(wp.chunked) + os << detail::buffers_to_string( + chunk_encode(buffers)); + else + os << detail::buffers_to_string( + buffers); + }; + for(;;) + { + { + auto result = wp.w(std::move(copy), ec, writef); + if(ec) + return os; + if(result) + break; + if(boost::indeterminate(result)) + { + copy = resume; + std::unique_lock lock(m); + cv.wait(lock, [&]{ return ready; }); + ready = false; + } + } + wp.sb.consume(wp.sb.size()); + for(;;) + { + auto result = wp.w(std::move(copy), ec, writef); + if(ec) + return os; + if(result) + break; + if(boost::indeterminate(result)) + { + copy = resume; + std::unique_lock lock(m); + cv.wait(lock, [&]{ return ready; }); + ready = false; + } + } + } + if(wp.chunked) + { + // VFALCO Unfortunately the current interface to the + // Writer concept prevents us from using coalescing the + // final body chunk with the final chunk delimiter. + // + // write final chunk + os << detail::buffers_to_string(chunk_encode_final()); + if(ec) + return os; + } + os << std::endl; + return os; +} + +//------------------------------------------------------------------------------ + +template +void +set_connection(bool keep_alive, + message& req) +{ + if(req.version >= 11) + { + if(! keep_alive) + req.headers.replace("Connection", "close"); + else + req.headers.erase("Connection"); + } + else + { + if(keep_alive) + req.headers.replace("Connection", "keep-alive"); + else + req.headers.erase("Connection"); + } +} + +template +void +set_connection(bool keep_alive, + message& resp, + message const& req) +{ + if(req.version >= 11) + { + if(rfc2616::token_in_list(req["Connection"], "close")) + keep_alive = false; + } + else + { + if(! rfc2616::token_in_list(req["Connection"], "keep-alive")) + keep_alive = false; + } + set_connection(keep_alive, resp); +} + +template +void +write_fields(Streambuf& streambuf, FieldSequence const& fields) +{ + static_assert(is_Streambuf::value, + "Streambuf requirements not met"); + //static_assert(is_FieldSequence::value, + // "FieldSequence requirements not met"); + for(auto const& field : fields) + { + detail::write(streambuf, field.name()); + detail::write(streambuf, ": "); + detail::write(streambuf, field.value()); + detail::write(streambuf, "\r\n"); + } +} + +template +bool +is_keep_alive(message const& msg) +{ + if(msg.version >= 11) + { + if(rfc2616::token_in_list( + msg.headers["Connection"], "close")) + return false; + return true; + } + if(rfc2616::token_in_list( + msg.headers["Connection"], "keep-alive")) + return true; + return false; +} + +template +bool +is_upgrade(message const& msg) +{ + if(msg.version < 11) + return false; + if(rfc2616::token_in_list( + msg.headers["Connection"], "upgrade")) + return true; + return false; +} + +} // http +} // beast + +#include + +#endif diff --git a/beast/http/impl/read.ipp b/beast/http/impl/read.ipp new file mode 100644 index 0000000000..bc45cda6f0 --- /dev/null +++ b/beast/http/impl/read.ipp @@ -0,0 +1,297 @@ +//------------------------------------------------------------------------------ +/* + 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_READ_IPP_H_INCLUDED +#define BEAST_HTTP_READ_IPP_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { + +template +class read_op +{ + using alloc_type = + handler_alloc; + + using parser_type = + parser; + + using message_type = + message; + + struct data + { + Stream& s; + Streambuf& sb; + message_type& m; + parser_type p; + Handler h; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, Stream& s_, + Streambuf& sb_, message_type& m_) + : s(s_) + , sb(sb_) + , m(m_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + read_op(read_op&&) = default; + read_op(read_op const&) = default; + + template + read_op(DeducedHandler&& h, Stream&s, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), s, + std::forward(args)...)) + { + (*this)(error_code{}, 0, false); + } + + void + operator()(error_code ec, + std::size_t bytes_transferred, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, read_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, read_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(read_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, read_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +void +read_op:: +operator()(error_code ec, std::size_t bytes_transferred, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(d.state != 99) + { + switch(d.state) + { + case 0: + { + auto const used = + d.p.write(d.sb.data(), ec); + if(ec) + { + // call handler + d.state = 99; + d.s.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + return; + } + d.sb.consume(used); + if(d.p.complete()) + { + // call handler + d.state = 99; + d.m = d.p.release(); + d.s.get_io_service().post( + bind_handler(std::move(*this), ec, 0)); + return; + } + d.state = 1; + break; + } + + case 1: + // read + d.state = 2; + d.s.async_read_some(d.sb.prepare( + read_size_helper(d.sb, 65536)), + std::move(*this)); + return; + + // got data + case 2: + { + if(ec == boost::asio::error::eof) + { + if(! d.p.started()) + { + // call handler + d.state = 99; + break; + } + // Caller will see eof on next read. + ec = {}; + d.p.write_eof(ec); + if(! ec) + { + assert(d.p.complete()); + d.m = d.p.release(); + } + // call handler + d.state = 99; + break; + } + if(ec) + { + // call handler + d.state = 99; + break; + } + d.sb.commit(bytes_transferred); + d.sb.consume(d.p.write(d.sb.data(), ec)); + if(ec) + { + // call handler + d.state = 99; + break; + } + if(d.p.complete()) + { + // call handler + d.state = 99; + d.m = d.p.release(); + break; + } + d.state = 1; + break; + } + } + } + d.h(ec); +} + +} // detail + +//------------------------------------------------------------------------------ + +template +void +read(SyncReadStream& stream, Streambuf& streambuf, + message& m, + error_code& ec) +{ + static_assert(is_SyncReadStream::value, + "SyncReadStream requirements not met"); + static_assert(is_Streambuf::value, + "Streambuf requirements not met"); + static_assert(is_ReadableBody::value, + "ReadableBody requirements not met"); + parser p; + for(;;) + { + auto used = + p.write(streambuf.data(), ec); + if(ec) + return; + streambuf.consume(used); + if(p.complete()) + { + m = p.release(); + break; + } + streambuf.commit(stream.read_some( + streambuf.prepare(read_size_helper( + streambuf, 65536)), ec)); + if(ec && ec != boost::asio::error::eof) + return; + if(ec == boost::asio::error::eof) + { + if(! p.started()) + return; + // Caller will see eof on next read. + ec = {}; + p.write_eof(ec); + if(ec) + return; + assert(p.complete()); + m = p.release(); + break; + } + } +} + +template +auto +async_read(AsyncReadStream& stream, Streambuf& streambuf, + message& m, + CompletionToken&& token) +{ + static_assert(is_AsyncReadStream::value, + "AsyncReadStream requirements not met"); + static_assert(is_Streambuf::value, + "Streambuf requirements not met"); + static_assert(is_ReadableBody::value, + "ReadableBody requirements not met"); + beast::async_completion completion(token); + detail::read_op{completion.handler, + stream, streambuf, m}; + return completion.result.get(); +} + +} // http +} // beast + +#endif diff --git a/beast/http/impl/write.ipp b/beast/http/impl/write.ipp new file mode 100644 index 0000000000..5a06575abc --- /dev/null +++ b/beast/http/impl/write.ipp @@ -0,0 +1,396 @@ +//------------------------------------------------------------------------------ +/* + 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_WRITE_IPP_INCLUDED +#define BEAST_HTTP_WRITE_IPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // + +namespace beast { +namespace http { + +namespace detail { + +template +class write_op +{ + using alloc_type = + handler_alloc; + + struct data + { + Stream& s; + // VFALCO How do we use handler_alloc in write_preparation? + write_preparation< + isRequest, Body, Headers> wp; + Handler h; + resume_context resume; + resume_context copy; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, Stream& s_, + message const& m_) + : s(s_) + , wp(m_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + write_op(write_op&&) = default; + write_op(write_op const&) = default; + + template + write_op(DeducedHandler&& h, Stream& s, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), s, + std::forward(args)...)) + { + auto& d = *d_; + d.resume = { + [self = *this]() mutable + { + self.d_->cont = false; + auto& ios = self.d_->s.get_io_service(); + ios.dispatch(bind_handler(std::move(self), + error_code{}, 0, false)); + }}; + d.copy = d.resume; + (*this)(error_code{}, 0, false); + } + + explicit + write_op(std::shared_ptr d) + : d_(std::move(d)) + { + } + + void + operator()(error_code ec, + std::size_t bytes_transferred, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, write_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, write_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(write_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, write_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +void +write_op:: +operator()(error_code ec, std::size_t, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + { + d.wp.init(ec); + if(ec) + { + // call handler + d.state = 99; + d.s.get_io_service().post(bind_handler( + std::move(*this), ec, 0, false)); + return; + } + d.state = 1; + break; + } + + case 1: + { + auto const result = d.wp.w(std::move(d.copy), ec, + [&](auto const& buffers) + { + // write headers and body + if(d.wp.chunked) + boost::asio::async_write(d.s, + append_buffers(d.wp.sb.data(), + chunk_encode(buffers)), + std::move(*this)); + else + boost::asio::async_write(d.s, + append_buffers(d.wp.sb.data(), + buffers), std::move(*this)); + }); + if(ec) + { + // call handler + d.state = 99; + d.s.get_io_service().post(bind_handler( + std::move(*this), ec, false)); + return; + } + if(boost::indeterminate(result)) + { + // suspend + d.copy = d.resume; + return; + } + if(result) + d.state = d.wp.chunked ? 4 : 5; + else + d.state = 2; + return; + } + + // sent headers and body + case 2: + d.wp.sb.consume(d.wp.sb.size()); + d.state = 3; + break; + + case 3: + { + auto const result = d.wp.w(std::move(d.copy), ec, + [&](auto const& buffers) + { + // write body + if(d.wp.chunked) + boost::asio::async_write(d.s, + chunk_encode(buffers), + std::move(*this)); + else + boost::asio::async_write(d.s, + buffers, std::move(*this)); + }); + if(ec) + { + // call handler + d.state = 99; + break; + } + if(boost::indeterminate(result)) + { + // suspend + d.copy = d.resume; + return; + } + if(result) + d.state = d.wp.chunked ? 4 : 5; + else + d.state = 2; + return; + } + + case 4: + // VFALCO Unfortunately the current interface to the + // Writer concept prevents us from using coalescing the + // final body chunk with the final chunk delimiter. + // + // write final chunk + d.state = 5; + boost::asio::async_write(d.s, + chunk_encode_final(), std::move(*this)); + return; + + case 5: + if(d.wp.close) + { + // VFALCO TODO Decide on an error code + ec = boost::asio::error::eof; + } + d.state = 99; + break; + } + } + d.h(ec); + d.resume = {}; + d.copy = {}; +} + +} // detail + +//------------------------------------------------------------------------------ + +template +void +write(SyncWriteStream& stream, + message const& msg, + boost::system::error_code& ec) +{ + static_assert(is_WritableBody::value, + "WritableBody requirements not met"); + detail::write_preparation wp(msg); + wp.init(ec); + if(ec) + return; + std::mutex m; + std::condition_variable cv; + bool ready = false; + resume_context resume{ + [&] + { + std::lock_guard lock(m); + ready = true; + cv.notify_one(); + }}; + auto copy = resume; + for(;;) + { + { + auto result = wp.w(std::move(copy), ec, + [&](auto const& buffers) + { + // write headers and body + if(wp.chunked) + boost::asio::write(stream, append_buffers( + wp.sb.data(), chunk_encode(buffers)), ec); + else + boost::asio::write(stream, append_buffers( + wp.sb.data(), buffers), ec); + }); + if(ec) + return; + if(result) + break; + if(boost::indeterminate(result)) + { + boost::asio::write(stream, wp.sb.data(), ec); + if(ec) + return; + wp.sb.consume(wp.sb.size()); + copy = resume; + std::unique_lock lock(m); + cv.wait(lock, [&]{ return ready; }); + ready = false; + } + } + wp.sb.consume(wp.sb.size()); + for(;;) + { + auto result = wp.w(std::move(copy), ec, + [&](auto const& buffers) + { + // write body + if(wp.chunked) + boost::asio::write(stream, + chunk_encode(buffers), ec); + else + boost::asio::write(stream, buffers, ec); + }); + if(ec) + return; + if(result) + break; + if(boost::indeterminate(result)) + { + copy = resume; + std::unique_lock lock(m); + cv.wait(lock, [&]{ return ready; }); + ready = false; + } + } + } + if(wp.chunked) + { + // VFALCO Unfortunately the current interface to the + // Writer concept prevents us from using coalescing the + // final body chunk with the final chunk delimiter. + // + // write final chunk + boost::asio::write(stream, chunk_encode_final(), ec); + if(ec) + return; + } + if(wp.close) + { + // VFALCO TODO Decide on an error code + ec = boost::asio::error::eof; + } +} + +template +auto +async_write(AsyncWriteStream& stream, + message const& msg, + CompletionToken&& token) +{ + static_assert( + is_AsyncWriteStream::value, + "AsyncWriteStream requirements not met"); + static_assert(is_WritableBody::value, + "WritableBody requirements not met"); + beast::async_completion completion(token); + detail::write_op{completion.handler, stream, msg}; + return completion.result.get(); +} + +} // http +} // beast + +#endif diff --git a/beast/http/message.h b/beast/http/message.h index bdab45b0a7..6dc09a3b7f 100644 --- a/beast/http/message.h +++ b/beast/http/message.h @@ -20,12 +20,171 @@ #ifndef BEAST_HTTP_MESSAGE_H_INCLUDED #define BEAST_HTTP_MESSAGE_H_INCLUDED -#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { + +struct request_fields +{ + http::method_t method; + std::string url; +}; + +struct response_fields +{ + int status; + std::string reason; +}; + +} // detail + +struct request_params +{ + http::method_t method; + std::string url; + int version; +}; + +struct response_params +{ + int status; + std::string reason; + int version; +}; + +/** A HTTP message. + + A message can be a request or response, depending on the `isRequest` + template argument value. Requests and responses have different types, + so functions may be overloaded on them if desired. + + The `Body` template argument type determines the model used + to read or write the content body of the message. + + @tparam isRequest `true` if this is a request. + + @tparam Body A type meeting the requirements of Body. + + @tparam Headers A type meeting the requirements of Headers. +*/ +template +struct message + : std::conditional_t +{ + /** The trait type characterizing the body. + + The body member will be of type body_type::value_type. + */ + using body_type = Body; + using headers_type = Headers; + + using is_request = + std::integral_constant; + + int version; // 10 or 11 + headers_type headers; + typename Body::value_type body; + + message(); + message(message&&) = default; + message(message const&) = default; + message& operator=(message&&) = default; + message& operator=(message const&) = default; + + /** Construct a HTTP request. + */ + explicit + message(request_params params); + + /** Construct a HTTP response. + */ + explicit + message(response_params params); + + /// Serialize the request or response line to a Streambuf. + template + void + write_firstline(Streambuf& streambuf) const + { + write_firstline(streambuf, + std::integral_constant{}); + } + + /// Diagnostics only + template + friend + std::ostream& + operator<<(std::ostream& os, + message const& m); + +private: + template + void + write_firstline(Streambuf& streambuf, + std::true_type) const; + + template + void + write_firstline(Streambuf& streambuf, + std::false_type) const; +}; + +#if ! GENERATING_DOCS + +/// A typical HTTP request +template>> +using request = message; + +/// A typical HTTP response +template>> +using response = message; + +#endif + +// For diagnostic output only +template +std::ostream& +operator<<(std::ostream& os, + message const& m); + +/// Write a FieldSequence to a Streambuf. +template +void +write_fields(Streambuf& streambuf, FieldSequence const& fields); + +/// Returns `true` if a message indicates a keep alive +template +bool +is_keep_alive(message const& msg); + +/// Returns `true` if a message indicates a HTTP Upgrade request or response +template +bool +is_upgrade(message const& msg); + +} // http +} // beast + +#include + +//------------------------------------------------------------------------------ + #include #include #include -#include -#include +#include #include #include #include @@ -34,7 +193,7 @@ #include namespace beast { -namespace http { +namespace deprecated_http { inline std::pair @@ -70,17 +229,16 @@ private: public: ~message() = default; - message (message const&) = delete; - message& operator= (message const&) = delete; + message (message const&) = default; + message (message&& other) = default; + message& operator= (message const&) = default; + message& operator= (message&& other) = default; template message(); - message (message&& other) = default; - message& operator= (message&& other) = default; - // Memberspace - beast::http::headers headers; + beast::http::headers> headers; bool request() const @@ -221,49 +379,31 @@ write (Streambuf& 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)); + http::detail::write (stream, to_string(m.method())); + http::detail::write (stream, " "); + http::detail::write (stream, m.url()); + http::detail::write (stream, " HTTP/"); + http::detail::write (stream, std::to_string(m.version().first)); + http::detail::write (stream, "."); + http::detail::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()); + http::detail::write (stream, "HTTP/"); + http::detail::write (stream, std::to_string(m.version().first)); + http::detail::write (stream, "."); + http::detail::write (stream, std::to_string(m.version().second)); + http::detail::write (stream, " "); + http::detail::write (stream, std::to_string(m.status())); + http::detail::write (stream, " "); + http::detail::write (stream, m.reason()); } - write (stream, "\r\n"); - write(stream, m.headers); - write (stream, "\r\n"); + http::detail::write (stream, "\r\n"); + write_fields(stream, m.headers); + http::detail::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 +} // deprecated_http } // beast #endif diff --git a/beast/http/method.h b/beast/http/method.h index 8144713a39..bc58af68af 100644 --- a/beast/http/method.h +++ b/beast/http/method.h @@ -78,8 +78,51 @@ enum class method_t http_unlink }; +template std::string -to_string (method_t m); +to_string(method_t m) +{ + switch(m) + { + case method_t::http_delete: return "DELETE"; + case method_t::http_get: return "GET"; + case method_t::http_head: return "HEAD"; + case method_t::http_post: return "POST"; + case method_t::http_put: return "PUT"; + + case method_t::http_connect: return "CONNECT"; + case method_t::http_options: return "OPTIONS"; + case method_t::http_trace: return "TRACE"; + + case method_t::http_copy: return "COPY"; + case method_t::http_lock: return "LOCK"; + case method_t::http_mkcol: return "MKCOL"; + case method_t::http_move: return "MOVE"; + case method_t::http_propfind: return "PROPFIND"; + case method_t::http_proppatch: return "PROPPATCH"; + case method_t::http_search: return "SEARCH"; + case method_t::http_unlock: return "UNLOCK"; + + case method_t::http_report: return "REPORT"; + case method_t::http_mkactivity: return "MKACTIVITY"; + case method_t::http_checkout: return "CHECKOUT"; + case method_t::http_merge: return "MERGE"; + + case method_t::http_msearch: return "MSEARCH"; + case method_t::http_notify: return "NOTIFY"; + case method_t::http_subscribe: return "SUBSCRIBE"; + case method_t::http_unsubscribe: return "UNSUBSCRIBE"; + + case method_t::http_patch: return "PATCH"; + case method_t::http_purge: return "PURGE"; + + default: + assert(false); + break; + }; + + return "GET"; +} template Stream& @@ -89,10 +132,60 @@ operator<< (Stream& s, method_t m) } /** Returns the string corresponding to the numeric HTTP status code. */ +template std::string -status_text (int status); +status_text (int status) +{ + switch(status) + { + case 100: return "Continue"; + case 101: return "Switching Protocols"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 300: return "Multiple Choices"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + //case 306: return ""; + case 307: return "Temporary Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Request Entity Too Large"; + case 414: return "Request-URI Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Requested Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + default: + break; + } + return "Unknown HTTP status"; +} -} -} +} // http +} // beast #endif diff --git a/beast/http/parser.h b/beast/http/parser.h index 36f784c63f..826803e397 100644 --- a/beast/http/parser.h +++ b/beast/http/parser.h @@ -20,6 +20,157 @@ #ifndef BEAST_HTTP_PARSER_H_INCLUDED #define BEAST_HTTP_PARSER_H_INCLUDED +#include +#include +#include +#include +#include +#include // +#include + +namespace beast { +namespace http { + +/** A HTTP parser. + + The parser may only be used once. +*/ +template +class parser + : public basic_parser> +{ + using message_type = + message; + + message_type m_; + typename message_type::body_type::reader r_; + bool started_ = false; + +public: + parser(parser&&) = default; + + parser() + : http::basic_parser(isRequest) + , r_(m_) + { + } + + /// Returns `true` if at least one byte has been processed + bool + started() + { + return started_; + } + + message_type + release() + { + return std::move(m_); + } + +private: + friend class http::basic_parser; + + void + on_start() + { + started_ = true; + } + + void + on_field(std::string const& field, std::string const& value) + { + m_.headers.insert(field, value); + } + + void + on_headers_complete(error_code&) + { + // vFALCO TODO Decode the Content-Length and + // Transfer-Encoding, see if we can reserve the buffer. + // + // r_.reserve(content_length) + } + + bool + on_request(http::method_t method, std::string const& url, + int major, int minor, bool keep_alive, bool upgrade, + std::true_type) + { + m_.method = method; + m_.url = url; + m_.version = major * 10 + minor; + return true; + } + + bool + on_request(http::method_t, std::string const&, + int, int, bool, bool, + std::false_type) + { + return true; + } + + bool + on_request(http::method_t method, std::string const& url, + int major, int minor, bool keep_alive, bool upgrade) + { + return on_request(method, url, + major, minor, keep_alive, upgrade, + typename message_type::is_request{}); + } + + bool + on_response(int status, std::string const& reason, + int major, int minor, bool keep_alive, bool upgrade, + std::true_type) + { + m_.status = status; + m_.reason = reason; + m_.version = major * 10 + minor; + // VFALCO TODO return expect_body_ + return true; + } + + bool + on_response(int, std::string const&, int, int, bool, bool, + std::false_type) + { + return true; + } + + bool + on_response(int status, std::string const& reason, + int major, int minor, bool keep_alive, bool upgrade) + { + return on_response( + status, reason, major, minor, keep_alive, upgrade, + std::integral_constant{}); + } + + void + on_body(void const* data, + std::size_t size, error_code& ec) + { + r_.write(data, size, ec); + } + + void + on_complete() + { + } +}; + +} // http +} // beast + +//------------------------------------------------------------------------------ + +// +// LEGACY +// + +#include #include #include #include @@ -27,167 +178,105 @@ #include namespace beast { -namespace http { +namespace deprecated_http { /** Parser for HTTP messages. The result is stored in a message object. */ -class parser : public beast::http::basic_parser +class parser + : public beast::http::basic_parser { -private: - std::reference_wrapper message_; +// friend class basic_parser; + + message& m_; std::function write_body_; public: + parser(parser&&) = default; + parser(parser const&) = delete; + parser& operator=(parser&&) = delete; + parser& operator=(parser const&) = delete; + /** Construct a parser for HTTP request or response. The headers plus request or status line are stored in message. The content-body, if any, is passed as a series of calls to the write_body function. Transfer encodings are applied before any data is passed to the write_body function. */ - parser (std::function write_body, + parser(std::function write_body, message& m, bool request) - : beast::http::basic_parser (request) - , message_(m) + : basic_parser(request) + , m_(m) , write_body_(std::move(write_body)) { - message_.get().request(request); + m_.request(request); } - parser (message& m, body& b, bool request) - : beast::http::basic_parser (request) - , message_(m) + parser(message& m, body& b, bool request) + : basic_parser(request) + , m_(m) { write_body_ = [&b](void const* data, std::size_t size) { b.write(data, size); }; - - message_.get().request(request); + m_.request(request); } - parser& operator= (parser&& other) = default; - -private: - template +//private: void - do_start (); - - template - bool - 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 + on_start() + { + } + + void + on_headers_complete(error_code&) { - do_start(); } bool - on_request (method_t method, std::string const& url, - int major, int minor, bool keep_alive, bool upgrade) override + on_request(http::method_t method, std::string const& url, + int major, int minor, bool keep_alive, bool upgrade) { - return do_request (method, url, major, minor, keep_alive, upgrade); + m_.method(method); + m_.url(url); + m_.version(major, minor); + m_.keep_alive(keep_alive); + m_.upgrade(upgrade); + return true; } bool - on_response (int status, std::string const& text, - int major, int minor, bool keep_alive, bool upgrade) override + on_response(int status, std::string const& text, + int major, int minor, bool keep_alive, bool upgrade) { - return do_response (status, text, major, minor, keep_alive, upgrade); + m_.status(status); + m_.reason(text); + m_.version(major, minor); + m_.keep_alive(keep_alive); + m_.upgrade(upgrade); + return true; } void - on_field (std::string const& field, std::string const& value) override + on_field(std::string const& field, std::string const& value) { - do_field (field, value); + m_.headers.insert(field, value); } void - on_body (void const* data, std::size_t bytes) override + on_body(void const* data, std::size_t bytes, error_code&) { - do_body (data, bytes); + write_body_(data, bytes); } void - on_complete() override + on_complete() { - do_complete(); } }; -//------------------------------------------------------------------------------ -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) -{ - write_body_(data, bytes); -} - -template -void -parser::do_complete() -{ -} - -} // http +} // deprecated_http } // beast #endif diff --git a/beast/http/read.h b/beast/http/read.h new file mode 100644 index 0000000000..6707831675 --- /dev/null +++ b/beast/http/read.h @@ -0,0 +1,119 @@ +//------------------------------------------------------------------------------ +/* + 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_READ_H_INCLUDED +#define BEAST_HTTP_READ_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** Read a HTTP message from a stream. + + @param stream The stream to read the message from. + + @param streambuf A Streambuf used to hold unread bytes. The + implementation may read past the end of the message. The extra + bytes are stored here, to be presented in a subsequent call to + read. + + @param msg An object used to store the read message. Any + contents will be overwritten. + + @throws boost::system::system_error on failure. +*/ +template +void +read(SyncReadStream& stream, Streambuf& streambuf, + message& msg) +{ + error_code ec; + read(stream, streambuf, msg, ec); + if(ec) + throw boost::system::system_error{ec}; +} + +/** Read a HTTP message from a stream. + + @param stream The stream to read the message from. + + @param streambuf A Streambuf used to hold unread bytes. The + implementation may read past the end of the message. The extra + bytes are stored here, to be presented in a subsequent call to + read. + + @param msg An object used to store the read message. Any + contents will be overwritten. + + @param ec Set to the error, if any occurred. +*/ +template +void +read(SyncReadStream& stream, Streambuf& streambuf, + message& msg, + error_code& ec); + +/** Start reading a HTTP message from a stream asynchronously. + + @param stream The stream to read the message from. + + @param streambuf A Streambuf used to hold unread bytes. The + implementation may read past the end of the message. The extra + bytes are stored here, to be presented in a subsequent call to + async_read. + + @param msg An object used to store the read message. Any + contents will be overwritten. + + @param token The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). +*/ +template +#if GENERATING_DOCS +void_or_deduced +#else +auto +#endif +async_read(AsyncReadStream& stream, Streambuf& streambuf, + message& msg, + CompletionToken&& token); + +} // http +} // beast + +#include + +#endif diff --git a/beast/http/impl/method.cpp b/beast/http/reason.h similarity index 52% rename from beast/http/impl/method.cpp rename to beast/http/reason.h index 9cc119c19b..7d97809125 100644 --- a/beast/http/impl/method.cpp +++ b/beast/http/reason.h @@ -1,75 +1,32 @@ //------------------------------------------------------------------------------ /* This file is part of Beast: https://github.com/vinniefalco/Beast - Copyright 2013: Vinnie Falco + 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 + 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 + 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 +#ifndef BEAST_HTTP_REASON_H_INCLUDED +#define BEAST_HTTP_REASON_H_INCLUDED namespace beast { namespace http { -std::string -to_string (method_t m) -{ - switch(m) - { - case method_t::http_delete: return "DELETE"; - case method_t::http_get: return "GET"; - case method_t::http_head: return "HEAD"; - case method_t::http_post: return "POST"; - case method_t::http_put: return "PUT"; - - case method_t::http_connect: return "CONNECT"; - case method_t::http_options: return "OPTIONS"; - case method_t::http_trace: return "TRACE"; - - case method_t::http_copy: return "COPY"; - case method_t::http_lock: return "LOCK"; - case method_t::http_mkcol: return "MKCOL"; - case method_t::http_move: return "MOVE"; - case method_t::http_propfind: return "PROPFIND"; - case method_t::http_proppatch: return "PROPPATCH"; - case method_t::http_search: return "SEARCH"; - case method_t::http_unlock: return "UNLOCK"; - - case method_t::http_report: return "REPORT"; - case method_t::http_mkactivity: return "MKACTIVITY"; - case method_t::http_checkout: return "CHECKOUT"; - case method_t::http_merge: return "MERGE"; - - case method_t::http_msearch: return "MSEARCH"; - case method_t::http_notify: return "NOTIFY"; - case method_t::http_subscribe: return "SUBSCRIBE"; - case method_t::http_unsubscribe: return "UNSUBSCRIBE"; - - case method_t::http_patch: return "PATCH"; - case method_t::http_purge: return "PURGE"; - - default: - assert(false); - break; - }; - - return "GET"; -} - -std::string -status_text (int status) +/** Returns the text for a known status code integer. */ +template +char const* +reason_string(int status) { switch(status) { @@ -88,7 +45,6 @@ status_text (int status) case 303: return "See Other"; case 304: return "Not Modified"; case 305: return "Use Proxy"; - //case 306: return ""; case 307: return "Temporary Redirect"; case 400: return "Bad Request"; case 401: return "Unauthorized"; @@ -114,11 +70,15 @@ status_text (int status) case 503: return "Service Unavailable"; case 504: return "Gateway Timeout"; case 505: return "HTTP Version Not Supported"; + + case 306: return ""; default: break; } - return "Unknown HTTP status"; + return ""; } -} -} +} // http +} // beast + +#endif diff --git a/beast/http/detail/header_traits.h b/beast/http/resume_context.h similarity index 52% rename from beast/http/detail/header_traits.h rename to beast/http/resume_context.h index 28a98bd6eb..944c712b2f 100644 --- a/beast/http/detail/header_traits.h +++ b/beast/http/resume_context.h @@ -17,47 +17,30 @@ */ //============================================================================== -#ifndef BEAST_HTTP_DETAIL_HEADER_TRAITS_H_INCLUDED -#define BEAST_HTTP_DETAIL_HEADER_TRAITS_H_INCLUDED +#ifndef BEAST_HTTP_RESUME_CONTEXT_H_INCLUDED +#define BEAST_HTTP_RESUME_CONTEXT_H_INCLUDED -#include - -#include - -#include -#include +#include namespace beast { namespace http { -namespace detail { -// Utilities for dealing with HTTP headers +/** A functor that resumes a write operation. -template > -using basic_field_string = - std::basic_string ; + An rvalue reference to an object of this type is provided by the + write implementation to the `writer` associated with the body of + a message being sent. -using field_string = basic_field_string <>; - -using field_string_ref = boost::basic_string_ref ; - -/** Returns `true` if two header fields are the same. - The comparison is case-insensitive. + If it is desired that the `writer` suspend the write operation (for + example, to wait until data is ready), it can take ownership of + the resume context using a move. Then, it returns `boost::indeterminate` + to indicate that the write operation should suspend. Later, the calling + code invokes the resume function and the write operation continues + from where it left off. */ -template -inline -bool field_eq ( - std::basic_string , Alloc1> const& s1, - std::basic_string , Alloc2> const& s2) -{ - return field_string_ref (s1.c_str(), s1.size()) == - field_string_ref (s2.c_str(), s2.size()); -} +using resume_context = std::function; -/** Returns the string with leading and trailing LWS removed. */ - -} -} -} +} // http +} // beast #endif diff --git a/beast/http/rfc2616.h b/beast/http/rfc2616.h index 351034c645..053c3109ef 100644 --- a/beast/http/rfc2616.h +++ b/beast/http/rfc2616.h @@ -20,10 +20,14 @@ #ifndef BEAST_HTTP_RFC2616_H_INCLUDED #define BEAST_HTTP_RFC2616_H_INCLUDED -#include +#include +#include +#include #include +#include #include #include +#include // for std::tie, remove ASAP #include #include @@ -36,20 +40,34 @@ namespace beast { */ namespace rfc2616 { +namespace detail { + +struct ci_equal_pred +{ + bool operator()(char c1, char c2) + { + // VFALCO TODO Use a table lookup here + return std::tolower(c1) == std::tolower(c2); + } +}; + +} // detail + /** Returns `true` if `c` is linear white space. + This excludes the CRLF sequence allowed for line continuations. */ -template +inline bool -is_lws (CharT c) +is_lws(char c) { return c == ' ' || c == '\t'; } /** Returns `true` if `c` is any whitespace character. */ -template +inline bool -is_white (CharT c) +is_white(char c) { switch (c) { @@ -61,18 +79,19 @@ is_white (CharT c) } /** Returns `true` if `c` is a control character. */ -template +inline bool -is_ctl (CharT c) +is_control(char c) { return c <= 31 || c >= 127; } /** Returns `true` if `c` is a separator. */ -template +inline bool -is_sep (CharT c) +is_separator(char c) { + // VFALCO Could use a static table switch (c) { case '(': case ')': case '<': case '>': case '@': @@ -83,12 +102,20 @@ is_sep (CharT c) return false; } +/** Returns `true` if `c` is a character. */ +inline +bool +is_char(char c) +{ + return c >= 0 && c <= 127; +} + template FwdIter trim_left (FwdIter first, FwdIter last) { return std::find_if_not (first, last, - &is_white ); + is_white); } template @@ -166,8 +193,9 @@ trim (std::string const& s) */ template >, - class Char> + std::basic_string::value_type>>, + class Char> Result split(FwdIt first, FwdIt last, Char delim) { @@ -239,7 +267,8 @@ split(FwdIt first, FwdIt last, Char delim) template >> + std::basic_string::value_type>>> Result split_commas(FwdIt first, FwdIt last) { @@ -248,11 +277,194 @@ split_commas(FwdIt first, FwdIt last) template > Result -split_commas(std::string const& s) +split_commas(boost::string_ref const& s) { return split_commas(s.begin(), s.end()); } +//------------------------------------------------------------------------------ + +/** Iterates through a comma separated list. + + Meets the requirements of ForwardIterator. + + List defined in rfc2616 2.1. + + @note Values returned may contain backslash escapes. +*/ +class list_iterator +{ + using iter_type = boost::string_ref::const_iterator; + + iter_type it_; + iter_type end_; + boost::string_ref value_; + +public: + using value_type = boost::string_ref; + using pointer = value_type const*; + using reference = value_type const&; + using difference_type = std::ptrdiff_t; + using iterator_category = + std::forward_iterator_tag; + + list_iterator(iter_type begin, iter_type end) + : it_(begin) + , end_(end) + { + if(it_ != end_) + increment(); + } + + bool + operator==(list_iterator const& other) const + { + return other.it_ == it_ && other.end_ == end_ + && other.value_.size() == value_.size(); + } + + bool + operator!=(list_iterator const& other) const + { + return !(*this == other); + } + + reference + operator*() const + { + return value_; + } + + pointer + operator->() const + { + return &*(*this); + } + + list_iterator& + operator++() + { + increment(); + return *this; + } + + list_iterator + operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + +private: + template + void + increment(); +}; + +template +void +list_iterator::increment() +{ + value_.clear(); + while(it_ != end_) + { + if(*it_ == '"') + { + // quoted-string + ++it_; + if(it_ == end_) + return; + if(*it_ != '"') + { + auto start = it_; + for(;;) + { + ++it_; + if(it_ == end_) + { + value_ = boost::string_ref( + &*start, std::distance(start, it_)); + return; + } + if(*it_ == '"') + { + value_ = boost::string_ref( + &*start, std::distance(start, it_)); + ++it_; + return; + } + } + } + ++it_; + } + else if(*it_ == ',') + { + it_++; + continue; + } + else if(is_lws(*it_)) + { + ++it_; + continue; + } + else + { + auto start = it_; + for(;;) + { + ++it_; + if(it_ == end_ || + *it_ == ',' || + is_lws(*it_)) + { + value_ = boost::string_ref( + &*start, std::distance(start, it_)); + return; + } + } + } + } +} + +/** Returns true if two strings are equal. + + A case-insensitive comparison is used. +*/ +inline +bool +ci_equal(boost::string_ref s1, boost::string_ref s2) +{ + return boost::range::equal(s1, s2, + detail::ci_equal_pred{}); +} + +/** Returns a range representing the list. */ +inline +auto +make_list(boost::string_ref const& field) +{ + return boost::iterator_range{ + list_iterator{field.begin(), field.end()}, + list_iterator{field.end(), field.end()}}; + +} + +/** Returns true if the specified token exists in the list. + + A case-insensitive comparison is used. +*/ +template +bool +token_in_list(boost::string_ref const& value, + boost::string_ref const& token) +{ + for(auto const& item : make_list(value)) + if(ci_equal(item, token)) + return true; + return false; +} + } // rfc2616 } // beast diff --git a/beast/http/impl/joyent_parser.cpp b/beast/http/src/beast_http_nodejs_parser.cpp similarity index 68% rename from beast/http/impl/joyent_parser.cpp rename to beast/http/src/beast_http_nodejs_parser.cpp index 4e29f7bc4d..cd0620f971 100644 --- a/beast/http/impl/joyent_parser.cpp +++ b/beast/http/src/beast_http_nodejs_parser.cpp @@ -17,13 +17,10 @@ */ //============================================================================== -#include +#include #include #include -namespace beast { -namespace joyent { - #ifdef _MSC_VER # pragma warning (push) # pragma warning (disable: 4127) // conditional expression is constant @@ -34,33 +31,10 @@ namespace joyent { # pragma warning (pop) #endif -} -} - -namespace boost { -namespace system { - -template <> -struct is_error_code_enum - : std::true_type -{ -}; - -template <> -struct is_error_condition_enum - : std::true_type -{ -}; - -} -} - -namespace beast { -namespace joyent { - -http::method_t -convert_http_method (joyent::http_method m) +beast::http::method_t +convert_http_method(http_method m) { + using namespace beast; switch (m) { case HTTP_DELETE: return http::method_t::http_delete; @@ -114,58 +88,3 @@ convert_http_method (joyent::http_method m) return http::method_t::http_get; } - -boost::system::error_code -convert_http_errno (joyent::http_errno err) -{ - class http_error_category_t - : public boost::system::error_category - { - private: - using error_code = boost::system::error_code; - using error_condition = boost::system::error_condition; - - public: - char const* - name() const noexcept override - { - return "http_errno"; - } - - std::string - message (int ev) const override - { - return joyent::http_errno_name ( - joyent::http_errno (ev)); - } - - error_condition - default_error_condition (int ev) const noexcept override - { - return error_condition (ev, *this); - } - - bool - equivalent (int code, error_condition const& condition - ) const noexcept override - { - return default_error_condition (code) == condition; - } - - bool - equivalent (error_code const& code, int condition - ) const noexcept override - { - return *this == code.category() && - code.value() == condition; - } - }; - - static http_error_category_t http_error_category; - - return boost::system::error_code ( - err, http_error_category); -} - -} -} diff --git a/beast/http/impl/joyent_parser.h b/beast/http/src/nodejs_parser.h similarity index 76% rename from beast/http/impl/joyent_parser.h rename to beast/http/src/nodejs_parser.h index 9987b3b463..ae5ab9ad69 100644 --- a/beast/http/impl/joyent_parser.h +++ b/beast/http/src/nodejs_parser.h @@ -17,28 +17,29 @@ */ //============================================================================== -#ifndef BEAST_HTTP_JOYENT_PARSER_H_INCLUDED -#define BEAST_HTTP_JOYENT_PARSER_H_INCLUDED +#ifndef BEAST_HTTP_NODEJS_PARSER_H_INCLUDED +#define BEAST_HTTP_NODEJS_PARSER_H_INCLUDED #include - -// TODO Use +#include #include -// Wraps the C-language joyent http parser header in a namespace +beast::http::method_t +convert_http_method(http_method m); -namespace beast { -namespace joyent { - -#include - -http::method_t -convert_http_method (joyent::http_method m); - -boost::system::error_code -convert_http_errno (joyent::http_errno err); - -} -} +namespace boost { +namespace system { +template<> +struct is_error_code_enum + : std::true_type +{ +}; +template<> +struct is_error_condition_enum + : std::true_type +{ +}; +} // system +} // boost #endif diff --git a/beast/http/tests/chunked_encoder.test.cpp b/beast/http/src/test/beast_http_chunked_encoder_test.cpp similarity index 100% rename from beast/http/tests/chunked_encoder.test.cpp rename to beast/http/src/test/beast_http_chunked_encoder_test.cpp diff --git a/beast/http/src/test/beast_http_message_test.cpp b/beast/http/src/test/beast_http_message_test.cpp new file mode 100644 index 0000000000..7266f7ef9f --- /dev/null +++ b/beast/http/src/test/beast_http_message_test.cpp @@ -0,0 +1,186 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { +namespace test { + +class sync_echo_http_server +{ +public: + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + +private: + unit_test::suite& suite_; + boost::asio::io_service ios_; + socket_type sock_; + boost::asio::ip::tcp::acceptor acceptor_; + unit_test::thread thread_; + +public: + sync_echo_http_server( + endpoint_type ep, unit_test::suite& suite) + : suite_(suite) + , sock_(ios_) + , acceptor_(ios_) + { + error_code ec; + acceptor_.open(ep.protocol(), ec); + maybe_throw(ec, "open"); + acceptor_.bind(ep, ec); + maybe_throw(ec, "bind"); + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + maybe_throw(ec, "listen"); + acceptor_.async_accept(sock_, + std::bind(&sync_echo_http_server::on_accept, this, + beast::asio::placeholders::error)); + thread_ = unit_test::thread(suite_, + [&] + { + ios_.run(); + }); + } + + ~sync_echo_http_server() + { + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + thread_.join(); + } + +private: + void + fail(error_code ec, std::string what) + { + suite_.log << + what << ": " << ec.message(); + } + + void + maybe_throw(error_code ec, std::string what) + { + if(ec && + ec != boost::asio::error::operation_aborted) + { + fail(ec, what); + throw ec; + } + } + + void + on_accept(error_code ec) + { + if(ec == boost::asio::error::operation_aborted) + return; + maybe_throw(ec, "accept"); + std::thread{&sync_echo_http_server::do_client, this, + std::move(sock_), boost::asio::io_service::work{ + sock_.get_io_service()}}.detach(); + acceptor_.async_accept(sock_, + std::bind(&sync_echo_http_server::on_accept, this, + beast::asio::placeholders::error)); + } + + void + do_client(socket_type sock, boost::asio::io_service::work) + { + error_code ec; + streambuf rb; + for(;;) + { + request req; + read(sock, rb, req, ec); + if(ec) + break; + response resp( + {100, "OK", req.version}); + resp.body = "Completed successfully."; + write(sock, resp, ec); + if(ec) + break; + } + } +}; + +class http_message_test : public unit_test::suite +{ +public: + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + + void + syncEcho(endpoint_type ep) + { + boost::asio::io_service ios; + socket_type sock(ios); + sock.connect(ep); + + streambuf rb; + { + request req( + {beast::http::method_t::http_get, "/", 11}); + req.body = "Beast.HTTP"; + req.headers.replace("Host", + ep.address().to_string() + ":" + + std::to_string(ep.port())); + write(sock, req); + } + { + response m; + read(sock, rb, m); + } + } + + void + testAsio() + { + endpoint_type ep{ + address_type::from_string("127.0.0.1"), 6000}; + sync_echo_http_server s(ep, *this); + syncEcho(ep); + } + + void run() override + { + testAsio(); + pass(); + } +}; + +BEAST_DEFINE_TESTSUITE_MANUAL(http_message,http,beast); + +} // test +} // http +} // beast diff --git a/beast/http/tests/parser.test.cpp b/beast/http/src/test/beast_http_parser_test.cpp similarity index 58% rename from beast/http/tests/parser.test.cpp rename to beast/http/src/test/beast_http_parser_test.cpp index 6049d59d5a..a54e50dc11 100644 --- a/beast/http/tests/parser.test.cpp +++ b/beast/http/src/test/beast_http_parser_test.cpp @@ -23,46 +23,30 @@ #include namespace beast { -namespace http { +namespace deprecated_http { +namespace test { -class message_test : public beast::unit_test::suite +class parser_test : public beast::unit_test::suite { public: - std::pair - request (std::string const& text) + message + request(std::string const& text) { - message m; body b; - parser p (m, b, true); - auto result (p.write (boost::asio::buffer(text))); + message m; + parser p(m, b, true); + auto const used = + p.write(boost::asio::buffer(text)); + expect(used == text.size()); p.write_eof(); - return std::make_pair (std::move(m), result.first); - } - - void - dump() - { - auto const result = request ( - "GET / HTTP/1.1\r\n" - //"Connection: Upgrade\r\n" - //"Upgrade: Ripple\r\n" - "Field: \t Value \t \r\n" - "Blib: Continu\r\n" - " ation\r\n" - "Field: Hey\r\n" - "Content-Length: 1\r\n" - "\r\n" - "x" - ); - log << result.first.headers; - log << "|" << result.first.headers["Field"] << "|"; + return m; } void test_headers() { - headers h; - h.append("Field", "Value"); + beast::http::headers> h; + h.insert("Field", "Value"); expect (h.erase("Field") == 1); } @@ -76,14 +60,14 @@ public: "GET / HTTP/1.1\r\n" "\r\n" ; - message m; body b; + message m; parser p (m, b, true); - auto result (p.write (boost::asio::buffer(text))); - expect (! result.first); - auto result2 (p.write_eof()); - expect (! result2); - expect (p.complete()); + auto const used = p.write( + boost::asio::buffer(text)); + expect(used == text.size()); + p.write_eof(); + expect(p.complete()); } { @@ -92,17 +76,19 @@ public: "GET\r\n" "\r\n" ; - message m; body b; - parser p (m, b, true); - auto result = p.write (boost::asio::buffer(text)); - if (expect (result.first)) - expect (result.first.message() == "invalid HTTP method"); + message m; + parser p(m, b, true); + boost::system::error_code ec; + p.write(boost::asio::buffer(text), ec); + if(expect(ec)) + expect(ec.message() == "invalid HTTP method"); } } }; -BEAST_DEFINE_TESTSUITE(message,http,beast); +BEAST_DEFINE_TESTSUITE(parser,http,beast); -} // http +} // test +} // deprecated_http } // beast diff --git a/beast/http/src/test/beast_http_rfc2616_test.cpp b/beast/http/src/test/beast_http_rfc2616_test.cpp new file mode 100644 index 0000000000..54c220e5fe --- /dev/null +++ b/beast/http/src/test/beast_http_rfc2616_test.cpp @@ -0,0 +1,126 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include +#include + + +namespace beast { +namespace rfc2616 { +namespace test { + +class rfc2616_test : public beast::unit_test::suite +{ +public: + void + checkSplit(std::string const& s, + std::vector const& expected) + { + auto const parsed = split_commas(s.begin(), s.end()); + expect (parsed == expected); + } + + void testSplit() + { + checkSplit("", {}); + checkSplit(" ", {}); + checkSplit(" ", {}); + checkSplit("\t", {}); + checkSplit(" \t ", {}); + checkSplit(",", {}); + checkSplit(",,", {}); + checkSplit(" ,", {}); + checkSplit(" , ,", {}); + checkSplit("x", {"x"}); + checkSplit(" x", {"x"}); + checkSplit(" \t x", {"x"}); + checkSplit("x ", {"x"}); + checkSplit("x \t", {"x"}); + checkSplit(" \t x \t ", {"x"}); + checkSplit("\"\"", {}); + checkSplit(" \"\"", {}); + checkSplit("\"\" ", {}); + checkSplit("\"x\"", {"x"}); + checkSplit("\" \"", {" "}); + checkSplit("\" x\"", {" x"}); + checkSplit("\"x \"", {"x "}); + checkSplit("\" x \"", {" x "}); + checkSplit("\"\tx \"", {"\tx "}); + checkSplit("x,y", {"x", "y"}); + checkSplit("x ,\ty ", {"x", "y"}); + checkSplit("x, y, z", {"x","y","z"}); + checkSplit("x, \"y\", z", {"x","y","z"}); + checkSplit(",,x,,\"y\",,", {"x","y"}); + } + + void + checkIter(std::string const& s, + std::vector const& expected) + { + std::vector got; + for(auto const& v : make_list(s)) + got.emplace_back(v); + expect(got == expected); + } + + void + testIter() + { + checkIter("x", {"x"}); + checkIter(" x", {"x"}); + checkIter("x\t", {"x"}); + checkIter("\tx ", {"x"}); + checkIter(",x", {"x"}); + checkIter("x,", {"x"}); + checkIter(",x,", {"x"}); + checkIter(" , x\t,\t", {"x"}); + checkIter("x,y", {"x", "y"}); + checkIter("x, ,y ", {"x", "y"}); + checkIter("\"x\"", {"x"}); + } + + void + testList() + { + expect(token_in_list("x", "x")); + expect(token_in_list("x,y", "x")); + expect(token_in_list("x,y", "y")); + expect(token_in_list("x, y ", "y")); + expect(token_in_list("x", "X")); + expect(token_in_list("Y", "y")); + expect(token_in_list("close, keepalive", "close")); + expect(token_in_list("close, keepalive", "keepalive")); + } + + void + run() + { + testSplit(); + testIter(); + testList(); + } +}; + +BEAST_DEFINE_TESTSUITE(rfc2616,http,beast); + +} // test +} // rfc2616 +} // beast diff --git a/beast/http/streambuf_body.h b/beast/http/streambuf_body.h new file mode 100644 index 0000000000..e89a48a1d7 --- /dev/null +++ b/beast/http/streambuf_body.h @@ -0,0 +1,109 @@ +//------------------------------------------------------------------------------ +/* + 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_STREAMBUF_BODY_H_INCLUDED +#define BEAST_HTTP_STREAMBUF_BODY_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A Body represented by a Streambuf +*/ +template +struct basic_streambuf_body +{ + using value_type = Streambuf; + + class reader + { + value_type& sb_; + + public: + template + explicit + reader(message& m) noexcept + : sb_(m.body) + { + } + + void + write(void const* data, + std::size_t size, error_code&) noexcept + { + using boost::asio::buffer; + using boost::asio::buffer_copy; + sb_.commit(buffer_copy( + sb_.prepare(size), buffer(data, size))); + } + }; + + class writer + { + Streambuf const& body_; + + public: + template + explicit + writer(message const& m) + : body_(m.body) + { + } + + void + init(error_code& ec) + { + } + + std::size_t + content_length() const + { + return body_.size(); + } + + template + boost::tribool + operator()(resume_context&&, error_code&, Write&& write) + { + write(body_.data()); + return true; + } + + auto + data() const noexcept + { + return body_.data(); + } + }; +}; + +using streambuf_body = basic_streambuf_body; + +} // http +} // beast + +#endif diff --git a/beast/http/string_body.h b/beast/http/string_body.h new file mode 100644 index 0000000000..c00326ab7d --- /dev/null +++ b/beast/http/string_body.h @@ -0,0 +1,99 @@ +//------------------------------------------------------------------------------ +/* + 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_STRING_BODY_H_INCLUDED +#define BEAST_HTTP_STRING_BODY_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** A Body represented by a std::string. +*/ +struct string_body +{ + using value_type = std::string; + + class reader + { + value_type& s_; + + public: + template + explicit + reader(message& m) noexcept + : s_(m.body) + { + } + + void + write(void const* data, + std::size_t size, error_code&) noexcept + { + auto const n = s_.size(); + s_.resize(n + size); + std::memcpy(&s_[n], data, size); + } + }; + + class writer + { + value_type const& body_; + + public: + template + explicit + writer(message const& msg) + : body_(msg.body) + { + } + + void + init(error_code& ec) + { + } + + std::size_t + content_length() const + { + return body_.size(); + } + + template + boost::tribool + operator()(resume_context&&, error_code&, Write&& write) + { + write(boost::asio::buffer(body_)); + return true; + } + }; +}; + +} // http +} // beast + +#endif diff --git a/beast/http/tests/URL.test.cpp b/beast/http/tests/URL.test.cpp deleted file mode 100644 index af4687a847..0000000000 --- a/beast/http/tests/URL.test.cpp +++ /dev/null @@ -1,64 +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 { - -class URL_test : public unit_test::suite -{ -public: - void check_url_parsing (std::string const& url, bool expected) - { - auto result = parse_URL (url); - - expect (result.first == expected, - (expected ? "Failed to parse " : "Succeeded in parsing ") + url); - expect (to_string (result.second) == url); - } - - void test_url_parsing () - { - char const* const urls[] = - { - "http://en.wikipedia.org/wiki/URI#Examples_of_URI_references", - "ftp://ftp.funet.fi/pub/standards/RFC/rfc959.txt" - "ftp://test:test@example.com:21/path/specifier/is/here" - "http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/reference.html", - "foo://username:password@example.com:8042/over/there/index.dtb?type=animal&name=narwhal#nose", - }; - - testcase ("URL parsing"); - - for (auto url : urls) - check_url_parsing (url, true); - } - - void - run () - { - test_url_parsing (); - } -}; - -BEAST_DEFINE_TESTSUITE(URL,http,beast); - -} diff --git a/beast/http/tests/rfc2616.test.cpp b/beast/http/tests/rfc2616.test.cpp deleted file mode 100644 index fd6c01c507..0000000000 --- a/beast/http/tests/rfc2616.test.cpp +++ /dev/null @@ -1,86 +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. -*/ -//============================================================================== - -#if BEAST_INCLUDE_BEASTCONFIG -#include "../../BeastConfig.h" -#endif - -#include -#include -#include -#include - -namespace beast { -namespace rfc2616 { - -class rfc2616_test : public beast::unit_test::suite -{ -public: - void - check (std::string const& s, - std::vector const& expected) - { - auto const parsed = split_commas(s.begin(), s.end()); - expect (parsed == expected); - } - - void test_split_commas() - { - testcase("split_commas"); - check ("", {}); - check (" ", {}); - check (" ", {}); - check ("\t", {}); - check (" \t ", {}); - check (",", {}); - check (",,", {}); - check (" ,", {}); - check (" , ,", {}); - check ("x", {"x"}); - check (" x", {"x"}); - check (" \t x", {"x"}); - check ("x ", {"x"}); - check ("x \t", {"x"}); - check (" \t x \t ", {"x"}); - check ("\"\"", {}); - check (" \"\"", {}); - check ("\"\" ", {}); - check ("\"x\"", {"x"}); - check ("\" \"", {" "}); - check ("\" x\"", {" x"}); - check ("\"x \"", {"x "}); - check ("\" x \"", {" x "}); - check ("\"\tx \"", {"\tx "}); - check ("x,y", { "x", "y" }); - check ("x ,\ty ", { "x", "y" }); - check ("x, y, z", {"x","y","z"}); - check ("x, \"y\", z", {"x","y","z"}); - } - - void - run() - { - test_split_commas(); - } -}; - -BEAST_DEFINE_TESTSUITE(rfc2616,http,beast); - -} -} diff --git a/beast/http/type_check.h b/beast/http/type_check.h new file mode 100644 index 0000000000..8bde8001b0 --- /dev/null +++ b/beast/http/type_check.h @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------------ +/* + 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_TYPE_CHECK_H_INCLUDED +#define BEAST_HTTP_TYPE_CHECK_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include // + +namespace beast { +namespace http { + +#if GENERATING_DOCS +namespace detail { +#else +namespace concept { +#endif + +struct Reader +{ + template + Reader(message&) noexcept; + void write(void const*, std::size_t, error_code&) noexcept; +}; + +} // concept + +/// Evaluates to std::true_type if `T` models Body +template +struct is_Body : std::true_type +{ +}; + +/// Evalulates to std::true_type if Body has a reader +template +struct is_ReadableBody : std::true_type +{ +}; + +/// Evalulates to std::true_type if Body has a writer +template +struct is_WritableBody : std::true_type +{ +}; + +/// Evaluates to std::true_type if `T` models HTTPMessage +template +struct is_HTTPMessage : std::false_type +{ +}; + +/// Evaluates to std::true_type if `HTTPMessage` is a request +template +struct is_HTTPRequest : std::true_type +{ +}; + +} // http +} // beast + +#endif diff --git a/beast/http/write.h b/beast/http/write.h new file mode 100644 index 0000000000..bafa2ec655 --- /dev/null +++ b/beast/http/write.h @@ -0,0 +1,103 @@ +//------------------------------------------------------------------------------ +/* + 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_WRITE_H_INCLUDED +#define BEAST_HTTP_WRITE_H_INCLUDED + +#include +#include +#include +#include + +namespace beast { +namespace http { + +/** Write a HTTP message to a stream. + + @param stream The stream to send the message on. + + @param msg The message to send. + + @throws boost::system::error code on failure. +*/ +template +void +write(SyncWriteStream& stream, + message const& msg) +{ + error_code ec; + write(stream, msg, ec); + if(ec) + throw boost::system::system_error{ec}; +} + +/** Write a HTTP message to a stream. + + @param stream The stream to send the message on. + + @param msg The message to send. + + @param ec Set to the error, if any occurred. +*/ +template +void +write(SyncWriteStream& stream, + message const& msg, + error_code& ec); + +/** Start writing a HTTP message to a stream asynchronously. + + @param stream The stream to send the message on. + + @param msg The message to send. + + @param token The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + + @note The message must remain valid at least until the + completion handler is called, no copies are made. +*/ +template +#if GENERATING_DOCS +void_or_deduced +#else +auto +#endif +async_write(AsyncWriteStream& stream, + message const& msg, + CompletionToken&& token); + +} // http +} // beast + +#include + +#endif diff --git a/beast/unity/beast_http_unity.cpp b/beast/unity/beast_http_unity.cpp new file mode 100644 index 0000000000..8e8684a258 --- /dev/null +++ b/beast/unity/beast_http_unity.cpp @@ -0,0 +1,27 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include +#include + +// VFALCO Must come last otherwise Windows 10 SDK +// gets a compile error in winnt.h +#include diff --git a/examples/Jamfile b/examples/Jamfile new file mode 100644 index 0000000000..327a8182ce --- /dev/null +++ b/examples/Jamfile @@ -0,0 +1,19 @@ +# +# Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +import os ; + +exe http_crawl : + ../beast/http/src/beast_http_nodejs_parser.cpp + http_crawl.cpp + urls_large_data.cpp + ; + +exe http_server : + ../beast/http/src/beast_http_nodejs_parser.cpp + http_server.cpp + ; diff --git a/examples/file_body.h b/examples/file_body.h new file mode 100644 index 0000000000..34fa46e00a --- /dev/null +++ b/examples/file_body.h @@ -0,0 +1,94 @@ +//------------------------------------------------------------------------------ +/* + 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_EXAMPLE_FILE_BODY_H_INCLUDED +#define BEAST_EXAMPLE_FILE_BODY_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +struct file_body +{ + using value_type = std::string; + + class writer + { + std::size_t size_; + std::size_t offset_ = 0; + std::string const& path_; + FILE* file_ = nullptr; + char buf_[4096]; + std::size_t buf_len_; + + public: + static bool constexpr is_single_pass = false; + + template + writer(message const& m) noexcept + : path_(m.body) + { + } + + ~writer() + { + if(file_) + fclose(file_); + } + + void + init(error_code& ec) noexcept + { + file_ = fopen(path_.c_str(), "rb"); + if(! file_) + ec = boost::system::errc::make_error_code( + static_cast(errno)); + else + size_ = boost::filesystem::file_size(path_); + } + + auto + content_length() const + { + return size_; + } + + template + boost::tribool + operator()(resume_context&&, error_code&, Write&& write) + { + buf_len_ = std::min(size_ - offset_, sizeof(buf_)); + fread(buf_, 1, sizeof(buf_), file_); + offset_ += buf_len_; + write(boost::asio::buffer(buf_, buf_len_)); + return offset_ >= size_; + } + }; +}; + +} // http +} // beast + +#endif diff --git a/examples/http_async_server.h b/examples/http_async_server.h new file mode 100644 index 0000000000..8bc6ebf2b2 --- /dev/null +++ b/examples/http_async_server.h @@ -0,0 +1,205 @@ +//------------------------------------------------------------------------------ +/* + 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_EXAMPLE_HTTP_ASYNC_SERVER_H_INCLUDED +#define BEAST_EXAMPLE_HTTP_ASYNC_SERVER_H_INCLUDED + +#include "file_body.h" +#include "http_stream.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +class http_async_server +{ + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + + using req_type = request; + using resp_type = response; + + boost::asio::io_service ios_; + socket_type sock_; + boost::asio::ip::tcp::acceptor acceptor_; + std::string root_; + std::vector thread_; + +public: + http_async_server(endpoint_type const& ep, + int threads, std::string const& root) + : sock_(ios_) + , acceptor_(ios_) + , root_(root) + { + acceptor_.open(ep.protocol()); + acceptor_.bind(ep); + acceptor_.listen( + boost::asio::socket_base::max_connections); + acceptor_.async_accept(sock_, + std::bind(&http_async_server::on_accept, this, + beast::asio::placeholders::error)); + thread_.reserve(threads); + for(int i = 0; i < threads; ++i) + thread_.emplace_back( + [&] { ios_.run(); }); + } + + ~http_async_server() + { + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + for(auto& t : thread_) + t.join(); + } + +private: + class peer : public std::enable_shared_from_this + { + int id_; + stream stream_; + boost::asio::io_service::strand strand_; + std::string root_; + req_type req_; + + public: + peer(peer&&) = default; + peer(peer const&) = default; + peer& operator=(peer&&) = delete; + peer& operator=(peer const&) = delete; + + explicit + peer(socket_type&& sock, std::string const& root) + : id_([] + { + static int n = 0; + return ++n; + }()) + , stream_(std::move(sock)) + , strand_(stream_.get_io_service()) + , root_(root) + { + } + + void run() + { + do_read(); + } + + void do_read() + { + stream_.async_read(req_, strand_.wrap( + std::bind(&peer::on_read, shared_from_this(), + asio::placeholders::error))); + } + + void on_read(error_code ec) + { + if(ec) + return fail(ec, "read"); + do_read(); + auto path = req_.url; + if(path == "/") + path = "/index.html"; + path = root_ + path; + if(! boost::filesystem::exists(path)) + { + response resp( + {404, "Not Found", req_.version}); + resp.headers.replace("Server", "http_async_server"); + resp.body = "The file '" + path + "' was not found"; + stream_.async_write(std::move(resp), + std::bind(&peer::on_write, shared_from_this(), + asio::placeholders::error)); + return; + } + response resp( + {200, "OK", req_.version}); + resp.headers.replace("Server", "http_async_server"); + resp.headers.replace("Content-Type", "text/html"); + resp.body = path; + stream_.async_write(std::move(resp), + std::bind(&peer::on_write, shared_from_this(), + asio::placeholders::error)); + } + + void on_write(error_code ec) + { + if(ec) + fail(ec, "write"); + } + + private: + void + fail(error_code ec, std::string what) + { + if(ec != boost::asio::error::operation_aborted) + { + std::cerr << + "#" << std::to_string(id_) << " " << + what << ": " << ec.message() << std::endl; + } + } + }; + + void + fail(error_code ec, std::string what) + { + std::cerr << + what << ": " << ec.message() << std::endl; + } + + void + maybe_throw(error_code ec, std::string what) + { + if(ec) + { + fail(ec, what); + throw ec; + } + } + + void + on_accept(error_code ec) + { + if(! acceptor_.is_open()) + return; + maybe_throw(ec, "accept"); + socket_type sock(std::move(sock_)); + acceptor_.async_accept(sock_, + std::bind(&http_async_server::on_accept, this, + asio::placeholders::error)); + std::make_shared(std::move(sock), root_)->run(); + } +}; + +} // http +} // beast + +#endif diff --git a/examples/http_crawl.cpp b/examples/http_crawl.cpp new file mode 100644 index 0000000000..dd4779a1a4 --- /dev/null +++ b/examples/http_crawl.cpp @@ -0,0 +1,67 @@ +//------------------------------------------------------------------------------ +/* + 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 "http_stream.h" +#include "urls_large_data.h" + +#include +#include + +using namespace beast::http; +using namespace boost::asio; + +template +void +err(error_code const& ec, String const& what) +{ + std::cerr << what << ": " << ec.message() << std::endl; +} + +int main(int, char const*[]) +{ + io_service ios; + for(auto const& host : urls_large_data()) + { + try + { + ip::tcp::resolver r(ios); + auto it = r.resolve( + ip::tcp::resolver::query{host, "http"}); + stream hs(ios); + connect(hs.lowest_layer(), it); + auto ep = hs.lowest_layer().remote_endpoint(); + request req({method_t::http_get, "/", 11}); + req.headers.insert("Host", host + + std::string(":") + std::to_string(ep.port())); + req.headers.insert("User-Agent", "beast/http"); + hs.write(req); + response resp; + hs.read(resp); + std::cout << resp; + } + catch(boost::system::system_error const& ec) + { + std::cerr << host << ": " << ec.what(); + } + catch(...) + { + std::cerr << host << ": unknown exception" << std::endl; + } + } +} diff --git a/examples/http_server.cpp b/examples/http_server.cpp new file mode 100644 index 0000000000..8e310239f6 --- /dev/null +++ b/examples/http_server.cpp @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +/* + 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 "http_async_server.h" +#include "http_sync_server.h" +#include "sig_wait.h" + +#include + +#include + +int main(int ac, char const* av[]) +{ + using namespace beast::http; + namespace po = boost::program_options; + po::options_description desc("Options"); + + desc.add_options() + ("root,r", po::value()->implicit_value("."), + "Set the root directory for serving files") + ("port,p", po::value()->implicit_value(8080), + "Set the port number for the server") + ("ip", po::value()->implicit_value("0.0.0.0"), + "Set the IP address to bind to, \"0.0.0.0\" for all") + ("threads,n", po::value()->implicit_value(4), + "Set the number of threads to use") + ("sync,s", "Launch a synchronous server") + ; + po::variables_map vm; + po::store(po::parse_command_line(ac, av, desc), vm); + + std::string root = "."; + if(vm.count("root")) + root = vm["root"].as(); + + std::uint16_t port = 8080; + if(vm.count("port")) + port = vm["port"].as(); + + std::string ip = "0.0.0.0"; + if(vm.count("ip")) + ip = vm["ip"].as(); + + std::size_t threads = 4; + if(vm.count("threads")) + threads = vm["threads"].as(); + + bool sync = vm.count("sync") > 0; + + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + + endpoint_type ep{address_type::from_string(ip), port}; + + if(sync) + { + http_sync_server server(ep, root); + sig_wait(); + } + else + { + http_async_server server(ep, threads, root); + sig_wait(); + } +} diff --git a/examples/http_server_response.h b/examples/http_server_response.h new file mode 100644 index 0000000000..54221aa159 --- /dev/null +++ b/examples/http_server_response.h @@ -0,0 +1,31 @@ +//------------------------------------------------------------------------------ +/* + 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_EXAMPLE_SERVER_RESPONSE_H_INCLUDED +#define BEAST_HTTP_EXAMPLE_SERVER_RESPONSE_H_INCLUDED + +namespace beast { +namespace http { +namespace example { + +} // example +} // http +} // beast + +#endif diff --git a/examples/http_stream.h b/examples/http_stream.h new file mode 100644 index 0000000000..9b7a7e1790 --- /dev/null +++ b/examples/http_stream.h @@ -0,0 +1,488 @@ +//------------------------------------------------------------------------------ +/* + 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_STREAM_H_INCLUDED +#define BEAST_HTTP_STREAM_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +namespace detail { + +class stream_base +{ +protected: + struct op + : boost::intrusive::list_base_hook< + boost::intrusive::link_mode< + boost::intrusive::normal_link>> + { + virtual ~op() = default; + virtual void operator()() = 0; + virtual void cancel() = 0; + }; + + using op_list = typename boost::intrusive::make_list< + op, boost::intrusive::constant_time_size>::type; + + op_list wr_q_; + bool wr_active_ = false; +}; + +} // detail + +/** Provides message-oriented functionality using HTTP. + + The stream class template provides asynchronous and blocking + message-oriented functionality necessary for clients and servers + to utilize the HTTP protocol. + + @par Thread Safety + @e Distinct @e objects: Safe.@n + @e Shared @e objects: Unsafe. The application must ensure that + all asynchronous operations are performed within the same + implicit or explicit strand. + + @par Example + + To use the class template with an `ip::tcp::socket`, you would write: + + @code + http::stream hs(io_service); + @endcode + Alternatively, you can write: + @code + ip::tcp::socket sock(io_service); + http::stream hs(sock); + @endcode + + @note A stream object must not be destroyed while there are + pending asynchronous operations associated with it. + + @par Concepts + AsyncReadStream, AsyncWriteStream, Stream, SyncReadStream, SyncWriteStream. + */ +template> +class stream : public detail::stream_base +{ + NextLayer next_layer_; + basic_streambuf rd_buf_; + +public: + /// The type of the next layer. + using next_layer_type = + std::remove_reference_t; + + /// The type of the lowest layer. + using lowest_layer_type = + typename next_layer_type::lowest_layer_type; + + /// The type of endpoint of the lowest layer. + using endpoint_type = + typename lowest_layer_type::endpoint_type; + + /// The protocol of the next layer. + using protocol_type = + typename lowest_layer_type::protocol_type; + + /// The type of resolver of the next layer. + using resolver_type = + typename protocol_type::resolver; + + /** Destructor. + + @note A stream object must not be destroyed while there + are pending asynchronous operations associated with it. + */ + ~stream(); + + /** Move constructor. + + Undefined behavior if operations are active or pending. + */ + stream(stream&&) = default; + + /** Move assignment. + + Undefined behavior if operations are active or pending. + */ + stream& operator=(stream&&) = default; + + /** Construct a HTTP stream. + + This constructor creates a HTTP stream and initialises + the next layer. + + @throws Any exceptions thrown by the Stream constructor. + + @param args The arguments to be passed to initialise the + next layer. The arguments are forwarded to the next layer's + constructor. + */ + template + explicit + stream(Args&&... args); + + /** Get the io_service associated with the stream. + + This function may be used to obtain the io_service object + that the stream uses to dispatch handlers for asynchronous + operations. + + @return A reference to the io_service object that the stream + will use to dispatch handlers. Ownership is not transferred + to the caller. + */ + boost::asio::io_service& + get_io_service() + { + return next_layer_.lowest_layer().get_io_service(); + } + + /** Get a reference to the next layer. + + This function returns a reference to the next layer + in a stack of stream layers. + + @return A reference to the next layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + next_layer_type& + next_layer() + { + return next_layer_; + } + + /** Get a reference to the next layer. + + This function returns a reference to the next layer in a + stack of stream layers. + + @return A reference to the next layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + next_layer_type const& + next_layer() const + { + return next_layer_; + } + + /** Get a reference to the lowest layer. + + This function returns a reference to the lowest layer + in a stack of stream layers. + + @return A reference to the lowest layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + lowest_layer_type& + lowest_layer() + { + return next_layer_.lowest_layer(); + } + + /** Get a reference to the lowest layer. + + This function returns a reference to the lowest layer + in a stack of stream layers. + + @return A reference to the lowest layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + lowest_layer_type const& + lowest_layer() const + { + return next_layer_.lowest_layer(); + } + + /** Cancel pending operations. + + This will cancel all of the asynchronous operations pending, + including pipelined writes that have not been started. Handlers for + canceled writes will be called with + `boost::asio::error::operation_aborted`. + + @throws boost::system::system_error Thrown on failure. + */ + void + cancel() + { + error_code ec; + cancel(ec); + if(ec) + throw boost::system::system_error{ec}; + } + + /** Cancel pending operations. + + This will cancel all of the asynchronous operations pending, + including pipelined writes that have not been started. Handlers for + canceled writes will be called with + `boost::asio::error::operation_aborted`. + + @param ec Set to indicate what error occurred, if any. + */ + void + cancel(error_code& ec); + + /** Read a HTTP message from the stream. + + This function is used to read a single HTTP message from the stream. + The call will block until one of the followign conditions is true: + + @li A message has been read. + + @li An error occurred. + + The operation is implemented in terms of zero or more calls to the + next layer's `read_some` function. + + @param msg An object used to store the message. The previous + contents of the object will be overwritten. + + @throws boost::system::system_error Thrown on failure. + */ + template + void + read(message& msg) + { + error_code ec; + read(msg, ec); + if(ec) + throw boost::system::system_error{ec}; + } + + /** Read a HTTP message from the stream. + + This function is used to read a single HTTP message from the stream. + The call will block until one of the followign conditions is true: + + @li A message has been read. + + @li An error occurred. + + The operation is implemented in terms of zero or more calls to the + next layer's `read_some` function. + + @param msg An object used to store the message. The previous + contents of the object will be overwritten. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + read(message& msg, + error_code& ec); + + /** Start reading a HTTP message from the stream asynchronously. + + This function is used to asynchronously read a single HTTP message + from the stream. The function call always returns immediately. The + asynchronous operation will continue until one of the following + conditions is true: + + @li The message has been written. + + @li An error occurred. + + This operation is implemented in terms of zero or more calls to the + next layer's async_read_some function, and is known as a composed + operation. The program must ensure that the stream performs no other + read operations or any other composed operations that perform reads + until this operation completes. + + @param msg An object used to store the message. The previous + contents of the object will be overwritten. Ownership of the message + is not transferred; the caller must guarantee that the object remains + valid until the handler is called. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + */ + template + #if GENERATING_DOCS + void_or_deduced + #else + auto + #endif + async_read(message& msg, + ReadHandler&& handler); + + /** Write a HTTP message to the stream. + + This function is used to write a single HTTP message to the + stream. The call will block until one of the following conditions + is true: + + @li The entire message is sent. + + @li An error occurred. + + If the semantics of the message require that the connection is + closed to indicate the end of the content body, + `boost::asio::error::eof` is thrown after the message is sent. + successfuly. The caller is responsible for actually closing the + connection. For regular TCP/IP streams this means shutting down the + send side, while SSL streams may call the SSL shutdown function. + + @param msg The message to send. + + @throws boost::system::system_error Thrown on failure. + */ + template + void + write(message const& msg) + { + error_code ec; + write(msg, ec); + if(ec) + throw boost::system::system_error{ec}; + } + + /** Write a HTTP message to the stream. + + This function is used to write a single HTTP message to the + stream. The call will block until one of the following conditions + is true: + + @li The entire message is sent. + + @li An error occurred. + + If the semantics of the message require that the connection is + closed to indicate the end of the content body, + `boost::asio::error::eof` is returned after the message is sent. + successfuly. The caller is responsible for actually closing the + connection. For regular TCP/IP streams this means shutting down the + send side, while SSL streams may call the SSL shutdown function. + + @param msg The message to send. + + @param ec Set to the error, if any occurred. + */ + template + void + write(message const& msg, + error_code& ec); + + /** Start pipelining a HTTP message to the stream asynchronously. + + This function is used to queue a message to be sent on the stream. + Unlike the free function, this version will place the message on an + outgoing message queue if there is already a write pending. + + If the semantics of the message require that the connection is + closed to indicate the end of the content body, the handler + is called with the error `boost::asio::error::eof` after the message + has been sent successfully. The caller is responsible for actually + closing the connection. For regular TCP/IP streams this means + shutting down the send side, while SSL streams may call the SSL + `async_shutdown` function. + + @param msg The message to send. A copy of the message will be made. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + */ + template + #if GENERATING_DOCS + void_or_deduced + #else + auto + #endif + async_write(message const& msg, + WriteHandler&& handler); + + /** Start pipelining a HTTP message to the stream asynchronously. + + This function is used to queue a message to be sent on the stream. + Unlike the free function, this version will place the message on an + outgoing message queue if there is already a write pending. + + If the semantics of the message require that the connection is + closed to indicate the end of the content body, the handler + is called with the error boost::asio::error::eof. The caller is + responsible for actually closing the connection. For regular + TCP/IP streams this means shutting down the send side, while SSL + streams may call the SSL async_shutdown function. + + @param msg The message to send. Ownership of the message, which + must be movable, is transferred to the implementation. The message + will not be destroyed until the asynchronous operation completes. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from within + this function. Invocation of the handler will be performed in a + manner equivalent to using boost::asio::io_service::post(). + */ + template + #if GENERATING_DOCS + void_or_deduced + #else + auto + #endif + async_write(message&& msg, + WriteHandler&& handler); + +private: + template class read_op; + template class write_op; + + void + cancel_all(); +}; + +} // http +} // beast + +#include "http_stream.ipp" + +#endif diff --git a/examples/http_stream.ipp b/examples/http_stream.ipp new file mode 100644 index 0000000000..3432de8f06 --- /dev/null +++ b/examples/http_stream.ipp @@ -0,0 +1,419 @@ +//------------------------------------------------------------------------------ +/* + 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_STREAM_IPP_INCLUDED +#define BEAST_HTTP_STREAM_IPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace http { + +template +template +class stream::read_op +{ + using alloc_type = + handler_alloc; + + struct data + { + stream& s; + message& m; + Handler h; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, stream& s_, + message& m_) + : s(s_) + , m(m_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + read_op(read_op&&) = default; + read_op(read_op const&) = default; + + template + read_op(DeducedHandler&& h, + stream& s, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), s, + std::forward(args)...)) + { + (*this)(error_code{}, false); + } + + void operator()(error_code const& ec, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, read_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, read_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(read_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, read_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +stream:: +read_op:: +operator()(error_code const& ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + d.state = 99; + beast::http::async_read(d.s.next_layer_, + d.s.rd_buf_, d.m, std::move(*this)); + return; + } + } + d.h(ec); +} + +//------------------------------------------------------------------------------ + +template +template +class stream::write_op : public op +{ + using alloc_type = + handler_alloc; + + struct data + { + stream& s; + message m; + Handler h; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, stream& s_, + message const& m_, + bool cont_) + : s(s_) + , m(m_) + , h(std::forward(h_)) + , cont(cont_) + { + } + + template + data(DeducedHandler&& h_, stream& s_, + message&& m_, + bool cont_) + : s(s_) + , m(std::move(m_)) + , h(std::forward(h_)) + , cont(cont_) + { + } + }; + + std::shared_ptr d_; + +public: + write_op(write_op&&) = default; + write_op(write_op const&) = default; + + template + write_op(DeducedHandler&& h, + stream& s, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), s, + std::forward(args)...)) + { + } + + void + operator()() override + { + (*this)(error_code{}, false); + } + + void cancel() override; + + void operator()(error_code const& ec, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, write_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, write_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(write_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, write_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +stream:: +write_op:: +cancel() +{ + auto& d = *d_; + d.s.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted)); +} + +template +template +void +stream:: +write_op:: +operator()(error_code const& ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + d.state = 99; + beast::http::async_write(d.s.next_layer_, + d.m, std::move(*this)); + return; + } + } + d.h(ec); + if(! d.s.wr_q_.empty()) + { + auto& op = d.s.wr_q_.front(); + op(); + // VFALCO Use allocator + delete &op; + d.s.wr_q_.pop_front(); + } + else + { + d.s.wr_active_ = false; + } +} + +//------------------------------------------------------------------------------ + +template +stream:: +~stream() +{ + // Can't destroy with pending operations! + assert(wr_q_.empty()); +} + +template +template +stream:: +stream(Args&&... args) + : next_layer_(std::forward(args)...) +{ +} + +template +void +stream:: +cancel(error_code& ec) +{ + cancel_all(); + lowest_layer().cancel(ec); +} + +template +template +void +stream:: +read(message& msg, + error_code& ec) +{ + beast::http::read(next_layer_, rd_buf_, msg, ec); +} + +template +template +auto +stream:: +async_read(message& msg, + ReadHandler&& handler) +{ + async_completion< + ReadHandler, void(error_code) + > completion(handler); + read_op{ + completion.handler, *this, msg}; + return completion.result.get(); +} + +template +template +void +stream:: +write(message const& msg, + error_code& ec) +{ + beast::http::write(next_layer_, msg, ec); +} + +template +template +auto +stream:: +async_write(message const& msg, + WriteHandler&& handler) +{ + async_completion< + WriteHandler, void(error_code)> completion(handler); + auto const cont = wr_active_ || + boost_asio_handler_cont_helpers::is_continuation(handler); + if(! wr_active_) + { + wr_active_ = true; + write_op{ + completion.handler, *this, msg, cont }(); + } + else + { + // VFALCO Use allocator + wr_q_.push_back(*new write_op( + completion.handler, *this, msg, cont)); + } + return completion.result.get(); +} + +template +template +auto +stream:: +async_write(message&& msg, + WriteHandler&& handler) +{ + async_completion< + WriteHandler, void(error_code)> completion(handler); + auto const cont = wr_active_ || + boost_asio_handler_cont_helpers::is_continuation(handler); + if(! wr_active_) + { + wr_active_ = true; + write_op{completion.handler, + *this, std::move(msg), cont}(); + } + else + { + // VFALCO Use allocator + wr_q_.push_back(*new write_op(completion.handler, + *this, std::move(msg), cont)); + } + return completion.result.get(); +} + +template +void +stream:: +cancel_all() +{ + for(auto it = wr_q_.begin(); it != wr_q_.end();) + { + auto& op = *it++; + op.cancel(); + // VFALCO Use allocator + delete &op; + } + wr_q_.clear(); +} + +} // http +} // beast + +#endif diff --git a/examples/http_sync_server.h b/examples/http_sync_server.h new file mode 100644 index 0000000000..610e1464de --- /dev/null +++ b/examples/http_sync_server.h @@ -0,0 +1,170 @@ +//------------------------------------------------------------------------------ +/* + 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_EXAMPLE_HTTP_SYNC_SERVER_H_INCLUDED +#define BEAST_EXAMPLE_HTTP_SYNC_SERVER_H_INCLUDED + +#include "file_body.h" +#include "http_stream.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace beast { +namespace http { + +class http_sync_server +{ + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + + using req_type = request; + using resp_type = response; + + boost::asio::io_service ios_; + socket_type sock_; + boost::asio::ip::tcp::acceptor acceptor_; + std::string root_; + std::thread thread_; + +public: + http_sync_server(endpoint_type const& ep, + std::string const& root) + : sock_(ios_) + , acceptor_(ios_) + , root_(root) + { + acceptor_.open(ep.protocol()); + acceptor_.bind(ep); + acceptor_.listen( + boost::asio::socket_base::max_connections); + acceptor_.async_accept(sock_, + std::bind(&http_sync_server::on_accept, this, + beast::asio::placeholders::error)); + thread_ = std::thread{[&]{ ios_.run(); }}; + } + + ~http_sync_server() + { + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + thread_.join(); + } + + void + fail(error_code ec, std::string what) + { + std::cerr << + what << ": " << ec.message() << std::endl; + } + + void + maybe_throw(error_code ec, std::string what) + { + if(ec) + { + fail(ec, what); + throw ec; + } + } + + void + on_accept(error_code ec) + { + if(! acceptor_.is_open()) + return; + maybe_throw(ec, "accept"); + static int id_ = 0; + std::thread{ + [ + id = ++id_, + this, + sock = std::move(sock_), + work = boost::asio::io_service::work{ios_} + ]() mutable + { + do_peer(id, std::move(sock)); + }}.detach(); + acceptor_.async_accept(sock_, + std::bind(&http_sync_server::on_accept, this, + asio::placeholders::error)); + } + + void + fail(int id, error_code const& ec) + { + if(ec != boost::asio::error::operation_aborted && + ec != boost::asio::error::eof) + std::cerr << + "#" << std::to_string(id) << " " << std::endl; + } + + void + do_peer(int id, socket_type&& sock) + { + http::stream hs(std::move(sock)); + error_code ec; + for(;;) + { + req_type req; + hs.read(req, ec); + if(ec) + break; + auto path = req.url; + if(path == "/") + path = "/index.html"; + path = root_ + path; + if(! boost::filesystem::exists(path)) + { + response resp( + {404, "Not Found", req.version}); + resp.headers.replace("Server", "http_sync_server"); + resp.body = "The file '" + path + "' was not found"; + hs.write(resp, ec); + if(ec) + break; + } + response resp( + {200, "OK", req.version}); + resp.headers.replace("Server", "http_sync_server"); + resp.headers.replace("Content-Type", "text/html"); + resp.body = path; + hs.write(resp, ec); + if(ec) + break; + } + fail(id, ec); + } +}; + +} // http +} // beast + +#endif diff --git a/examples/sig_wait.h b/examples/sig_wait.h new file mode 100644 index 0000000000..f2bd7b55d8 --- /dev/null +++ b/examples/sig_wait.h @@ -0,0 +1,49 @@ +//------------------------------------------------------------------------------ +/* + 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_EXAMPLE_SIG_WAIT_H_INCLUDED +#define BEAST_EXAMPLE_SIG_WAIT_H_INCLUDED + +#include +#include +#include + +// Block until SIGINT or SIGTERM +inline +void +sig_wait() +{ + boost::asio::io_service ios; + boost::asio::signal_set signals( + ios, SIGINT, SIGTERM); + std::mutex m; + bool stop = false; + std::condition_variable cv; + signals.async_wait( + [&](boost::system::error_code const&, int) + { + std::lock_guard lock(m); + stop = true; + cv.notify_one(); + }); + std::unique_lock lock(m); + cv.wait(lock, [&]{ return stop; }); +} + +#endif diff --git a/beast/http/tests/urls_large_data.cpp b/examples/urls_large_data.cpp similarity index 99% rename from beast/http/tests/urls_large_data.cpp rename to examples/urls_large_data.cpp index 60eb0251cf..782d0ca243 100644 --- a/beast/http/tests/urls_large_data.cpp +++ b/examples/urls_large_data.cpp @@ -17,15 +17,12 @@ */ //============================================================================== -#include - -namespace beast { -namespace http { +#include "urls_large_data.h" // Data from Alexa top 1 million sites // http://s3.amazonaws.com/alexa-static/top-1m.csv.zip // -std::vector const& +std::vector const& urls_large_data() { static std::vector const urls ({ @@ -10032,7 +10029,3 @@ urls_large_data() return urls; } - -} -} - diff --git a/beast/http/tests/urls_large_data.h b/examples/urls_large_data.h similarity index 86% rename from beast/http/tests/urls_large_data.h rename to examples/urls_large_data.h index 34a0c09e8e..5918d7c03b 100644 --- a/beast/http/tests/urls_large_data.h +++ b/examples/urls_large_data.h @@ -17,18 +17,12 @@ */ //============================================================================== -#ifndef BEAST_HTTP_URLS_LARGE_DATA_H_INCLUDED -#define BEAST_HTTP_URLS_LARGE_DATA_H_INCLUDED +#ifndef URLS_LARGE_DATA_H_INCLUDED +#define URLS_LARGE_DATA_H_INCLUDED #include -namespace beast { -namespace http { - -std::vector const& +std::vector const& urls_large_data(); -} -} - #endif diff --git a/test/asio/Jamfile b/test/asio/Jamfile index 751b853806..57ec7cec8e 100644 --- a/test/asio/Jamfile +++ b/test/asio/Jamfile @@ -14,6 +14,7 @@ unit-test all : asio.cpp async_completion.cpp basic_streambuf.cpp + ../basic_headers.cpp bind_handler.cpp buffers_adapter.cpp buffers_debug.cpp diff --git a/test/basic_headers.cpp b/test/basic_headers.cpp new file mode 100644 index 0000000000..ece03c32af --- /dev/null +++ b/test/basic_headers.cpp @@ -0,0 +1,63 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include + +namespace beast { +namespace http { +namespace test { + +class basic_headers_test : public unit_test::suite +{ +public: + template + using bha = basic_headers; + + using bh = basic_headers>; + + template + static + void + fill(std::size_t n, basic_headers& h) + { + for(std::size_t i = 1; i<= n; ++i) + h.insert(std::to_string(i), i); + } + + void testHeaders() + { + bh h1; + expect(h1.empty()); + fill(1, h1); + expect(h1.size() == 1); + bh h2; + h2 = h1; + expect(h2.size() == 1); + h2.insert("2", "2"); + expect(std::distance(h2.begin(), h2.end()) == 2); + h1 = std::move(h2); + expect(h1.size() == 2); + expect(h2.size() == 0); + bh h3(std::move(h1)); + expect(h3.size() == 2); + expect(h1.size() == 0); + } + + void run() override + { + testHeaders(); + } +}; + +BEAST_DEFINE_TESTSUITE(basic_headers,http,beast); + +} // test +} // asio +} // beast