summaryrefslogtreecommitdiffstats
path: root/net/proxy/proxy_service.cc
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/proxy/proxy_service.cc
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/proxy/proxy_service.cc')
-rw-r--r--net/proxy/proxy_service.cc284
1 files changed, 235 insertions, 49 deletions
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())