diff options
author | ericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-02-23 19:04:40 +0000 |
---|---|---|
committer | ericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-02-23 19:04:40 +0000 |
commit | 45bdf86341670d95573acbc699f2d2998a25cf1d (patch) | |
tree | 7aa8e9afee81bb43b076110760a09abcc9df046c /net/proxy/proxy_service.cc | |
parent | 7d1fbb6f3cab062e4c2d819aad3e8a11118db730 (diff) | |
download | chromium_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.cc | 284 |
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()) |