diff --git a/boost/network/protocol/http/client/async_impl.hpp b/boost/network/protocol/http/client/async_impl.hpp index 9d0c9c5b4..768cdc923 100644 --- a/boost/network/protocol/http/client/async_impl.hpp +++ b/boost/network/protocol/http/client/async_impl.hpp @@ -37,13 +37,13 @@ struct async_client typedef function body_generator_function_type; async_client(bool cache_resolved, bool follow_redirect, - bool always_verify_peer, + bool always_verify_peer, int timeout, boost::shared_ptr service, optional const& certificate_filename, optional const& verify_path, optional const& certificate_file, optional const& private_key_file) - : connection_base(cache_resolved, follow_redirect), + : connection_base(cache_resolved, follow_redirect, timeout), service_ptr(service.get() ? service : boost::make_shared()), diff --git a/boost/network/protocol/http/client/connection/async_base.hpp b/boost/network/protocol/http/client/connection/async_base.hpp index 0f6c244a6..fa2fa0df6 100644 --- a/boost/network/protocol/http/client/connection/async_base.hpp +++ b/boost/network/protocol/http/client/connection/async_base.hpp @@ -39,6 +39,7 @@ namespace boost { namespace network { namespace http { namespace impl { bool follow_redirect, bool always_verify_peer, bool https, + int timeout, optional certificate_filename=optional(), optional const & verify_path=optional(), optional certificate_file=optional(), @@ -52,6 +53,7 @@ namespace boost { namespace network { namespace http { namespace impl { resolver, resolve, follow_redirect, + timeout, delegate_factory_type::new_connection_delegate( resolver.get_io_service(), https, diff --git a/boost/network/protocol/http/client/connection/async_normal.hpp b/boost/network/protocol/http/client/connection/async_normal.hpp index d2219183d..c6b2fcd43 100644 --- a/boost/network/protocol/http/client/connection/async_normal.hpp +++ b/boost/network/protocol/http/client/connection/async_normal.hpp @@ -68,8 +68,11 @@ struct http_async_connection connection_delegate_ptr; http_async_connection(resolver_type& resolver, resolve_function resolve, - bool follow_redirect, connection_delegate_ptr delegate) - : follow_redirect_(follow_redirect), + bool follow_redirect, int timeout, + connection_delegate_ptr delegate) + : timeout_(timeout), + timer_(resolver.get_io_service()), + follow_redirect_(follow_redirect), resolver_(resolver), resolve_(resolve), request_strand_(resolver.get_io_service()), @@ -92,6 +95,13 @@ struct http_async_connection request_strand_.wrap(boost::bind( &this_type::handle_resolved, this_type::shared_from_this(), port_, get_body, callback, generator, _1, _2))); + if (timeout_ > 0) { + timer_.expires_from_now(boost::posix_time::seconds(timeout_)); + timer_.async_wait(request_strand_.wrap( + boost::bind(&this_type::handle_timeout, + this_type::shared_from_this(), + _1))); + } return response_; } @@ -107,6 +117,11 @@ struct http_async_connection this->source_promise.set_exception(boost::copy_exception(error)); this->destination_promise.set_exception(boost::copy_exception(error)); this->body_promise.set_exception(boost::copy_exception(error)); + this->timer_.cancel(); + } + + void handle_timeout(boost::system::error_code const &ec) { + if (!ec) delegate_->disconnect(); } void handle_resolved(boost::uint16_t port, bool get_body, @@ -348,6 +363,7 @@ struct http_async_connection this->source_promise.set_value(""); this->part.assign('\0'); this->response_parser_.reset(); + this->timer_.cancel(); } else { // This means the connection has not been closed yet and we want // to get more @@ -436,6 +452,8 @@ struct http_async_connection return body; } + int timeout_; + boost::asio::deadline_timer timer_; bool follow_redirect_; resolver_type& resolver_; resolve_function resolve_; diff --git a/boost/network/protocol/http/client/connection/connection_delegate.hpp b/boost/network/protocol/http/client/connection/connection_delegate.hpp index 0e18b231c..23b244c79 100644 --- a/boost/network/protocol/http/client/connection/connection_delegate.hpp +++ b/boost/network/protocol/http/client/connection/connection_delegate.hpp @@ -16,6 +16,7 @@ struct connection_delegate { function handler) = 0; virtual void read_some(asio::mutable_buffers_1 const & read_buffer, function handler) = 0; + virtual void disconnect() = 0; virtual ~connection_delegate() {} }; diff --git a/boost/network/protocol/http/client/connection/normal_delegate.hpp b/boost/network/protocol/http/client/connection/normal_delegate.hpp index d18f1461a..3fe7b894d 100644 --- a/boost/network/protocol/http/client/connection/normal_delegate.hpp +++ b/boost/network/protocol/http/client/connection/normal_delegate.hpp @@ -22,6 +22,7 @@ struct normal_delegate : connection_delegate { function handler); virtual void read_some(asio::mutable_buffers_1 const & read_buffer, function handler); + virtual void disconnect(); ~normal_delegate(); private: diff --git a/boost/network/protocol/http/client/connection/normal_delegate.ipp b/boost/network/protocol/http/client/connection/normal_delegate.ipp index f084dc163..144f2b143 100644 --- a/boost/network/protocol/http/client/connection/normal_delegate.ipp +++ b/boost/network/protocol/http/client/connection/normal_delegate.ipp @@ -37,6 +37,16 @@ void boost::network::http::impl::normal_delegate::read_some( socket_->async_read_some(read_buffer, handler); } +void boost::network::http::impl::normal_delegate::disconnect() { + if (socket_.get() && socket_->is_open()) { + boost::system::error_code ignored; + socket_->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored); + if (!ignored) { + socket_->close(ignored); + } + } +} + boost::network::http::impl::normal_delegate::~normal_delegate() {} #endif /* BOOST_NETWORK_PROTOCOL_HTTP_CLIENT_CONNECTION_NORMAL_DELEGATE_IPP_20110819 */ diff --git a/boost/network/protocol/http/client/connection/ssl_delegate.hpp b/boost/network/protocol/http/client/connection/ssl_delegate.hpp index 26cab7666..a1351edbc 100644 --- a/boost/network/protocol/http/client/connection/ssl_delegate.hpp +++ b/boost/network/protocol/http/client/connection/ssl_delegate.hpp @@ -36,6 +36,7 @@ struct ssl_delegate : connection_delegate, virtual void read_some( asio::mutable_buffers_1 const &read_buffer, function handler); + virtual void disconnect(); ~ssl_delegate(); private: diff --git a/boost/network/protocol/http/client/connection/ssl_delegate.ipp b/boost/network/protocol/http/client/connection/ssl_delegate.ipp index 23cd199b8..83c874382 100755 --- a/boost/network/protocol/http/client/connection/ssl_delegate.ipp +++ b/boost/network/protocol/http/client/connection/ssl_delegate.ipp @@ -78,6 +78,16 @@ void boost::network::http::impl::ssl_delegate::read_some( socket_->async_read_some(read_buffer, handler); } +void boost::network::http::impl::ssl_delegate::disconnect() { + if (socket_.get() && socket_->lowest_layer().is_open()) { + boost::system::error_code ignored; + socket_->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored); + if (!ignored) { + socket_->lowest_layer().close(ignored); + } + } +} + boost::network::http::impl::ssl_delegate::~ssl_delegate() {} #endif /* BOOST_NETWORK_PROTOCOL_HTTP_CLIENT_CONNECTION_SSL_DELEGATE_IPP_20110819 \ diff --git a/boost/network/protocol/http/client/connection/sync_base.hpp b/boost/network/protocol/http/client/connection/sync_base.hpp index 96411762e..fe7d10492 100644 --- a/boost/network/protocol/http/client/connection/sync_base.hpp +++ b/boost/network/protocol/http/client/connection/sync_base.hpp @@ -239,7 +239,7 @@ struct sync_connection_base { // ranges static sync_connection_base* new_connection(resolver_type& resolver, resolver_function_type resolve, - bool https, bool always_verify_peer, + bool https, bool always_verify_peer, int timeout, optional const& certificate_filename = optional(), optional const& verify_path = @@ -253,7 +253,7 @@ struct sync_connection_base { return dynamic_cast< sync_connection_base*>( new https_sync_connection( - resolver, resolve, + resolver, resolve, always_verify_peer, timeout, certificate_filename, verify_path, certificate_file, private_key_file)); #else @@ -263,7 +263,8 @@ struct sync_connection_base { return dynamic_cast< sync_connection_base*>( new http_sync_connection(resolver, - resolve)); + resolve, + timeout)); } virtual void init_socket(string_type const& hostname, diff --git a/boost/network/protocol/http/client/connection/sync_normal.hpp b/boost/network/protocol/http/client/connection/sync_normal.hpp index 3f66afbb1..88694d56d 100644 --- a/boost/network/protocol/http/client/connection/sync_normal.hpp +++ b/boost/network/protocol/http/client/connection/sync_normal.hpp @@ -25,7 +25,8 @@ struct sync_connection_base; template struct http_sync_connection : public virtual sync_connection_base, - sync_connection_base_impl { + sync_connection_base_impl, + boost::enable_shared_from_this > { typedef typename resolver_policy::type resolver_base; typedef typename resolver_base::resolver_type resolver_type; typedef typename string::type string_type; @@ -33,12 +34,17 @@ struct http_sync_connection resolver_type&, string_type const&, string_type const&)> resolver_function_type; + typedef http_sync_connection this_type; typedef sync_connection_base_impl connection_base; typedef function body_generator_function_type; - http_sync_connection(resolver_type& resolver, resolver_function_type resolve) + http_sync_connection(resolver_type& resolver, + resolver_function_type resolve, + int timeout) : connection_base(), + timeout_(timeout), + timer_(resolver.get_io_service()), resolver_(resolver), resolve_(resolve), socket_(resolver.get_io_service()) {} @@ -69,6 +75,12 @@ struct http_sync_connection connection_base::send_request_impl(socket_, method, request_buffer); } } + if (timeout_ > 0) { + timer_.expires_from_now(boost::posix_time::seconds(timeout_)); + timer_.async_wait(boost::bind(&this_type::handle_timeout, + this_type::shared_from_this(), + _1)); + } } void read_status(basic_response& response_, @@ -97,6 +109,7 @@ struct http_sync_connection bool is_open() { return socket_.is_open(); } void close_socket() { + timer_.cancel(); if (!is_open()) return; boost::system::error_code ignored; @@ -107,7 +120,12 @@ struct http_sync_connection } private: + void handle_timeout(boost::system::error_code const &ec) { + if (!ec) close_socket(); + } + int timeout_; + boost::asio::deadline_timer timer_; resolver_type& resolver_; resolver_function_type resolve_; boost::asio::ip::tcp::socket socket_; diff --git a/boost/network/protocol/http/client/connection/sync_ssl.hpp b/boost/network/protocol/http/client/connection/sync_ssl.hpp index 8c9b78851..b146d3043 100644 --- a/boost/network/protocol/http/client/connection/sync_ssl.hpp +++ b/boost/network/protocol/http/client/connection/sync_ssl.hpp @@ -33,21 +33,25 @@ struct sync_connection_base; template struct https_sync_connection : public virtual sync_connection_base, - sync_connection_base_impl { + sync_connection_base_impl, + boost::enable_shared_from_this > { typedef typename resolver_policy::type resolver_base; typedef typename resolver_base::resolver_type resolver_type; typedef typename string::type string_type; typedef function resolver_function_type; + typedef https_sync_connection this_type; typedef sync_connection_base_impl connection_base; typedef function body_generator_function_type; // FIXME make the certificate filename and verify path parameters be // optional ranges - https_sync_connection(resolver_type& resolver, resolver_function_type resolve, + https_sync_connection(resolver_type& resolver, + resolver_function_type resolve, bool always_verify_peer, + int timeout, optional const& certificate_filename = optional(), optional const& verify_path = @@ -57,6 +61,8 @@ struct https_sync_connection optional const& private_key_file = optional()) : connection_base(), + timeout_(timeout), + timer_(resolver.get_io_service()), resolver_(resolver), resolve_(resolve), context_(resolver.get_io_service(), @@ -107,6 +113,12 @@ struct https_sync_connection connection_base::send_request_impl(socket_, method, request_buffer); } } + if (timeout_ > 0) { + timer_.expires_from_now(boost::posix_time::seconds(timeout_)); + timer_.async_wait(boost::bind(&this_type::handle_timeout, + this_type::shared_from_this(), + _1)); + } } void read_status(basic_response& response_, @@ -135,6 +147,7 @@ struct https_sync_connection bool is_open() { return socket_.lowest_layer().is_open(); } void close_socket() { + timer_.cancel(); boost::system::error_code ignored; socket_.lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored); @@ -145,6 +158,12 @@ struct https_sync_connection ~https_sync_connection() { close_socket(); } private: + void handle_timeout(boost::system::error_code const &ec) { + if (!ec) close_socket(); + } + + int timeout_; + boost::asio::deadline_timer timer_; resolver_type& resolver_; resolver_function_type resolve_; boost::asio::ssl::context context_; diff --git a/boost/network/protocol/http/client/facade.hpp b/boost/network/protocol/http/client/facade.hpp index b789549f8..1132662c8 100644 --- a/boost/network/protocol/http/client/facade.hpp +++ b/boost/network/protocol/http/client/facade.hpp @@ -172,7 +172,8 @@ struct basic_client_facade { options.openssl_verify_path(), options.openssl_certificate_file(), options.openssl_private_key_file(), - options.io_service())); + options.io_service(), + options.timeout())); } }; diff --git a/boost/network/protocol/http/client/options.hpp b/boost/network/protocol/http/client/options.hpp index 7949a6a4f..b8048b9ce 100644 --- a/boost/network/protocol/http/client/options.hpp +++ b/boost/network/protocol/http/client/options.hpp @@ -28,7 +28,8 @@ struct client_options { openssl_certificate_file_(), openssl_private_key_file_(), io_service_(), - always_verify_peer_(false) {} + always_verify_peer_(false), + timeout_(0) {} client_options(client_options const& other) : cache_resolved_(other.cache_resolved_), @@ -38,7 +39,8 @@ struct client_options { openssl_certificate_file_(other.openssl_certificate_file_), openssl_private_key_file_(other.openssl_private_key_file_), io_service_(other.io_service_), - always_verify_peer_(other.always_verify_peer_) {} + always_verify_peer_(other.always_verify_peer_), + timeout_(other.timeout_) {} client_options& operator=(client_options other) { other.swap(*this); @@ -55,6 +57,7 @@ struct client_options { swap(openssl_private_key_file_, other.openssl_private_key_file_); swap(io_service_, other.io_service_); swap(always_verify_peer_, other.always_verify_peer_); + swap(timeout_, other.timeout_); } client_options& cache_resolved(bool v) { @@ -92,6 +95,11 @@ struct client_options { return *this; } + client_options& timeout(int v) { + timeout_ = v; + return *this; + } + bool cache_resolved() const { return cache_resolved_; } bool follow_redirects() const { return follow_redirects_; } @@ -118,6 +126,8 @@ struct client_options { bool always_verify_peer() const { return always_verify_peer_; } + int timeout() const { return timeout_; } + private: bool cache_resolved_; bool follow_redirects_; @@ -127,6 +137,7 @@ struct client_options { boost::optional openssl_private_key_file_; boost::shared_ptr io_service_; bool always_verify_peer_; + int timeout_; }; template diff --git a/boost/network/protocol/http/client/pimpl.hpp b/boost/network/protocol/http/client/pimpl.hpp index 4ee152c8a..f61b1273f 100644 --- a/boost/network/protocol/http/client/pimpl.hpp +++ b/boost/network/protocol/http/client/pimpl.hpp @@ -71,8 +71,10 @@ struct basic_client_impl optional const& verify_path, optional const& certificate_file, optional const& private_key_file, - boost::shared_ptr service) - : base_type(cache_resolved, follow_redirect, always_verify_peer, service, + boost::shared_ptr service, + int timeout) + : base_type(cache_resolved, follow_redirect, + always_verify_peer, timeout, service, certificate_filename, verify_path, certificate_file, private_key_file) {} diff --git a/boost/network/protocol/http/client/sync_impl.hpp b/boost/network/protocol/http/client/sync_impl.hpp index 529560c03..e724cf008 100644 --- a/boost/network/protocol/http/client/sync_impl.hpp +++ b/boost/network/protocol/http/client/sync_impl.hpp @@ -47,7 +47,7 @@ struct sync_client bool always_verify_peer_; sync_client(bool cache_resolved, bool follow_redirect, - bool always_verify_peer, + bool always_verify_peer, int timeout, boost::shared_ptr service, optional const& certificate_filename = optional(), @@ -57,7 +57,7 @@ struct sync_client optional(), optional const& private_key_file = optional()) - : connection_base(cache_resolved, follow_redirect), + : connection_base(cache_resolved, follow_redirect, timeout), service_ptr(service.get() ? service : make_shared()), service_(*service_ptr), @@ -79,7 +79,7 @@ struct sync_client body_generator_function_type generator) { typename connection_base::connection_ptr connection_; connection_ = connection_base::get_connection( - resolver_, request_, + resolver_, request_, always_verify_peer_, certificate_filename_, verify_path_, certificate_file_, private_key_file_); return connection_->send_request(method, request_, get_body, callback, diff --git a/boost/network/protocol/http/policies/async_connection.hpp b/boost/network/protocol/http/policies/async_connection.hpp index 864ea0e0e..e3a827dd9 100644 --- a/boost/network/protocol/http/policies/async_connection.hpp +++ b/boost/network/protocol/http/policies/async_connection.hpp @@ -38,7 +38,7 @@ struct async_connection_policy : resolver_policy::type { struct connection_impl { connection_impl(bool follow_redirect, bool always_verify_peer, resolve_function resolve, resolver_type& resolver, - bool https, + bool https, int timeout, optional const& certificate_filename, optional const& verify_path, optional const& certificate_file, @@ -46,7 +46,7 @@ struct async_connection_policy : resolver_policy::type { pimpl = impl::async_connection_base< Tag, version_major, version_minor>::new_connection(resolve, resolver, follow_redirect, - always_verify_peer, https, + always_verify_peer, https, timeout, certificate_filename, verify_path, certificate_file, private_key_file); } @@ -81,7 +81,7 @@ struct async_connection_policy : resolver_policy::type { boost::bind(&async_connection_policy::resolve, this, _1, _2, _3, _4), - resolver, boost::iequals(protocol_, string_type("https")), + resolver, boost::iequals(protocol_, string_type("https")), timeout_, certificate_filename, verify_path, certificate_file, private_key_file)); return connection_; @@ -89,10 +89,11 @@ struct async_connection_policy : resolver_policy::type { void cleanup() {} - async_connection_policy(bool cache_resolved, bool follow_redirect) - : resolver_base(cache_resolved), follow_redirect_(follow_redirect) {} + async_connection_policy(bool cache_resolved, bool follow_redirect, int timeout) + : resolver_base(cache_resolved), follow_redirect_(follow_redirect), timeout_(timeout) {} bool follow_redirect_; + int timeout_; }; } // namespace http diff --git a/boost/network/protocol/http/policies/pooled_connection.hpp b/boost/network/protocol/http/policies/pooled_connection.hpp index a910a5fc6..a3e3eaa19 100644 --- a/boost/network/protocol/http/policies/pooled_connection.hpp +++ b/boost/network/protocol/http/policies/pooled_connection.hpp @@ -57,6 +57,8 @@ struct pooled_connection_policy : resolver_policy::type { resolver_function_type resolve, get_connection_function get_connection, bool https, + bool always_verify_peer, + int timeout, optional const& certificate_filename = optional(), optional const& verify_path = @@ -69,6 +71,8 @@ struct pooled_connection_policy : resolver_policy::type { new_connection(resolver, resolve, https, + always_verify_peer, + timeout, certificate_filename, verify_path, certificate_file, @@ -190,10 +194,12 @@ struct pooled_connection_policy : resolver_policy::type { typedef unordered_map host_connection_map; host_connection_map host_connections; bool follow_redirect_; + int timeout_; connection_ptr get_connection( resolver_type& resolver, basic_request const& request_, + bool always_verify_peer, optional const& certificate_filename = optional(), optional const& verify_path = @@ -224,11 +230,14 @@ struct pooled_connection_policy : resolver_policy::type { this, _1, _2, + always_verify_peer, _3, _4, _5, _6), boost::iequals(request_.protocol(), string_type("https")), + always_verify_peer, + timeout_, certificate_filename, verify_path, certificate_file, @@ -239,10 +248,11 @@ struct pooled_connection_policy : resolver_policy::type { return it->second; } - pooled_connection_policy(bool cache_resolved, bool follow_redirect) + pooled_connection_policy(bool cache_resolved, bool follow_redirect, int timeout) : resolver_base(cache_resolved), host_connections(), - follow_redirect_(follow_redirect) {} + follow_redirect_(follow_redirect), + timeout_(timeout) {} }; } // namespace http diff --git a/boost/network/protocol/http/policies/simple_connection.hpp b/boost/network/protocol/http/policies/simple_connection.hpp index 76a8ea9b2..0ce19cba0 100644 --- a/boost/network/protocol/http/policies/simple_connection.hpp +++ b/boost/network/protocol/http/policies/simple_connection.hpp @@ -44,6 +44,7 @@ struct simple_connection_policy : resolver_policy::type { bool always_verify_peer, string_type const& hostname, string_type const& port, resolver_function_type resolve, bool https, + int timeout, optional const& certificate_filename = optional(), optional const& verify_path = @@ -56,6 +57,7 @@ struct simple_connection_policy : resolver_policy::type { pimpl.reset(impl::sync_connection_base< Tag, version_major, version_minor>::new_connection(resolver, resolve, https, + always_verify_peer, timeout, certificate_filename, verify_path, certificate_file, private_key_file)); } @@ -123,7 +125,7 @@ struct simple_connection_policy : resolver_policy::type { boost::bind(&simple_connection_policy::resolve, this, _1, _2, _3), - boost::iequals(request_.protocol(), string_type("https")), + boost::iequals(request_.protocol(), string_type("https")), timeout_, certificate_filename, verify_path, certificate_file, private_key_file)); return connection_; @@ -131,11 +133,12 @@ struct simple_connection_policy : resolver_policy::type { void cleanup() {} - simple_connection_policy(bool cache_resolved, bool follow_redirect) - : resolver_base(cache_resolved), follow_redirect_(follow_redirect) {} + simple_connection_policy(bool cache_resolved, bool follow_redirect, int timeout) + : resolver_base(cache_resolved), follow_redirect_(follow_redirect), timeout_(timeout) {} // member variables bool follow_redirect_; + int timeout_; }; } // namespace http diff --git a/libs/network/test/http/client_get_timeout_test.cpp b/libs/network/test/http/client_get_timeout_test.cpp index 535216977..fd08a63b2 100644 --- a/libs/network/test/http/client_get_timeout_test.cpp +++ b/libs/network/test/http/client_get_timeout_test.cpp @@ -20,3 +20,25 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(http_get_test_timeout_1_0, client, client_types) { BOOST_CHECK_EQUAL ( 12121, port_ ); BOOST_CHECK_THROW ( response_ = client_.get(request); temp = body(response_); , std::exception ); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(http_get_test_timeout_with_options, client, client_types) { + typename client::request request("http://commondatastorage.googleapis.com/cpp-netlib-downloads/0.11.0/cpp-netlib-0.11.0.zip"); + typename client::response response; + typename client::options options; + client client_(options.timeout(1)); + typename client::response::string_type temp; + BOOST_CHECK_THROW ( response = client_.get(request); temp = body(response); , std::exception ); +} + +#ifdef BOOST_NETWORK_ENABLE_HTTPS + +BOOST_AUTO_TEST_CASE_TEMPLATE(https_get_test_timeout_with_options, client, client_types) { + typename client::request request("https://codeload.github.com/cpp-netlib/cpp-netlib/zip/0.11-devel"); + typename client::response response; + typename client::options options; + client client_(options.timeout(1)); + typename client::response::string_type temp; + BOOST_CHECK_THROW ( response = client_.get(request); temp = body(response); , std::exception ); +} + +#endif