Skip to content

Instantly share code, notes, and snippets.

@alibitek
Created June 7, 2014 15:18
Show Gist options
  • Save alibitek/8912e0005355e47fdc25 to your computer and use it in GitHub Desktop.
Save alibitek/8912e0005355e47fdc25 to your computer and use it in GitHub Desktop.
#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