Skip to content

Instantly share code, notes, and snippets.

@codemedic
Forked from kikairoya/asio_range_util.hpp
Created November 23, 2013 00:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save codemedic/7609239 to your computer and use it in GitHub Desktop.
Save codemedic/7609239 to your computer and use it in GitHub Desktop.
#ifndef ASIO_RANGE_UTIL_HPP_
#define ASIO_RANGE_UTIL_HPP_
#include <boost/range/iterator_range.hpp>
#include <boost/range/algorithm/search.hpp>
#include <boost/asio/buffer.hpp>
namespace httpc {
template <typename T>
inline boost::iterator_range<T *> make_iterator_range_from_memory(T *head, size_t size) {
return boost::make_iterator_range(head, head + size);
}
template <typename Ptr>
inline boost::iterator_range<Ptr> make_iterator_range_from_buffer(const boost::asio::const_buffer &b) {
return make_iterator_range_from_memory(boost::asio::buffer_cast<Ptr>(b), boost::asio::buffer_size(b));
}
struct lazy_range_search_t {
template <typename R, typename S>
struct result { typedef BOOST_DEDUCED_TYPENAME boost::range_iterator<R>::type type; };
template <typename R, typename S>
BOOST_DEDUCED_TYPENAME result<R, S>::type operator ()(const R &r, const S &s) const {
return boost::search(r, s);
}
};
template <typename R, typename EndFinder>
inline R make_partial_range(R r, const EndFinder &fn) { return R(boost::begin(r), fn(r)); }
}
#endif
#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));
}
}
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;
}
#endif
#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);
}
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment