| Index: net/proxy/proxy_service.cc
|
| ===================================================================
|
| --- net/proxy/proxy_service.cc (revision 10072)
|
| +++ net/proxy/proxy_service.cc (working copy)
|
| @@ -38,7 +38,35 @@
|
| }
|
| };
|
|
|
| +// 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 ----------------------------------------------------------------
|
|
|
| // static
|
| @@ -194,27 +222,44 @@
|
| 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() {
|
| // Clear these to inform QueryComplete that it should not try to
|
| // access them.
|
| @@ -223,7 +268,12 @@
|
| 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 @@
|
|
|
| // 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;
|
| + // The PacRequest may have been cancelled after it was started.
|
| + if (!was_cancelled()) {
|
| + service_->DidCompletePacRequest(results_->config_id_, result_code);
|
|
|
| - if (!was_cancelled) {
|
| - service_->DidCompletePacRequest(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 @@
|
| : 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 @@
|
| 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,40 +451,112 @@
|
| }
|
|
|
| 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);
|
| + // otherwise, we have no proxy config
|
| + result->UseDirect();
|
| + return OK;
|
| +}
|
|
|
| - // 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);
|
| - }
|
| +void ProxyService::InitPacThread() {
|
| + if (!pac_thread_.get()) {
|
| + pac_thread_.reset(new base::Thread("pac-thread"));
|
| + pac_thread_->Start();
|
| + }
|
| +}
|
|
|
| - req->Query(sanitized_url, result);
|
| +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();
|
| + }
|
| +}
|
|
|
| - if (pac_request)
|
| - *pac_request = req;
|
| +void ProxyService::ProcessPendingRequests(PacRequest* recent_req) {
|
| + if (pending_requests_.empty())
|
| + return;
|
|
|
| - return ERR_IO_PENDING; // Wait for callback.
|
| + // 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;
|
| +
|
| + // 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,
|
| ProxyInfo* result,
|
| CompletionCallback* callback,
|
| @@ -453,8 +607,30 @@
|
| 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 @@
|
| 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())
|
|
|