Created
June 7, 2014 15:18
-
-
Save alibitek/8912e0005355e47fdc25 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #pragma once | |
| #ifndef HTTP_CLIENT_HPP | |
| #define HTTP_CLIENT_HPP | |
| #include <string> | |
| #include <map> | |
| #include <algorithm> | |
| #include <boost/range.hpp> | |
| #include <boost/range/functions.hpp> | |
| #include <boost/range/numeric.hpp> | |
| #include <boost/range/adaptors.hpp> | |
| #include <boost/range/join.hpp> | |
| #include <boost/asio.hpp> | |
| #include <boost/asio/ssl.hpp> | |
| #include <boost/lexical_cast.hpp> | |
| #include <boost/spirit/include/phoenix.hpp> | |
| //#include "util.hpp" | |
| //#include "http_connection.hpp" | |
| #include "asio_range_util.hpp" | |
| #include "http_response.hpp" | |
| namespace httpc | |
| { | |
| //------------------------------------------------------------------------------------------------- | |
| using std::string; | |
| namespace asio = boost::asio; | |
| namespace ip = boost::asio::ip; | |
| using boost::asio::ip::tcp; | |
| namespace ssl = boost::asio::ssl; | |
| //------------------------------------------------------------------------------------------------- | |
| namespace detail | |
| { | |
| //------------------------------------------------------------------------------------------------- | |
| template <typename Con> | |
| inline string get_schema_string(); | |
| //------------------------------------------------------------------------------------------------- | |
| template <> | |
| inline string get_schema_string<tcp_connection>() { return "http"; } | |
| //------------------------------------------------------------------------------------------------- | |
| template <> | |
| inline string get_schema_string<ssl_connection>() { return "https"; } | |
| //------------------------------------------------------------------------------------------------- | |
| struct gen_single_header : std::unary_function<const std::pair<string, string> &, string> | |
| { | |
| string operator()(const std::pair<string, string> &p) const | |
| { | |
| return p.first + ": " + p.second + "\r\n"; | |
| } | |
| }; | |
| //------------------------------------------------------------------------------------------------- | |
| template <typename Range> | |
| inline std::vector<BOOST_DEDUCED_TYPENAME boost::range_value<Range>::type> as_vector(const Range &r) | |
| { | |
| return std::vector<BOOST_DEDUCED_TYPENAME boost::range_value<Range>::type>(boost::begin(r), boost::end(r)); | |
| } | |
| //------------------------------------------------------------------------------------------------- | |
| } // end namespace detail | |
| template <typename Connection> | |
| class basic_http_client | |
| { | |
| public: | |
| typedef basic_http_client<Connection> this_type; | |
| typedef Connection connection_type; | |
| typedef BOOST_DEDUCED_TYPENAME Connection::stream_type stream_type; | |
| typedef tcp::resolver resolver_type; | |
| typedef asio::streambuf streambuf_type; | |
| public: | |
| //--------------------------------------------------------------------------------------------- | |
| basic_http_client(stream_type &s, const string &host) | |
| : schema(detail::get_schema_string<Connection>()), | |
| host(host), path(), query(), header(), | |
| con(s, *resolver_type(s.get_io_service()).resolve(resolver_type::query(host, schema))), | |
| st(s), sb() { } | |
| //--------------------------------------------------------------------------------------------- | |
| basic_http_client(stream_type &s, const resolver_type::endpoint_type &host) | |
| : schema(detail::get_schema_string<Connection>()), | |
| host(host), path(), query(), header(), con(s, host), st(s), sb() { } | |
| //--------------------------------------------------------------------------------------------- | |
| string get_uri() const { return schema + "://" + host + path; } | |
| //--------------------------------------------------------------------------------------------- | |
| template <typename HttpResult> | |
| void request(HttpResult &res) { send_request(res, string()); } | |
| //--------------------------------------------------------------------------------------------- | |
| template <typename HttpResult> | |
| void request(HttpResult &res, boost::none_t) { send_request(res, string()); } | |
| //--------------------------------------------------------------------------------------------- | |
| template <typename HttpResult, typename Range> | |
| void request(HttpResult &res, const Range &data) { send_request(res, data); } | |
| public: | |
| string schema; | |
| string host; | |
| string path; | |
| string query; | |
| std::map<string, string> header; | |
| private: | |
| //--------------------------------------------------------------------------------------------- | |
| string make_request(size_t content_length) const | |
| { | |
| const string request = (content_length ? "POST " : "GET ") + get_uri() + (query.empty() ? "" : "?") + query + " HTTP/1.1\r\nHost: " + host + "\r\n"; | |
| if (content_length > 0) return request + "Content-Length: " + boost::lexical_cast<string>(content_length) + "\r\n"; | |
| return request; | |
| } | |
| //--------------------------------------------------------------------------------------------- | |
| template <typename HttpResult> | |
| void header_read_complete(HttpResult &res, const boost::system::error_code &ec, std::size_t) | |
| { | |
| boost::asio::detail::throw_error(ec); | |
| { | |
| const string eoh = "\r\n\r\n"; | |
| namespace phx = boost::phoenix; | |
| sb.consume(res.parse_header(make_partial_range( | |
| make_iterator_range_from_buffer<const char *>(sb.data()), | |
| phx::bind(lazy_range_search_t(), phx::arg_names::_1, eoh) + eoh.length() | |
| ))); | |
| } | |
| const string trn_enc = util::map_get_or(res.header, "Transfer-Encoding", ""); | |
| if (trn_enc == "chunked") | |
| { | |
| asio::async_read_until(st, sb, "\r\n", boost::bind( | |
| &this_type::chunk_size_read_complete<HttpResult>, | |
| this, | |
| res, | |
| asio::placeholders::error, | |
| asio::placeholders::bytes_transferred | |
| )); | |
| } | |
| else | |
| { | |
| plain_body_read_complete( | |
| res, | |
| 0, | |
| boost::lexical_cast<size_t>(util::map_get_or(res.header, "Content-Length", "0")), | |
| ec, | |
| asio::buffer_size(sb.data()) | |
| ); | |
| } | |
| } | |
| //--------------------------------------------------------------------------------------------- | |
| template <typename HttpResult> | |
| void plain_body_read_complete(HttpResult &res, size_t prev_txed, size_t length, const boost::system::error_code &ec, std::size_t txed) | |
| { | |
| boost::asio::detail::throw_error(ec); | |
| if (prev_txed + txed < length) | |
| { | |
| asio::async_read(st, sb, asio::transfer_at_least(1), boost::bind( | |
| &this_type::plain_body_read_complete<HttpResult>, | |
| this, | |
| res, | |
| prev_txed + txed, | |
| length, | |
| asio::placeholders::error, | |
| asio::placeholders::bytes_transferred | |
| )); | |
| } | |
| sb.consume(res.append_data_body(make_iterator_range_from_buffer<const char *>(sb.data()))); | |
| } | |
| //--------------------------------------------------------------------------------------------- | |
| template <typename HttpResult> | |
| void chunk_size_read_complete(HttpResult &res, const boost::system::error_code &ec, std::size_t) | |
| { | |
| boost::asio::detail::throw_error(ec); | |
| size_t chunk_size = 0; | |
| { | |
| const string eol = "\r\n"; | |
| namespace phx = boost::phoenix; | |
| sb.consume(detail::parse_chunk_size(make_partial_range( | |
| make_iterator_range_from_buffer<const char *>(sb.data()), | |
| phx::bind(lazy_range_search_t(), phx::arg_names::_1, eol) + eol.length() | |
| ), chunk_size)); | |
| } | |
| if (chunk_size == 0) return; | |
| const size_t bufsize = asio::buffer_size(sb.data()); | |
| if (chunk_size + 2 > bufsize) | |
| { | |
| asio::async_read(st, sb, asio::transfer_at_least(chunk_size + 2 - bufsize), boost::bind( | |
| &this_type::chunk_body_read_complete<HttpResult>, | |
| this, | |
| res, | |
| chunk_size, | |
| asio::placeholders::error, | |
| asio::placeholders::bytes_transferred | |
| )); | |
| } | |
| else | |
| { | |
| chunk_body_read_complete(res, chunk_size, ec, 0); | |
| } | |
| } | |
| //--------------------------------------------------------------------------------------------- | |
| template <typename HttpResult> | |
| void chunk_body_read_complete(HttpResult &res, size_t chunk_size, const boost::system::error_code &ec, std::size_t) | |
| { | |
| boost::asio::detail::throw_error(ec); | |
| sb.consume(res.append_data_body(make_iterator_range_from_memory(asio::buffer_cast<const char *>(sb.data()), chunk_size)) + 2); | |
| asio::async_read_until(st, sb, "\r\n", boost::bind( | |
| &this_type::chunk_size_read_complete<HttpResult>, | |
| this, | |
| res, | |
| asio::placeholders::error, | |
| asio::placeholders::bytes_transferred | |
| )); | |
| } | |
| //--------------------------------------------------------------------------------------------- | |
| template <typename HttpResult, typename Range> | |
| void send_request(HttpResult &res, const Range &data) | |
| { | |
| asio::write( | |
| st, | |
| asio::buffer(detail::as_vector(boost::join( | |
| boost::accumulate( | |
| header | boost::adaptors::transformed(detail::gen_single_header()), | |
| make_request(data.size()) | |
| ) + "\r\n", | |
| data)))); | |
| query.clear(); | |
| asio::async_read_until(st, sb, "\r\n\r\n", boost::bind( | |
| &this_type::header_read_complete<HttpResult>, | |
| this, | |
| res, | |
| asio::placeholders::error, | |
| asio::placeholders::bytes_transferred | |
| )); | |
| } | |
| //--------------------------------------------------------------------------------------------- | |
| template <typename Buffer, typename Handler> | |
| void async_send_request(Buffer &b, Handler) | |
| { | |
| } | |
| //--------------------------------------------------------------------------------------------- | |
| private: | |
| connection_type con; | |
| stream_type &st; | |
| streambuf_type sb; | |
| }; | |
| //------------------------------------------------------------------------------------------------- | |
| typedef basic_http_client<tcp_connection> http_client; | |
| typedef basic_http_client<ssl_connection> https_client; | |
| //------------------------------------------------------------------------------------------------- | |
| } // end namespace httpc | |
| #endif | |
| #pragma once | |
| #ifndef UTIL_HPP | |
| #define UTIL_HPP | |
| #include <boost/mpl/bool.hpp> | |
| #include <boost/mpl/if.hpp> | |
| #include <boost/range.hpp> | |
| #include <boost/range/numeric.hpp> | |
| namespace util | |
| { | |
| namespace detail | |
| { | |
| //------------------------------------------------------------------------------------------------- | |
| template <typename Range> | |
| struct has_range_buffer: boost::mpl::false_ { }; | |
| //------------------------------------------------------------------------------------------------- | |
| template <typename charT, typename Traits, typename Alloc> | |
| struct has_range_buffer< std::basic_string<charT, Traits, Alloc> >: boost::mpl::true_ { }; | |
| //------------------------------------------------------------------------------------------------- | |
| template <typename T, typename Alloc> | |
| struct has_range_buffer< vector<T, Alloc> >: boost::mpl::true_ { }; | |
| //------------------------------------------------------------------------------------------------- | |
| template <typename T, size_t N> | |
| struct has_range_buffer< T [N] >: boost::mpl::true_ { }; | |
| //------------------------------------------------------------------------------------------------- | |
| template <typename T, size_t N> | |
| struct has_range_buffer< array<T, N> >: boost::mpl::true_ { }; | |
| //------------------------------------------------------------------------------------------------- | |
| } // end namespace detail | |
| //------------------------------------------------------------------------------------------------- | |
| template <typename Range> | |
| struct range_buffer : boost::mpl::if_< detail::has_range_buffer<Range>, Range, | |
| std::vector<typename boost::range_value<Range>::type> > { }; | |
| //------------------------------------------------------------------------------------------------- | |
| template <typename Range> | |
| inline typename boost::enable_if<typename detail::has_range_buffer<Range>::type, | |
| typename boost::range_pointer<Range>::type>::type | |
| get_ptr(Range &r) | |
| { | |
| return &*boost::begin(r); | |
| } | |
| //------------------------------------------------------------------------------------------------- | |
| template <typename Range> | |
| inline typename boost::enable_if<typename detail::has_range_buffer<Range>::type, | |
| typename boost::range_pointer<const Range>::type>::type | |
| get_ptr(const Range &r) | |
| { | |
| return &*boost::begin(r); | |
| } | |
| //------------------------------------------------------------------------------------------------- | |
| template <typename Range> | |
| inline typename boost::enable_if<typename detail::has_range_buffer<Range>::type, Range &>::type | |
| get_buffer(Range &r) | |
| { | |
| return r; | |
| } | |
| //------------------------------------------------------------------------------------------------- | |
| template <typename Range> | |
| inline typename boost::enable_if<typename detail::has_range_buffer<Range>::type, const Range &>::type | |
| get_buffer(const Range &r) | |
| { | |
| return r; | |
| } | |
| //------------------------------------------------------------------------------------------------- | |
| template <typename Range> | |
| inline typename boost::disable_if<typename detail::has_range_buffer<Range>::type, | |
| typename range_buffer<Range>::type>::type | |
| get_buffer(const Range &r) | |
| { | |
| return typename range_buffer<Range>::type(boost::begin(r), boost::end(r)); | |
| } | |
| //------------------------------------------------------------------------------------------------- | |
| struct hexanizer: std::binary_function<const std::string &, char, std::string> | |
| { | |
| hexanizer(const std::string &head): head(head) { } | |
| std::string operator()(const std::string &s, char c) const | |
| { | |
| static const char hex[] = "0123456789ABCDEF"; | |
| return s + head + hex[(c >> 4) & 0xF] + hex[c & 0xF]; | |
| } | |
| std::string head; | |
| }; | |
| //------------------------------------------------------------------------------------------------- | |
| namespace detail | |
| { | |
| //------------------------------------------------------------------------------------------------- | |
| inline bool isalnum(int c) | |
| { | |
| return ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); | |
| } | |
| //------------------------------------------------------------------------------------------------- | |
| struct do_escape : std::binary_function<const std::string &, char, std::string> | |
| { | |
| do_escape() | |
| : hexz("%") | |
| {} | |
| std::string operator()(const string &str, char c) | |
| { | |
| if (isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') | |
| { | |
| return str + c; | |
| } | |
| return hexz(str, c); | |
| } | |
| hexanizer hexz; | |
| }; | |
| //------------------------------------------------------------------------------------------------- | |
| } // end namespace detail | |
| //------------------------------------------------------------------------------------------------- | |
| template <typename Range> | |
| inline std::string url_escape(const Range &s) | |
| { | |
| return boost::accumulate(s, std::string(), detail::do_escape()); | |
| } | |
| //------------------------------------------------------------------------------------------------- | |
| template <typename Range> | |
| struct base64_encoder : std::unary_function<const Range &, std::string> | |
| { | |
| static char base64_convert_single(char c) | |
| { | |
| if (c < 0) return '='; | |
| else if (c < 26) return 'A' + c; | |
| else if (c < 52) return 'a' + c - 26; | |
| else if (c < 62) return '0' + c - 52; | |
| else if (c == 62) return '+'; | |
| else if (c == 63) return '/'; | |
| else return '='; | |
| } | |
| std::string operator()(const Range &src) const | |
| { | |
| typedef typename boost::range_iterator<const Range>::type iterator; | |
| const iterator end = boost::end(src); | |
| iterator ite = boost::begin(src); | |
| std::string ret; | |
| while (ite != end) | |
| { | |
| uint32_t tmp = 0; | |
| int pad = 0; | |
| for (int n = 0; n < 3; ++n) | |
| { | |
| tmp <<= 8; | |
| if (ite != end) tmp |= *ite++; | |
| else ++pad; | |
| } | |
| ret += base64_convert_single((tmp >> 18) & 0x3F); | |
| ret += base64_convert_single((tmp >> 12) & 0x3F); | |
| ret += pad > 1 ? '=' : base64_convert_single((tmp >> 6) & 0x3F); | |
| ret += pad > 0 ? '=' : base64_convert_single((tmp >> 0) & 0x3F); | |
| } | |
| return ret; | |
| } | |
| }; | |
| //------------------------------------------------------------------------------------------------- | |
| template <typename Range> | |
| inline std::string base64_encode(const Range &src) | |
| { | |
| return base64_encoder<Range>()(src); | |
| } | |
| //------------------------------------------------------------------------------------------------- | |
| template <typename Map> | |
| inline const typename Map::mapped_type &map_get_or(const Map &m, | |
| const typename Map::key_type &k, | |
| const typename Map::mapped_type &v) | |
| { | |
| const typename Map::const_iterator ite = m.find(k); | |
| if (ite == m.end()) return v; | |
| return ite->second; | |
| } | |
| //------------------------------------------------------------------------------------------------- | |
| } | |
| #pragma once | |
| #ifndef HTTP_RESPONSE_HPP | |
| #define HTTP_RESPONSE_HPP | |
| #include <map> | |
| #include <string> | |
| #include <algorithm> | |
| #include <exception> | |
| #include <boost/range.hpp> | |
| #include <boost/range/algorithm/copy.hpp> | |
| #include <boost/spirit/include/qi.hpp> | |
| #include <boost/spirit/include/phoenix.hpp> | |
| namespace httpc | |
| { | |
| //------------------------------------------------------------------------------------------------- | |
| struct http_response_header | |
| { | |
| typedef std::string string; | |
| std::pair<int, string> status; | |
| std::map<string, string> header; | |
| //--------------------------------------------------------------------------------------------- | |
| template <typename Range> | |
| size_t parse_header(const Range &r) | |
| { | |
| namespace phx = boost::phoenix; | |
| namespace qi = boost::spirit::qi; | |
| typedef BOOST_DEDUCED_TYPENAME boost::range_iterator<const Range>::type iterator; | |
| iterator ite = boost::begin(r); | |
| const iterator end = boost::end(r); | |
| typedef std::pair<string, string> pair_type; | |
| const qi::rule<iterator, pair_type()> header_line((qi::as_string[+(qi::char_ - '\r' - ':')] > ':' > *qi::lit(' ') > qi::as_string[+(qi::char_ - '\r')] > "\r\n")[qi::_val = phx::construct<pair_type>(qi::_1, qi::_2)]); | |
| qi::parse(ite, end, ("HTTP/" > qi::digit > '.' > qi::digit > ' ' > qi::int_[phx::ref(status.first) = qi::_1] > ' ' > qi::as_string[+(qi::char_ - '\r')][phx::ref(status.second) = qi::_1] > "\r\n")); | |
| qi::parse(ite, end, *(header_line)[phx::insert(phx::ref(header), qi::_1)] > "\r\n"); | |
| if (ite != end) throw std::runtime_error("invalid header"); | |
| return boost::size(r); | |
| } | |
| //--------------------------------------------------------------------------------------------- | |
| }; | |
| //------------------------------------------------------------------------------------------------- | |
| struct plain_response : http_response_header | |
| { | |
| template <typename Range> | |
| size_t append_data_body(const Range &r) | |
| { | |
| boost::copy(r, std::ostream_iterator<char>(std::cout)); | |
| std::cout << "\n******" << std::endl; | |
| return boost::size(r); | |
| } | |
| }; | |
| //------------------------------------------------------------------------------------------------- | |
| namespace detail | |
| { | |
| //------------------------------------------------------------------------------------------------- | |
| template <typename Range> | |
| inline size_t parse_chunk_size(const Range &r, size_t &s) | |
| { | |
| namespace phx = boost::phoenix; | |
| namespace qi = boost::spirit::qi; | |
| typedef BOOST_DEDUCED_TYPENAME boost::range_iterator<const Range>::type iterator; | |
| iterator ite = boost::begin(r); | |
| const iterator end = boost::end(r); | |
| qi::parse(ite, end, (qi::hex[phx::ref(s) = qi::_1] >> +(qi::char_ - '\r') > "\r\n")); | |
| return std::distance(ite, end); | |
| } | |
| //------------------------------------------------------------------------------------------------- | |
| } // end namespace detail | |
| } // end namespace httpc | |
| #endif | |
| #endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment