diff --git a/contrib/http_examples/simple_wget.cpp b/contrib/http_examples/simple_wget.cpp index 579abcdd6..f4b9de49f 100644 --- a/contrib/http_examples/simple_wget.cpp +++ b/contrib/http_examples/simple_wget.cpp @@ -21,11 +21,11 @@ namespace http = network::http; namespace { - std::string get_filename(const std::string& path) { - auto index = path.find_last_of('/'); - auto filename = path.substr(index + 1); - return filename.empty() ? "index.html" : filename; - } +std::string get_filename(const std::string& path) { + auto index = path.find_last_of('/'); + auto filename = path.substr(index + 1); + return filename.empty() ? "index.html" : filename; +} } // namespace int main(int argc, char* argv[]) { diff --git a/http/src/http/v2/client/client.cpp b/http/src/http/v2/client/client.cpp index 10dda096f..2ee705f76 100644 --- a/http/src/http/v2/client/client.cpp +++ b/http/src/http/v2/client/client.cpp @@ -1,10 +1,10 @@ -// Copyright (C) 2013 by Glyn Matthews +// Copyright (C) 2013, 2014 by Glyn Matthews // 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) -#include #include +#include #include #include #include @@ -16,14 +16,13 @@ #include #include #include -#include namespace network { namespace http { namespace v2 { using boost::asio::ip::tcp; - struct request_helper { + struct request_context { std::shared_ptr connection_; @@ -35,15 +34,16 @@ namespace network { boost::asio::streambuf request_buffer_; boost::asio::streambuf response_buffer_; - // TODO configure deadline timer for timeouts - - request_helper(std::shared_ptr connection, - request request, - request_options options) - : connection_(connection) - , request_(request) - , options_(options) { } + std::uint64_t total_bytes_written_, total_bytes_read_; + request_context( + std::shared_ptr connection, + request request, request_options options) + : connection_(connection), + request_(request), + options_(options), + total_bytes_written_(0), + total_bytes_read_(0) {} }; struct client::impl { @@ -51,37 +51,46 @@ namespace network { explicit impl(client_options options); impl(std::unique_ptr mock_resolver, - std::unique_ptr mock_connection, - client_options options); + std::unique_ptr + mock_connection, client_options options); + + ~impl(); - ~impl() noexcept; + void set_error(const boost::system::error_code &ec, + std::shared_ptr context); - std::future execute(std::shared_ptr helper); + std::future execute(std::shared_ptr context); + + void timeout(const boost::system::error_code &ec, + std::shared_ptr context); void connect(const boost::system::error_code &ec, tcp::resolver::iterator endpoint_iterator, - std::shared_ptr helper); + std::shared_ptr context); void write_request(const boost::system::error_code &ec, - std::shared_ptr helper); + std::shared_ptr context); + + void write_body(const boost::system::error_code &ec, + std::size_t bytes_written, + std::shared_ptr context); void read_response(const boost::system::error_code &ec, std::size_t bytes_written, - std::shared_ptr helper); + std::shared_ptr context); void read_response_status(const boost::system::error_code &ec, std::size_t bytes_written, - std::shared_ptr helper, - std::shared_ptr res); + std::shared_ptr context); void read_response_headers(const boost::system::error_code &ec, std::size_t bytes_read, - std::shared_ptr helper, + std::shared_ptr context, std::shared_ptr res); void read_response_body(const boost::system::error_code &ec, std::size_t bytes_read, - std::shared_ptr helper, + std::shared_ptr context, std::shared_ptr res); client_options options_; @@ -90,143 +99,227 @@ namespace network { boost::asio::io_service::strand strand_; std::unique_ptr resolver_; std::shared_ptr mock_connection_; + bool timedout_; + boost::asio::deadline_timer timer_; std::thread lifetime_thread_; }; client::impl::impl(client_options options) - : options_(options) - , sentinel_(new boost::asio::io_service::work(io_service_)) - , strand_(io_service_) - , resolver_(new client_connection::tcp_resolver(io_service_, options_.cache_resolved())) - , lifetime_thread_([=] () { io_service_.run(); }) { - - } - - client::impl::impl(std::unique_ptr mock_resolver, - std::unique_ptr mock_connection, - client_options options) - : options_(options) - , sentinel_(new boost::asio::io_service::work(io_service_)) - , strand_(io_service_) - , resolver_(std::move(mock_resolver)) - , lifetime_thread_([=] () { io_service_.run(); }) { - - } - - client::impl::~impl() noexcept { + : options_(options), + sentinel_(new boost::asio::io_service::work(io_service_)), + strand_(io_service_), + resolver_(new client_connection::tcp_resolver( + io_service_, options_.cache_resolved())), + timedout_(false), + timer_(io_service_), + lifetime_thread_([=]() { io_service_.run(); }) {} + + client::impl::impl( + std::unique_ptr mock_resolver, + std::unique_ptr mock_connection, + client_options options) + : options_(options), + sentinel_(new boost::asio::io_service::work(io_service_)), + strand_(io_service_), + resolver_(std::move(mock_resolver)), + timedout_(false), + timer_(io_service_), + lifetime_thread_([=]() { io_service_.run(); }) {} + + client::impl::~impl() { sentinel_.reset(); lifetime_thread_.join(); } - std::future client::impl::execute(std::shared_ptr helper) { - std::future res = helper->response_promise_.get_future(); - // TODO see linearize.hpp + void client::impl::set_error(const boost::system::error_code &ec, + std::shared_ptr context) { + context->response_promise_.set_exception(std::make_exception_ptr( + std::system_error(ec.value(), std::system_category()))); + timer_.cancel(); + } + + std::future client::impl::execute( + std::shared_ptr context) { + std::future res = context->response_promise_.get_future(); // If there is no user-agent, provide one as a default. - auto user_agent = helper->request_.header("User-Agent"); + auto user_agent = context->request_.header("User-Agent"); if (!user_agent) { - helper->request_.append_header("User-Agent", options_.user_agent()); + context->request_.append_header("User-Agent", options_.user_agent()); } - auto url = helper->request_.url(); - auto host = url.host()? - uri::string_type(std::begin(*url.host()), std::end(*url.host())) : uri::string_type(); - auto port = url.port()? *url.port() : 80; - - resolver_->async_resolve(host, port, - strand_.wrap( - [=](const boost::system::error_code &ec, - tcp::resolver::iterator endpoint_iterator) { - connect(ec, endpoint_iterator, helper); - })); + // Get the host and port from the request and resolve + auto url = context->request_.url(); + auto host = url.host() ? uri::string_type(std::begin(*url.host()), + std::end(*url.host())) + : uri::string_type(); + auto port = url.port() ? *url.port() : 80; + + resolver_->async_resolve( + host, port, + strand_.wrap([=](const boost::system::error_code &ec, + tcp::resolver::iterator endpoint_iterator) { + connect(ec, endpoint_iterator, context); + })); + + if (options_.timeout() > std::chrono::milliseconds(0)) { + timer_.expires_from_now(boost::posix_time::milliseconds(options_.timeout().count())); + timer_.async_wait(strand_.wrap([=](const boost::system::error_code &ec) { + timeout(ec, context); + })); + } return res; } + void client::impl::timeout(const boost::system::error_code &ec, + std::shared_ptr context) { + if (!ec) { + context->connection_->disconnect(); + } + timedout_ = true; + } + void client::impl::connect(const boost::system::error_code &ec, tcp::resolver::iterator endpoint_iterator, - std::shared_ptr helper) { + std::shared_ptr context) { if (ec) { - helper->response_promise_.set_exception( - std::make_exception_ptr(std::system_error(ec.value(), std::system_category()))); + set_error(ec, context); return; } - auto host = helper->request_.url().host(); + // make a connection to an endpoint + auto host = context->request_.url().host(); tcp::endpoint endpoint(*endpoint_iterator); - helper->connection_->async_connect(endpoint, - std::string(std::begin(*host), std::end(*host)), - strand_.wrap( - [=] (const boost::system::error_code &ec) { - if (ec && endpoint_iterator != tcp::resolver::iterator()) { - // copy iterator because it is const after the lambda - // capture - auto it = endpoint_iterator; - boost::system::error_code ignore; - connect(ignore, ++it, helper); - return; - } - - write_request(ec, helper); - })); + context->connection_->async_connect( + endpoint, std::string(std::begin(*host), std::end(*host)), + strand_.wrap([=](const boost::system::error_code &ec) { + // If there is no connection, try again on another endpoint + if (ec && endpoint_iterator != tcp::resolver::iterator()) { + // copy iterator because it is const after the lambda + // capture + auto it = endpoint_iterator; + boost::system::error_code ignore; + connect(ignore, ++it, context); + return; + } + + write_request(ec, context); + })); } - void client::impl::write_request(const boost::system::error_code &ec, - std::shared_ptr helper) { + void client::impl::write_request( + const boost::system::error_code &ec, + std::shared_ptr context) { + if (timedout_) { + set_error(boost::asio::error::timed_out, context); + return; + } + if (ec) { - helper->response_promise_.set_exception( - std::make_exception_ptr(std::system_error(ec.value(), std::system_category()))); + set_error(ec, context); return; } - std::ostream request_stream(&helper->request_buffer_); - request_stream << helper->request_; + // write the request to an I/O stream. + std::ostream request_stream(&context->request_buffer_); + request_stream << context->request_; if (!request_stream) { - helper->response_promise_.set_exception( - std::make_exception_ptr(client_exception(client_error::invalid_request))); + context->response_promise_.set_exception(std::make_exception_ptr( + client_exception(client_error::invalid_request))); + timer_.cancel(); + } + + context->connection_->async_write( + context->request_buffer_, + strand_.wrap([=](const boost::system::error_code &ec, + std::size_t bytes_written) { + write_body(ec, bytes_written, context); + })); + } + + void client::impl::write_body(const boost::system::error_code &ec, + std::size_t bytes_written, + std::shared_ptr context) { + if (timedout_) { + set_error(boost::asio::error::timed_out, context); + return; + } + + if (ec) { + set_error(ec, context); + return; + } + + // update progress + context->total_bytes_written_ += bytes_written; + if (auto progress = context->options_.progress()) { + progress(client_message::transfer_direction::bytes_written, + context->total_bytes_written_); } + // write the body to an I/O stream + std::ostream request_stream(&context->request_buffer_); // TODO write payload to request_buffer_ + if (!request_stream) { + context->response_promise_.set_exception(std::make_exception_ptr( + client_exception(client_error::invalid_request))); + } - helper->connection_->async_write(helper->request_buffer_, - strand_.wrap( - [=] (const boost::system::error_code &ec, - std::size_t bytes_written) { - read_response(ec, bytes_written, helper); - })); + context->connection_->async_write( + context->request_buffer_, + strand_.wrap([=](const boost::system::error_code &ec, + std::size_t bytes_written) { + read_response(ec, bytes_written, context); + })); } - void client::impl::read_response(const boost::system::error_code &ec, std::size_t, - std::shared_ptr helper) { + void client::impl::read_response( + const boost::system::error_code &ec, std::size_t bytes_written, + std::shared_ptr context) { + if (timedout_) { + set_error(boost::asio::error::timed_out, context); + return; + } + if (ec) { - helper->response_promise_.set_exception( - std::make_exception_ptr(std::system_error(ec.value(), std::system_category()))); + set_error(ec, context); return; } - std::shared_ptr res(new response{}); - helper->connection_->async_read_until(helper->response_buffer_, - "\r\n", - strand_.wrap( - [=] (const boost::system::error_code &ec, - std::size_t bytes_read) { - read_response_status(ec, bytes_read, helper, res); - })); + // update progress. + context->total_bytes_written_ += bytes_written; + if (auto progress = context->options_.progress()) { + progress(client_message::transfer_direction::bytes_written, + context->total_bytes_written_); + } + + // Create a response object and fill it with the status from the server. + context->connection_->async_read_until( + context->response_buffer_, "\r\n", + strand_.wrap([=](const boost::system::error_code &ec, + std::size_t bytes_read) { + read_response_status(ec, bytes_read, context); + })); } - void client::impl::read_response_status(const boost::system::error_code &ec, - std::size_t, - std::shared_ptr helper, - std::shared_ptr res) { + void client::impl::read_response_status( + const boost::system::error_code &ec, std::size_t, + std::shared_ptr context) { + if (timedout_) { + set_error(boost::asio::error::timed_out, context); + return; + } + if (ec) { - helper->response_promise_.set_exception( - std::make_exception_ptr(std::system_error(ec.value(), std::system_category()))); + set_error(ec, context); return; } - std::istream is(&helper->response_buffer_); + // Update the reponse status. + std::istream is(&context->response_buffer_); string_type version; is >> version; unsigned int status; @@ -234,50 +327,66 @@ namespace network { string_type message; std::getline(is, message); + // if options_.follow_redirects() + // and if status in range 300 - 307 + // then take the request and reset the URL from the 'Location' header + // restart connection + // Not that the 'Location' header can be a *relative* URI + + std::shared_ptr res(new response{}); res->set_version(version); - res->set_status(network::http::v2::status::code(status)); + res->set_status(network::http::status::code(status)); res->set_status_message(boost::trim_copy(message)); - helper->connection_->async_read_until(helper->response_buffer_, - "\r\n\r\n", - strand_.wrap( - [=] (const boost::system::error_code &ec, - std::size_t bytes_read) { - read_response_headers(ec, bytes_read, helper, res); - })); + // Read the response headers. + context->connection_->async_read_until( + context->response_buffer_, "\r\n\r\n", + strand_.wrap([=](const boost::system::error_code &ec, + std::size_t bytes_read) { + read_response_headers(ec, bytes_read, context, res); + })); } - void client::impl::read_response_headers(const boost::system::error_code &ec, - std::size_t, - std::shared_ptr helper, - std::shared_ptr res) { + void client::impl::read_response_headers( + const boost::system::error_code &ec, std::size_t, + std::shared_ptr context, + std::shared_ptr res) { + if (timedout_) { + set_error(boost::asio::error::timed_out, context); + return; + } + if (ec) { - helper->response_promise_.set_exception( - std::make_exception_ptr(std::system_error(ec.value(), std::system_category()))); + set_error(ec, context); return; } // fill headers - std::istream is(&helper->response_buffer_); + std::istream is(&context->response_buffer_); string_type header; while (std::getline(is, header) && (header != "\r")) { auto delim = boost::find_first_of(header, ":"); string_type key(std::begin(header), delim); - while (*++delim == ' ') { } + while (*++delim == ' ') { + } string_type value(delim, std::end(header)); res->add_header(key, value); } - helper->connection_->async_read(helper->response_buffer_, - strand_.wrap( - [=] (const boost::system::error_code &ec, - std::size_t bytes_read) { - read_response_body(ec, bytes_read, helper, res); - })); + // read the response body. + context->connection_->async_read( + context->response_buffer_, + strand_.wrap([=](const boost::system::error_code &ec, + std::size_t bytes_read) { + read_response_body(ec, bytes_read, context, res); + })); } namespace { - std::istream &getline_with_newline(std::istream &is, std::string &line) { + // I don't want to to delimit with newlines when using the input + // stream operator, so that's why I wrote this function. + std::istream &getline_with_newline(std::istream &is, + std::string &line) { line.clear(); std::istream::sentry se(is, true); @@ -286,71 +395,85 @@ namespace network { while (true) { int c = sb->sbumpc(); switch (c) { - case EOF: - if (line.empty()) { - is.setstate(std::ios::eofbit); - } - return is; - default: - line += static_cast(c); + case EOF: + if (line.empty()) { + is.setstate(std::ios::eofbit); + } + return is; + default: + line += static_cast(c); } } } - } // namespace + } // namespace + + void client::impl::read_response_body( + const boost::system::error_code &ec, std::size_t bytes_read, + std::shared_ptr context, + std::shared_ptr res) { + if (timedout_) { + set_error(boost::asio::error::timed_out, context); + return; + } + + if (ec && ec != boost::asio::error::eof) { + set_error(ec, context); + return; + } - void client::impl::read_response_body(const boost::system::error_code &ec, - std::size_t bytes_read, - std::shared_ptr helper, - std::shared_ptr res) { + // update progress. + context->total_bytes_read_ += bytes_read; + if (auto progress = context->options_.progress()) { + progress(client_message::transfer_direction::bytes_read, + context->total_bytes_read_); + } + + // If there's no data else to read, then set the response and exit. if (bytes_read == 0) { - helper->response_promise_.set_value(*res); + context->response_promise_.set_value(*res); + timer_.cancel(); return; } - std::istream is(&helper->response_buffer_); + std::istream is(&context->response_buffer_); string_type line; + line.reserve(bytes_read); while (!getline_with_newline(is, line).eof()) { - res->append_body(line); + res->append_body(std::move(line)); } - helper->connection_->async_read(helper->response_buffer_, - strand_.wrap( - [=] (const boost::system::error_code &ec, - std::size_t bytes_read) { - read_response_body(ec, bytes_read, helper, res); - })); + // Keep reading the response body until we have nothing else to read. + context->connection_->async_read( + context->response_buffer_, + strand_.wrap([=](const boost::system::error_code &ec, + std::size_t bytes_read) { + read_response_body(ec, bytes_read, context, res); + })); } - client::client(client_options options) - : pimpl_(new impl(options)) { - - } + client::client(client_options options) : pimpl_(new impl(options)) {} - client::client(std::unique_ptr mock_resolver, - std::unique_ptr mock_connection, - client_options options) - : pimpl_(new impl(std::move(mock_resolver), std::move(mock_connection), options)) { } + client::client( + std::unique_ptr mock_resolver, + std::unique_ptr mock_connection, + client_options options) + : pimpl_(new impl(std::move(mock_resolver), + std::move(mock_connection), options)) {} - client::~client() noexcept { - delete pimpl_; - } + client::~client() { } - std::future client::execute(request req, request_options options) { + std::future client::execute(request req, + request_options options) { std::shared_ptr connection; if (pimpl_->mock_connection_) { connection = pimpl_->mock_connection_; - } - else { + } else { // TODO factory based on HTTP or HTTPS - if (req.is_https()) { - connection = std::make_shared(pimpl_->io_service_, - pimpl_->options_); - } - else { - connection = std::make_shared(pimpl_->io_service_); - } + connection = std::make_shared( + pimpl_->io_service_); } - return pimpl_->execute(std::make_shared(connection, req, options)); + return pimpl_->execute( + std::make_shared(connection, req, options)); } std::future client::get(request req, request_options options) { @@ -368,7 +491,8 @@ namespace network { return execute(req, options); } - std::future client::delete_(request req, request_options options) { + std::future client::delete_(request req, + request_options options) { req.method(method::delete_); return execute(req, options); } @@ -378,10 +502,11 @@ namespace network { return execute(req, options); } - std::future client::options(request req, request_options options) { + std::future client::options(request req, + request_options options) { req.method(method::options); return execute(req, options); } - } // namespace v2 - } // namespace http -} // namespace network + } // namespace v2 + } // namespace http +} // namespace network diff --git a/http/src/http/v2/client/client_errors.cpp b/http/src/http/v2/client/client_errors.cpp index d0a5bd03c..783453e3d 100644 --- a/http/src/http/v2/client/client_errors.cpp +++ b/http/src/http/v2/client/client_errors.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2013 by Glyn Matthews +// Copyright (C) 2013, 2014 by Glyn Matthews // 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) @@ -9,71 +9,57 @@ namespace network { namespace http { - namespace v2 { - + inline namespace v2 { class client_category_impl : public std::error_category { - public: - - client_category_impl() = default; + public: + client_category_impl() = default; - virtual ~client_category_impl() noexcept; + virtual ~client_category_impl() noexcept override; - virtual const char *name() const noexcept; - - virtual std::string message(int ev) const; + virtual const char *name() const noexcept override; + virtual std::string message(int ev) const override; }; - client_category_impl::~client_category_impl() noexcept { - - } + client_category_impl::~client_category_impl() noexcept {} const char *client_category_impl::name() const noexcept { - static const char name[] = "client_error"; - return name; + static const char name[] = "client_error"; + return name; } std::string client_category_impl::message(int ev) const { - switch (client_error(ev)) { - - case client_error::invalid_request: - return "Invalid HTTP request."; - case client_error::invalid_response: - return "Invalid HTTP response."; - default: - break; - } - return "Unknown client error."; + switch (client_error(ev)) { + + case client_error::invalid_request: + return "Invalid HTTP request."; + case client_error::invalid_response: + return "Invalid HTTP response."; + default: + break; + } + return "Unknown client error."; } const std::error_category &client_category() { - static client_category_impl client_category; - return client_category; + static client_category_impl client_category; + return client_category; } std::error_code make_error_code(client_error e) { - return std::error_code(static_cast(e), client_category()); + return std::error_code(static_cast(e), client_category()); } invalid_url::invalid_url() - : std::invalid_argument("Requires HTTP or HTTPS URL.") { - - } + : std::invalid_argument("Requires HTTP or HTTPS URL.") {} - invalid_url::~invalid_url() noexcept { - - } + invalid_url::~invalid_url() noexcept {} client_exception::client_exception(client_error error) - : std::system_error(make_error_code(error)) { - - } - - client_exception::~client_exception() noexcept { - - } + : std::system_error(make_error_code(error)) {} - } // namespace v2 - } // namespace network -} // namespace network + client_exception::~client_exception() noexcept {} + } // namespace v2 + } // namespace http +} // namespace network diff --git a/http/src/network/http/v2/client/client.hpp b/http/src/network/http/v2/client/client.hpp index 9ed5950e2..e69a9491a 100644 --- a/http/src/network/http/v2/client/client.hpp +++ b/http/src/network/http/v2/client/client.hpp @@ -1,4 +1,4 @@ -// Copyright (C) 2013 by Glyn Matthews +// Copyright (C) 2013, 2014 by Glyn Matthews // Copyright Dean Michael Berris 2012. // Copyright Google, Inc. 2012. // Distributed under the Boost Software License, Version 1.0. @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -28,353 +27,338 @@ #include namespace network { - namespace http { - namespace v2 { - namespace client_connection { - class async_resolver; - class async_connection; - } // namespace client_connection - - /** - * \ingroup http_client - * \class client_options network/http/v2/client/client.hpp network/http/v2/client.hpp - * \brief A set of options to configure an HTTP client. - */ - class client_options { - - public: - - /** - * \brief Constructor. - */ - client_options() - : io_service_(boost::none) - , follow_redirects_(false) - , cache_resolved_(false) - , use_proxy_(false) - , always_verify_peer_(false) - , user_agent_(std::string("cpp-netlib/") + NETLIB_VERSION) - , timeout_(30000) { } - - /** - * \brief Copy constructor. - */ - client_options(const client_options &other) - : io_service_(other.io_service_) - , follow_redirects_(other.follow_redirects_) - , cache_resolved_(other.cache_resolved_) - , use_proxy_(other.use_proxy_) - , always_verify_peer_(other.always_verify_peer_) - , user_agent_(other.user_agent_) - , timeout_(other.timeout_) - , openssl_certificate_paths_(other.openssl_certificate_paths_) - , openssl_verify_paths_(other.openssl_verify_paths_) { } - - /** - * \brief Move constructor. - */ - client_options(client_options &&other) - : io_service_(std::move(other.io_service_)) - , follow_redirects_(std::move(other.follow_redirects_)) - , cache_resolved_(std::move(other.cache_resolved_)) - , use_proxy_(std::move(other.use_proxy_)) - , always_verify_peer_(std::move(other.always_verify_peer_)) - , user_agent_(std::move(other.user_agent_)) - , timeout_(std::move(other.timeout_)) - , openssl_certificate_paths_(std::move(other.openssl_certificate_paths_)) - , openssl_verify_paths_(std::move(other.openssl_verify_paths_)) { } - - /** - * \brief Assignment operator. - */ - client_options &operator = (client_options other) { - other.swap(*this); - return *this; - } - - /** - * \brief Destructor. - */ - ~client_options() noexcept { - - } - - /** - * \brief Swap. - */ - void swap(client_options &other) noexcept { - using std::swap; - std::swap(io_service_, other.io_service_); - swap(follow_redirects_, other.follow_redirects_); - swap(cache_resolved_, other.cache_resolved_); - swap(use_proxy_, other.use_proxy_); - swap(always_verify_peer_, other.always_verify_peer_); - swap(user_agent_, other.user_agent_); - swap(timeout_, other.timeout_); - swap(openssl_certificate_paths_, other.openssl_certificate_paths_); - swap(openssl_verify_paths_, other.openssl_verify_paths_); - } - - /** - * \brief Overrides the client's I/O service. - * \param io_service The new io_service object to use. - */ - client_options &io_service(boost::asio::io_service &io_service) { - io_service_ = io_service; - return *this; - } - - /** - * \brief Gets the overridden I/O service. - * \returns An optional io_service object. - */ - boost::optional io_service() const { - return io_service_; - } - - /** - * \brief Tells the client to follow redirects. - * \param follow_redirects If \c true, then the client must - * follow redirects, if \c false it doesn't. - * \returns \c *this - */ - client_options &follow_redirects(bool follow_redirects) { - follow_redirects_ = follow_redirects; - return *this; - } - - /** - * \brief Tests if the client follows redirects. - * \returns \c true if the client follows redirects, \c false - * otherwise. - */ - bool follow_redirects() const { - return follow_redirects_; - } - - /** - * \brief Tells the client to cache resolved connections. - * \param cache_resolved If \c true, then the client must - * cache resolved connections, if \c false it - * doesn't. - * \returns \c *this - */ - client_options &cache_resolved(bool cache_resolved) { - cache_resolved_ = cache_resolved; - return *this; - } - - /** - * \brief Tests if the client caches resolved connections. - * \returns \c true if the client caches resolved connections, - * \c false otherwise. - */ - bool cache_resolved() const { - return cache_resolved_; - } - - /** - * \brief Tells the client to use a proxy. - * \param use_proxy If \c true, then the client must use a - * proxy, if \c false it doesn't. - */ - client_options &use_proxy(bool use_proxy) { - use_proxy_ = use_proxy; - return *this; - } - - /** - * \brief Tests if the client uses a proxy. - * \returns \c true if the client uses a proxy, \c false - * otherwise. - */ - bool use_proxy() const { - return use_proxy_; - } - - /** - * \brief Sets the client timeout in milliseconds. - * \param timeout The timeout value in milliseconds. - */ - client_options &timeout(std::chrono::milliseconds timeout) { - timeout_ = timeout; - return *this; - } - - /** - * \brief Gets the current timeout value. - * \returns The timeout value in milliseconds. - */ - std::chrono::milliseconds timeout() const { - return timeout_; - } - - /** - * \brief Adds an OpenSSL certificate path. - * \param path The certificate path. - */ - client_options &openssl_certificate_path(std::string path) { - openssl_certificate_paths_.emplace_back(std::move(path)); - return *this; - } - - /** - * \brief Returns a list of OpenSSL certificate paths. - * \returns A list of OpenSSL certificate paths. - */ - std::vector openssl_certificate_paths() const { - return openssl_certificate_paths_; - } - - /** - * \brief Adds an OpenSSL verify path. - * \param path The verify path. - */ - client_options &openssl_verify_path(std::string path) { - openssl_verify_paths_.emplace_back(std::move(path)); - return *this; - } - - /** - * \brief Returns a list of OpenSSL verify paths. - * \returns A list of OpenSSL verify paths. - */ - std::vector openssl_verify_paths() const { - return openssl_verify_paths_; - } - - client_options &always_verify_peer(bool always_verify_peer) { - always_verify_peer_ = always_verify_peer; - return *this; - } - - bool always_verify_peer() const { - return always_verify_peer_; - } - - client_options &user_agent(const std::string &user_agent) { - user_agent_ = user_agent; - return *this; - } - - std::string user_agent() const { - return user_agent_; - } - - private: - - boost::optional io_service_; - bool follow_redirects_; - bool cache_resolved_; - bool use_proxy_; - bool always_verify_peer_; - std::string user_agent_; - std::chrono::milliseconds timeout_; - std::vector openssl_certificate_paths_; - std::vector openssl_verify_paths_; - - }; - - inline - void swap(client_options &lhs, client_options &rhs) noexcept { - lhs.swap(rhs); - } - - typedef client_message::request_options request_options; - typedef client_message::request request; - typedef client_message::response response; - - /** - * \ingroup http_client - * \class client network/http/v2/client/client.hpp network/http/v2/client.hpp - * \brief A class that encapsulates the operations and methods - * for communicating with an HTTP server. - */ - class client { - - client(const client&) = delete; - client& operator=(const client&) = delete; - - public: - - /** - * \typedef string_type - * \brief The client string_type. - */ - typedef request::string_type string_type; - - /** - * \brief Constructor. - * \param options Client options. - */ - explicit client(client_options options = client_options()); - - client(std::unique_ptr mock_resolver, - std::unique_ptr mock_connection, - client_options options = client_options()); - - /** - * \brief Destructor. - */ - ~client() noexcept; - - /** - * \brief Executes an HTTP request. - * \param req The request object. - * \param options The request options. - */ - std::future execute(request req, request_options options = request_options()); - - /** - * \brief Executes an HTTP GET request. - * \param req The request object. - * \param options The request options. - */ - std::future get(request req, request_options options = request_options()); - - /** - * \brief Executes an HTTP POST request. - * \param req The request object. - * \param options The request options. - */ - std::future post(request req, request_options options = request_options()); - - /** - * \brief Executes an HTTP PUT request. - * \param req The request object. - * \param options The request options. - */ - std::future put(request req, request_options options = request_options()); - - /** - * \brief Executes an HTTP DELETE request. - * \param req The request object. - * \param options The request options. - */ - std::future delete_(request req, request_options options = request_options()); - - /** - * \brief Executes an HTTP HEAD request. - * \param req The request object. - * \param options The request options. - */ - std::future head(request req, request_options options = request_options()); - - /** - * \brief Executes an HTTP OPTIONS request. - * \param req The request object. - * \param options The request options. - */ - std::future options(request req, request_options options = request_options()); - - private: - - struct impl; - impl *pimpl_; - - }; - } // namespace v2 - } // namespace http +namespace http { +inline namespace v2 { +namespace client_connection { +class async_resolver; +class async_connection; +} // namespace client_connection + +/** + * \ingroup http_client + * \class client_options network/http/v2/client/client.hpp network/http/v2/client.hpp + * \brief A set of options to configure an HTTP client. + */ +class client_options { + +public: + + /** + * \brief Constructor. + */ + client_options() + : follow_redirects_(false) + , cache_resolved_(false) + , use_proxy_(false) + , always_verify_peer_(false) + , user_agent_(std::string("cpp-netlib/") + NETLIB_VERSION) + , timeout_(30000) { } + + /** + * \brief Copy constructor. + */ + client_options(const client_options &other) = default; + + /** + * \brief Move constructor. + */ + client_options(client_options &&other) = default; + + /** + * \brief Assignment operator. + */ + client_options &operator = (client_options other) { + other.swap(*this); + return *this; + } + + /** + * \brief Destructor. + */ + ~client_options() = default; + + /** + * \brief Swap. + */ + void swap(client_options &other) noexcept { + using std::swap; + swap(follow_redirects_, other.follow_redirects_); + swap(cache_resolved_, other.cache_resolved_); + swap(use_proxy_, other.use_proxy_); + swap(always_verify_peer_, other.always_verify_peer_); + swap(user_agent_, other.user_agent_); + swap(timeout_, other.timeout_); + swap(openssl_certificate_paths_, other.openssl_certificate_paths_); + swap(openssl_verify_paths_, other.openssl_verify_paths_); + } + + /** + * \brief Tells the client to follow redirects. + * \param follow_redirects If \c true, then the client must + * follow redirects, if \c false it doesn't. + * \returns \c *this + */ + client_options &follow_redirects(bool follow_redirects) { + follow_redirects_ = follow_redirects; + return *this; + } + + /** + * \brief Tests if the client follows redirects. + * \returns \c true if the client follows redirects, \c false + * otherwise. + */ + bool follow_redirects() const { + return follow_redirects_; + } + + /** + * \brief Tells the client to cache resolved connections. + * \param cache_resolved If \c true, then the client must + * cache resolved connections, if \c false it + * doesn't. + * \returns \c *this + */ + client_options &cache_resolved(bool cache_resolved) { + cache_resolved_ = cache_resolved; + return *this; + } + + /** + * \brief Tests if the client caches resolved connections. + * \returns \c true if the client caches resolved connections, + * \c false otherwise. + */ + bool cache_resolved() const { + return cache_resolved_; + } + + /** + * \brief Tells the client to use a proxy. + * \param use_proxy If \c true, then the client must use a + * proxy, if \c false it doesn't. + * \returns \c *this + */ + client_options &use_proxy(bool use_proxy) { + use_proxy_ = use_proxy; + return *this; + } + + /** + * \brief Tests if the client uses a proxy. + * \returns \c true if the client uses a proxy, \c false + * otherwise. + */ + bool use_proxy() const { + return use_proxy_; + } + + /** + * \brief Sets the client timeout in milliseconds. + * \param timeout The timeout value in milliseconds. + * \returns \c *this + */ + client_options &timeout(std::chrono::milliseconds timeout) { + timeout_ = timeout; + return *this; + } + + /** + * \brief Gets the current timeout value. + * \returns The timeout value in milliseconds. + */ + std::chrono::milliseconds timeout() const { + return timeout_; + } + + /** + * \brief Adds an OpenSSL certificate path. + * \param path The certificate path. + * \returns \c *this + */ + client_options &openssl_certificate_path(std::string path) { + openssl_certificate_paths_.emplace_back(std::move(path)); + return *this; + } + + /** + * \brief Returns a list of OpenSSL certificate paths. + * \returns A list of OpenSSL certificate paths. + */ + std::vector openssl_certificate_paths() const { + return openssl_certificate_paths_; + } + + /** + * \brief Adds an OpenSSL verify path. + * \param path The verify path. + * \returns \c *this + */ + client_options &openssl_verify_path(std::string path) { + openssl_verify_paths_.emplace_back(std::move(path)); + return *this; + } + + /** + * \brief Returns a list of OpenSSL verify paths. + * \returns A list of OpenSSL verify paths. + */ + std::vector openssl_verify_paths() const { + return openssl_verify_paths_; + } + + /** + * \brief + * \returns \c *this + */ + client_options &always_verify_peer(bool always_verify_peer) { + always_verify_peer_ = always_verify_peer; + return *this; + } + + /** + * \brief + * \returns + */ + bool always_verify_peer() const { + return always_verify_peer_; + } + + /** + * \brief + * \returns \c *this + */ + client_options &user_agent(const std::string &user_agent) { + user_agent_ = user_agent; + return *this; + } + + /** + * \brief + * \returns + */ + std::string user_agent() const { + return user_agent_; + } + +private: + + bool follow_redirects_; + bool cache_resolved_; + bool use_proxy_; + bool always_verify_peer_; + std::string user_agent_; + std::chrono::milliseconds timeout_; + std::vector openssl_certificate_paths_; + std::vector openssl_verify_paths_; + +}; + +/** + * \brief + * \param lhs + * \param rhs + */ +inline +void swap(client_options &lhs, client_options &rhs) noexcept { + lhs.swap(rhs); +} + +typedef client_message::request_options request_options; +typedef client_message::request request; +typedef client_message::response response; + +/** + * \ingroup http_client + * \class client network/http/v2/client/client.hpp network/http/v2/client.hpp + * \brief A class that encapsulates the operations and methods + * for communicating with an HTTP server. + */ +class client { + + client(const client&) = delete; + client& operator=(const client&) = delete; + +public: + + /** + * \typedef string_type + * \brief The client string_type. + */ + typedef request::string_type string_type; + + /** + * \brief Constructor. + * \param options Client options. + */ + explicit client(client_options options = client_options()); + + client(std::unique_ptr mock_resolver, + std::unique_ptr mock_connection, + client_options options = client_options()); + + /** + * \brief Destructor. + */ + ~client(); + + /** + * \brief Executes an HTTP request. + * \param req The request object. + * \param options The request options. + */ + std::future execute(request req, request_options options = request_options()); + + /** + * \brief Executes an HTTP GET request. + * \param req The request object. + * \param options The request options. + */ + std::future get(request req, request_options options = request_options()); + + /** + * \brief Executes an HTTP POST request. + * \param req The request object. + * \param options The request options. + */ + std::future post(request req, request_options options = request_options()); + + /** + * \brief Executes an HTTP PUT request. + * \param req The request object. + * \param options The request options. + */ + std::future put(request req, request_options options = request_options()); + + /** + * \brief Executes an HTTP DELETE request. + * \param req The request object. + * \param options The request options. + */ + std::future delete_(request req, request_options options = request_options()); + + /** + * \brief Executes an HTTP HEAD request. + * \param req The request object. + * \param options The request options. + */ + std::future head(request req, request_options options = request_options()); + + /** + * \brief Executes an HTTP OPTIONS request. + * \param req The request object. + * \param options The request options. + */ + std::future options(request req, request_options options = request_options()); + +private: + + struct impl; + std::unique_ptr pimpl_; + +}; +} // namespace v2 +} // namespace http } // namespace network #endif // NETWORK_HTTP_V2_CLIENT_CLIENT_INC diff --git a/http/src/network/http/v2/client/client_errors.hpp b/http/src/network/http/v2/client/client_errors.hpp index 2cf4a3582..72a9a88cc 100644 --- a/http/src/network/http/v2/client/client_errors.hpp +++ b/http/src/network/http/v2/client/client_errors.hpp @@ -17,75 +17,75 @@ #include namespace network { - namespace http { - namespace v2 { - /** - * \ingroup http_client - * \enum client_error network/http/v2/client/client_errors.hpp network/http/v2/client.hpp - * \brief An enumeration of all types of client error. - */ - enum class client_error { - // request - invalid_request, - - // response - invalid_response, - }; - - /** - * \brief Gets the error category for HTTP client errors. - */ - const std::error_category &client_category(); - - /** - * \brief Makes an error code object from a client_error enum. - */ - std::error_code make_error_code(client_error e); - - /** - * \ingroup http_client - * \class invalid_url network/http/v2/client/client_errors.hpp network/http/v2/client.hpp - * \brief An exception thrown if the URL provides is invalid. - */ - class invalid_url : public std::invalid_argument { - - public: - - /** - * \brief Constructor. - */ - explicit invalid_url(); - - /** - * \brief Destructor. - */ - virtual ~invalid_url() noexcept; - - }; - - /** - * \ingroup http_client - * \class client_exception network/http/v2/client/client_errors.hpp network/http/v2/client.hpp - * \brief An exception thrown when there is a client error. - */ - class client_exception : public std::system_error { - - public: - - /** - * \brief Constructor. - */ - explicit client_exception(client_error error); - - /** - * \brief Destructor. - */ - virtual ~client_exception() noexcept; - - }; - - } // namespace v2 - } // namespace http +namespace http { +inline namespace v2 { +/** + * \ingroup http_client + * \enum client_error network/http/v2/client/client_errors.hpp network/http/v2/client.hpp + * \brief An enumeration of all types of client error. + */ +enum class client_error { + // request + invalid_request, + + // response + invalid_response, +}; + +/** + * \brief Gets the error category for HTTP client errors. + */ +const std::error_category &client_category(); + +/** + * \brief Makes an error code object from a client_error enum. + */ +std::error_code make_error_code(client_error e); + +/** + * \ingroup http_client + * \class invalid_url network/http/v2/client/client_errors.hpp network/http/v2/client.hpp + * \brief An exception thrown if the URL provides is invalid. + */ +class invalid_url : public std::invalid_argument { + +public: + + /** + * \brief Constructor. + */ + explicit invalid_url(); + + /** + * \brief Destructor. + */ + virtual ~invalid_url() noexcept; + +}; + +/** + * \ingroup http_client + * \class client_exception network/http/v2/client/client_errors.hpp network/http/v2/client.hpp + * \brief An exception thrown when there is a client error. + */ +class client_exception : public std::system_error { + +public: + + /** + * \brief Constructor. + */ + explicit client_exception(client_error error); + + /** + * \brief Destructor. + */ + virtual ~client_exception() noexcept; + +}; + +} // namespace v2 +} // namespace http } // namespace network #endif // NETWORK_HTTP_V2_CLIENT_CLIENT_ERRORS_INC diff --git a/http/src/network/http/v2/client/connection/async_connection.hpp b/http/src/network/http/v2/client/connection/async_connection.hpp index c81e8da97..60f97a256 100644 --- a/http/src/network/http/v2/client/connection/async_connection.hpp +++ b/http/src/network/http/v2/client/connection/async_connection.hpp @@ -20,7 +20,7 @@ namespace network { namespace http { - namespace v2 { + inline namespace v2 { namespace client_connection { /** * \class async_connection network/http/v2/client/connection/async_connection.hpp @@ -94,6 +94,11 @@ namespace network { virtual void async_read(boost::asio::streambuf &command_streambuf, read_callback callback) = 0; + /** + * \brief Breaks the connection. + */ + virtual void disconnect() = 0; + /** * \brief Cancels an operation on a connection. */ diff --git a/http/src/network/http/v2/client/connection/async_resolver.hpp b/http/src/network/http/v2/client/connection/async_resolver.hpp index c573bf199..f88cd9fbc 100644 --- a/http/src/network/http/v2/client/connection/async_resolver.hpp +++ b/http/src/network/http/v2/client/connection/async_resolver.hpp @@ -18,7 +18,7 @@ namespace network { namespace http { - namespace v2 { + inline namespace v2 { namespace client_connection { /** * \class async_resolver network/http/v2/client/connection/async_resolver.hpp diff --git a/http/src/network/http/v2/client/connection/endpoint_cache.hpp b/http/src/network/http/v2/client/connection/endpoint_cache.hpp index 37d605c39..b3cf13015 100644 --- a/http/src/network/http/v2/client/connection/endpoint_cache.hpp +++ b/http/src/network/http/v2/client/connection/endpoint_cache.hpp @@ -18,52 +18,80 @@ namespace network { namespace http { - namespace v2 { + inline namespace v2 { namespace client_connection { - class endpoint_cache { - - typedef boost::asio::ip::tcp::resolver resolver; - - typedef resolver::iterator resolver_iterator; - - typedef std::unordered_map cache_type; - - public: - - typedef cache_type::iterator iterator; - - cache_type::iterator begin() { - return endpoints_.begin(); - } - - cache_type::iterator end() { - return endpoints_.end(); - } - - void insert(const std::string &host, - resolver_iterator endpoint_iterator) { - endpoints_.insert(std::make_pair(host, endpoint_iterator)); - } - - iterator find(const std::string &host) { - return endpoints_.find(boost::to_lower_copy(host)); - } - - void clear() { - endpoint_cache().swap(*this); - } - - void swap(endpoint_cache &other) noexcept { - endpoints_.swap(other.endpoints_); - } - - private: - - cache_type endpoints_; - - }; + /** + * \class endpoint_cache network/http/v2/client/connection/endpoint_cache.hpp + * \brief + */ + class endpoint_cache { + + /** + * \brief + */ + typedef boost::asio::ip::tcp::resolver resolver; + + typedef resolver::iterator resolver_iterator; + + typedef std::unordered_map cache_type; + + public: + + /** + * \brief + */ + typedef cache_type::iterator iterator; + + /** + * \brief + */ + cache_type::iterator begin() { + return endpoints_.begin(); + } + + /** + * \brief + */ + cache_type::iterator end() { + return endpoints_.end(); + } + + /** + * \brief + */ + void insert(const std::string &host, + resolver_iterator endpoint_iterator) { + endpoints_.insert(std::make_pair(host, endpoint_iterator)); + } + + /** + * \brief + */ + iterator find(const std::string &host) { + return endpoints_.find(boost::to_lower_copy(host)); + } + + /** + * \brief + */ + void clear() { + endpoint_cache().swap(*this); + } + + /** + * \brief + */ + void swap(endpoint_cache &other) noexcept { + endpoints_.swap(other.endpoints_); + } + + private: + + cache_type endpoints_; + + }; } // namespace client_connection } // namespace v2 } // namespace http diff --git a/http/src/network/http/v2/client/connection/normal_connection.hpp b/http/src/network/http/v2/client/connection/normal_connection.hpp index 71d064a06..4b2cce67b 100644 --- a/http/src/network/http/v2/client/connection/normal_connection.hpp +++ b/http/src/network/http/v2/client/connection/normal_connection.hpp @@ -21,59 +21,80 @@ namespace network { namespace http { - namespace v2 { + inline namespace v2 { namespace client_connection { - class normal_connection : public async_connection { - normal_connection(const normal_connection &) = delete; - normal_connection &operator = (const normal_connection &) = delete; - - public: - - explicit normal_connection(boost::asio::io_service &io_service) - : io_service_(io_service) { - - } - - virtual ~normal_connection() noexcept { - - } - - virtual void async_connect(const boost::asio::ip::tcp::endpoint &endpoint, - const std::string &host, - connect_callback callback) { - using boost::asio::ip::tcp; - socket_.reset(new tcp::socket{io_service_}); - socket_->async_connect(endpoint, callback); + /** + * \class + * \brief + */ + class normal_connection : public async_connection { + + normal_connection(const normal_connection &) = delete; + normal_connection &operator = (const normal_connection &) = delete; + + public: + + /** + * \brief + */ + explicit normal_connection(boost::asio::io_service &io_service) + : io_service_(io_service) { + + } + + /** + * \brief Destructor. + */ + virtual ~normal_connection() noexcept { + + } + + virtual void async_connect(const boost::asio::ip::tcp::endpoint &endpoint, + const std::string &host, + connect_callback callback) { + using boost::asio::ip::tcp; + socket_.reset(new tcp::socket{io_service_}); + socket_->async_connect(endpoint, callback); + } + + virtual void async_write(boost::asio::streambuf &command_streambuf, + write_callback callback) { + boost::asio::async_write(*socket_, command_streambuf, callback); + } + + virtual void async_read_until(boost::asio::streambuf &command_streambuf, + const std::string &delim, + read_callback callback) { + boost::asio::async_read_until(*socket_, command_streambuf, delim, callback); + } + + virtual void async_read(boost::asio::streambuf &command_streambuf, + read_callback callback) { + boost::asio::async_read(*socket_, command_streambuf, + boost::asio::transfer_at_least(1), callback); + } + + virtual void disconnect() { + if (socket_ && socket_->is_open()) { + boost::system::error_code ec; + socket_->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); + if (!ec) { + socket_->close(ec); + } } + } - virtual void async_write(boost::asio::streambuf &command_streambuf, - write_callback callback) { - boost::asio::async_write(*socket_, command_streambuf, callback); - } - - virtual void async_read_until(boost::asio::streambuf &command_streambuf, - const std::string &delim, - read_callback callback) { - boost::asio::async_read_until(*socket_, command_streambuf, delim, callback); - } - - virtual void async_read(boost::asio::streambuf &command_streambuf, - read_callback callback) { - boost::asio::async_read(*socket_, command_streambuf, - boost::asio::transfer_at_least(1), callback); - } - - virtual void cancel() { - socket_->cancel(); - } + virtual void cancel() { + socket_->cancel(); + } - private: + private: - boost::asio::io_service &io_service_; - std::unique_ptr socket_; + boost::asio::io_service &io_service_; + std::unique_ptr socket_; - }; + }; } // namespace client_connection } // namespace v2 } // namespace http diff --git a/http/src/network/http/v2/client/connection/ssl_connection.hpp b/http/src/network/http/v2/client/connection/ssl_connection.hpp index 12ba8f2a3..92bd9983f 100644 --- a/http/src/network/http/v2/client/connection/ssl_connection.hpp +++ b/http/src/network/http/v2/client/connection/ssl_connection.hpp @@ -23,103 +23,123 @@ namespace network { namespace http { - namespace v2 { + inline namespace v2 { namespace client_connection { - class ssl_connection : public async_connection { - - ssl_connection(const ssl_connection &) = delete; - ssl_connection &operator = (const ssl_connection &) = delete; - - public: - - ssl_connection(boost::asio::io_service &io_service, const client_options &options) - : io_service_(io_service) - , options_(options) { - - } - - virtual ~ssl_connection() noexcept { - - } - - virtual void async_connect(const boost::asio::ip::tcp::endpoint &endpoint, - const std::string &host, - connect_callback callback) { - context_.reset(new boost::asio::ssl::context(boost::asio::ssl::context::sslv23)); - auto certificate_paths = options_.openssl_certificate_paths(); - auto verifier_paths = options_.openssl_verify_paths(); - bool use_default_verification = certificate_paths.empty() && verifier_paths.empty(); - if (!use_default_verification) { - for (auto path : certificate_paths) { - context_->load_verify_file(path); - } - for (auto path : verifier_paths) { - context_->add_verify_path(path); - } - context_->set_verify_mode(boost::asio::ssl::context::verify_peer); - context_->set_verify_callback(boost::asio::ssl::rfc2818_verification(host)); + /** + * \class + * \brief + */ + class ssl_connection : public async_connection { + + ssl_connection(const ssl_connection &) = delete; + ssl_connection &operator = (const ssl_connection &) = delete; + + public: + + /** + * \brief + */ + ssl_connection(boost::asio::io_service &io_service, const client_options &options) + : io_service_(io_service) + , options_(options) { + + } + + /** + * \brief Destructor. + */ + virtual ~ssl_connection() noexcept { + + } + + virtual void async_connect(const boost::asio::ip::tcp::endpoint &endpoint, + const std::string &host, + connect_callback callback) { + context_.reset(new boost::asio::ssl::context(boost::asio::ssl::context::sslv23)); + auto certificate_paths = options_.openssl_certificate_paths(); + auto verifier_paths = options_.openssl_verify_paths(); + bool use_default_verification = certificate_paths.empty() && verifier_paths.empty(); + if (!use_default_verification) { + for (auto path : certificate_paths) { + context_->load_verify_file(path); } - else { - context_->set_default_verify_paths(); - context_->set_verify_mode(boost::asio::ssl::context::verify_none); + for (auto path : verifier_paths) { + context_->add_verify_path(path); } - socket_.reset(new boost::asio::ssl::stream< - boost::asio::ip::tcp::socket>(io_service_, *context_)); - - using namespace std::placeholders; - socket_->lowest_layer().async_connect(endpoint, - [=] (const boost::system::error_code &ec) { - handle_connected(ec, callback); - }); + context_->set_verify_mode(boost::asio::ssl::context::verify_peer); + context_->set_verify_callback(boost::asio::ssl::rfc2818_verification(host)); } - - virtual void async_write(boost::asio::streambuf &command_streambuf, - write_callback callback) { - boost::asio::async_write(*socket_, command_streambuf, callback); - } - - virtual void async_read_until(boost::asio::streambuf &command_streambuf, - const std::string &delim, - read_callback callback) { - boost::asio::async_read_until(*socket_, command_streambuf, delim, callback); + else { + context_->set_default_verify_paths(); + context_->set_verify_mode(boost::asio::ssl::context::verify_none); } - - virtual void async_read(boost::asio::streambuf &command_streambuf, - read_callback callback) { - boost::asio::async_read(*socket_, command_streambuf, - boost::asio::transfer_at_least(1), callback); + socket_.reset(new boost::asio::ssl::stream< + boost::asio::ip::tcp::socket>(io_service_, *context_)); + + using namespace std::placeholders; + socket_->lowest_layer().async_connect(endpoint, + [=] (const boost::system::error_code &ec) { + handle_connected(ec, callback); + }); + } + + virtual void async_write(boost::asio::streambuf &command_streambuf, + write_callback callback) { + boost::asio::async_write(*socket_, command_streambuf, callback); + } + + virtual void async_read_until(boost::asio::streambuf &command_streambuf, + const std::string &delim, + read_callback callback) { + boost::asio::async_read_until(*socket_, command_streambuf, delim, callback); + } + + virtual void async_read(boost::asio::streambuf &command_streambuf, + read_callback callback) { + boost::asio::async_read(*socket_, command_streambuf, + boost::asio::transfer_at_least(1), callback); + } + + virtual void disconnect() { + if (socket_ && socket_->lowest_layer().is_open()) { + boost::system::error_code ec; + socket_->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); + if (!ec) { + socket_->lowest_layer().close(ec); + } } + } - virtual void cancel() { - //socket_->cancel(); - } + virtual void cancel() { + socket_->lowest_layer().cancel(); + } - private: + private: - void handle_connected(const boost::system::error_code &ec, connect_callback callback) { - if (!ec) { - auto existing_session = SSL_get1_session(socket_->native_handle()); - if (existing_session) { - socket_->async_handshake(boost::asio::ssl::stream_base::client, callback); - } - else { - SSL_set_session(socket_->native_handle(), existing_session); - SSL_connect(socket_->native_handle()); - callback(ec); - } + void handle_connected(const boost::system::error_code &ec, connect_callback callback) { + if (!ec) { + auto existing_session = SSL_get1_session(socket_->native_handle()); + if (existing_session) { + socket_->async_handshake(boost::asio::ssl::stream_base::client, callback); } else { + SSL_set_session(socket_->native_handle(), existing_session); + SSL_connect(socket_->native_handle()); callback(ec); } } + else { + callback(ec); + } + } - boost::asio::io_service &io_service_; - client_options options_; - std::unique_ptr context_; - std::unique_ptr< - boost::asio::ssl::stream> socket_; + boost::asio::io_service &io_service_; + client_options options_; + std::unique_ptr context_; + std::unique_ptr< + boost::asio::ssl::stream> socket_; - }; + }; } // namespace client_connection } // namespace v2 } // namespace http diff --git a/http/src/network/http/v2/client/connection/tcp_resolver.hpp b/http/src/network/http/v2/client/connection/tcp_resolver.hpp index bfc8eb6ae..8092c6eeb 100644 --- a/http/src/network/http/v2/client/connection/tcp_resolver.hpp +++ b/http/src/network/http/v2/client/connection/tcp_resolver.hpp @@ -24,81 +24,81 @@ #include namespace network { - namespace http { - namespace v2 { - namespace client_connection { - /** - * \class tcp_resolver network/http/v2/client/connection/tcp_resolver.hpp - * \brief Resolves and maintains a cache of hosts. - */ - class tcp_resolver : public async_resolver { - - tcp_resolver(const tcp_resolver &) = delete; - tcp_resolver &operator = (const tcp_resolver &) = delete; - - public: - - using async_resolver::resolver; - using async_resolver::resolver_iterator; - using async_resolver::resolve_callback; - - /** - * \brief Constructor. - */ - tcp_resolver(boost::asio::io_service &service, bool cache_resolved = false) - : resolver_(service) - , cache_resolved_(cache_resolved) { - - } - - /** - * \brief Destructor. - */ - virtual ~tcp_resolver() noexcept { - - } - - virtual void async_resolve(const std::string &host, std::uint16_t port, - resolve_callback handler) { - if (cache_resolved_) { - auto it = endpoint_cache_.find(host); - if (it != endpoint_cache_.end()) { - boost::system::error_code ec; - handler(ec, it->second); - return; - } - } - - resolver::query query(host, std::to_string(port)); - resolver_.async_resolve(query, - [host, handler, this](const boost::system::error_code &ec, - resolver_iterator endpoint_iterator) { - if (ec) { - handler(ec, resolver_iterator()); - } - else { - if (cache_resolved_) { - endpoint_cache_.insert(host, endpoint_iterator); - } - handler(ec, endpoint_iterator); - } - }); - } - - virtual void clear_resolved_cache() { - endpoint_cache_.clear(); - } - - private: - - resolver resolver_; - bool cache_resolved_; - endpoint_cache endpoint_cache_; - - }; - } // namespace client_connection - } // namespace v2 - } // namespace http +namespace http { +inline namespace v2 { +namespace client_connection { +/** + * \class tcp_resolver network/http/v2/client/connection/tcp_resolver.hpp + * \brief Resolves and maintains a cache of hosts. + */ +class tcp_resolver : public async_resolver { + + tcp_resolver(const tcp_resolver &) = delete; + tcp_resolver &operator = (const tcp_resolver &) = delete; + +public: + + using async_resolver::resolver; + using async_resolver::resolver_iterator; + using async_resolver::resolve_callback; + + /** + * \brief Constructor. + */ + tcp_resolver(boost::asio::io_service &service, bool cache_resolved = false) + : resolver_(service) + , cache_resolved_(cache_resolved) { + + } + + /** + * \brief Destructor. + */ + virtual ~tcp_resolver() noexcept { + + } + + virtual void async_resolve(const std::string &host, std::uint16_t port, + resolve_callback handler) { + if (cache_resolved_) { + auto it = endpoint_cache_.find(host); + if (it != endpoint_cache_.end()) { + boost::system::error_code ec; + handler(ec, it->second); + return; + } + } + + resolver::query query(host, std::to_string(port)); + resolver_.async_resolve(query, + [host, handler, this](const boost::system::error_code &ec, + resolver_iterator endpoint_iterator) { + if (ec) { + handler(ec, resolver_iterator()); + } + else { + if (cache_resolved_) { + endpoint_cache_.insert(host, endpoint_iterator); + } + handler(ec, endpoint_iterator); + } + }); + } + + virtual void clear_resolved_cache() { + endpoint_cache_.clear(); + } + +private: + + resolver resolver_; + bool cache_resolved_; + endpoint_cache endpoint_cache_; + +}; +} // namespace client_connection +} // namespace v2 +} // namespace http } // namespace network #endif // NETWORK_HTTP_V2_CLIENT_CONNECTION_TCP_RESOLVER_INC diff --git a/http/src/network/http/v2/client/request.hpp b/http/src/network/http/v2/client/request.hpp index 2ce5b54cb..f82ab528a 100644 --- a/http/src/network/http/v2/client/request.hpp +++ b/http/src/network/http/v2/client/request.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -33,485 +34,546 @@ #include namespace network { - namespace http { - namespace v2 { - namespace client_message { - /** - * \ingroup http_client - * \class request_options network/http/v2/client/request.hpp network/http/v2/client.hpp - * \brief A class used to configure an HTTP request. - */ - class request_options { - - public: - - /** - * \brief Constructor. - */ - request_options() - : resolve_timeout_(30000) - , read_timeout_(30000) - , total_timeout_(30000) - , max_redirects_(10) { } - - /** - * \brief Copy constructor. - */ - request_options(request_options const &other) - : resolve_timeout_(other.resolve_timeout_) - , read_timeout_(other.read_timeout_) - , total_timeout_(other.total_timeout_) - , max_redirects_(other.max_redirects_) { } - - /** - * \brief Move constructor. - */ - request_options(request_options &&other) - : resolve_timeout_(std::move(other.resolve_timeout_)) - , read_timeout_(std::move(other.read_timeout_)) - , total_timeout_(std::move(other.total_timeout_)) - , max_redirects_(std::move(other.max_redirects_)) { } - - /** - * \brief Assignment operator. - */ - request_options &operator = (request_options other) { - other.swap(*this); - return *this; - } - - /** - * \brief Destructor. - */ - ~request_options() noexcept { - - } - - void swap(request_options &other) noexcept { - using std::swap; - swap(resolve_timeout_, other.resolve_timeout_); - swap(read_timeout_, other.read_timeout_); - swap(total_timeout_, other.total_timeout_); - } - - request_options &resolve_timeout(std::uint64_t resolve_timeout) { - resolve_timeout_ = resolve_timeout; - return *this; - } - - std::uint64_t resolve_timeout() const { - return resolve_timeout_; - } - - request_options &read_timeout(std::uint64_t read_timeout) { - read_timeout_ = read_timeout; - return *this; - } - - std::uint64_t read_timeout() const { - return read_timeout_; - } - - request_options &total_timeout(std::uint64_t total_timeout) { - total_timeout_ = total_timeout; - return *this; - } - - std::uint64_t total_timeout() const { - return total_timeout_; - } - - request_options &max_redirects(int max_redirects) { - max_redirects_ = max_redirects; - return *this; - } - - int max_redirects() const { - return max_redirects_; - } - - private: - - std::uint64_t resolve_timeout_; - std::uint64_t read_timeout_; - std::uint64_t total_timeout_; - int max_redirects_; - - }; - - inline - void swap(request_options &lhs, request_options &rhs) noexcept { - lhs.swap(rhs); - } - - /** - * \ingroup http_client - * \class byte_source network/http/v2/client/request.hpp network/http/v2/client.hpp - * \brief An abstract class that allows a request object to read - * data from any source. - */ - class byte_source { - - public: - - /** - * \typedef string_type - * \brief The byte_source string type. - */ - typedef std::string string_type; - - /** - * \typedef size_type - * \brief The byte_source size type. - */ - typedef std::size_t size_type; - - /** - * \brief Destructor. - */ - virtual ~byte_source() noexcept {} - - /** - * \brief Allows the request to read the data into a local - * copy of its source string. - */ - virtual size_type read(string_type &source, size_type length) = 0; - - }; - - /** - * \ingroup http_client - * \class string_byte_source network/http/v2/client/request.hpp network/http/v2/client.hpp - * \brief A class that wraps a string as a byte source. - */ - class string_byte_source : public byte_source { - - public: - - /** - * \brief Constructor. - */ - explicit string_byte_source(string_type source); - - /** - * \brief Destructor. - */ - virtual ~string_byte_source() {} - - virtual size_type read(string_type &source, size_type length); - - private: - - string_type source_; - - }; - - /** - * \ingroup http_client - * \class request network/http/v2/client/request.hpp network/http/v2/client.hpp - * \brief A class that models an HTTP request. - */ - class request { - - public: - - /** - * \typedef string_type - * \brief The request string type. - */ - typedef byte_source::string_type string_type; - - /** - * \typedef size_type - * \brief The request size type. - */ - typedef byte_source::size_type size_type; - - /** - * \typedef headers_type - * \brief The request headers type. - */ - typedef std::vector> headers_type; - - /** - * \typedef headers_iterator - * \brief The request headers iterator. - */ - typedef headers_type::iterator headers_iterator; - - /** - * \typedef const_headers_iterator - * \brief The request headers const_iterator. - */ - typedef headers_type::const_iterator const_headers_iterator; - - /** - * \brief Constructor. - */ - request() - : byte_source_(nullptr) { } - - /** - * \brief Constructor. - */ - explicit request(uri url) - : url_(url) { - if (auto scheme = url.scheme()) { - if ((!boost::equal(*scheme, boost::as_literal("http"))) && - (!boost::equal(*scheme, boost::as_literal("https")))) { - throw invalid_url(); - } - - if (auto path = url.path()) { - std::copy(std::begin(*path), std::end(*path), - std::back_inserter(path_)); - } - - if (auto query = url.query()) { - path_.push_back('?'); - std::copy(std::begin(*query), std::end(*query), - std::back_inserter(path_)); - } - - if (auto fragment = url.fragment()) { - path_.push_back('#'); - std::copy(std::begin(*fragment), std::end(*fragment), - std::back_inserter(path_)); - } - - std::ostringstream oss; - std::copy(std::begin(*url.host()), std::end(*url.host()), - std::ostream_iterator(oss)); - if (url.port()) { - oss << ":"; - std::copy(std::begin(*url.port()), std::end(*url.port()), - std::ostream_iterator(oss)); - } - append_header("Host", oss.str()); - } - else { - throw invalid_url(); - } - } - - /** - * \brief Copy constructor. - */ - request(const request &other) - : url_(other.url_) - , method_(other.method_) - , path_(other.path_) - , version_(other.version_) - , headers_(other.headers_) - , byte_source_(other.byte_source_) { } - - /** - * \brief Move constructor. - */ - request(request &&other) noexcept - : url_(std::move(other.url_)) - , method_(std::move(other.method_)) - , path_(std::move(other.path_)) - , version_(std::move(other.version_)) - , headers_(std::move(other.headers_)) - , byte_source_(std::move(other.byte_source_)) { } - - /** - * \brief Assignment operator. - */ - request &operator = (request other) { - other.swap(*this); - return *this; - } - - /** - * \brief Destructor. - */ - ~request() noexcept { - - } - - /** - * \brief Swaps one request object with another. - * \param other The other request object. - */ - void swap(request &other) noexcept { - using std::swap; - swap(url_, other.url_); - swap(method_, other.method_); - swap(path_, other.path_); - swap(version_, other.version_); - swap(headers_, other.headers_); - swap(byte_source_, other.byte_source_); - } - - request &url(const uri &url) { - // throw invalid_url - url_ = url; - return *this; - } - - uri url() const { - return url_; - } - - /** - * \brief Checks whether this is an HTTPS request. - * \returns \c true if it is HTTPS, \c false otherwise. - */ - bool is_https() const { - return url_.scheme() && boost::equal(*url_.scheme(), boost::as_literal("https")); - } - - /** - * \brief Sets the HTTP request method. - * \param method The HTTP request method. - * \returns *this - */ - request &method(network::http::v2::method method) { - method_ = method; - return *this; - } - - /** - * \brief Gets the HTTP request method. - * \returns The HTTP request method. - */ - network::http::v2::method method() const { - return method_; - } - - /** - * \brief Sets the HTTP path. - * \param path The HTTP path. - * \returns *this - */ - request &path(string_type path) { - path_ = path; - return *this; - } - - /** - * \brief Gets the HTTP path. - * \returns The HTTP path. - */ - string_type path() const { - return path_; - } - - /** - * \brief Sets the HTTP request version. - * \param version 1.0 or 1.1. - */ - request &version(string_type version) { - version_ = std::move(version); - return *this; - } - - /** - * \brief Gets the HTTP request version. - * \returns The HTTP request version. - */ - string_type version() const { - return version_; - } - - request &body(std::shared_ptr byte_source) { - byte_source_ = byte_source; - return *this; - } - - template - void body(size_type length, Handler &&handler) { - string_type body; - byte_source_->read(body, length); - handler(body); - } - - /** - * \brief Appends a header to the request. - * \param name The header name. - * \param value The header value. - * - * Duplicates are allowed. - */ - request &append_header(string_type name, string_type value) { - headers_.emplace_back(std::make_pair(name, value)); - return *this; - } - - boost::optional header(const string_type &name) { - for (auto header : headers_) { - if (boost::iequals(header.first, name)) { - return header.second; - } - } - return boost::optional(); - } - - const_headers_iterator headers_begin() const { - return std::begin(headers_); - } - - const_headers_iterator headers_end() const { - return std::end(headers_); - } - - /** - * \brief Returns the headers range. - * \returns An iterator range covering all headers. - */ - boost::iterator_range headers() const { - return boost::make_iterator_range(headers_begin(), headers_end()); - } - - /** - * \brief Removes a header from the request. - * \param name The name of the header to be removed. - * - * If the header name can not be found, nothing happens. If - * the header is duplicated, then both entries are removed. - */ - void remove_header(const string_type &name) { - auto it = std::remove_if(std::begin(headers_), std::end(headers_), - [&name] (const std::pair &header) { - return boost::iequals(header.first, name); - }); - headers_.erase(it, std::end(headers_)); - } - - /** - * \brief Clears all HTTP request headers. - */ - void clear_headers() { - headers_type().swap(headers_); - } - - private: - - network::uri url_; - network::http::v2::method method_; - string_type path_; - string_type version_; - headers_type headers_; - std::shared_ptr byte_source_; - - friend std::ostream &operator << (std::ostream &os, const request &req) { - os << req.method_ << " " << req.path_ << " HTTP/" << req.version_ << "\r\n"; - for (auto header : req.headers_) { - os << header.first << ": " << header.second << "\r\n"; - } - return os << "\r\n"; - } - }; - - inline - void swap(request &lhs, request &rhs) noexcept { - lhs.swap(rhs); - } - } // namespace client_message - } // namespace v2 - } // namespace http +namespace http { +inline namespace v2 { +namespace client_message { +enum class transfer_direction { + bytes_written, bytes_read, +}; + +/** + * \ingroup http_client + * \class request_options network/http/v2/client/request.hpp network/http/v2/client.hpp + * \brief A class used to configure an HTTP request. + */ +class request_options { + +public: + + /** + * \brief Constructor. + */ + request_options() + : resolve_timeout_(30000) + , read_timeout_(30000) + , total_timeout_(30000) + , max_redirects_(10) { } + + /** + * \brief Copy constructor. + */ + request_options(request_options const &other) = default; + + /** + * \brief Move constructor. + */ + request_options(request_options &&other) = default; + + /** + * \brief Assignment operator. + */ + request_options &operator = (request_options other) { + other.swap(*this); + return *this; + } + + /** + * \brief Destructor. + */ + ~request_options() = default; + + /** + * \brief + * \param other + */ + void swap(request_options &other) noexcept { + using std::swap; + swap(resolve_timeout_, other.resolve_timeout_); + swap(read_timeout_, other.read_timeout_); + swap(total_timeout_, other.total_timeout_); + } + + /** + * \brief + * \returns + */ + request_options &resolve_timeout(std::uint64_t resolve_timeout) { + resolve_timeout_ = resolve_timeout; + return *this; + } + + /** + * \brief + * \returns + */ + std::uint64_t resolve_timeout() const { + return resolve_timeout_; + } + + /** + * \brief + * \returns + */ + request_options &read_timeout(std::uint64_t read_timeout) { + read_timeout_ = read_timeout; + return *this; + } + + /** + * \brief + * \returns + */ + std::uint64_t read_timeout() const { + return read_timeout_; + } + + /** + * \brief + * \returns + */ + request_options &total_timeout(std::uint64_t total_timeout) { + total_timeout_ = total_timeout; + return *this; + } + + /** + * \brief + * \returns + */ + std::uint64_t total_timeout() const { + return total_timeout_; + } + + /** + * \brief + * \returns + */ + request_options &max_redirects(int max_redirects) { + max_redirects_ = max_redirects; + return *this; + } + + /** + * \brief + * \returns + */ + int max_redirects() const { + return max_redirects_; + } + + /** + * \brief + * \returns + */ + request_options &progress(std::function handler) { + progress_handler_ = handler; + return *this; + } + + /** + * \brief + * \returns + */ + std::function progress() const { + return progress_handler_; + } + +private: + + std::uint64_t resolve_timeout_; + std::uint64_t read_timeout_; + std::uint64_t total_timeout_; + int max_redirects_; + std::function progress_handler_; + +}; + +/** + * \brief + * \param lhs + * \param rhs + */ +inline +void swap(request_options &lhs, request_options &rhs) noexcept { + lhs.swap(rhs); +} + +/** + * \ingroup http_client + * \class byte_source network/http/v2/client/request.hpp network/http/v2/client.hpp + * \brief An abstract class that allows a request object to read + * data from any source. + */ +class byte_source { + +public: + + /** + * \typedef string_type + * \brief The byte_source string type. + */ + typedef std::string string_type; + + /** + * \typedef size_type + * \brief The byte_source size type. + */ + typedef std::size_t size_type; + + /** + * \brief Destructor. + */ + virtual ~byte_source() {} + + /** + * \brief Allows the request to read the data into a local + * copy of its source string. + */ + virtual size_type read(string_type &source, size_type length) = 0; + +}; + +/** + * \ingroup http_client + * \class string_byte_source network/http/v2/client/request.hpp network/http/v2/client.hpp + * \brief A class that wraps a string as a byte source. + */ +class string_byte_source : public byte_source { + +public: + + /** + * \brief Constructor. + */ + explicit string_byte_source(string_type source); + + /** + * \brief Destructor. + */ + virtual ~string_byte_source() {} + + virtual size_type read(string_type &source, size_type length); + +private: + + string_type source_; + +}; + +/** + * \ingroup http_client + * \class request network/http/v2/client/request.hpp network/http/v2/client.hpp + * \brief A class that models an HTTP request. + */ +class request { + +public: + + /** + * \typedef string_type + * \brief The request string type. + */ + typedef byte_source::string_type string_type; + + /** + * \typedef size_type + * \brief The request size type. + */ + typedef byte_source::size_type size_type; + + /** + * \typedef headers_type + * \brief The request headers type. + */ + typedef std::vector> headers_type; + + /** + * \typedef headers_iterator + * \brief The request headers iterator. + */ + typedef headers_type::iterator headers_iterator; + + /** + * \typedef const_headers_iterator + * \brief The request headers const_iterator. + */ + typedef headers_type::const_iterator const_headers_iterator; + + /** + * \brief Constructor. + */ + request() = default; + + /** + * \brief Constructor. + */ + explicit request(uri url) { + this->url(url); + } + + /** + * \brief Copy constructor. + */ + request(const request &other) = default; + + /** + * \brief Move constructor. + */ + request(request &&other) noexcept = default; + + /** + * \brief Destructor. + */ + ~request() = default; + + /** + * \brief Swaps one request object with another. + * \param other The other request object. + */ + void swap(request &other) noexcept { + using std::swap; + swap(url_, other.url_); + swap(method_, other.method_); + swap(path_, other.path_); + swap(version_, other.version_); + swap(headers_, other.headers_); + swap(byte_source_, other.byte_source_); + } + + /** + * \brief + * \returns + */ + request &url(const uri &url) { + if (auto scheme = url.scheme()) { + if ((!boost::equal(*scheme, boost::as_literal("http"))) && + (!boost::equal(*scheme, boost::as_literal("https")))) { + throw invalid_url(); + } + + if (auto path = url.path()) { + std::copy(std::begin(*path), std::end(*path), + std::back_inserter(path_)); + } + + if (auto query = url.query()) { + path_.push_back('?'); + std::copy(std::begin(*query), std::end(*query), + std::back_inserter(path_)); + } + + if (auto fragment = url.fragment()) { + path_.push_back('#'); + std::copy(std::begin(*fragment), std::end(*fragment), + std::back_inserter(path_)); + } + + std::ostringstream oss; + std::copy(std::begin(*url.host()), std::end(*url.host()), + std::ostream_iterator(oss)); + if (url.port()) { + oss << ":"; + std::copy(std::begin(*url.port()), std::end(*url.port()), + std::ostream_iterator(oss)); + } + append_header("Host", oss.str()); + } + else { + throw invalid_url(); + } + url_ = url; + return *this; + } + + /** + * \brief + * \returns + */ + uri url() const { + return url_; + } + + /** + * \brief Checks whether this is an HTTPS request. + * \returns \c true if it is HTTPS, \c false otherwise. + */ + bool is_https() const { + return url_.scheme() && boost::equal(*url_.scheme(), boost::as_literal("https")); + } + + /** + * \brief Sets the HTTP request method. + * \param method The HTTP request method. + * \returns *this + */ + request &method(network::http::v2::method method) { + method_ = method; + return *this; + } + + /** + * \brief Gets the HTTP request method. + * \returns The HTTP request method. + */ + network::http::v2::method method() const { + return method_; + } + + /** + * \brief Sets the HTTP path. + * \param path The HTTP path. + * \returns *this + */ + request &path(string_type path) { + path_ = path; + return *this; + } + + /** + * \brief Gets the HTTP path. + * \returns The HTTP path. + */ + string_type path() const { + return path_; + } + + /** + * \brief Sets the HTTP request version. + * \param version 1.0 or 1.1. + */ + request &version(string_type version) { + version_ = std::move(version); + return *this; + } + + /** + * \brief Gets the HTTP request version. + * \returns The HTTP request version. + */ + string_type version() const { + return version_; + } + + /** + * \brief + * \returns + */ + request &body(std::shared_ptr byte_source) { + byte_source_ = byte_source; + return *this; + } + + /** + * \brief + * \returns + */ + template + void body(size_type length, Handler &&handler) { + string_type body; + byte_source_->read(body, length); + handler(body); + } + + /** + * \brief Appends a header to the request. + * \param name The header name. + * \param value The header value. + * + * Duplicates are allowed. + */ + request &append_header(string_type name, string_type value) { + headers_.emplace_back(name, value); + return *this; + } + + /** + * \brief + * \returns + */ + boost::optional header(const string_type &name) { + for (auto header : headers_) { + if (boost::iequals(header.first, name)) { + return header.second; + } + } + return boost::optional(); + } + + /** + * \brief + * \returns + */ + const_headers_iterator headers_begin() const { + return std::begin(headers_); + } + + /** + * \brief + * \returns + */ + const_headers_iterator headers_end() const { + return std::end(headers_); + } + + /** + * \brief Returns the headers range. + * \returns An iterator range covering all headers. + */ + boost::iterator_range headers() const { + return boost::make_iterator_range(headers_begin(), headers_end()); + } + + /** + * \brief Removes a header from the request. + * \param name The name of the header to be removed. + * + * If the header name can not be found, nothing happens. If + * the header is duplicated, then both entries are removed. + */ + void remove_header(const string_type &name) { + auto it = std::remove_if(std::begin(headers_), std::end(headers_), + [&name] (const std::pair &header) { + return boost::iequals(header.first, name); + }); + headers_.erase(it, std::end(headers_)); + } + + /** + * \brief Clears all HTTP request headers. + */ + void clear_headers() { + headers_type().swap(headers_); + } + +private: + + network::uri url_; + network::http::v2::method method_; + string_type path_; + string_type version_; + headers_type headers_; + std::shared_ptr byte_source_; + + friend std::ostream &operator << (std::ostream &os, const request &req) { + os << req.method_ << " " << req.path_ << " HTTP/" << req.version_ << "\r\n"; + for (auto header : req.headers_) { + os << header.first << ": " << header.second << "\r\n"; + } + return os << "\r\n"; + } +}; + +/** + * \brief + * \returns + */ +inline +void swap(request &lhs, request &rhs) noexcept { + lhs.swap(rhs); +} +} // namespace client_message +} // namespace v2 +} // namespace http } // namespace network #endif // NETWORK_HTTP_V2_CLIENT_REQUEST_INC diff --git a/http/src/network/http/v2/client/response.hpp b/http/src/network/http/v2/client/response.hpp index b2183e864..21cf37c0c 100644 --- a/http/src/network/http/v2/client/response.hpp +++ b/http/src/network/http/v2/client/response.hpp @@ -24,194 +24,187 @@ #include namespace network { - namespace http { - namespace v2 { - namespace client_message { - /** - * \ingroup http_client - * \class response network/http/v2/client/response.hpp - * \brief A class that encapsulates an HTTP response. - */ - class response { - - public: - - /** - * \typedef string_type - * \brief The response string_type. - */ - typedef std::string string_type; - - /** - * \typedef headers_type - * \brief The response headers type. - */ - typedef std::vector> headers_type; - - /** - * \typedef headers_iterator - * \brief The response headers iterator. - */ - typedef headers_type::iterator headers_iterator; - - /** - * \typedef const_headers_iterator - * \brief The response headers const_iterator. - */ - typedef headers_type::const_iterator const_headers_iterator; - - /** - * \brief Constructor. - */ - response() { } - - /** - * \brief Copy constructor. - * \param other The other response object. - */ - response(const response &other) - : version_(other.version_) - , status_(other.status_) - , status_message_(other.status_message_) - , headers_(other.headers_) - , body_(other.body_) { - - } - - /** - * \brief Move constructor. - * \param other The other response object. - */ - response(response &&other) noexcept - : version_(std::move(other.version_)) - , status_(std::move(other.status_)) - , status_message_(std::move(other.status_message_)) - , headers_(std::move(other.headers_)) - , body_(std::move(other.body_)) { - - } - - /** - * \brief Assignment operator. - * \param other The other response object. - */ - response &operator= (response other) { - other.swap(*this); - return *this; - } - - /** - * \brief Swap function. - * \param other The other response object. - */ - void swap(response &other) noexcept { - using std::swap; - swap(version_, other.version_); - swap(status_, other.status_); - swap(status_message_, other.status_message_); - swap(headers_, other.headers_); - swap(body_, other.body_); - } - - /** - * \brief Sets the HTTP version. - * \param version The HTTP version (1.0 or 1.1). - */ - void set_version(string_type version) { - version_ = version; - } - - /** - * \brief Returns the HTTP version. - * \returns The HTTP version. - */ - string_type version() const { - return version_; - } - - /** - * \brief Sets the HTTP response status code. - * \param status The HTTP response status code. - */ - void set_status(network::http::v2::status::code status) { - status_ = status; - } - - /** - * \brief Returns the HTTP response status. - * \returns The status code. - */ - network::http::v2::status::code status() const { - return status_; - } - - /** - * \brief Sets the HTTP response status message. - * \param status The HTTP response status message. - */ - void set_status_message(string_type status_message) { - status_message_ = status_message; - } - - /** - * \brief Returns the HTTP response status message. - * \returns The status message. - */ - string_type status_message() const { - return status_message_; - } - - /** - * \brief Adds a header to the HTTP response. - * \param name The header name. - * \param value The header value. - */ - void add_header(const string_type &name, const string_type &value) { - headers_.push_back(std::make_pair(name, value)); - } - - const_headers_iterator headers_begin() const { - return std::begin(headers_); - } - - const_headers_iterator headers_end() const { - return std::end(headers_); - } - - /** - * \brief Returns the full range of headers. - * \returns An iterator range covering the HTTP response - * headers. - */ - boost::iterator_range headers() const { - return boost::make_iterator_range(headers_begin(), headers_end()); - } - - void append_body(string_type body) { - body_.append(body); - } - - string_type body() const { - return body_; - } - - private: - - string_type version_; - network::http::v2::status::code status_; - string_type status_message_; - headers_type headers_; - string_type body_; - - }; - - inline - void swap(response &lhs, response &rhs) noexcept { - lhs.swap(rhs); - } - } // namespace client_message - } // namespace v2 - } // namespace http +namespace http { +inline namespace v2 { +namespace client_message { +/** + * \ingroup http_client + * \class response network/http/v2/client/response.hpp + * \brief A class that encapsulates an HTTP response. + */ +class response { + +public: + + /** + * \typedef string_type + * \brief The response string_type. + */ + typedef std::string string_type; + + /** + * \typedef headers_type + * \brief The response headers type. + */ + typedef std::vector> headers_type; + + /** + * \typedef headers_iterator + * \brief The response headers iterator. + */ + typedef headers_type::iterator headers_iterator; + + /** + * \typedef const_headers_iterator + * \brief The response headers const_iterator. + */ + typedef headers_type::const_iterator const_headers_iterator; + + /** + * \brief Constructor. + */ + response() = default; + + /** + * \brief Copy constructor. + * \param other The other response object. + */ + response(const response &other) = default; + + /** + * \brief Move constructor. + * \param other The other response object. + */ + response(response &&other) noexcept = default; + + /** + * \brief Swap function. + * \param other The other response object. + */ + void swap(response &other) noexcept { + using std::swap; + swap(version_, other.version_); + swap(status_, other.status_); + swap(status_message_, other.status_message_); + swap(headers_, other.headers_); + swap(body_, other.body_); + } + + /** + * \brief Sets the HTTP version. + * \param version The HTTP version (1.0 or 1.1). + */ + void set_version(string_type version) { + version_ = version; + } + + /** + * \brief Returns the HTTP version. + * \returns The HTTP version. + */ + string_type version() const { + return version_; + } + + /** + * \brief Sets the HTTP response status code. + * \param status The HTTP response status code. + */ + void set_status(network::http::v2::status::code status) { + status_ = status; + } + + /** + * \brief Returns the HTTP response status. + * \returns The status code. + */ + network::http::v2::status::code status() const { + return status_; + } + + /** + * \brief Sets the HTTP response status message. + * \param status The HTTP response status message. + */ + void set_status_message(string_type status_message) { + status_message_ = status_message; + } + + /** + * \brief Returns the HTTP response status message. + * \returns The status message. + */ + string_type status_message() const { + return status_message_; + } + + /** + * \brief Adds a header to the HTTP response. + * \param name The header name. + * \param value The header value. + */ + void add_header(string_type name, string_type value) { + headers_.emplace_back(name, value); + } + + /** + * \brief + * \returns + */ + const_headers_iterator headers_begin() const { + return std::begin(headers_); + } + + /** + * \brief + * \returns + */ + const_headers_iterator headers_end() const { + return std::end(headers_); + } + + /** + * \brief Returns the full range of headers. + * \returns An iterator range covering the HTTP response + * headers. + */ + boost::iterator_range headers() const { + return boost::make_iterator_range(headers_begin(), headers_end()); + } + + /** + * \brief + * \param body + */ + void append_body(string_type body) { + body_.append(body); + } + + /** + * \brief + * \returns + */ + string_type body() const { + return body_; + } + +private: + + string_type version_; + network::http::v2::status::code status_; + string_type status_message_; + headers_type headers_; + string_type body_; + +}; + +inline +void swap(response &lhs, response &rhs) noexcept { + lhs.swap(rhs); +} +} // namespace client_message +} // namespace v2 +} // namespace http } // namespace network #endif // NETWORK_HTTP_V2_CLIENT_RESPONSE_INC diff --git a/http/src/network/http/v2/method.hpp b/http/src/network/http/v2/method.hpp index d31fdb5a9..0f20bcf46 100644 --- a/http/src/network/http/v2/method.hpp +++ b/http/src/network/http/v2/method.hpp @@ -15,38 +15,38 @@ #include namespace network { - namespace http { - namespace v2 { - enum class method { get, post, put, delete_, head, options, trace, connect, merge, patch, }; +namespace http { +inline namespace v2 { +enum class method { get, post, put, delete_, head, options, trace, connect, merge, patch, }; - inline - std::ostream &operator << (std::ostream &os, method m) { - switch (m) { - case method::get: - return os << "GET"; - case method::post: - return os << "POST"; - case method::put: - return os << "PUT"; - case method::delete_: - return os << "DELETE"; - case method::head: - return os << "HEAD"; - case method::options: - return os << "OPTIONS"; - case method::trace: - return os << "TRACE"; - case method::connect: - return os << "CONNECT"; - case method::merge: - return os << "MERGE"; - case method::patch: - return os << "PATCH"; - } - return os; - } - } // namespace v2 - } // namespace http +inline +std::ostream &operator << (std::ostream &os, method m) { + switch (m) { + case method::get: + return os << "GET"; + case method::post: + return os << "POST"; + case method::put: + return os << "PUT"; + case method::delete_: + return os << "DELETE"; + case method::head: + return os << "HEAD"; + case method::options: + return os << "OPTIONS"; + case method::trace: + return os << "TRACE"; + case method::connect: + return os << "CONNECT"; + case method::merge: + return os << "MERGE"; + case method::patch: + return os << "PATCH"; + } + return os; +} +} // namespace v2 +} // namespace http } // namespace network diff --git a/http/src/network/http/v2/status.hpp b/http/src/network/http/v2/status.hpp index 6dab5710e..b93ae91cc 100644 --- a/http/src/network/http/v2/status.hpp +++ b/http/src/network/http/v2/status.hpp @@ -19,155 +19,155 @@ #include namespace network { - namespace http { - namespace v2 { - namespace status { - /** - * \ingroup http - * \enum code - * \brief Provides the full set of HTTP status codes. - */ - enum class code { - // informational - continue_ = 100, - switch_protocols = 101, +namespace http { +inline namespace v2 { +namespace status { +/** + * \ingroup http + * \enum code + * \brief Provides the full set of HTTP status codes. + */ +enum class code { + // informational + continue_ = 100, + switch_protocols = 101, - // successful - ok = 200, - created = 201, - accepted = 202, - non_auth_info = 203, - no_content = 204, - reset_content = 205, - partial_content = 206, + // successful + ok = 200, + created = 201, + accepted = 202, + non_auth_info = 203, + no_content = 204, + reset_content = 205, + partial_content = 206, - // redirection - multiple_choices = 300, - moved_permanently = 301, - found = 302, - see_other = 303, - not_modified = 304, - use_proxy = 305, - temporary_redirect = 307, + // redirection + multiple_choices = 300, + moved_permanently = 301, + found = 302, + see_other = 303, + not_modified = 304, + use_proxy = 305, + temporary_redirect = 307, - // client error - bad_request = 400, - unauthorized = 401, - payment_required = 402, - forbidden = 403, - not_found = 404, - method_not_allowed = 405, - not_acceptable = 406, - proxy_auth_required = 407, - request_timeout = 408, - conflict = 409, - gone = 410, - length_required = 411, - precondition_failed = 412, - request_entity_too_large = 413, - request_uri_too_long = 414, - unsupported_media_type = 415, - request_range_not_satisfiable = 416, - expectation_failed = 417, - precondition_required = 428, - too_many_requests = 429, - request_header_fields_too_large = 431, + // client error + bad_request = 400, + unauthorized = 401, + payment_required = 402, + forbidden = 403, + not_found = 404, + method_not_allowed = 405, + not_acceptable = 406, + proxy_auth_required = 407, + request_timeout = 408, + conflict = 409, + gone = 410, + length_required = 411, + precondition_failed = 412, + request_entity_too_large = 413, + request_uri_too_long = 414, + unsupported_media_type = 415, + request_range_not_satisfiable = 416, + expectation_failed = 417, + precondition_required = 428, + too_many_requests = 429, + request_header_fields_too_large = 431, - // server error - internal_error = 500, - not_implemented = 501, - bad_gateway = 502, - service_unavailable = 503, - gateway_timeout = 504, - http_version_not_supported = 505, - network_authentication_required = 511, - }; - } // namespace status - } // namespace v2 - } // namespace http + // server error + internal_error = 500, + not_implemented = 501, + bad_gateway = 502, + service_unavailable = 503, + gateway_timeout = 504, + http_version_not_supported = 505, + network_authentication_required = 511, + }; +} // namespace status +} // namespace v2 +} // namespace http } // namespace network #if !defined(DOXYGEN_SHOULD_SKIP_THIS) namespace std { - template <> - struct hash { - std::size_t operator()(network::http::v2::status::code status_code) const { - hash hasher; - return hasher(static_cast(status_code)); - } - }; +template <> +struct hash { + std::size_t operator()(network::http::v2::status::code status_code) const { + hash hasher; + return hasher(static_cast(status_code)); + } +}; } // namespace std #endif // !defined(DOXYGEN_SHOULD_SKIP_THIS) namespace network { - namespace http { - namespace v2 { - namespace status { - /** - * \ingroup http_client - * \fn - * \brief Returns a message based on the status code provided. - * \param code The status code. - * \returns The corresponding status message. - */ - inline - std::string message(code status_code) { - static std::unordered_map status_messages{ - {code::continue_, "Continue"}, - {code::switch_protocols, "Switching Protocols"}, - {code::ok, "OK"}, - {code::created, "Created"}, - {code::accepted, "Accepted"}, - {code::non_auth_info, "Non-Authoritative Information"}, - {code::no_content, "No Content"}, - {code::reset_content, "Reset Content"}, - {code::partial_content, "Partial Content"}, - {code::multiple_choices, "Multiple Choices"}, - {code::moved_permanently, "Moved Permanently"}, - {code::found, "Found"}, - {code::see_other, "See Other"}, - {code::not_modified, "Not Modified"}, - {code::use_proxy, "Use Proxy"}, - {code::temporary_redirect, "Temporary Redirect"}, - {code::bad_request, "Bad Request"}, - {code::unauthorized, "Unauthorized"}, - {code::payment_required, "Payment Required"}, - {code::forbidden, "Forbidden"}, - {code::not_found, "Not Found"}, - {code::method_not_allowed, "Method Not Allowed"}, - {code::not_acceptable, "Not Acceptable"}, - {code::proxy_auth_required, "Proxy Authentication Required"}, - {code::request_timeout, "Request Timeout"}, - {code::conflict, "Conflict"}, - {code::gone, "Gone"}, - {code::length_required, "Length Required"}, - {code::precondition_failed, "Precondition Failed"}, - {code::request_entity_too_large, "Request Entity Too Large"}, - {code::request_uri_too_long, "Request Uri Too Long"}, - {code::unsupported_media_type, "Unsupported Media Type"}, - {code::request_range_not_satisfiable, "Request Range Not Satisfiable"}, - {code::expectation_failed, "Expectation Failed"}, - {code::precondition_required, "Precondition Required"}, - {code::too_many_requests, "Too Many Requests"}, - {code::request_header_fields_too_large, "Request Header Fields Too Large"}, - {code::internal_error, "Internal Error"}, - {code::not_implemented, "Not Implemented"}, - {code::bad_gateway, "Bad Gateway"}, - {code::service_unavailable, "Service Unavailable"}, - {code::gateway_timeout, "Gateway Timeout"}, - {code::http_version_not_supported, "HTTP Version Not Supported"}, - {code::network_authentication_required, "Network Authentication Required"}, - }; +namespace http { +inline namespace v2 { +namespace status { +/** + * \ingroup http_client + * \fn + * \brief Returns a message based on the status code provided. + * \param code The status code. + * \returns The corresponding status message. + */ +inline +std::string message(code status_code) { + static std::unordered_map status_messages{ + {code::continue_, "Continue"}, + {code::switch_protocols, "Switching Protocols"}, + {code::ok, "OK"}, + {code::created, "Created"}, + {code::accepted, "Accepted"}, + {code::non_auth_info, "Non-Authoritative Information"}, + {code::no_content, "No Content"}, + {code::reset_content, "Reset Content"}, + {code::partial_content, "Partial Content"}, + {code::multiple_choices, "Multiple Choices"}, + {code::moved_permanently, "Moved Permanently"}, + {code::found, "Found"}, + {code::see_other, "See Other"}, + {code::not_modified, "Not Modified"}, + {code::use_proxy, "Use Proxy"}, + {code::temporary_redirect, "Temporary Redirect"}, + {code::bad_request, "Bad Request"}, + {code::unauthorized, "Unauthorized"}, + {code::payment_required, "Payment Required"}, + {code::forbidden, "Forbidden"}, + {code::not_found, "Not Found"}, + {code::method_not_allowed, "Method Not Allowed"}, + {code::not_acceptable, "Not Acceptable"}, + {code::proxy_auth_required, "Proxy Authentication Required"}, + {code::request_timeout, "Request Timeout"}, + {code::conflict, "Conflict"}, + {code::gone, "Gone"}, + {code::length_required, "Length Required"}, + {code::precondition_failed, "Precondition Failed"}, + {code::request_entity_too_large, "Request Entity Too Large"}, + {code::request_uri_too_long, "Request Uri Too Long"}, + {code::unsupported_media_type, "Unsupported Media Type"}, + {code::request_range_not_satisfiable, "Request Range Not Satisfiable"}, + {code::expectation_failed, "Expectation Failed"}, + {code::precondition_required, "Precondition Required"}, + {code::too_many_requests, "Too Many Requests"}, + {code::request_header_fields_too_large, "Request Header Fields Too Large"}, + {code::internal_error, "Internal Error"}, + {code::not_implemented, "Not Implemented"}, + {code::bad_gateway, "Bad Gateway"}, + {code::service_unavailable, "Service Unavailable"}, + {code::gateway_timeout, "Gateway Timeout"}, + {code::http_version_not_supported, "HTTP Version Not Supported"}, + {code::network_authentication_required, "Network Authentication Required"}, + }; - auto it = status_messages.find(status_code); - if (it != status_messages.end()) { - return it->second; - } - return "Invalid Status Code"; - } - } // namespace status - } // namespace v2 - } // namespace http + auto it = status_messages.find(status_code); + if (it != status_messages.end()) { + return it->second; + } + return "Invalid Status Code"; +} +} // namespace status +} // namespace v2 +} // namespace http } // namespace network diff --git a/http/test/v2/client/units/client_resolution_test.cpp b/http/test/v2/client/units/client_resolution_test.cpp index 4f6683b2b..689d0f9e7 100644 --- a/http/test/v2/client/units/client_resolution_test.cpp +++ b/http/test/v2/client/units/client_resolution_test.cpp @@ -51,6 +51,8 @@ class fake_async_connection : public http_cc::async_connection { virtual void async_read(boost::asio::streambuf &, read_callback) { } + virtual void disconnect() { } + virtual void cancel() { } }; diff --git a/http/test/v2/client/units/request_options_test.cpp b/http/test/v2/client/units/request_options_test.cpp index fc8e2e94c..9fb0d9aab 100644 --- a/http/test/v2/client/units/request_options_test.cpp +++ b/http/test/v2/client/units/request_options_test.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2013 by Glyn Matthews +// Copyright (C) 2013, 2014 by Glyn Matthews // 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) @@ -34,3 +34,13 @@ TEST(request_options_test, set_max_redirects) { opts.max_redirects(5); ASSERT_EQ(5, opts.max_redirects()); } + +TEST(request_options_test, set_progress_handler) { + std::uint64_t bytes = 0; + http_cm::request_options opts; + opts.progress([&bytes] (http_cm::transfer_direction direction, std::uint64_t bytes_) { + bytes = bytes_; + }); + opts.progress()(http_cm::transfer_direction::bytes_written, 42); + ASSERT_EQ(42, bytes); +} diff --git a/uri b/uri index de3ac1491..9567e16a5 160000 --- a/uri +++ b/uri @@ -1 +1 @@ -Subproject commit de3ac1491e3b66392f6a771a478935155e25f01e +Subproject commit 9567e16a5936bf0ebcea8b33d56863251dc94e9b