diff options
author | eroman@chromium.org <eroman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-01-05 05:47:56 +0000 |
---|---|---|
committer | eroman@chromium.org <eroman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-01-05 05:47:56 +0000 |
commit | 1f6fdd45b31443447f6cdc7b6454f751fec9296d (patch) | |
tree | 751ac9d50d605e12c66e0505c432125a0d6fea9c | |
parent | d66c757a9a434f48069b114fb49191e4790f9038 (diff) | |
download | chromium_src-1f6fdd45b31443447f6cdc7b6454f751fec9296d.zip chromium_src-1f6fdd45b31443447f6cdc7b6454f751fec9296d.tar.gz chromium_src-1f6fdd45b31443447f6cdc7b6454f751fec9296d.tar.bz2 |
Poll PAC scripts for content changes.
An exponential poll delay is used which doubles the delay after each attempt (for instance polls might occur at 4 seconds, then 8 seconds, then 16 seconds, up to a maximum of 2 minutes).
This same mechanism also applies to the resolution of "auto-detect" settings.
The intent of this change is to make it possible to
(1) Notice server-side changes made to the PAC scripts
(2) Recover from spurious network errors when initially downloading PAC scripts.
BUG=90581
TEST=Configure Chrome to use a PAC script which fails to load (i.e. a non-existent file:// url, or remap the host's DNS record). Start Chrome and verify using about:net-internals that it has failed to fetch the PAC script and has fallen back to DIRECT settings. Fix the PAC URL to work and wait for chrome to notice the change and reconfigure itself (will take a maximum of 2 minutes).
Review URL: http://codereview.chromium.org/9078003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@116462 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | net/proxy/mock_proxy_resolver.h | 6 | ||||
-rw-r--r-- | net/proxy/mock_proxy_script_fetcher.cc | 17 | ||||
-rw-r--r-- | net/proxy/mock_proxy_script_fetcher.h | 6 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_script_data.cc | 19 | ||||
-rw-r--r-- | net/proxy/proxy_resolver_script_data.h | 5 | ||||
-rw-r--r-- | net/proxy/proxy_service.cc | 395 | ||||
-rw-r--r-- | net/proxy/proxy_service.h | 24 | ||||
-rw-r--r-- | net/proxy/proxy_service_unittest.cc | 519 |
8 files changed, 896 insertions, 95 deletions
diff --git a/net/proxy/mock_proxy_resolver.h b/net/proxy/mock_proxy_resolver.h index bf1b4a9..7dabea5 100644 --- a/net/proxy/mock_proxy_resolver.h +++ b/net/proxy/mock_proxy_resolver.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -94,6 +94,10 @@ class MockAsyncProxyResolverBase : public ProxyResolver { SetPacScriptRequest* pending_set_pac_script_request() const; + bool has_pending_set_pac_script_request() const { + return pending_set_pac_script_request_.get() != NULL; + } + void RemovePendingRequest(Request* request); void RemovePendingSetPacScriptRequest(SetPacScriptRequest* request); diff --git a/net/proxy/mock_proxy_script_fetcher.cc b/net/proxy/mock_proxy_script_fetcher.cc index 9aeab1c..c190aa2 100644 --- a/net/proxy/mock_proxy_script_fetcher.cc +++ b/net/proxy/mock_proxy_script_fetcher.cc @@ -1,10 +1,11 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/proxy/mock_proxy_script_fetcher.h" #include "base/logging.h" +#include "base/message_loop.h" #include "base/string16.h" #include "base/utf_string_conversions.h" #include "net/base/net_errors.h" @@ -12,7 +13,8 @@ namespace net { MockProxyScriptFetcher::MockProxyScriptFetcher() - : pending_request_text_(NULL) { + : pending_request_text_(NULL), + waiting_for_fetch_(false) { } MockProxyScriptFetcher::~MockProxyScriptFetcher() {} @@ -26,6 +28,10 @@ int MockProxyScriptFetcher::Fetch(const GURL& url, string16* text, pending_request_url_ = url; pending_request_callback_ = callback; pending_request_text_ = text; + + if (waiting_for_fetch_) + MessageLoop::current()->Quit(); + return ERR_IO_PENDING; } @@ -53,4 +59,11 @@ bool MockProxyScriptFetcher::has_pending_request() const { return !pending_request_callback_.is_null(); } +void MockProxyScriptFetcher::WaitUntilFetch() { + DCHECK(!has_pending_request()); + waiting_for_fetch_ = true; + MessageLoop::current()->Run(); + waiting_for_fetch_ = false; +} + } // namespace net diff --git a/net/proxy/mock_proxy_script_fetcher.h b/net/proxy/mock_proxy_script_fetcher.h index 4e88c17..a1b59f5 100644 --- a/net/proxy/mock_proxy_script_fetcher.h +++ b/net/proxy/mock_proxy_script_fetcher.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -34,10 +34,14 @@ class MockProxyScriptFetcher : public ProxyScriptFetcher { const GURL& pending_request_url() const; bool has_pending_request() const; + // Spins the message loop until this->Fetch() is invoked. + void WaitUntilFetch(); + private: GURL pending_request_url_; CompletionCallback pending_request_callback_; string16* pending_request_text_; + bool waiting_for_fetch_; }; } // namespace net diff --git a/net/proxy/proxy_resolver_script_data.cc b/net/proxy/proxy_resolver_script_data.cc index 0314fc3..dbe1e32 100644 --- a/net/proxy/proxy_resolver_script_data.cc +++ b/net/proxy/proxy_resolver_script_data.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -45,6 +45,23 @@ const GURL& ProxyResolverScriptData::url() const { return url_; } +bool ProxyResolverScriptData::Equals( + const ProxyResolverScriptData* other) const { + if (type() != other->type()) + return false; + + switch (type()) { + case TYPE_SCRIPT_CONTENTS: + return utf16() == other->utf16(); + case TYPE_SCRIPT_URL: + return url() == other->url(); + case TYPE_AUTO_DETECT: + return true; + } + + return false; // Shouldn't be reached. +} + ProxyResolverScriptData::ProxyResolverScriptData(Type type, const GURL& url, const string16& utf16) diff --git a/net/proxy/proxy_resolver_script_data.h b/net/proxy/proxy_resolver_script_data.h index 7631d13..751b0e2 100644 --- a/net/proxy/proxy_resolver_script_data.h +++ b/net/proxy/proxy_resolver_script_data.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -54,6 +54,9 @@ class NET_EXPORT_PRIVATE ProxyResolverScriptData // (only valid for type() == TYPE_SCRIPT_URL). const GURL& url() const; + // Returns true if |this| matches |other|. + bool Equals(const ProxyResolverScriptData* other) const; + private: friend class base::RefCountedThreadSafe<ProxyResolverScriptData>; ProxyResolverScriptData(Type type, diff --git a/net/proxy/proxy_service.cc b/net/proxy/proxy_service.cc index c8193b4..fbfe21e 100644 --- a/net/proxy/proxy_service.cc +++ b/net/proxy/proxy_service.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -10,6 +10,7 @@ #include "base/bind_helpers.h" #include "base/compiler_specific.h" #include "base/logging.h" +#include "base/memory/weak_ptr.h" #include "base/message_loop.h" #include "base/message_loop_proxy.h" #include "base/string_util.h" @@ -52,7 +53,7 @@ const size_t kMaxNumNetLogEntries = 100; const size_t kDefaultNumPacThreads = 4; // When the IP address changes we don't immediately re-run proxy auto-config. -// Instead, we wait for |kNumMillisToStallAfterNetworkChanges| before +// Instead, we wait for |kDelayAfterNetworkChangesMs| before // attempting to re-valuate proxy auto-config. // // During this time window, any resolve requests sent to the ProxyService will @@ -65,30 +66,19 @@ const size_t kDefaultNumPacThreads = 4; // we were to run proxy auto-config right away, it could fail due to spurious // DNS failures. (see http://crbug.com/50779 for more details.) // -// By adding the wait window, we give things a chance to get properly set up. -// Now by the time we run the proxy-autoconfig there is a lower chance of -// getting transient DNS / connect failures. -// -// Admittedly this is a hack. Ideally we would have NetworkChangeNotifier -// deliver a reliable signal indicating that the network has changed AND is -// ready for action... But until then, we can reduce the likelihood of users -// getting wedged because of proxy detection failures on network switch. -// -// The obvious downside to this strategy is it introduces an additional -// latency when switching networks. This delay shouldn't be too disruptive -// assuming network switches are infrequent and user initiated. However if -// NetworkChangeNotifier delivers network changes more frequently this could -// cause jankiness. (NetworkChangeNotifier broadcasts a change event when ANY -// interface goes up/down. So in theory if the non-primary interface were -// hopping on and off wireless networks our constant delayed reconfiguration -// could add noticeable jank.) -// -// The specific hard-coded wait time below is arbitrary. -// Basically I ran some experiments switching between wireless networks on -// a Linux Ubuntu (Lucid) laptop, and experimentally found this timeout fixes -// things. It is entirely possible that the value is insufficient for other -// setups. -const int64 kNumMillisToStallAfterNetworkChanges = 2000; +// By adding the wait window, we give things a better chance to get properly +// set up. Network failures can happen at any time though, so we additionally +// poll the PAC script for changes, which will allow us to recover from these +// sorts of problems. +const int64 kDelayAfterNetworkChangesMs = 2000; + +// The initial number of milliseconds to wait before refetching PAC script after +// a fetch error/success. +const int64 kInitialPollDelayForErrorMs = 4000; +const int64 kInitialPollDelayForSuccessMs = 16000; + +// The maximum poll delay for checking PAC scripts. +const int64 kMaxPollDelayMs = 120000; // 2 minutes. // Config getter that always returns direct settings. class ProxyConfigServiceDirect : public ProxyConfigService { @@ -323,18 +313,16 @@ class BadProxyListNetLogParam : public NetLog::EventParameters { // figure out what we should configure against. // (2) Feed the fetched PAC script into the ProxyResolver. // -// TODO(eroman): This is something of a temporary shim while refactoring to keep -// things similar. It will probably end up changing while solving bug 90581. +// InitProxyResolver is a single-use class which encapsulates cancellation as +// part of its destructor. Start() or StartSkipDecider() should be called just +// once. The instance can be destroyed at any time, and the request will be +// cancelled. class ProxyService::InitProxyResolver { public: - InitProxyResolver(ProxyResolver* proxy_resolver, - ProxyScriptFetcher* proxy_script_fetcher, - DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher, - NetLog* net_log) - : decider_(proxy_script_fetcher, dhcp_proxy_script_fetcher, net_log), - effective_config_(NULL), - proxy_resolver_(proxy_resolver) { + InitProxyResolver() + : proxy_resolver_(NULL), + next_state_(STATE_NONE) { } ~InitProxyResolver() { @@ -345,19 +333,63 @@ class ProxyService::InitProxyResolver { } } - int Init(const ProxyConfig& config, - base::TimeDelta wait_delay, - ProxyConfig* effective_config, - const CompletionCallback& callback) { + // Begins initializing the proxy resolver; calls |callback| when done. + int Start(ProxyResolver* proxy_resolver, + ProxyScriptFetcher* proxy_script_fetcher, + DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher, + NetLog* net_log, + const ProxyConfig& config, + base::TimeDelta wait_delay, + const CompletionCallback& callback) { + DCHECK_EQ(STATE_NONE, next_state_); + proxy_resolver_ = proxy_resolver; + + decider_.reset(new ProxyScriptDecider( + proxy_script_fetcher, dhcp_proxy_script_fetcher, net_log)); config_ = config; wait_delay_ = wait_delay; - effective_config_ = effective_config; callback_ = callback; next_state_ = STATE_DECIDE_PROXY_SCRIPT; return DoLoop(OK); } + // Similar to Start(), however it skips the ProxyScriptDecider stage. Instead + // |effective_config|, |decider_result| and |script_data| will be used as the + // inputs for initializing the ProxyResolver. + int StartSkipDecider(ProxyResolver* proxy_resolver, + const ProxyConfig& effective_config, + int decider_result, + ProxyResolverScriptData* script_data, + const CompletionCallback& callback) { + DCHECK_EQ(STATE_NONE, next_state_); + proxy_resolver_ = proxy_resolver; + + effective_config_ = effective_config; + script_data_ = script_data; + callback_ = callback; + + if (decider_result != OK) + return decider_result; + + next_state_ = STATE_SET_PAC_SCRIPT; + return DoLoop(OK); + } + + // Returns the proxy configuration that was selected by ProxyScriptDecider. + // Should only be called upon completion of the initialization. + const ProxyConfig& effective_config() const { + DCHECK_EQ(STATE_NONE, next_state_); + return effective_config_; + } + + // Returns the PAC script data that was selected by ProxyScriptDecider. + // Should only be called upon completion of the initialization. + ProxyResolverScriptData* script_data() { + DCHECK_EQ(STATE_NONE, next_state_); + return script_data_.get(); + } + private: enum State { STATE_NONE, @@ -400,7 +432,7 @@ class ProxyService::InitProxyResolver { int DoDecideProxyScript() { next_state_ = STATE_DECIDE_PROXY_SCRIPT_COMPLETE; - return decider_.Start( + return decider_->Start( config_, wait_delay_, proxy_resolver_->expects_pac_bytes(), base::Bind(&InitProxyResolver::OnIOCompletion, base::Unretained(this))); } @@ -409,17 +441,19 @@ class ProxyService::InitProxyResolver { if (result != OK) return result; - *effective_config_ = decider_.effective_config(); + effective_config_ = decider_->effective_config(); + script_data_ = decider_->script_data(); + next_state_ = STATE_SET_PAC_SCRIPT; return OK; } int DoSetPacScript() { - DCHECK(decider_.script_data()); + DCHECK(script_data_); // TODO(eroman): Should log this latency to the NetLog. next_state_ = STATE_SET_PAC_SCRIPT_COMPLETE; return proxy_resolver_->SetPacScript( - decider_.script_data(), + script_data_, base::Bind(&InitProxyResolver::OnIOCompletion, base::Unretained(this))); } @@ -440,9 +474,10 @@ class ProxyService::InitProxyResolver { } ProxyConfig config_; + ProxyConfig effective_config_; + scoped_refptr<ProxyResolverScriptData> script_data_; base::TimeDelta wait_delay_; - ProxyScriptDecider decider_; - ProxyConfig* effective_config_; + scoped_ptr<ProxyScriptDecider> decider_; ProxyResolver* proxy_resolver_; CompletionCallback callback_; State next_state_; @@ -450,6 +485,203 @@ class ProxyService::InitProxyResolver { DISALLOW_COPY_AND_ASSIGN(InitProxyResolver); }; +// ProxyService::ProxyScriptDeciderPoller ------------------------------------- + +// This helper class encapsulates the logic to schedule and run periodic +// background checks to see if the PAC script (or effective proxy configuration) +// has changed. If a change is detected, then the caller will be notified via +// the ChangeCallback. +class ProxyService::ProxyScriptDeciderPoller { + public: + typedef base::Callback<void(int, ProxyResolverScriptData*, + const ProxyConfig&)> ChangeCallback; + + // Builds a poller helper, and starts polling for updates. Whenever a change + // is observed, |callback| will be invoked with the details. + // + // |config| specifies the (unresolved) proxy configuration to poll. + // |proxy_resolver_expects_pac_bytes| the type of proxy resolver we expect + // to use the resulting script data with + // (so it can choose the right format). + // |proxy_script_fetcher| this pointer must remain alive throughout our + // lifetime. It is the dependency that will be used + // for downloading proxy scripts. + // |dhcp_proxy_script_fetcher| similar to |proxy_script_fetcher|, but for + // the DHCP dependency. + // |init_net_error| This is the initial network error (possibly success) + // encountered by the first PAC fetch attempt. We use it + // to schedule updates more aggressively if the initial + // fetch resulted in an error. + // |init_script_data| the initial script data from the PAC fetch attempt. + // This is the baseline used to determine when the + // script's contents have changed. + // |net_log| the NetLog to log progress into. + ProxyScriptDeciderPoller(ChangeCallback callback, + const ProxyConfig& config, + bool proxy_resolver_expects_pac_bytes, + ProxyScriptFetcher* proxy_script_fetcher, + DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher, + int init_net_error, + ProxyResolverScriptData* init_script_data, + NetLog* net_log) + : ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), + change_callback_(callback), + config_(config), + proxy_resolver_expects_pac_bytes_(proxy_resolver_expects_pac_bytes), + proxy_script_fetcher_(proxy_script_fetcher), + dhcp_proxy_script_fetcher_(dhcp_proxy_script_fetcher), + last_error_(init_net_error), + last_script_data_(init_script_data) { + // Set the initial poll delay -- we check more aggressively right after a + // failure in case it was due to a spurious network error. + current_poll_delay_ms_ = GetInitialWaitDelayMs(init_net_error); + + StartPollTimer(); + } + + // ---------------------------------- + // Policy for the retry scheduling. + // ---------------------------------- + + static int64 GetInitialWaitDelayMs(int error) { + switch (poll_policy_) { + case POLL_POLICY_REGULAR: + // Use a shorter wait delay if the initial fetch resulted in an error. + return (error != OK) ? + kInitialPollDelayForErrorMs : kInitialPollDelayForSuccessMs; + case POLL_POLICY_IMMEDIATE: + // Wait a mere 1 millisecond. + return 1; + case POLL_POLICY_NEVER: + // Unreasonably large wait delay, will never fire the check. + return 0xFFFFFFFF; + } + + // Shouldn't be reached + return -1; + } + + static int64 GetNextWaitDelayMs(int64 current_delay_ms) { + switch (poll_policy_) { + case POLL_POLICY_REGULAR: + // Increase the delay exponentially up to 2 minutes. + return std::min(current_delay_ms * 2, kMaxPollDelayMs); + case POLL_POLICY_IMMEDIATE: + case POLL_POLICY_NEVER: + return current_delay_ms; + } + + // Shouldn't be reached + return -1; + } + + static PollPolicy set_policy(PollPolicy policy) { + PollPolicy prev = poll_policy_; + poll_policy_ = policy; + return prev; + } + + private: + void StartPollTimer() { + DCHECK(!decider_.get()); + + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&ProxyScriptDeciderPoller::OnPollTimerFired, + weak_factory_.GetWeakPtr()), + current_poll_delay_ms_); + } + + void OnPollTimerFired() { + // Start the proxy script decider to see if anything has changed. + // TODO(eroman): Pass a proper NetLog rather than NULL. + decider_.reset(new ProxyScriptDecider( + proxy_script_fetcher_, dhcp_proxy_script_fetcher_, NULL)); + int result = decider_->Start( + config_, base::TimeDelta(), proxy_resolver_expects_pac_bytes_, + base::Bind(&ProxyScriptDeciderPoller::OnProxyScriptDeciderCompleted, + base::Unretained(this))); + + if (result != ERR_IO_PENDING) + OnProxyScriptDeciderCompleted(result); + } + + void OnProxyScriptDeciderCompleted(int result) { + if (HasScriptDataChanged(result, decider_->script_data())) { + // Something has changed, we must notify the ProxyService so it can + // re-initialize its ProxyResolver. Note that we post a notification task + // rather than calling it directly -- this is done to avoid an ugly + // destruction sequence, since |this| might be destroyed as a result of + // the notification. + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind( + &ProxyScriptDeciderPoller::NotifyProxyServiceOfChange, + weak_factory_.GetWeakPtr(), + result, + make_scoped_refptr(decider_->script_data()), + decider_->effective_config())); + return; + } + + decider_.reset(); + + // Schedule the next poll check. + current_poll_delay_ms_ = GetNextWaitDelayMs(current_poll_delay_ms_); + StartPollTimer(); + } + + bool HasScriptDataChanged(int result, ProxyResolverScriptData* script_data) { + if (result != last_error_) { + // Something changed -- it was failing before and now it succeeded, or + // conversely it succeeded before and now it failed. Or it failed in + // both cases, however the specific failure error codes differ. + return true; + } + + if (result != OK) { + // If it failed last time and failed again with the same error code this + // time, then nothing has actually changed. + return false; + } + + // Otherwise if it succeeded both this time and last time, we need to look + // closer and see if we ended up downloading different content for the PAC + // script. + return !script_data->Equals(last_script_data_); + } + + void NotifyProxyServiceOfChange( + int result, + const scoped_refptr<ProxyResolverScriptData>& script_data, + const ProxyConfig& effective_config) { + // Note that |this| may be deleted after calling into the ProxyService. + change_callback_.Run(result, script_data, effective_config); + } + + base::WeakPtrFactory<ProxyScriptDeciderPoller> weak_factory_; + + ChangeCallback change_callback_; + ProxyConfig config_; + bool proxy_resolver_expects_pac_bytes_; + ProxyScriptFetcher* proxy_script_fetcher_; + DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher_; + + int last_error_; + scoped_refptr<ProxyResolverScriptData> last_script_data_; + + scoped_ptr<ProxyScriptDecider> decider_; + int64 current_poll_delay_ms_; + + static PollPolicy poll_policy_; + + DISALLOW_COPY_AND_ASSIGN(ProxyScriptDeciderPoller); +}; + +// static +ProxyService::PollPolicy ProxyService::ProxyScriptDeciderPoller::poll_policy_ = + ProxyService::POLL_POLICY_REGULAR; + // ProxyService::PacRequest --------------------------------------------------- class ProxyService::PacRequest @@ -592,7 +824,7 @@ ProxyService::ProxyService(ProxyConfigService* config_service, net_log_(net_log), stall_proxy_auto_config_delay_( base::TimeDelta::FromMilliseconds( - kNumMillisToStallAfterNetworkChanges)) { + kDelayAfterNetworkChangesMs)) { NetworkChangeNotifier::AddIPAddressObserver(this); ResetConfigService(config_service); } @@ -858,6 +1090,24 @@ void ProxyService::OnInitProxyResolverComplete(int result) { DCHECK_EQ(STATE_WAITING_FOR_INIT_PROXY_RESOLVER, current_state_); DCHECK(init_proxy_resolver_.get()); DCHECK(fetched_config_.HasAutomaticSettings()); + config_ = init_proxy_resolver_->effective_config(); + + // At this point we have decided which proxy settings to use (i.e. which PAC + // script if any). We start up a background poller to periodically revisit + // this decision. If the contents of the PAC script change, or if the + // result of proxy auto-discovery changes, this poller will notice it and + // will trigger a re-initialization using the newly discovered PAC. + script_poller_.reset(new ProxyScriptDeciderPoller( + base::Bind(&ProxyService::InitializeUsingDecidedConfig, + base::Unretained(this)), + fetched_config_, + resolver_->expects_pac_bytes(), + proxy_script_fetcher_.get(), + dhcp_proxy_script_fetcher_.get(), + result, + init_proxy_resolver_->script_data(), + NULL)); + init_proxy_resolver_.reset(); if (result != OK) { @@ -876,6 +1126,8 @@ void ProxyService::OnInitProxyResolverComplete(int result) { } permanent_error_ = result; + // TODO(eroman): Make this ID unique in the case where configuration changed + // due to ProxyScriptDeciderPoller. config_.set_id(fetched_config_.id()); // Resume any requests which we had to defer until the PAC script was @@ -1022,6 +1274,7 @@ ProxyService::State ProxyService::ResetProxyConfig(bool reset_fetched_config) { permanent_error_ = OK; proxy_retry_info_.clear(); + script_poller_.reset(); init_proxy_resolver_.reset(); SuspendAllPendingRequests(); config_ = ProxyConfig(); @@ -1099,6 +1352,12 @@ ProxyConfigService* ProxyService::CreateSystemProxyConfigService( #endif } +// static +ProxyService::PollPolicy ProxyService::set_pac_script_poll_policy( + PollPolicy policy) { + return ProxyScriptDeciderPoller::set_policy(policy); +} + void ProxyService::OnProxyConfigChanged( const ProxyConfig& config, ProxyConfigService::ConfigAvailability availability) { @@ -1154,18 +1413,42 @@ void ProxyService::InitializeUsingLastFetchedConfig() { // Start downloading + testing the PAC scripts for this new configuration. current_state_ = STATE_WAITING_FOR_INIT_PROXY_RESOLVER; - init_proxy_resolver_.reset( - new InitProxyResolver(resolver_.get(), - proxy_script_fetcher_.get(), - dhcp_proxy_script_fetcher_.get(), - net_log_)); - // If we changed networks recently, we should delay running proxy auto-config. base::TimeDelta wait_delay = stall_proxy_autoconfig_until_ - base::TimeTicks::Now(); - int rv = init_proxy_resolver_->Init( - fetched_config_, wait_delay, &config_, + init_proxy_resolver_.reset(new InitProxyResolver()); + int rv = init_proxy_resolver_->Start( + resolver_.get(), + proxy_script_fetcher_.get(), + dhcp_proxy_script_fetcher_.get(), + net_log_, + fetched_config_, + wait_delay, + base::Bind(&ProxyService::OnInitProxyResolverComplete, + base::Unretained(this))); + + if (rv != ERR_IO_PENDING) + OnInitProxyResolverComplete(rv); +} + +void ProxyService::InitializeUsingDecidedConfig( + int decider_result, + ProxyResolverScriptData* script_data, + const ProxyConfig& effective_config) { + DCHECK(fetched_config_.is_valid()); + DCHECK(fetched_config_.HasAutomaticSettings()); + + ResetProxyConfig(false); + + current_state_ = STATE_WAITING_FOR_INIT_PROXY_RESOLVER; + + init_proxy_resolver_.reset(new InitProxyResolver()); + int rv = init_proxy_resolver_->StartSkipDecider( + resolver_.get(), + effective_config, + decider_result, + script_data, base::Bind(&ProxyService::OnInitProxyResolverComplete, base::Unretained(this))); @@ -1174,7 +1457,7 @@ void ProxyService::InitializeUsingLastFetchedConfig() { } void ProxyService::OnIPAddressChanged() { - // See the comment block by |kNumMillisToStallAfterNetworkChanges| for info. + // See the comment block by |kDelayAfterNetworkChangesMs| for info. stall_proxy_autoconfig_until_ = base::TimeTicks::Now() + stall_proxy_auto_config_delay_; diff --git a/net/proxy/proxy_service.h b/net/proxy/proxy_service.h index 41eabb3..dd9b712 100644 --- a/net/proxy/proxy_service.h +++ b/net/proxy/proxy_service.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -32,6 +32,7 @@ class DhcpProxyScriptFetcher; class HostResolver; class NetworkDelegate; class ProxyResolver; +class ProxyResolverScriptData; class ProxyScriptDecider; class ProxyScriptFetcher; @@ -42,6 +43,13 @@ class NET_EXPORT ProxyService : public NetworkChangeNotifier::IPAddressObserver, public ProxyConfigService::Observer, NON_EXPORTED_BASE(public base::NonThreadSafe) { public: + // Only used by unit-tests. + enum PollPolicy { + POLL_POLICY_REGULAR, // Normal PAC poll policy (retry periodically). + POLL_POLICY_NEVER, // Don't re-fetch PAC scripts for changes. + POLL_POLICY_IMMEDIATE, // Check every 1 ms. + }; + // The instance takes ownership of |config_service| and |resolver|. // |net_log| is a possibly NULL destination to send log events to. It must // remain alive for the lifetime of this ProxyService. @@ -236,11 +244,16 @@ class NET_EXPORT ProxyService : public NetworkChangeNotifier::IPAddressObserver, stall_proxy_auto_config_delay_ = delay; } + // This method should only be used by unit tests. Returns the previously + // active policy. + static PollPolicy set_pac_script_poll_policy(PollPolicy policy); + private: FRIEND_TEST_ALL_PREFIXES(ProxyServiceTest, UpdateConfigAfterFailedAutodetect); FRIEND_TEST_ALL_PREFIXES(ProxyServiceTest, UpdateConfigFromPACToDirect); friend class PacRequest; class InitProxyResolver; + class ProxyScriptDeciderPoller; // TODO(eroman): change this to a std::set. Note that this requires updating // some tests in proxy_service_unittest.cc such as: @@ -300,6 +313,12 @@ class NET_EXPORT ProxyService : public NetworkChangeNotifier::IPAddressObserver, // Start initialization using |fetched_config_|. void InitializeUsingLastFetchedConfig(); + // Start the initialization skipping past the "decision" phase. + void InitializeUsingDecidedConfig( + int decider_result, + ProxyResolverScriptData* script_data, + const ProxyConfig& effective_config); + // NetworkChangeNotifier::IPAddressObserver // When this is called, we re-fetch PAC scripts and re-run WPAD. virtual void OnIPAddressChanged() OVERRIDE; @@ -348,6 +367,9 @@ class NET_EXPORT ProxyService : public NetworkChangeNotifier::IPAddressObserver, // |proxy_resolver_| must outlive |init_proxy_resolver_|. scoped_ptr<InitProxyResolver> init_proxy_resolver_; + // Helper to poll the PAC script for changes. + scoped_ptr<ProxyScriptDeciderPoller> script_poller_; + State current_state_; // Either OK or an ERR_* value indicating that a permanent error (e.g. diff --git a/net/proxy/proxy_service_unittest.cc b/net/proxy/proxy_service_unittest.cc index 271dfc4..0be99f2 100644 --- a/net/proxy/proxy_service_unittest.cc +++ b/net/proxy/proxy_service_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -28,6 +28,38 @@ namespace net { namespace { +// This test fixture is used to partially disable the background polling done by +// the ProxyService (which it uses to detect whenever its PAC script contents or +// WPAD results have changed). +// +// We disable the feature by setting the poll interval to something really +// large, so it will never actually be reached even on the slowest bots that run +// these tests. +// +// We disable the polling in order to avoid any timing dependencies in the +// tests. If the bot were to run the tests very slowly and we hadn't disabled +// polling, then it might start a background re-try in the middle of our test +// and confuse our expectations leading to flaky failures. +// +// The tests which verify the polling code re-enable the polling behavior but +// are careful to avoid timing problems. +class ProxyServiceTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { + testing::Test::SetUp(); + previous_policy_ = ProxyService::set_pac_script_poll_policy( + ProxyService::POLL_POLICY_NEVER); + } + + virtual void TearDown() OVERRIDE { + // Restore the original policy. + ProxyService::set_pac_script_poll_policy(previous_policy_); + testing::Test::TearDown(); + } + + ProxyService::PollPolicy previous_policy_; +}; + const char kValidPacScript1[] = "pac-script-v1-FindProxyForURL"; const char kValidPacScript2[] = "pac-script-v2-FindProxyForURL"; @@ -73,7 +105,7 @@ class MockProxyConfigService: public ProxyConfigService { } // namespace -TEST(ProxyServiceTest, Direct) { +TEST_F(ProxyServiceTest, Direct) { MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver; ProxyService service(new MockProxyConfigService( ProxyConfig::CreateDirect()), resolver, NULL); @@ -104,7 +136,7 @@ TEST(ProxyServiceTest, Direct) { entries, 2, NetLog::TYPE_PROXY_SERVICE)); } -TEST(ProxyServiceTest, PAC) { +TEST_F(ProxyServiceTest, PAC) { MockProxyConfigService* config_service = new MockProxyConfigService("http://foopy/proxy.pac"); @@ -154,7 +186,7 @@ TEST(ProxyServiceTest, PAC) { // Test that the proxy resolver does not see the URL's username/password // or its reference section. -TEST(ProxyServiceTest, PAC_NoIdentityOrHash) { +TEST_F(ProxyServiceTest, PAC_NoIdentityOrHash) { MockProxyConfigService* config_service = new MockProxyConfigService("http://foopy/proxy.pac"); @@ -183,7 +215,7 @@ TEST(ProxyServiceTest, PAC_NoIdentityOrHash) { // ProxyService will cancel the outstanding request. } -TEST(ProxyServiceTest, PAC_FailoverWithoutDirect) { +TEST_F(ProxyServiceTest, PAC_FailoverWithoutDirect) { MockProxyConfigService* config_service = new MockProxyConfigService("http://foopy/proxy.pac"); MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver; @@ -241,7 +273,7 @@ TEST(ProxyServiceTest, PAC_FailoverWithoutDirect) { // // The important check of this test is to make sure that DIRECT is not somehow // cached as being a bad proxy. -TEST(ProxyServiceTest, PAC_FailoverAfterDirect) { +TEST_F(ProxyServiceTest, PAC_FailoverAfterDirect) { MockProxyConfigService* config_service = new MockProxyConfigService("http://foopy/proxy.pac"); MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver; @@ -302,7 +334,7 @@ TEST(ProxyServiceTest, PAC_FailoverAfterDirect) { EXPECT_TRUE(info.is_empty()); } -TEST(ProxyServiceTest, ProxyResolverFails) { +TEST_F(ProxyServiceTest, ProxyResolverFails) { // Test what happens when the ProxyResolver fails. The download and setting // of the PAC script have already succeeded, so this corresponds with a // javascript runtime error while calling FindProxyForURL(). @@ -357,7 +389,7 @@ TEST(ProxyServiceTest, ProxyResolverFails) { EXPECT_EQ("foopy_valid:8080", info.proxy_server().ToURI()); } -TEST(ProxyServiceTest, ProxyScriptFetcherFailsDownloadingMandatoryPac) { +TEST_F(ProxyServiceTest, ProxyScriptFetcherFailsDownloadingMandatoryPac) { // Test what happens when the ProxyScriptResolver fails to download a // mandatory PAC script. @@ -400,7 +432,7 @@ TEST(ProxyServiceTest, ProxyScriptFetcherFailsDownloadingMandatoryPac) { EXPECT_FALSE(info.is_direct()); } -TEST(ProxyServiceTest, ProxyResolverFailsParsingJavaScriptMandatoryPac) { +TEST_F(ProxyServiceTest, ProxyResolverFailsParsingJavaScriptMandatoryPac) { // Test what happens when the ProxyResolver fails that is configured to use a // mandatory PAC script. The download of the PAC script has already // succeeded but the PAC script contains no valid javascript. @@ -447,7 +479,7 @@ TEST(ProxyServiceTest, ProxyResolverFailsParsingJavaScriptMandatoryPac) { EXPECT_FALSE(info.is_direct()); } -TEST(ProxyServiceTest, ProxyResolverFailsInJavaScriptMandatoryPac) { +TEST_F(ProxyServiceTest, ProxyResolverFailsInJavaScriptMandatoryPac) { // Test what happens when the ProxyResolver fails that is configured to use a // mandatory PAC script. The download and setting of the PAC script have // already succeeded, so this corresponds with a javascript runtime error @@ -507,7 +539,7 @@ TEST(ProxyServiceTest, ProxyResolverFailsInJavaScriptMandatoryPac) { EXPECT_EQ("foopy_valid:8080", info.proxy_server().ToURI()); } -TEST(ProxyServiceTest, ProxyFallback) { +TEST_F(ProxyServiceTest, ProxyFallback) { // Test what happens when we specify multiple proxy servers and some of them // are bad. @@ -624,7 +656,7 @@ TEST(ProxyServiceTest, ProxyFallback) { // This test is similar to ProxyFallback, but this time we have an explicit // fallback choice to DIRECT. -TEST(ProxyServiceTest, ProxyFallbackToDirect) { +TEST_F(ProxyServiceTest, ProxyFallbackToDirect) { MockProxyConfigService* config_service = new MockProxyConfigService("http://foopy/proxy.pac"); @@ -685,7 +717,7 @@ TEST(ProxyServiceTest, ProxyFallbackToDirect) { EXPECT_EQ(ERR_FAILED, rv); } -TEST(ProxyServiceTest, ProxyFallback_NewSettings) { +TEST_F(ProxyServiceTest, ProxyFallback_NewSettings) { // Test proxy failover when new settings are available. MockProxyConfigService* config_service = @@ -778,7 +810,7 @@ TEST(ProxyServiceTest, ProxyFallback_NewSettings) { EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); } -TEST(ProxyServiceTest, ProxyFallback_BadConfig) { +TEST_F(ProxyServiceTest, ProxyFallback_BadConfig) { // Test proxy failover when the configuration is bad. MockProxyConfigService* config_service = @@ -864,7 +896,7 @@ TEST(ProxyServiceTest, ProxyFallback_BadConfig) { EXPECT_EQ("foopy1:8080", info3.proxy_server().ToURI()); } -TEST(ProxyServiceTest, ProxyFallback_BadConfigMandatory) { +TEST_F(ProxyServiceTest, ProxyFallback_BadConfigMandatory) { // Test proxy failover when the configuration is bad. ProxyConfig config( @@ -954,7 +986,7 @@ TEST(ProxyServiceTest, ProxyFallback_BadConfigMandatory) { EXPECT_EQ("foopy1:8080", info3.proxy_server().ToURI()); } -TEST(ProxyServiceTest, ProxyBypassList) { +TEST_F(ProxyServiceTest, ProxyBypassList) { // Test that the proxy bypass rules are consulted. TestCompletionCallback callback[2]; @@ -985,7 +1017,7 @@ TEST(ProxyServiceTest, ProxyBypassList) { } -TEST(ProxyServiceTest, PerProtocolProxyTests) { +TEST_F(ProxyServiceTest, PerProtocolProxyTests) { ProxyConfig config; config.proxy_rules().ParseFromString("http=foopy1:8080;https=foopy2:8080"); config.set_auto_detect(false); @@ -1042,7 +1074,7 @@ TEST(ProxyServiceTest, PerProtocolProxyTests) { // If only HTTP and a SOCKS proxy are specified, check if ftp/https queries // fall back to the SOCKS proxy. -TEST(ProxyServiceTest, DefaultProxyFallbackToSOCKS) { +TEST_F(ProxyServiceTest, DefaultProxyFallbackToSOCKS) { ProxyConfig config; config.proxy_rules().ParseFromString("http=foopy1:8080;socks=foopy2:1080"); config.set_auto_detect(false); @@ -1100,7 +1132,7 @@ TEST(ProxyServiceTest, DefaultProxyFallbackToSOCKS) { } // Test cancellation of an in-progress request. -TEST(ProxyServiceTest, CancelInProgressRequest) { +TEST_F(ProxyServiceTest, CancelInProgressRequest) { MockProxyConfigService* config_service = new MockProxyConfigService("http://foopy/proxy.pac"); @@ -1173,7 +1205,7 @@ TEST(ProxyServiceTest, CancelInProgressRequest) { } // Test the initial PAC download for resolver that expects bytes. -TEST(ProxyServiceTest, InitialPACScriptDownload) { +TEST_F(ProxyServiceTest, InitialPACScriptDownload) { MockProxyConfigService* config_service = new MockProxyConfigService("http://foopy/proxy.pac"); @@ -1253,7 +1285,7 @@ TEST(ProxyServiceTest, InitialPACScriptDownload) { } // Test changing the ProxyScriptFetcher while PAC download is in progress. -TEST(ProxyServiceTest, ChangeScriptFetcherWhilePACDownloadInProgress) { +TEST_F(ProxyServiceTest, ChangeScriptFetcherWhilePACDownloadInProgress) { MockProxyConfigService* config_service = new MockProxyConfigService("http://foopy/proxy.pac"); @@ -1312,7 +1344,7 @@ TEST(ProxyServiceTest, ChangeScriptFetcherWhilePACDownloadInProgress) { } // Test cancellation of a request, while the PAC script is being fetched. -TEST(ProxyServiceTest, CancelWhilePACFetching) { +TEST_F(ProxyServiceTest, CancelWhilePACFetching) { MockProxyConfigService* config_service = new MockProxyConfigService("http://foopy/proxy.pac"); @@ -1402,7 +1434,7 @@ TEST(ProxyServiceTest, CancelWhilePACFetching) { } // Test that if auto-detect fails, we fall-back to the custom pac. -TEST(ProxyServiceTest, FallbackFromAutodetectToCustomPac) { +TEST_F(ProxyServiceTest, FallbackFromAutodetectToCustomPac) { ProxyConfig config; config.set_auto_detect(true); config.set_pac_url(GURL("http://foopy/proxy.pac")); @@ -1473,7 +1505,7 @@ TEST(ProxyServiceTest, FallbackFromAutodetectToCustomPac) { // This is the same test as FallbackFromAutodetectToCustomPac, except // the auto-detect script fails parsing rather than downloading. -TEST(ProxyServiceTest, FallbackFromAutodetectToCustomPac2) { +TEST_F(ProxyServiceTest, FallbackFromAutodetectToCustomPac2) { ProxyConfig config; config.set_auto_detect(true); config.set_pac_url(GURL("http://foopy/proxy.pac")); @@ -1546,7 +1578,7 @@ TEST(ProxyServiceTest, FallbackFromAutodetectToCustomPac2) { // Test that if all of auto-detect, a custom PAC script, and manual settings // are given, then we will try them in that order. -TEST(ProxyServiceTest, FallbackFromAutodetectToCustomToManual) { +TEST_F(ProxyServiceTest, FallbackFromAutodetectToCustomToManual) { ProxyConfig config; config.set_auto_detect(true); config.set_pac_url(GURL("http://foopy/proxy.pac")); @@ -1603,7 +1635,7 @@ TEST(ProxyServiceTest, FallbackFromAutodetectToCustomToManual) { } // Test that the bypass rules are NOT applied when using autodetect. -TEST(ProxyServiceTest, BypassDoesntApplyToPac) { +TEST_F(ProxyServiceTest, BypassDoesntApplyToPac) { ProxyConfig config; config.set_auto_detect(true); config.set_pac_url(GURL("http://foopy/proxy.pac")); @@ -1624,7 +1656,8 @@ TEST(ProxyServiceTest, BypassDoesntApplyToPac) { ProxyInfo info1; TestCompletionCallback callback1; int rv = service.ResolveProxy( - GURL("http://www.google.com"), &info1, callback1.callback(), NULL, BoundNetLog()); + GURL("http://www.google.com"), &info1, callback1.callback(), NULL, + BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); // Check that nothing has been sent to the proxy resolver yet. @@ -1674,7 +1707,7 @@ TEST(ProxyServiceTest, BypassDoesntApplyToPac) { // request to the script fetcher. When run under valgrind, should not // have any memory errors (used to be that the ProxyScriptFetcher was // being deleted prior to the InitProxyResolver). -TEST(ProxyServiceTest, DeleteWhileInitProxyResolverHasOutstandingFetch) { +TEST_F(ProxyServiceTest, DeleteWhileInitProxyResolverHasOutstandingFetch) { ProxyConfig config = ProxyConfig::CreateFromCustomPacURL(GURL("http://foopy/proxy.pac")); @@ -1708,7 +1741,7 @@ TEST(ProxyServiceTest, DeleteWhileInitProxyResolverHasOutstandingFetch) { // request to the proxy resolver. When run under valgrind, should not // have any memory errors (used to be that the ProxyResolver was // being deleted prior to the InitProxyResolver). -TEST(ProxyServiceTest, DeleteWhileInitProxyResolverHasOutstandingSet) { +TEST_F(ProxyServiceTest, DeleteWhileInitProxyResolverHasOutstandingSet) { MockProxyConfigService* config_service = new MockProxyConfigService("http://foopy/proxy.pac"); @@ -1728,7 +1761,7 @@ TEST(ProxyServiceTest, DeleteWhileInitProxyResolverHasOutstandingSet) { resolver->pending_set_pac_script_request()->script_data()->url()); } -TEST(ProxyServiceTest, ResetProxyConfigService) { +TEST_F(ProxyServiceTest, ResetProxyConfigService) { ProxyConfig config1; config1.proxy_rules().ParseFromString("foopy1:8080"); config1.set_auto_detect(false); @@ -1756,7 +1789,7 @@ TEST(ProxyServiceTest, ResetProxyConfigService) { // Test that when going from a configuration that required PAC to one // that does NOT, we unset the variable |should_use_proxy_resolver_|. -TEST(ProxyServiceTest, UpdateConfigFromPACToDirect) { +TEST_F(ProxyServiceTest, UpdateConfigFromPACToDirect) { ProxyConfig config = ProxyConfig::CreateAutoDetect(); MockProxyConfigService* config_service = new MockProxyConfigService(config); @@ -1805,7 +1838,7 @@ TEST(ProxyServiceTest, UpdateConfigFromPACToDirect) { EXPECT_TRUE(info2.is_direct()); } -TEST(ProxyServiceTest, NetworkChangeTriggersPacRefetch) { +TEST_F(ProxyServiceTest, NetworkChangeTriggersPacRefetch) { MockProxyConfigService* config_service = new MockProxyConfigService("http://foopy/proxy.pac"); @@ -1916,4 +1949,426 @@ TEST(ProxyServiceTest, NetworkChangeTriggersPacRefetch) { EXPECT_NE(NetLog::TYPE_PROXY_CONFIG_CHANGED, entries[i].type); } +// This test verifies that the PAC script specified by the settings is +// periodically polled for changes. Specifically, if the initial fetch fails due +// to a network error, we will eventually re-configure the service to use the +// script once it becomes available. +TEST_F(ProxyServiceTest, PACScriptRefetchAfterFailure) { + // Change the retry policy to wait a mere 1 ms before retrying, so the test + // runs quickly. + ProxyService::set_pac_script_poll_policy( + ProxyService::POLL_POLICY_IMMEDIATE); + + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolverExpectsBytes* resolver = + new MockAsyncProxyResolverExpectsBytes; + + CapturingNetLog log(CapturingNetLog::kUnbounded); + + ProxyService service(config_service, resolver, &log); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers(fetcher, + new DoNothingDhcpProxyScriptFetcher()); + + // Start 1 request. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy( + GURL("http://request1"), &info1, callback1.callback(), + NULL, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // The first request should have triggered initial download of PAC script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + + // Nothing has been sent to the resolver yet. + EXPECT_TRUE(resolver->pending_requests().empty()); + + // At this point the ProxyService should be waiting for the + // ProxyScriptFetcher to invoke its completion callback, notifying it of + // PAC script download completion. + // + // We simulate a failed download attempt, the proxy service should now + // fall-back to DIRECT connections. + fetcher->NotifyFetchCompletion(ERR_FAILED, ""); + + ASSERT_TRUE(resolver->pending_requests().empty()); + + // Wait for completion callback, and verify it used DIRECT. + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_TRUE(info1.is_direct()); + + // At this point we have initialized the proxy service using a PAC script, + // however it failed and fell-back to DIRECT. + // + // A background task to periodically re-check the PAC script for validity will + // have been started. We will now wait for the next download attempt to start. + // + // Note that we shouldn't have to wait long here, since our test enables a + // special unit-test mode. + fetcher->WaitUntilFetch(); + + ASSERT_TRUE(resolver->pending_requests().empty()); + + // Make sure that our background checker is trying to download the expected + // PAC script (same one as before). This time we will simulate a successful + // download of the script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, kValidPacScript1); + + MessageLoop::current()->RunAllPending(); + + // Now that the PAC script is downloaded, it should be used to initialize the + // ProxyResolver. Simulate a successful parse. + EXPECT_EQ(ASCIIToUTF16(kValidPacScript1), + resolver->pending_set_pac_script_request()->script_data()->utf16()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + // At this point the ProxyService should have re-configured itself to use the + // PAC script (thereby recovering from the initial fetch failure). We will + // verify that the next Resolve request uses the resolver rather than + // DIRECT. + + // Start a second request. + ProxyInfo info2; + TestCompletionCallback callback2; + rv = service.ResolveProxy( + GURL("http://request2"), &info2, callback2.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // Check that it was sent to the resolver. + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[0]->url()); + + // Complete the pending second request. + resolver->pending_requests()[0]->results()->UseNamedProxy("request2:80"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // Wait for completion callback, and verify that the request ran as expected. + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); +} + +// This test verifies that the PAC script specified by the settings is +// periodically polled for changes. Specifically, if the initial fetch succeeds, +// however at a later time its *contents* change, we will eventually +// re-configure the service to use the new script. +TEST_F(ProxyServiceTest, PACScriptRefetchAfterContentChange) { + // Change the retry policy to wait a mere 1 ms before retrying, so the test + // runs quickly. + ProxyService::set_pac_script_poll_policy( + ProxyService::POLL_POLICY_IMMEDIATE); + + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolverExpectsBytes* resolver = + new MockAsyncProxyResolverExpectsBytes; + + CapturingNetLog log(CapturingNetLog::kUnbounded); + + ProxyService service(config_service, resolver, &log); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers(fetcher, + new DoNothingDhcpProxyScriptFetcher()); + + // Start 1 request. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy( + GURL("http://request1"), &info1, callback1.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // The first request should have triggered initial download of PAC script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + + // Nothing has been sent to the resolver yet. + EXPECT_TRUE(resolver->pending_requests().empty()); + + // At this point the ProxyService should be waiting for the + // ProxyScriptFetcher to invoke its completion callback, notifying it of + // PAC script download completion. + fetcher->NotifyFetchCompletion(OK, kValidPacScript1); + + // Now that the PAC script is downloaded, the request will have been sent to + // the proxy resolver. + EXPECT_EQ(ASCIIToUTF16(kValidPacScript1), + resolver->pending_set_pac_script_request()->script_data()->utf16()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url()); + + // Complete the pending request. + resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // Wait for completion callback, and verify that the request ran as expected. + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + // At this point we have initialized the proxy service using a PAC script. + // + // A background task to periodically re-check the PAC script for validity will + // have been started. We will now wait for the next download attempt to start. + // + // Note that we shouldn't have to wait long here, since our test enables a + // special unit-test mode. + fetcher->WaitUntilFetch(); + + ASSERT_TRUE(resolver->pending_requests().empty()); + + // Make sure that our background checker is trying to download the expected + // PAC script (same one as before). This time we will simulate a successful + // download of a DIFFERENT script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, kValidPacScript2); + + MessageLoop::current()->RunAllPending(); + + // Now that the PAC script is downloaded, it should be used to initialize the + // ProxyResolver. Simulate a successful parse. + EXPECT_EQ(ASCIIToUTF16(kValidPacScript2), + resolver->pending_set_pac_script_request()->script_data()->utf16()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + // At this point the ProxyService should have re-configured itself to use the + // new PAC script. + + // Start a second request. + ProxyInfo info2; + TestCompletionCallback callback2; + rv = service.ResolveProxy( + GURL("http://request2"), &info2, callback2.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // Check that it was sent to the resolver. + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[0]->url()); + + // Complete the pending second request. + resolver->pending_requests()[0]->results()->UseNamedProxy("request2:80"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // Wait for completion callback, and verify that the request ran as expected. + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); +} + +// This test verifies that the PAC script specified by the settings is +// periodically polled for changes. Specifically, if the initial fetch succeeds +// and so does the next poll, however the contents of the downloaded script +// have NOT changed, then we do not bother to re-initialize the proxy resolver. +TEST_F(ProxyServiceTest, PACScriptRefetchAfterContentUnchanged) { + // Change the retry policy to wait a mere 1 ms before retrying, so the test + // runs quickly. + ProxyService::set_pac_script_poll_policy( + ProxyService::POLL_POLICY_IMMEDIATE); + + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolverExpectsBytes* resolver = + new MockAsyncProxyResolverExpectsBytes; + + CapturingNetLog log(CapturingNetLog::kUnbounded); + + ProxyService service(config_service, resolver, &log); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers(fetcher, + new DoNothingDhcpProxyScriptFetcher()); + + // Start 1 request. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy( + GURL("http://request1"), &info1, callback1.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // The first request should have triggered initial download of PAC script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + + // Nothing has been sent to the resolver yet. + EXPECT_TRUE(resolver->pending_requests().empty()); + + // At this point the ProxyService should be waiting for the + // ProxyScriptFetcher to invoke its completion callback, notifying it of + // PAC script download completion. + fetcher->NotifyFetchCompletion(OK, kValidPacScript1); + + // Now that the PAC script is downloaded, the request will have been sent to + // the proxy resolver. + EXPECT_EQ(ASCIIToUTF16(kValidPacScript1), + resolver->pending_set_pac_script_request()->script_data()->utf16()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url()); + + // Complete the pending request. + resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // Wait for completion callback, and verify that the request ran as expected. + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + // At this point we have initialized the proxy service using a PAC script. + // + // A background task to periodically re-check the PAC script for validity will + // have been started. We will now wait for the next download attempt to start. + // + // Note that we shouldn't have to wait long here, since our test enables a + // special unit-test mode. + fetcher->WaitUntilFetch(); + + ASSERT_TRUE(resolver->pending_requests().empty()); + + // Make sure that our background checker is trying to download the expected + // PAC script (same one as before). We will simulate the same response as + // last time (i.e. the script is unchanged). + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, kValidPacScript1); + + MessageLoop::current()->RunAllPending(); + + ASSERT_FALSE(resolver->has_pending_set_pac_script_request()); + + // At this point the ProxyService is still running the same PAC script as + // before. + + // Start a second request. + ProxyInfo info2; + TestCompletionCallback callback2; + rv = service.ResolveProxy( + GURL("http://request2"), &info2, callback2.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // Check that it was sent to the resolver. + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[0]->url()); + + // Complete the pending second request. + resolver->pending_requests()[0]->results()->UseNamedProxy("request2:80"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // Wait for completion callback, and verify that the request ran as expected. + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); +} + +// This test verifies that the PAC script specified by the settings is +// periodically polled for changes. Specifically, if the initial fetch succeeds, +// however at a later time it starts to fail, we should re-configure the +// ProxyService to stop using that PAC script. +TEST_F(ProxyServiceTest, PACScriptRefetchAfterSuccess) { + // Change the retry policy to wait a mere 1 ms before retrying, so the test + // runs quickly. + ProxyService::set_pac_script_poll_policy( + ProxyService::POLL_POLICY_IMMEDIATE); + + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolverExpectsBytes* resolver = + new MockAsyncProxyResolverExpectsBytes; + + CapturingNetLog log(CapturingNetLog::kUnbounded); + + ProxyService service(config_service, resolver, &log); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetchers(fetcher, + new DoNothingDhcpProxyScriptFetcher()); + + // Start 1 request. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy( + GURL("http://request1"), &info1, callback1.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // The first request should have triggered initial download of PAC script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + + // Nothing has been sent to the resolver yet. + EXPECT_TRUE(resolver->pending_requests().empty()); + + // At this point the ProxyService should be waiting for the + // ProxyScriptFetcher to invoke its completion callback, notifying it of + // PAC script download completion. + fetcher->NotifyFetchCompletion(OK, kValidPacScript1); + + // Now that the PAC script is downloaded, the request will have been sent to + // the proxy resolver. + EXPECT_EQ(ASCIIToUTF16(kValidPacScript1), + resolver->pending_set_pac_script_request()->script_data()->utf16()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url()); + + // Complete the pending request. + resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // Wait for completion callback, and verify that the request ran as expected. + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + // At this point we have initialized the proxy service using a PAC script. + // + // A background task to periodically re-check the PAC script for validity will + // have been started. We will now wait for the next download attempt to start. + // + // Note that we shouldn't have to wait long here, since our test enables a + // special unit-test mode. + fetcher->WaitUntilFetch(); + + ASSERT_TRUE(resolver->pending_requests().empty()); + + // Make sure that our background checker is trying to download the expected + // PAC script (same one as before). This time we will simulate a failure + // to download the script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(ERR_FAILED, ""); + + MessageLoop::current()->RunAllPending(); + + // At this point the ProxyService should have re-configured itself to use + // DIRECT connections rather than the given proxy resolver. + + // Start a second request. + ProxyInfo info2; + TestCompletionCallback callback2; + rv = service.ResolveProxy( + GURL("http://request2"), &info2, callback2.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(info2.is_direct()); +} + } // namespace net |