summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-02-23 19:04:40 +0000
committerericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-02-23 19:04:40 +0000
commit45bdf86341670d95573acbc699f2d2998a25cf1d (patch)
tree7aa8e9afee81bb43b076110760a09abcc9df046c /net
parent7d1fbb6f3cab062e4c2d819aad3e8a11118db730 (diff)
downloadchromium_src-45bdf86341670d95573acbc699f2d2998a25cf1d.zip
chromium_src-45bdf86341670d95573acbc699f2d2998a25cf1d.tar.gz
chromium_src-45bdf86341670d95573acbc699f2d2998a25cf1d.tar.bz2
Add support to ProxyService for downloading a PAC script on behalf of the ProxyResolver. A ProxyResolver can select this new behavior by subclassing ProxyResolver with |does_fetch = false|. A consequence of this change is that proxy resolve requests are maintained in a queue by ProxyService (rather than implicitly in a queue on the PAC thread's message loop). This simplifies cancellation.This solves issue 7461, and is work-in-progress towards {2764, 74}BUG=7461
Review URL: http://codereview.chromium.org/21328 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@10197 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r--net/proxy/proxy_resolver_mac.h2
-rw-r--r--net/proxy/proxy_resolver_winhttp.cc2
-rw-r--r--net/proxy/proxy_service.cc284
-rw-r--r--net/proxy/proxy_service.h87
-rw-r--r--net/proxy/proxy_service_unittest.cc453
5 files changed, 776 insertions, 52 deletions
diff --git a/net/proxy/proxy_resolver_mac.h b/net/proxy/proxy_resolver_mac.h
index f207e1b..8796d45 100644
--- a/net/proxy/proxy_resolver_mac.h
+++ b/net/proxy/proxy_resolver_mac.h
@@ -13,6 +13,8 @@ namespace net {
// proxies.
class ProxyResolverMac : public ProxyResolver {
public:
+ ProxyResolverMac() : ProxyResolver(true) {}
+
// ProxyResolver methods:
virtual int GetProxyForURL(const GURL& query_url,
const GURL& pac_url,
diff --git a/net/proxy/proxy_resolver_winhttp.cc b/net/proxy/proxy_resolver_winhttp.cc
index 61a1d2f..aa7ab83 100644
--- a/net/proxy/proxy_resolver_winhttp.cc
+++ b/net/proxy/proxy_resolver_winhttp.cc
@@ -42,7 +42,7 @@ static void FreeInfo(WINHTTP_PROXY_INFO* info) {
}
ProxyResolverWinHttp::ProxyResolverWinHttp()
- : session_handle_(NULL) {
+ : ProxyResolver(true), session_handle_(NULL) {
}
ProxyResolverWinHttp::~ProxyResolverWinHttp() {
diff --git a/net/proxy/proxy_service.cc b/net/proxy/proxy_service.cc
index b97c010..daf0913 100644
--- a/net/proxy/proxy_service.cc
+++ b/net/proxy/proxy_service.cc
@@ -38,6 +38,34 @@ class ProxyConfigServiceNull : public ProxyConfigService {
}
};
+// Strip away any reference fragments and the username/password, as they
+// are not relevant to proxy resolution.
+static GURL SanitizeURLForProxyResolver(const GURL& url) {
+ // TODO(eroman): The following duplicates logic from
+ // HttpUtil::SpecForRequest. Should probably live in net_util.h
+ GURL::Replacements replacements;
+ replacements.ClearUsername();
+ replacements.ClearPassword();
+ replacements.ClearRef();
+ return url.ReplaceComponents(replacements);
+}
+
+// Runs on the PAC thread to notify the proxy resolver of the fetched PAC
+// script contents. This task shouldn't outlive ProxyService, since
+// |resolver| is owned by ProxyService.
+class NotifyFetchCompletionTask : public Task {
+ public:
+ NotifyFetchCompletionTask(ProxyResolver* resolver, const std::string& bytes)
+ : resolver_(resolver), bytes_(bytes) {}
+
+ virtual void Run() {
+ resolver_->SetPacScript(bytes_);
+ }
+
+ private:
+ ProxyResolver* resolver_;
+ std::string bytes_;
+};
// ProxyConfig ----------------------------------------------------------------
@@ -194,25 +222,42 @@ std::string ProxyInfo::ToPacString() {
class ProxyService::PacRequest :
public base::RefCountedThreadSafe<ProxyService::PacRequest> {
public:
+ // |service| -- the ProxyService that owns this request.
+ // |url| -- the url of the query.
+ // |results| -- the structure to fill with proxy resolve results.
PacRequest(ProxyService* service,
- const GURL& pac_url,
+ const GURL& url,
+ ProxyInfo* results,
CompletionCallback* callback)
: service_(service),
callback_(callback),
- results_(NULL),
- config_id_(service->config_id()),
- pac_url_(pac_url),
+ results_(results),
+ url_(url),
+ is_started_(false),
origin_loop_(MessageLoop::current()) {
DCHECK(callback);
}
- void Query(const GURL& url, ProxyInfo* results) {
- results_ = results;
- // Execute Query asynchronously
+ // Start the resolve proxy request on the PAC thread.
+ void Query() {
+ is_started_ = true;
AddRef(); // balanced in QueryComplete
+
+ GURL query_url = SanitizeURLForProxyResolver(url_);
+ const GURL& pac_url = service_->config_.pac_url;
+ results_->config_id_ = service_->config_.id();
+
service_->pac_thread()->message_loop()->PostTask(FROM_HERE,
NewRunnableMethod(this, &ProxyService::PacRequest::DoQuery,
- service_->resolver(), url, pac_url_));
+ service_->resolver(), query_url, pac_url));
+ }
+
+ // Run the request's callback on the current message loop.
+ void PostCallback(int result_code) {
+ AddRef(); // balanced in DoCallback
+ MessageLoop::current()->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &ProxyService::PacRequest::DoCallback,
+ result_code));
}
void Cancel() {
@@ -223,7 +268,12 @@ class ProxyService::PacRequest :
results_ = NULL;
}
+ // Returns true if Cancel() has been called.
+ bool was_cancelled() const { return callback_ == NULL; }
+
private:
+ friend class ProxyService;
+
// Runs on the PAC thread.
void DoQuery(ProxyResolver* resolver,
const GURL& query_url,
@@ -235,33 +285,43 @@ class ProxyService::PacRequest :
// Runs the completion callback on the origin thread.
void QueryComplete(int result_code) {
- // The PacRequest may have been cancelled after it was started. If it was
- // cancelled then |callback_|, |service_|, and |results_| will be NULL.
- bool was_cancelled = callback_ == NULL;
-
- if (!was_cancelled) {
- service_->DidCompletePacRequest(config_id_, result_code);
+ // The PacRequest may have been cancelled after it was started.
+ if (!was_cancelled()) {
+ service_->DidCompletePacRequest(results_->config_id_, result_code);
if (result_code == OK) {
results_->Use(results_buf_);
results_->RemoveBadProxies(service_->proxy_retry_info_);
}
callback_->Run(result_code);
+
+ // We check for cancellation once again, in case the callback deleted
+ // the owning ProxyService (whose destructor will in turn cancel us).
+ if (!was_cancelled())
+ service_->RemoveFrontOfRequestQueue(this);
}
Release(); // balances the AddRef in Query. we may get deleted after
// we return.
}
+ // Runs the completion callback on the origin thread.
+ void DoCallback(int result_code) {
+ if (!was_cancelled()) {
+ callback_->Run(result_code);
+ }
+ Release(); // balances the AddRef in PostCallback.
+ }
+
// Must only be used on the "origin" thread.
ProxyService* service_;
CompletionCallback* callback_;
ProxyInfo* results_;
- ProxyConfig::ID config_id_;
+ GURL url_;
+ bool is_started_;
// Usable from within DoQuery on the PAC thread.
ProxyInfo results_buf_;
- GURL pac_url_;
MessageLoop* origin_loop_;
};
@@ -272,7 +332,12 @@ ProxyService::ProxyService(ProxyConfigService* config_service,
: config_service_(config_service),
resolver_(resolver),
config_is_bad_(false),
- config_has_been_updated_(false) {
+ config_has_been_updated_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(proxy_script_fetcher_callback_(
+ this, &ProxyService::OnScriptFetchCompletion)),
+ fetched_pac_config_id_(ProxyConfig::INVALID_ID),
+ fetched_pac_error_(OK),
+ in_progress_fetch_config_id_(ProxyConfig::INVALID_ID) {
}
// static
@@ -309,15 +374,32 @@ int ProxyService::ResolveProxy(const GURL& url, ProxyInfo* result,
CompletionCallback* callback,
PacRequest** pac_request) {
DCHECK(callback);
- // The overhead of calling ProxyConfigService::GetProxyConfig is very low.
- const TimeDelta kProxyConfigMaxAge = TimeDelta::FromSeconds(5);
- // Periodically check for a new config.
- if (!config_has_been_updated_ ||
- (TimeTicks::Now() - config_last_update_time_) > kProxyConfigMaxAge)
- UpdateConfig();
+ // Check if the request can be completed right away. This is the case when
+ // using a direct connection, or when the config is bad.
+ UpdateConfigIfOld();
+ int rv = TryToCompleteSynchronously(url, result);
+ if (rv != ERR_IO_PENDING)
+ return rv;
+
+ // Otherwise, push the request into the work queue.
+ scoped_refptr<PacRequest> req = new PacRequest(this, url, result, callback);
+ pending_requests_.push_back(req);
+ ProcessPendingRequests(req.get());
+
+ // Completion will be notifed through |callback|, unless the caller cancels
+ // the request using |pac_request|.
+ if (pac_request)
+ *pac_request = req.get();
+ return rv; // ERR_IO_PENDING
+}
+
+int ProxyService::TryToCompleteSynchronously(const GURL& url,
+ ProxyInfo* result) {
result->config_id_ = config_.id();
+ DCHECK(config_.id() != ProxyConfig::INVALID_ID);
+
// Fallback to a "direct" (no proxy) connection if the current configuration
// is known to be bad.
if (config_is_bad_) {
@@ -369,38 +451,110 @@ int ProxyService::ResolveProxy(const GURL& url, ProxyInfo* result,
}
if (config_.pac_url.is_valid() || config_.auto_detect) {
- if (!pac_thread_.get()) {
- pac_thread_.reset(new base::Thread("pac-thread"));
- pac_thread_->Start();
+ // If we failed to download the PAC script, return the network error
+ // from the failed download. This is only going to happen for the first
+ // request after the failed download -- after that |config_is_bad_| will
+ // be set to true, so we short-cuircuit sooner.
+ if (fetched_pac_error_ != OK && !IsFetchingPacScript()) {
+ DidCompletePacRequest(fetched_pac_config_id_, fetched_pac_error_);
+ return fetched_pac_error_;
}
+ return ERR_IO_PENDING;
+ }
+ }
- scoped_refptr<PacRequest> req =
- new PacRequest(this, config_.pac_url, callback);
-
- // Strip away any reference fragments and the username/password, as they
- // are not relevant to proxy resolution.
- GURL sanitized_url;
- { // TODO(eroman): The following duplicates logic from
- // HttpUtil::SpecForRequest. Should probably live in net_util.h
- GURL::Replacements replacements;
- replacements.ClearUsername();
- replacements.ClearPassword();
- replacements.ClearRef();
- sanitized_url = url.ReplaceComponents(replacements);
- }
+ // otherwise, we have no proxy config
+ result->UseDirect();
+ return OK;
+}
- req->Query(sanitized_url, result);
+void ProxyService::InitPacThread() {
+ if (!pac_thread_.get()) {
+ pac_thread_.reset(new base::Thread("pac-thread"));
+ pac_thread_->Start();
+ }
+}
- if (pac_request)
- *pac_request = req;
+ProxyService::~ProxyService() {
+ // Cancel the inprogress request (if any), and free the rest.
+ for (PendingRequestsQueue::iterator it = pending_requests_.begin();
+ it != pending_requests_.end();
+ ++it) {
+ (*it)->Cancel();
+ }
+}
+
+void ProxyService::ProcessPendingRequests(PacRequest* recent_req) {
+ if (pending_requests_.empty())
+ return;
+
+ // While the PAC script is being downloaded, requests are blocked.
+ if (IsFetchingPacScript())
+ return;
+
+ // Get the next request to process (FIFO).
+ PacRequest* req = pending_requests_.front().get();
+ if (req->is_started_)
+ return;
- return ERR_IO_PENDING; // Wait for callback.
+ // The configuration may have changed since |req| was added to the
+ // queue. It could be this request now completes synchronously.
+ if (req != recent_req) {
+ UpdateConfigIfOld();
+ int rv = TryToCompleteSynchronously(req->url_, req->results_);
+ if (rv != ERR_IO_PENDING) {
+ req->PostCallback(rv);
+ RemoveFrontOfRequestQueue(req);
+ return;
}
}
- // otherwise, we have no proxy config
- result->UseDirect();
- return OK;
+ // Check if a new PAC script needs to be downloaded.
+ DCHECK(config_.id() != ProxyConfig::INVALID_ID);
+ if (!resolver_->does_fetch() && config_.id() != fetched_pac_config_id_) {
+ // For auto-detect we use the well known WPAD url.
+ GURL pac_url = config_.auto_detect ?
+ GURL("http://wpad/wpad.dat") : config_.pac_url;
+
+ in_progress_fetch_config_id_ = config_.id();
+
+ proxy_script_fetcher_->Fetch(
+ pac_url, &in_progress_fetch_bytes_, &proxy_script_fetcher_callback_);
+ return;
+ }
+
+ // The only choice left now is to actually run the ProxyResolver on
+ // the PAC thread.
+ InitPacThread();
+ req->Query();
+}
+
+void ProxyService::RemoveFrontOfRequestQueue(PacRequest* expected_req) {
+ DCHECK(pending_requests_.front().get() == expected_req);
+ pending_requests_.pop_front();
+
+ // Start next work item.
+ ProcessPendingRequests(NULL);
+}
+
+void ProxyService::OnScriptFetchCompletion(int result) {
+ DCHECK(IsFetchingPacScript());
+ DCHECK(!resolver_->does_fetch());
+
+ // Notify the ProxyResolver of the new script data (will be empty string if
+ // result != OK).
+ InitPacThread();
+ pac_thread()->message_loop()->PostTask(FROM_HERE,
+ new NotifyFetchCompletionTask(
+ resolver_.get(), in_progress_fetch_bytes_));
+
+ fetched_pac_config_id_ = in_progress_fetch_config_id_;
+ fetched_pac_error_ = result;
+ in_progress_fetch_config_id_ = ProxyConfig::INVALID_ID;
+ in_progress_fetch_bytes_.clear();
+
+ // Start a pending request if any.
+ ProcessPendingRequests(NULL);
}
int ProxyService::ReconsiderProxyAfterError(const GURL& url,
@@ -453,8 +607,30 @@ int ProxyService::ReconsiderProxyAfterError(const GURL& url,
return OK;
}
-void ProxyService::CancelPacRequest(PacRequest* pac_request) {
- pac_request->Cancel();
+// There are four states of the request we need to handle:
+// (1) Not started (just sitting in the queue).
+// (2) Executing PacRequest::DoQuery in the PAC thread.
+// (3) Waiting for PacRequest::QueryComplete to be run on the origin thread.
+// (4) Waiting for PacRequest::DoCallback to be run on the origin thread.
+void ProxyService::CancelPacRequest(PacRequest* req) {
+ DCHECK(req);
+
+ bool is_active_request = req->is_started_ && !pending_requests_.empty() &&
+ pending_requests_.front().get() == req;
+
+ req->Cancel();
+
+ if (is_active_request) {
+ RemoveFrontOfRequestQueue(req);
+ return;
+ }
+
+ // Otherwise just delete the request from the queue.
+ PendingRequestsQueue::iterator it = std::find(
+ pending_requests_.begin(), pending_requests_.end(), req);
+ if (it != pending_requests_.end()) {
+ pending_requests_.erase(it);
+ }
}
void ProxyService::DidCompletePacRequest(int config_id, int result_code) {
@@ -487,6 +663,16 @@ void ProxyService::UpdateConfig() {
proxy_retry_info_.clear();
}
+void ProxyService::UpdateConfigIfOld() {
+ // The overhead of calling ProxyConfigService::GetProxyConfig is very low.
+ const TimeDelta kProxyConfigMaxAge = TimeDelta::FromSeconds(5);
+
+ // Periodically check for a new config.
+ if (!config_has_been_updated_ ||
+ (TimeTicks::Now() - config_last_update_time_) > kProxyConfigMaxAge)
+ UpdateConfig();
+}
+
bool ProxyService::ShouldBypassProxyForURL(const GURL& url) {
std::string url_domain = url.scheme();
if (!url_domain.empty())
diff --git a/net/proxy/proxy_service.h b/net/proxy/proxy_service.h
index d14cc3e..3c29195 100644
--- a/net/proxy/proxy_service.h
+++ b/net/proxy/proxy_service.h
@@ -5,6 +5,7 @@
#ifndef NET_PROXY_PROXY_SERVICE_H_
#define NET_PROXY_PROXY_SERVICE_H_
+#include <deque>
#include <map>
#include <string>
#include <vector>
@@ -17,6 +18,7 @@
#include "base/waitable_event.h"
#include "googleurl/src/gurl.h"
#include "net/base/completion_callback.h"
+#include "net/proxy/proxy_script_fetcher.h"
#include "net/proxy/proxy_server.h"
class GURL;
@@ -102,6 +104,8 @@ class ProxyService {
ProxyService(ProxyConfigService* config_service,
ProxyResolver* resolver);
+ ~ProxyService();
+
// Used internally to handle PAC queries.
class PacRequest;
@@ -156,6 +160,13 @@ class ProxyService {
// so it falls back to direct connect.
static ProxyService* CreateNull();
+ // Set the ProxyScriptFetcher dependency. This is needed if the ProxyResolver
+ // is of type ProxyResolverWithoutFetch. ProxyService takes ownership of
+ // |proxy_script_fetcher|.
+ void SetProxyScriptFetcher(ProxyScriptFetcher* proxy_script_fetcher) {
+ proxy_script_fetcher_.reset(proxy_script_fetcher);
+ }
+
private:
friend class PacRequest;
@@ -169,6 +180,37 @@ class ProxyService {
// to reference the new configuration.
void UpdateConfig();
+ // Tries to update the configuration if it hasn't been checked in a while.
+ void UpdateConfigIfOld();
+
+ // Returns true if this ProxyService is downloading a PAC script on behalf
+ // of ProxyResolverWithoutFetch. Resolve requests will be frozen until
+ // the fetch has completed.
+ bool IsFetchingPacScript() const {
+ return in_progress_fetch_config_id_ != ProxyConfig::INVALID_ID;
+ }
+
+ // Callback for when the PAC script has finished downloading.
+ void OnScriptFetchCompletion(int result);
+
+ // Returns ERR_IO_PENDING if the request cannot be completed synchronously.
+ // Otherwise it fills |result| with the proxy information for |url|.
+ // Completing synchronously means we don't need to query ProxyResolver.
+ // (ProxyResolver runs on PAC thread.)
+ int TryToCompleteSynchronously(const GURL& url, ProxyInfo* result);
+
+ // Starts the PAC thread if it isn't already running.
+ void InitPacThread();
+
+ // Starts the next request from |pending_requests_| is possible.
+ // |recent_req| is the request that just got added, or NULL.
+ void ProcessPendingRequests(PacRequest* recent_req);
+
+ // Removes the front entry of the requests queue. |expected_req| is our
+ // expectation of what the front of the request queue is; it is only used by
+ // DCHECK for verification purposes.
+ void RemoveFrontOfRequestQueue(PacRequest* expected_req);
+
// Called to indicate that a PacRequest completed. The |config_id| parameter
// indicates the proxy configuration that was queried. |result_code| is OK
// if the PAC file could be downloaded and executed. Otherwise, it is an
@@ -201,6 +243,32 @@ class ProxyService {
// Map of the known bad proxies and the information about the retry time.
ProxyRetryInfoMap proxy_retry_info_;
+ // FIFO queue of pending/inprogress requests.
+ typedef std::deque<scoped_refptr<PacRequest> > PendingRequestsQueue;
+ PendingRequestsQueue pending_requests_;
+
+ // The fetcher to use when downloading PAC scripts for the ProxyResolver.
+ // This dependency can be NULL if our ProxyResolver has no need for
+ // external PAC script fetching.
+ scoped_ptr<ProxyScriptFetcher> proxy_script_fetcher_;
+
+ // Callback for when |proxy_script_fetcher_| is done.
+ CompletionCallbackImpl<ProxyService> proxy_script_fetcher_callback_;
+
+ // The ID of the configuration for which we last downloaded a PAC script.
+ // If no PAC script has been fetched yet, will be ProxyConfig::INVALID_ID.
+ ProxyConfig::ID fetched_pac_config_id_;
+
+ // The error corresponding with |fetched_pac_config_id_|, or OK.
+ int fetched_pac_error_;
+
+ // The ID of the configuration for which we are currently downloading the
+ // PAC script. If no fetch is in progress, will be ProxyConfig::INVALID_ID.
+ ProxyConfig::ID in_progress_fetch_config_id_;
+
+ // The results of the current in progress fetch, or empty string.
+ std::string in_progress_fetch_bytes_;
+
DISALLOW_COPY_AND_ASSIGN(ProxyService);
};
@@ -324,6 +392,13 @@ class ProxyConfigService {
// PAC Thread.
class ProxyResolver {
public:
+
+ // If a subclass sets |does_fetch| to false, then the owning ProxyResolver
+ // will download PAC scripts on our behalf, and notify changes with
+ // SetPacScript(). Otherwise the subclass is expected to fetch the
+ // PAC script internally, and SetPacScript() will go unused.
+ ProxyResolver(bool does_fetch) : does_fetch_(does_fetch) {}
+
virtual ~ProxyResolver() {}
// Query the proxy auto-config file (specified by |pac_url|) for the proxy to
@@ -332,6 +407,18 @@ class ProxyResolver {
virtual int GetProxyForURL(const GURL& query_url,
const GURL& pac_url,
ProxyInfo* results) = 0;
+
+ // Called whenever the PAC script has changed, with the contents of the
+ // PAC script. |bytes| may be empty string if there was a fetch error.
+ virtual void SetPacScript(const std::string& bytes) {
+ // Must override SetPacScript() if |does_fetch_ = true|.
+ NOTREACHED();
+ }
+
+ bool does_fetch() const { return does_fetch_; }
+
+ protected:
+ bool does_fetch_;
};
// Wrapper for invoking methods on a ProxyService synchronously.
diff --git a/net/proxy/proxy_service_unittest.cc b/net/proxy/proxy_service_unittest.cc
index 35df20f..84a9395 100644
--- a/net/proxy/proxy_service_unittest.cc
+++ b/net/proxy/proxy_service_unittest.cc
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "base/compiler_specific.h"
#include "googleurl/src/gurl.h"
#include "net/base/net_errors.h"
#include "net/proxy/proxy_service.h"
@@ -27,7 +28,8 @@ class MockProxyConfigService: public net::ProxyConfigService {
class MockProxyResolver : public net::ProxyResolver {
public:
- MockProxyResolver() : fail_get_proxy_for_url(false) {
+ MockProxyResolver() : net::ProxyResolver(true),
+ fail_get_proxy_for_url(false) {
}
virtual int GetProxyForURL(const GURL& query_url,
@@ -80,8 +82,272 @@ class SyncProxyService {
scoped_refptr<net::SyncProxyServiceHelper> sync_proxy_service_;
};
+// ResultFuture is a handle to get at the result from
+// ProxyService::ResolveProxyForURL() that ran on another thread.
+class ResultFuture : public base::RefCountedThreadSafe<ResultFuture> {
+ public:
+ // |service| is the ProxyService to issue requests on, and |io_message_loop|
+ // is the message loop where ProxyService lives.
+ ResultFuture(MessageLoop* io_message_loop,
+ net::ProxyService* service)
+ : io_message_loop_(io_message_loop),
+ service_(service),
+ request_(NULL),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ callback_(this, &ResultFuture::OnCompletion)),
+ completion_(true, false),
+ cancelled_(false, false),
+ started_(false, false),
+ did_complete_(false) {
+ }
+
+ // Block until the request has completed, then return the result.
+ int GetResultCode() {
+ DCHECK(MessageLoop::current() != io_message_loop_);
+ WaitUntilCompleted();
+ return result_code_;
+ }
+
+ // Block until the request has completed, then return the result.
+ const net::ProxyInfo& GetProxyInfo() {
+ DCHECK(MessageLoop::current() != io_message_loop_);
+ WaitUntilCompleted();
+ return proxy_info_;
+ }
+
+ // Cancel this request (wait until the cancel has been issued before
+ // returning).
+ void Cancel() {
+ DCHECK(MessageLoop::current() != io_message_loop_);
+ io_message_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &ResultFuture::DoCancel));
+ cancelled_.Wait();
+ }
+
+ // Return true if the request has already completed.
+ bool IsCompleted() {
+ DCHECK(MessageLoop::current() != io_message_loop_);
+ return did_complete_;
+ }
+
+ // Wait until the ProxyService completes this request.
+ void WaitUntilCompleted() {
+ DCHECK(MessageLoop::current() != io_message_loop_);
+ completion_.Wait();
+ DCHECK(did_complete_);
+ }
+
+ private:
+ friend class ProxyServiceWithFutures;
+
+ // Start the request. Return once ProxyService::GetProxyForURL() returns.
+ void StartResolve(const GURL& url) {
+ DCHECK(MessageLoop::current() != io_message_loop_);
+ io_message_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &ResultFuture::DoStartResolve, url));
+ started_.Wait();
+ }
+
+ // Called on |io_message_loop_|.
+ void DoStartResolve(const GURL& url) {
+ DCHECK(MessageLoop::current() == io_message_loop_);
+ int rv = service_->ResolveProxy(url, &proxy_info_, &callback_, &request_);
+ if (rv != net::ERR_IO_PENDING) {
+ // Completed synchronously.
+ OnCompletion(rv);
+ }
+ started_.Signal();
+ }
+
+ // Called on |io_message_loop_|.
+ void DoCancel() {
+ DCHECK(MessageLoop::current() == io_message_loop_);
+ if (!did_complete_)
+ service_->CancelPacRequest(request_);
+ cancelled_.Signal();
+ }
+
+ // Called on |io_message_loop_|.
+ void OnCompletion(int result) {
+ DCHECK(MessageLoop::current() == io_message_loop_);
+ DCHECK(!did_complete_);
+ did_complete_ = true;
+ result_code_ = result;
+ request_ = NULL;
+ completion_.Signal();
+ }
+
+ // The message loop where the ProxyService lives.
+ MessageLoop* io_message_loop_;
+
+ // The proxy service that started this request.
+ net::ProxyService* service_;
+
+ // The in-progress request.
+ net::ProxyService::PacRequest* request_;
+
+ net::CompletionCallbackImpl<ResultFuture> callback_;
+ base::WaitableEvent completion_;
+ base::WaitableEvent cancelled_;
+ base::WaitableEvent started_;
+ bool did_complete_;
+
+ // Results from the request.
+ int result_code_;
+ net::ProxyInfo proxy_info_;
+};
+
+// Wraps a ProxyService running on its own IO thread.
+class ProxyServiceWithFutures {
+ public:
+ ProxyServiceWithFutures(net::ProxyConfigService* config_service,
+ net::ProxyResolver* resolver)
+ : io_thread_("IO_Thread"),
+ service_(config_service, resolver) {
+ base::Thread::Options options;
+ options.message_loop_type = MessageLoop::TYPE_IO;
+ io_thread_.StartWithOptions(options);
+ }
+
+ // Start the request on |io_thread_|, and return a handle that can be
+ // used to access the results. The caller is responsible for freeing
+ // the ResultFuture.
+ void ResolveProxy(scoped_refptr<ResultFuture>* result, const GURL& url) {
+ (*result) = new ResultFuture(io_thread_.message_loop(), &service_);
+ (*result)->StartResolve(url);
+ }
+
+ void SetProxyScriptFetcher(net::ProxyScriptFetcher* proxy_script_fetcher) {
+ service_.SetProxyScriptFetcher(proxy_script_fetcher);
+ }
+
+ private:
+ base::Thread io_thread_;
+ net::ProxyService service_;
+};
+
+// A ProxyResolver which can be set to block upon reaching GetProxyForURL.
+class BlockableProxyResolver : public net::ProxyResolver {
+ public:
+ BlockableProxyResolver() : net::ProxyResolver(true),
+ should_block_(false),
+ unblocked_(true, true),
+ blocked_(true, false) {
+ }
+
+ void Block() {
+ should_block_ = true;
+ unblocked_.Reset();
+ }
+
+ void Unblock() {
+ should_block_ = false;
+ blocked_.Reset();
+ unblocked_.Signal();
+ }
+
+ void WaitUntilBlocked() {
+ blocked_.Wait();
+ }
+
+ // net::ProxyResolver implementation:
+ virtual int GetProxyForURL(const GURL& query_url,
+ const GURL& pac_url,
+ net::ProxyInfo* results) {
+ if (should_block_) {
+ blocked_.Signal();
+ unblocked_.Wait();
+ }
+
+ results->UseNamedProxy(query_url.host());
+ return net::OK;
+ }
+
+ private:
+ bool should_block_;
+ base::WaitableEvent unblocked_;
+ base::WaitableEvent blocked_;
+};
+
+// A mock ProxyResolverWithoutFetch which concatenates the query's host with
+// the last download PAC contents. This way the result describes what the last
+// downloaded PAC script's contents were, in addition to the query url itself.
+class MockProxyResolverWithoutFetch : public net::ProxyResolver {
+ public:
+ MockProxyResolverWithoutFetch() : net::ProxyResolver(false),
+ last_pac_contents_("NONE") {}
+
+ // net::ProxyResolver implementation:
+ virtual int GetProxyForURL(const GURL& query_url,
+ const GURL& pac_url,
+ net::ProxyInfo* results) {
+ results->UseNamedProxy(last_pac_contents_ + "." + query_url.host());
+ return net::OK;
+ }
+
+ virtual void SetPacScript(const std::string& bytes) {
+ last_pac_contents_ = bytes;
+ }
+
+ private:
+ std::string last_pac_contents_;
+};
+
} // namespace
+// A mock ProxyScriptFetcher. No result will be returned to the fetch client
+// until we call NotifyFetchCompletion() to set the results.
+class MockProxyScriptFetcher : public net::ProxyScriptFetcher {
+ public:
+ MockProxyScriptFetcher() : pending_request_loop_(NULL),
+ pending_request_callback_(NULL), pending_request_bytes_(NULL) {}
+
+ // net::ProxyScriptFetcher implementation.
+ virtual void Fetch(const GURL& url, std::string* bytes,
+ net::CompletionCallback* callback) {
+ DCHECK(!HasPendingRequest());
+
+ // Save the caller's information, and have them wait.
+ pending_request_loop_ = MessageLoop::current();
+ pending_request_url_ = url;
+ pending_request_callback_ = callback;
+ pending_request_bytes_ = bytes;
+ }
+
+ void NotifyFetchCompletion(int result, const std::string& bytes) {
+ DCHECK(HasPendingRequest());
+ pending_request_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &MockProxyScriptFetcher::DoNotifyFetchCompletion, result, bytes));
+ }
+
+ virtual void Cancel() {}
+
+ private:
+ // Runs on |pending_request_loop_|.
+ void DoNotifyFetchCompletion(int result, const std::string& bytes) {
+ DCHECK(HasPendingRequest());
+ *pending_request_bytes_ = bytes;
+ pending_request_callback_->Run(result);
+ }
+
+ bool HasPendingRequest() const {
+ return pending_request_loop_ != NULL;
+ }
+
+ MessageLoop* pending_request_loop_;
+ GURL pending_request_url_;
+ net::CompletionCallback* pending_request_callback_;
+ std::string* pending_request_bytes_;
+};
+
+// Template specialization so MockProxyScriptFetcher does not have to be refcounted.
+template<>
+void RunnableMethodTraits<MockProxyScriptFetcher>::RetainCallee(
+ MockProxyScriptFetcher* remover) {}
+template<>
+void RunnableMethodTraits<MockProxyScriptFetcher>::ReleaseCallee(
+ MockProxyScriptFetcher* remover) {}
+
// Test parsing from a PAC string.
TEST(ProxyListTest, SetFromPacString) {
const struct {
@@ -419,7 +685,7 @@ TEST(ProxyServiceTest, ProxyBypassList) {
config.proxy_rules = "foopy1:8080;foopy2:9090";
config.auto_detect = false;
config.proxy_bypass_local_names = true;
-
+
SyncProxyService service(new MockProxyConfigService(config),
new MockProxyResolver());
GURL url("http://www.google.com/");
@@ -537,3 +803,186 @@ TEST(ProxyServiceTest, PerProtocolProxyTests) {
EXPECT_EQ("foopy1:8080", info4.proxy_server().ToURI());
}
+// Test cancellation of a queued request.
+TEST(ProxyServiceTest, CancelQueuedRequest) {
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ BlockableProxyResolver* resolver = new BlockableProxyResolver;
+
+ ProxyServiceWithFutures service(config_service, resolver);
+
+ // Cause requests to pile up, by having them block in the PAC thread.
+ resolver->Block();
+
+ // Start 3 requests.
+ scoped_refptr<ResultFuture> result1;
+ service.ResolveProxy(&result1, GURL("http://request1"));
+
+ scoped_refptr<ResultFuture> result2;
+ service.ResolveProxy(&result2, GURL("http://request2"));
+
+ scoped_refptr<ResultFuture> result3;
+ service.ResolveProxy(&result3, GURL("http://request3"));
+
+ // Wait until the first request has become blocked in the PAC thread.
+ resolver->WaitUntilBlocked();
+
+ // Cancel the second request
+ result2->Cancel();
+
+ // Unblock the PAC thread.
+ resolver->Unblock();
+
+ // Wait for the final request to complete.
+ result3->WaitUntilCompleted();
+
+ // Verify that requests ran as expected.
+
+ EXPECT_TRUE(result1->IsCompleted());
+ EXPECT_EQ(net::OK, result1->GetResultCode());
+ EXPECT_EQ("request1:80", result1->GetProxyInfo().proxy_server().ToURI());
+
+ EXPECT_FALSE(result2->IsCompleted()); // Cancelled.
+
+ EXPECT_TRUE(result3->IsCompleted());
+ EXPECT_EQ(net::OK, result3->GetResultCode());
+ EXPECT_EQ("request3:80", result3->GetProxyInfo().proxy_server().ToURI());
+}
+
+// Test cancellation of an in-progress request.
+TEST(ProxyServiceTest, CancelInprogressRequest) {
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ BlockableProxyResolver* resolver = new BlockableProxyResolver;
+
+ ProxyServiceWithFutures service(config_service, resolver);
+
+ // Cause requests to pile up, by having them block in the PAC thread.
+ resolver->Block();
+
+ // Start 3 requests.
+ scoped_refptr<ResultFuture> result1;
+ service.ResolveProxy(&result1, GURL("http://request1"));
+
+ scoped_refptr<ResultFuture> result2;
+ service.ResolveProxy(&result2, GURL("http://request2"));
+
+ scoped_refptr<ResultFuture> result3;
+ service.ResolveProxy(&result3, GURL("http://request3"));
+
+ // Wait until the first request has become blocked in the PAC thread.
+ resolver->WaitUntilBlocked();
+
+ // Cancel the first request
+ result1->Cancel();
+
+ // Unblock the PAC thread.
+ resolver->Unblock();
+
+ // Wait for the final request to complete.
+ result3->WaitUntilCompleted();
+
+ // Verify that requests ran as expected.
+
+ EXPECT_FALSE(result1->IsCompleted()); // Cancelled.
+
+ EXPECT_TRUE(result2->IsCompleted());
+ EXPECT_EQ(net::OK, result2->GetResultCode());
+ EXPECT_EQ("request2:80", result2->GetProxyInfo().proxy_server().ToURI());
+
+ EXPECT_TRUE(result3->IsCompleted());
+ EXPECT_EQ(net::OK, result3->GetResultCode());
+ EXPECT_EQ("request3:80", result3->GetProxyInfo().proxy_server().ToURI());
+}
+
+// Test the initial PAC download for ProxyResolverWithoutFetch.
+TEST(ProxyServiceTest, InitialPACScriptDownload) {
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockProxyResolverWithoutFetch* resolver = new MockProxyResolverWithoutFetch;
+
+ ProxyServiceWithFutures service(config_service, resolver);
+
+ MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher;
+ service.SetProxyScriptFetcher(fetcher);
+
+ // Start 3 requests.
+ scoped_refptr<ResultFuture> result1;
+ service.ResolveProxy(&result1, GURL("http://request1"));
+
+ scoped_refptr<ResultFuture> result2;
+ service.ResolveProxy(&result2, GURL("http://request2"));
+
+ scoped_refptr<ResultFuture> result3;
+ service.ResolveProxy(&result3, GURL("http://request3"));
+
+ // 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(net::OK, "pac-v1");
+
+ // Complete all the requests.
+ result3->WaitUntilCompleted();
+
+ EXPECT_TRUE(result1->IsCompleted());
+ EXPECT_EQ(net::OK, result1->GetResultCode());
+ EXPECT_EQ("pac-v1.request1:80",
+ result1->GetProxyInfo().proxy_server().ToURI());
+
+ EXPECT_TRUE(result2->IsCompleted());
+ EXPECT_EQ(net::OK, result2->GetResultCode());
+ EXPECT_EQ("pac-v1.request2:80",
+ result2->GetProxyInfo().proxy_server().ToURI());
+
+ EXPECT_TRUE(result3->IsCompleted());
+ EXPECT_EQ(net::OK, result3->GetResultCode());
+ EXPECT_EQ("pac-v1.request3:80",
+ result3->GetProxyInfo().proxy_server().ToURI());
+}
+
+// Test cancellation of a request, while the PAC script is being fetched.
+TEST(ProxyServiceTest, CancelWhilePACFetching) {
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockProxyResolverWithoutFetch* resolver = new MockProxyResolverWithoutFetch;
+
+ ProxyServiceWithFutures service(config_service, resolver);
+
+ MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher;
+ service.SetProxyScriptFetcher(fetcher);
+
+ // Start 3 requests.
+ scoped_refptr<ResultFuture> result1;
+ service.ResolveProxy(&result1, GURL("http://request1"));
+
+ scoped_refptr<ResultFuture> result2;
+ service.ResolveProxy(&result2, GURL("http://request2"));
+
+ scoped_refptr<ResultFuture> result3;
+ service.ResolveProxy(&result3, GURL("http://request3"));
+
+ // Cancel the first 2 requests.
+ result1->Cancel();
+ result2->Cancel();
+
+ // 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(net::OK, "pac-v1");
+
+ // Complete all the requests.
+ result3->WaitUntilCompleted();
+
+ EXPECT_FALSE(result1->IsCompleted()); // Cancelled.
+ EXPECT_FALSE(result2->IsCompleted()); // Cancelled.
+
+ EXPECT_TRUE(result3->IsCompleted());
+ EXPECT_EQ(net::OK, result3->GetResultCode());
+ EXPECT_EQ("pac-v1.request3:80",
+ result3->GetProxyInfo().proxy_server().ToURI());
+}
+