From ac76f07be6cc74fde98c76e8ccfd428f82cfa5bd Mon Sep 17 00:00:00 2001 From: Chris Deering Date: Mon, 21 Dec 2015 11:47:57 +0000 Subject: [PATCH 1/3] Implementing HTTP and HTTPS client proxy support for non-Windows platforms. Fixing whitespace --- .../cpprest/details/http_client_impl.h | 5 +- .../include/cpprest/details/web_utilities.h | 2 + Release/src/http/client/http_client_asio.cpp | 438 ++++++++++++++---- .../functional/http/client/proxy_tests.cpp | 2 +- 4 files changed, 358 insertions(+), 89 deletions(-) diff --git a/Release/include/cpprest/details/http_client_impl.h b/Release/include/cpprest/details/http_client_impl.h index 2f88472a50..e83c672c83 100644 --- a/Release/include/cpprest/details/http_client_impl.h +++ b/Release/include/cpprest/details/http_client_impl.h @@ -296,9 +296,10 @@ class _http_client_communicator // URI to connect to. const http::uri m_uri; -private: - http_client_config m_client_config; + http_client_config m_client_config; + +private: bool m_opened; diff --git a/Release/include/cpprest/details/web_utilities.h b/Release/include/cpprest/details/web_utilities.h index 9224b72f54..0fe97193bb 100644 --- a/Release/include/cpprest/details/web_utilities.h +++ b/Release/include/cpprest/details/web_utilities.h @@ -30,6 +30,7 @@ namespace web namespace http { namespace client { namespace details { class winhttp_client; class winrt_client; +class asio_context; }}} namespace websockets { namespace client { namespace details { class winrt_callback_client; @@ -124,6 +125,7 @@ class credentials private: friend class http::client::details::winhttp_client; friend class http::client::details::winrt_client; + friend class http::client::details::asio_context; friend class websockets::client::details::winrt_callback_client; friend class websockets::client::details::wspp_callback_client; diff --git a/Release/src/http/client/http_client_asio.cpp b/Release/src/http/client/http_client_asio.cpp index bff2ff4843..3f6b70cbb7 100644 --- a/Release/src/http/client/http_client_asio.cpp +++ b/Release/src/http/client/http_client_asio.cpp @@ -74,20 +74,16 @@ class asio_connection friend class asio_connection_pool; friend class asio_client; public: - asio_connection(boost::asio::io_service& io_service, bool use_ssl, const std::function& ssl_context_callback) : + asio_connection(boost::asio::io_service& io_service, bool start_with_ssl, const std::function& ssl_context_callback) : m_socket(io_service), m_pool_timer(io_service), m_is_reused(false), - m_keep_alive(true) + m_keep_alive(true), + m_ssl_context_callback(ssl_context_callback) { - if (use_ssl) + if (start_with_ssl) { - boost::asio::ssl::context ssl_context(boost::asio::ssl::context::sslv23); - ssl_context.set_default_verify_paths(); - ssl_context.set_options(boost::asio::ssl::context::default_workarounds); - ssl_context_callback(ssl_context); - m_ssl_stream = utility::details::make_unique>(m_socket, ssl_context); - + upgrade_to_ssl(); } } @@ -96,6 +92,18 @@ class asio_connection close(); } + // This simply instantiates the internal state to support ssl. It does not perform the handshake. + void upgrade_to_ssl() + { + std::lock_guard lock(m_socket_lock); + assert(!is_ssl()); + boost::asio::ssl::context ssl_context(boost::asio::ssl::context::sslv23); + ssl_context.set_default_verify_paths(); + ssl_context.set_options(boost::asio::ssl::context::default_workarounds); + m_ssl_context_callback(ssl_context); + m_ssl_stream = utility::details::make_unique>(m_socket, ssl_context); + } + void close() { std::lock_guard lock(m_socket_lock); @@ -220,6 +228,8 @@ class asio_connection tcp::socket m_socket; std::unique_ptr > m_ssl_stream; + std::function m_ssl_context_callback; + boost::asio::deadline_timer m_pool_timer; bool m_is_reused; bool m_keep_alive; @@ -229,10 +239,10 @@ class asio_connection_pool { public: - asio_connection_pool(boost::asio::io_service& io_service, bool use_ssl, const std::chrono::seconds &idle_timeout, const std::function &ssl_context_callback) : + asio_connection_pool(boost::asio::io_service& io_service, bool start_with_ssl, const std::chrono::seconds &idle_timeout, const std::function &ssl_context_callback) : m_io_service(io_service), m_timeout_secs(static_cast(idle_timeout.count())), - m_use_ssl(use_ssl), + m_start_with_ssl(start_with_ssl), m_ssl_context_callback(ssl_context_callback) {} @@ -269,7 +279,7 @@ class asio_connection_pool lock.unlock(); // No connections in pool => create a new connection instance. - return std::make_shared(m_io_service, m_use_ssl, m_ssl_context_callback); + return std::make_shared(m_io_service, m_start_with_ssl, m_ssl_context_callback); } else { @@ -305,19 +315,21 @@ class asio_connection_pool boost::asio::io_service& m_io_service; const int m_timeout_secs; - const bool m_use_ssl; + const bool m_start_with_ssl; const std::function& m_ssl_context_callback; std::vector > m_connections; std::mutex m_connections_mutex; }; + + class asio_client : public _http_client_communicator, public std::enable_shared_from_this { public: asio_client(http::uri address, http_client_config client_config) : _http_client_communicator(std::move(address), std::move(client_config)) , m_pool(crossplat::threadpool::shared_instance().service(), - base_uri().scheme() == "https", + base_uri().scheme() == "https" && !m_client_config.proxy().is_specified(), std::chrono::seconds(30), // Unused sockets are kept in pool for 30 seconds. this->client_config().get_ssl_context_callback()) , m_resolver(crossplat::threadpool::shared_instance().service()) @@ -363,102 +375,331 @@ class asio_context : public request_context, public std::enable_shared_from_this ctx->m_timer.set_ctx(std::weak_ptr(ctx)); return ctx; } - - void start_request() + + class ssl_proxy_tunnel : public std::enable_shared_from_this { - if (m_request._cancellation_token().is_canceled()) + public: + ssl_proxy_tunnel(std::shared_ptr context, std::function)> ssl_tunnel_established) + : m_ssl_tunnel_established(ssl_tunnel_established), m_context(context) + {} + + void start_proxy_connect() { - request_context::report_error(make_error_code(std::errc::operation_canceled).value(), "Request canceled by user."); - return; - } + auto proxy = m_context->m_http_client->client_config().proxy(); + auto proxy_uri = proxy.address(); - const auto &base_uri = m_http_client->base_uri(); - auto encoded_resource = uri_builder(base_uri).append(m_request.relative_uri()).to_uri().resource().to_string(); - if (encoded_resource == "") - { - encoded_resource = "/"; - } + utility::string_t proxy_host = proxy_uri.host(); + int proxy_port = proxy_uri.port() == -1 ? 8080 : proxy_uri.port(); - const auto &method = m_request.method(); + const auto &base_uri = m_context->m_http_client->base_uri(); + const auto &host = base_uri.host(); - // stop injection of headers via method - // resource should be ok, since it's been encoded - // and host won't resolve - if (!::web::http::details::validate_method(method)) - { - report_exception(http_exception("The method string is invalid.")); - return; - } + std::ostream request_stream(&request_); + request_stream.imbue(std::locale::classic()); - const auto &host = base_uri.host(); - std::ostream request_stream(&m_body_buf); - request_stream.imbue(std::locale::classic()); + request_stream << "CONNECT " << host << ":" << 443 << " HTTP/1.1" << CRLF; + request_stream << "Host: " << host << ":" << 443 << CRLF; + request_stream << "Proxy-Connection: Keep-Alive" << CRLF; - request_stream << method << " " << encoded_resource << " " << "HTTP/1.1" << CRLF << "Host: " << host; + if(!m_context->m_http_client->client_config().proxy().credentials().username().empty()) + { + request_stream << m_context->generate_basic_proxy_auth_header() << CRLF; + } - int port = base_uri.port(); - if (base_uri.is_port_default()) - { - port = (m_connection->is_ssl() ? 443 : 80); - } - request_stream << ":" << port << CRLF; + request_stream << CRLF; - // Extra request headers are constructed here. - utility::string_t extra_headers; + m_context->m_timer.start(); - // Check user specified transfer-encoding. - std::string transferencoding; - if (m_request.headers().match(header_names::transfer_encoding, transferencoding) && transferencoding == "chunked") - { - m_needChunked = true; + tcp::resolver::query query(proxy_host, utility::conversions::print_string(proxy_port, std::locale::classic())); + + auto client = std::static_pointer_cast(m_context->m_http_client); + client->m_resolver.async_resolve(query, boost::bind(&ssl_proxy_tunnel::handle_resolve, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::iterator)); } - else if (!m_request.headers().match(header_names::content_length, m_content_length)) + + private: + void handle_resolve(const boost::system::error_code& ec, tcp::resolver::iterator endpoints) { - // Stream without content length is the signal of requiring transfer encoding chunked. - if (m_request.body()) + if (ec) + { + m_context->report_error("Error resolving proxy address", ec, httpclient_errorcode_context::connect); + } + else { - m_needChunked = true; - extra_headers.append(header_names::transfer_encoding); - extra_headers.append(":chunked" + CRLF); + m_context->m_timer.reset(); + auto endpoint = *endpoints; + m_context->m_connection->async_connect(endpoint, boost::bind(&ssl_proxy_tunnel::handle_tcp_connect, shared_from_this(), boost::asio::placeholders::error, ++endpoints)); } } - request_stream << flatten_http_headers(m_request.headers()); - request_stream << extra_headers; - // Enforce HTTP connection keep alive (even for the old HTTP/1.0 protocol). - request_stream << "Connection: Keep-Alive" << CRLF << CRLF; + void handle_tcp_connect(const boost::system::error_code& ec, tcp::resolver::iterator endpoints) + { + if (!ec) + { + m_context->m_timer.reset(); + m_context->m_connection->async_write(request_, boost::bind(&ssl_proxy_tunnel::handle_write_request, shared_from_this(), boost::asio::placeholders::error)); + } + else if (endpoints == tcp::resolver::iterator()) + { + m_context->report_error("Failed to connect to any resolved proxy endpoint", ec, httpclient_errorcode_context::connect); + } + else + { + m_context->m_timer.reset(); + //// Replace the connection. This causes old connection object to go out of scope. + auto client = std::static_pointer_cast(m_context->m_http_client); + m_context->m_connection = client->m_pool.obtain(); - // Start connection timeout timer. - m_timer.start(); + auto endpoint = *endpoints; + m_context->m_connection->async_connect(endpoint, boost::bind(&ssl_proxy_tunnel::handle_tcp_connect, shared_from_this(), boost::asio::placeholders::error, ++endpoints)); + } - if (m_connection->is_reused()) - { - // If socket is a reused connection, try to write the request directly. - write_request(); } - else + + void handle_write_request(const boost::system::error_code& err) { - // If the connection is new (unresolved and unconnected socket), then start async - // call to resolve first, leading eventually to request write. - tcp::resolver::query query(host, utility::conversions::print_string(port, std::locale::classic())); - auto client = std::static_pointer_cast(m_http_client); - client->m_resolver.async_resolve(query, boost::bind(&asio_context::handle_resolve, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::iterator)); + if (!err) + { + m_context->m_timer.reset(); + m_context->m_connection->async_read_until(response_, CRLF + CRLF, boost::bind(&ssl_proxy_tunnel::handle_status_line, shared_from_this(), boost::asio::placeholders::error)); + } + else + { + m_context->report_error("Failed to send connect request to proxy.", err, httpclient_errorcode_context::writebody); + } + } + + void handle_status_line(const boost::system::error_code& ec) + { + if (!ec) + { + m_context->m_timer.reset(); + std::istream response_stream(&response_); + response_stream.imbue(std::locale::classic()); + std::string http_version; + response_stream >> http_version; + status_code status_code; + response_stream >> status_code; + + if (!response_stream || http_version.substr(0, 5) != "HTTP/") + { + m_context->report_error("Invalid HTTP status line during proxy connection", ec, httpclient_errorcode_context::readheader); + return; + } + + if (status_code != 200) + { + utility::stringstream_t err_ss; + err_ss << U("Expected a 200 response from proxy, received: ") << status_code; + m_context->report_error(err_ss.str(), ec, httpclient_errorcode_context::readheader); + return; + } + + m_context->m_connection->upgrade_to_ssl(); + + m_ssl_tunnel_established(m_context); + } + else + { + // These errors tell if connection was closed. + const bool socket_was_closed((boost::asio::error::eof == ec) + || (boost::asio::error::connection_reset == ec) + || (boost::asio::error::connection_aborted == ec)); + if (socket_was_closed && m_context->m_connection->is_reused()) + { + // Failed to write to socket because connection was already closed while it was in the pool. + // close() here ensures socket is closed in a robust way and prevents the connection from being put to the pool again. + m_context->m_connection->close(); + + // Create a new context and copy the request object, completion event and + // cancellation registration to maintain the old state. + // This also obtains a new connection from pool. + auto new_ctx = m_context->create_request_context(m_context->m_http_client, m_context->m_request); + new_ctx->m_request_completion = m_context->m_request_completion; + new_ctx->m_cancellationRegistration = m_context->m_cancellationRegistration; + + auto client = std::static_pointer_cast(m_context->m_http_client); + // Resend the request using the new context. + client->send_request(new_ctx); + } + else + { + m_context->report_error("Failed to read HTTP status line from proxy", ec, httpclient_errorcode_context::readheader); + } + } } + + std::function)> m_ssl_tunnel_established; + std::shared_ptr m_context; + + boost::asio::streambuf request_; + boost::asio::streambuf response_; + }; + + + enum class http_proxy_type + { + none, + http, + ssl_tunnel + }; - // Register for notification on cancellation to abort this request. - if(m_request._cancellation_token() != pplx::cancellation_token::none()) + void start_request() + { + if (m_request._cancellation_token().is_canceled()) + { + request_context::report_error(make_error_code(std::errc::operation_canceled).value(), "Request canceled by user."); + return; + } + + http_proxy_type proxy_type = http_proxy_type::none; + utility::string_t proxy_host; + int proxy_port = -1; + + // There is no support for auto-detection of proxies on non-windows platforms, it must be specified explicitly from the client code. + if (m_http_client->client_config().proxy().is_specified()) + { + proxy_type = m_http_client->base_uri().scheme() == U("https") ? http_proxy_type::ssl_tunnel : http_proxy_type::http; + auto proxy = m_http_client->client_config().proxy(); + auto proxy_uri = proxy.address(); + proxy_port = proxy_uri.port() == -1 ? 8080 : proxy_uri.port(); + proxy_host = proxy_uri.host(); + } + + auto start_http_request_flow = [proxy_type, proxy_host, proxy_port](std::shared_ptr ctx) { - // weak_ptr prevents lambda from taking shared ownership of the context. - // Otherwise context replacement in the handle_status_line() would leak the objects. - std::weak_ptr ctx_weak(shared_from_this()); - m_cancellationRegistration = m_request._cancellation_token().register_callback([ctx_weak]() + if (ctx->m_request._cancellation_token().is_canceled()) { - if (auto ctx_lock = ctx_weak.lock()) + ctx->request_context::report_error(make_error_code(std::errc::operation_canceled).value(), "Request canceled by user."); + return; + } + + const auto &base_uri = ctx->m_http_client->base_uri(); + const auto full_uri = uri_builder(base_uri).append(ctx->m_request.relative_uri()).to_uri(); + + // For a normal http proxy, we need to specify the full request uri, otherwise just specify the resource + auto encoded_resource = proxy_type == http_proxy_type::http ? full_uri.to_string() : full_uri.resource().to_string(); + + if (encoded_resource == "") + { + encoded_resource = "/"; + } + + const auto &method = ctx->m_request.method(); + + // stop injection of headers via method + // resource should be ok, since it's been encoded + // and host won't resolve + if (!::web::http::details::validate_method(method)) + { + ctx->report_exception(http_exception("The method string is invalid.")); + return; + } + + std::ostream request_stream(&ctx->m_body_buf); + request_stream.imbue(std::locale::classic()); + const auto &host = base_uri.host(); + + request_stream << method << " " << encoded_resource << " " << "HTTP/1.1" << CRLF << "Host: " << host; + + int port = base_uri.port(); + + if (base_uri.is_port_default()) + { + port = (ctx->m_connection->is_ssl() ? 443 : 80); + } + + request_stream << ":" << port << CRLF; + + // Extra request headers are constructed here. + utility::string_t extra_headers; + + // Add header for basic proxy authentication + if (proxy_type == http_proxy_type::http && !ctx->m_http_client->client_config().proxy().credentials().username().empty()) + { + extra_headers.append(ctx->generate_basic_proxy_auth_header()); + } + + // Check user specified transfer-encoding. + std::string transferencoding; + if (ctx->m_request.headers().match(header_names::transfer_encoding, transferencoding) && transferencoding == "chunked") + { + ctx->m_needChunked = true; + } + else if (!ctx->m_request.headers().match(header_names::content_length, ctx->m_content_length)) + { + // Stream without content length is the signal of requiring transfer encoding chunked. + if (ctx->m_request.body()) { - // Shut down transmissions, close the socket and prevent connection from being pooled. - ctx_lock->m_connection->close(); + ctx->m_needChunked = true; + extra_headers.append(header_names::transfer_encoding); + extra_headers.append(":chunked" + CRLF); } - }); + } + + if (proxy_type == http_proxy_type::http) + { + extra_headers.append(header_names::cache_control); + extra_headers.append(": no-store, no-cache" + CRLF); + extra_headers.append(header_names::pragma); + extra_headers.append(": no-cache" + CRLF); + } + + request_stream << flatten_http_headers(ctx->m_request.headers()); + request_stream << extra_headers; + // Enforce HTTP connection keep alive (even for the old HTTP/1.0 protocol). + request_stream << "Connection: Keep-Alive" << CRLF << CRLF; + + // Start connection timeout timer. + if (!ctx->m_timer.has_started()) + { + ctx->m_timer.start(); + } + + if (ctx->m_connection->is_reused() || proxy_type == http_proxy_type::ssl_tunnel) + { + // If socket is a reused connection or we're connected via an ssl-tunneling proxy, try to write the request directly. In both cases we have already established a tcp connection. + ctx->write_request(); + } + else + { + // If the connection is new (unresolved and unconnected socket), then start async + // call to resolve first, leading eventually to request write. + + // For normal http proxies, we want to connect directly to the proxy server. It will relay our request. + auto tcp_host = proxy_type == http_proxy_type::http ? proxy_host : host; + auto tcp_port = proxy_type == http_proxy_type::http ? proxy_port : port; + + tcp::resolver::query query(tcp_host, utility::conversions::print_string(tcp_port, std::locale::classic())); + auto client = std::static_pointer_cast(ctx->m_http_client); + client->m_resolver.async_resolve(query, boost::bind(&asio_context::handle_resolve, ctx, boost::asio::placeholders::error, boost::asio::placeholders::iterator)); + } + + // Register for notification on cancellation to abort this request. + if (ctx->m_request._cancellation_token() != pplx::cancellation_token::none()) + { + // weak_ptr prevents lambda from taking shared ownership of the context. + // Otherwise context replacement in the handle_status_line() would leak the objects. + std::weak_ptr ctx_weak(ctx); + ctx->m_cancellationRegistration = ctx->m_request._cancellation_token().register_callback([ctx_weak]() + { + if (auto ctx_lock = ctx_weak.lock()) + { + // Shut down transmissions, close the socket and prevent connection from being pooled. + ctx_lock->m_connection->close(); + } + }); + } + }; + + if (proxy_type == http_proxy_type::ssl_tunnel) + { + // The ssl_tunnel_proxy keeps the context alive and then calls back once the ssl tunnel is established via 'start_http_request_flow' + std::shared_ptr ssl_tunnel = std::make_shared(shared_from_this(), start_http_request_flow); + ssl_tunnel->start_proxy_connect(); + } + else + { + start_http_request_flow(shared_from_this()); } } @@ -477,6 +718,24 @@ class asio_context : public request_context, public std::enable_shared_from_this private: + utility::string_t generate_basic_proxy_auth_header() + { + utility::string_t header; + + header.append(header_names::proxy_authorization); + header.append(": Basic "); + + auto credential_str = web::details::plaintext_string(new ::utility::string_t(m_http_client->client_config().proxy().credentials().username())); + credential_str->append(":"); + credential_str->append(*m_http_client->client_config().proxy().credentials().decrypt()); + + std::vector credentials_buffer(credential_str->begin(), credential_str->end()); + + header.append(utility::conversions::to_base64(credentials_buffer)); + header.append(CRLF); + return header; + } + void report_error(const utility::string_t &message, const boost::system::error_code &ec, httpclient_errorcode_context context = httpclient_errorcode_context::none) { // By default, errorcodeValue don't need to converted @@ -519,6 +778,7 @@ class asio_context : public request_context, public std::enable_shared_from_this void handle_connect(const boost::system::error_code& ec, tcp::resolver::iterator endpoints) { + m_timer.reset(); if (!ec) { @@ -640,6 +900,7 @@ class asio_context : public request_context, public std::enable_shared_from_this } } + void handle_write_chunked_body(const boost::system::error_code& ec) { if (ec) @@ -705,7 +966,7 @@ class asio_context : public request_context, public std::enable_shared_from_this // Reuse error handling. return handle_write_body(ec); } - + m_timer.reset(); const auto &progress = m_request._get_impl()->_progress_handler(); if (progress) @@ -786,7 +1047,7 @@ class asio_context : public request_context, public std::enable_shared_from_this response_stream >> http_version; status_code status_code; response_stream >> status_code; - + std::string status_message; std::getline(response_stream, status_message); @@ -1114,6 +1375,8 @@ class asio_context : public request_context, public std::enable_shared_from_this bool has_timedout() const { return m_state == timedout; } + bool has_started() const { return m_state == started; } + void stop() { m_state = stopped; @@ -1135,6 +1398,7 @@ class asio_context : public request_context, public std::enable_shared_from_this } } + private: enum timer_state { created, @@ -1164,6 +1428,8 @@ class asio_context : public request_context, public std::enable_shared_from_this #endif }; + + http_network_handler::http_network_handler(const uri &base_uri, const http_client_config &client_config) : m_http_client_impl(std::make_shared(base_uri, client_config)) {} diff --git a/Release/tests/functional/http/client/proxy_tests.cpp b/Release/tests/functional/http/client/proxy_tests.cpp index 1d73fa5cb7..e7b9d1cfb0 100644 --- a/Release/tests/functional/http/client/proxy_tests.cpp +++ b/Release/tests/functional/http/client/proxy_tests.cpp @@ -90,7 +90,7 @@ TEST_FIXTURE(uri_address, no_proxy_options_on_winrt) #ifndef __cplusplus_winrt // Can't specify a proxy with WinRT implementation. -TEST_FIXTURE(uri_address, proxy_with_credentials, "Ignore:Linux", "NYI", "Ignore:Apple", "NYI", "Ignore:Android", "NYI") +TEST_FIXTURE(uri_address, proxy_with_credentials) { uri u(U("http://netproxy.redmond.corp.microsoft.com")); From a20b66910611c55baddd4759516846df7a5feca7 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 22 Dec 2015 19:20:13 +0000 Subject: [PATCH 2/3] Updating contributors.txt --- CONTRIBUTORS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index e53f210625..0e4c99aea0 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -34,5 +34,6 @@ Gery Vessere (gery@vessere.com) Cisco Systems Gergely Lukacsy (glukacsy) +Chris Deering (deeringc) thomasschaub From d7c9e7c1a55c53d55fc7ea8402b9f43cc3ea7be7 Mon Sep 17 00:00:00 2001 From: Chris Deering Date: Tue, 5 Jan 2016 12:40:15 +0000 Subject: [PATCH 3/3] Updates from code review. Adding new manual test cases for http_proxy and https_proxy --- .../cpprest/details/http_client_impl.h | 5 +-- Release/src/http/client/http_client_asio.cpp | 18 ++++---- .../functional/http/client/proxy_tests.cpp | 43 ++++++++++++++++++- 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/Release/include/cpprest/details/http_client_impl.h b/Release/include/cpprest/details/http_client_impl.h index e83c672c83..2f88472a50 100644 --- a/Release/include/cpprest/details/http_client_impl.h +++ b/Release/include/cpprest/details/http_client_impl.h @@ -296,11 +296,10 @@ class _http_client_communicator // URI to connect to. const http::uri m_uri; - - http_client_config m_client_config; - private: + http_client_config m_client_config; + bool m_opened; pplx::extensibility::critical_section_t m_open_lock; diff --git a/Release/src/http/client/http_client_asio.cpp b/Release/src/http/client/http_client_asio.cpp index 3f6b70cbb7..8fcfe120e9 100644 --- a/Release/src/http/client/http_client_asio.cpp +++ b/Release/src/http/client/http_client_asio.cpp @@ -329,7 +329,7 @@ class asio_client : public _http_client_communicator, public std::enable_shared_ asio_client(http::uri address, http_client_config client_config) : _http_client_communicator(std::move(address), std::move(client_config)) , m_pool(crossplat::threadpool::shared_instance().service(), - base_uri().scheme() == "https" && !m_client_config.proxy().is_specified(), + base_uri().scheme() == "https" && !_http_client_communicator::client_config().proxy().is_specified(), std::chrono::seconds(30), // Unused sockets are kept in pool for 30 seconds. this->client_config().get_ssl_context_callback()) , m_resolver(crossplat::threadpool::shared_instance().service()) @@ -394,14 +394,14 @@ class asio_context : public request_context, public std::enable_shared_from_this const auto &base_uri = m_context->m_http_client->base_uri(); const auto &host = base_uri.host(); - std::ostream request_stream(&request_); + std::ostream request_stream(&m_request); request_stream.imbue(std::locale::classic()); request_stream << "CONNECT " << host << ":" << 443 << " HTTP/1.1" << CRLF; request_stream << "Host: " << host << ":" << 443 << CRLF; request_stream << "Proxy-Connection: Keep-Alive" << CRLF; - if(!m_context->m_http_client->client_config().proxy().credentials().username().empty()) + if(m_context->m_http_client->client_config().proxy().credentials().is_set()) { request_stream << m_context->generate_basic_proxy_auth_header() << CRLF; } @@ -436,7 +436,7 @@ class asio_context : public request_context, public std::enable_shared_from_this if (!ec) { m_context->m_timer.reset(); - m_context->m_connection->async_write(request_, boost::bind(&ssl_proxy_tunnel::handle_write_request, shared_from_this(), boost::asio::placeholders::error)); + m_context->m_connection->async_write(m_request, boost::bind(&ssl_proxy_tunnel::handle_write_request, shared_from_this(), boost::asio::placeholders::error)); } else if (endpoints == tcp::resolver::iterator()) { @@ -460,7 +460,7 @@ class asio_context : public request_context, public std::enable_shared_from_this if (!err) { m_context->m_timer.reset(); - m_context->m_connection->async_read_until(response_, CRLF + CRLF, boost::bind(&ssl_proxy_tunnel::handle_status_line, shared_from_this(), boost::asio::placeholders::error)); + m_context->m_connection->async_read_until(m_response, CRLF + CRLF, boost::bind(&ssl_proxy_tunnel::handle_status_line, shared_from_this(), boost::asio::placeholders::error)); } else { @@ -473,7 +473,7 @@ class asio_context : public request_context, public std::enable_shared_from_this if (!ec) { m_context->m_timer.reset(); - std::istream response_stream(&response_); + std::istream response_stream(&m_response); response_stream.imbue(std::locale::classic()); std::string http_version; response_stream >> http_version; @@ -531,8 +531,8 @@ class asio_context : public request_context, public std::enable_shared_from_this std::function)> m_ssl_tunnel_established; std::shared_ptr m_context; - boost::asio::streambuf request_; - boost::asio::streambuf response_; + boost::asio::streambuf m_request; + boost::asio::streambuf m_response; }; @@ -614,7 +614,7 @@ class asio_context : public request_context, public std::enable_shared_from_this utility::string_t extra_headers; // Add header for basic proxy authentication - if (proxy_type == http_proxy_type::http && !ctx->m_http_client->client_config().proxy().credentials().username().empty()) + if (proxy_type == http_proxy_type::http && ctx->m_http_client->client_config().proxy().credentials().is_set()) { extra_headers.append(ctx->generate_basic_proxy_auth_header()); } diff --git a/Release/tests/functional/http/client/proxy_tests.cpp b/Release/tests/functional/http/client/proxy_tests.cpp index e7b9d1cfb0..115d39f5e3 100644 --- a/Release/tests/functional/http/client/proxy_tests.cpp +++ b/Release/tests/functional/http/client/proxy_tests.cpp @@ -90,7 +90,7 @@ TEST_FIXTURE(uri_address, no_proxy_options_on_winrt) #ifndef __cplusplus_winrt // Can't specify a proxy with WinRT implementation. -TEST_FIXTURE(uri_address, proxy_with_credentials) +TEST_FIXTURE(uri_address, http_proxy_with_credentials) { uri u(U("http://netproxy.redmond.corp.microsoft.com")); @@ -124,6 +124,47 @@ TEST_FIXTURE(uri_address, proxy_with_credentials) throw; } } + +TEST_FIXTURE(uri_address, http_proxy, "Ignore", "Manual") +{ + // In order to run this test, replace this proxy uri with one that you have access to. + uri u(U("http://netproxy.redmond.corp.microsoft.com")); + + web_proxy proxy(u); + VERIFY_IS_TRUE(proxy.is_specified()); + VERIFY_ARE_EQUAL(u, proxy.address()); + + http_client_config config; + config.set_proxy(proxy); + + http_client client(U("http://httpbin.org"), config); + + http_response response = client.request(methods::GET).get(); + VERIFY_ARE_EQUAL(status_codes::OK, response.status_code()); + response.content_ready().wait(); +} + + +TEST_FIXTURE(uri_address, https_proxy, "Ignore", "Manual") +{ + // In order to run this test, replace this proxy uri with one that you have access to. + uri u(U("http://netproxy.redmond.corp.microsoft.com")); + + web_proxy proxy(u); + VERIFY_IS_TRUE(proxy.is_specified()); + VERIFY_ARE_EQUAL(u, proxy.address()); + + http_client_config config; + config.set_proxy(proxy); + + http_client client(U("https://httpbin.org"), config); + + http_response response = client.request(methods::GET).get(); + VERIFY_ARE_EQUAL(status_codes::OK, response.status_code()); + response.content_ready().wait(); +} + + #endif } // SUITE(proxy_tests)