Created
April 7, 2011 05:11
-
-
Save kikairoya/907076 to your computer and use it in GitHub Desktop.
asio http client without istream
This file contains 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
#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 |
This file contains 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
#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 |
This file contains 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
#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
The following two includes are missing:
include "util.hpp"
include "http_connection.hpp"
Can you post them here?
Also can you provide a quick snippet of code on a simple HTTP GET request?
Thanks.