Chromium Code Reviews| Index: content/browser/loader/async_revalidation_driver.cc |
| diff --git a/content/browser/loader/async_revalidation_driver.cc b/content/browser/loader/async_revalidation_driver.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..c371bb16892784b0a18862dd52233152233d4c72 |
| --- /dev/null |
| +++ b/content/browser/loader/async_revalidation_driver.cc |
| @@ -0,0 +1,323 @@ |
| +// Copyright 2015 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 "content/browser/loader/async_revalidation_driver.h" |
| + |
| +#include "base/location.h" |
| +#include "base/logging.h" |
| +#include "base/metrics/user_metrics_action.h" |
| +#include "base/single_thread_task_runner.h" |
| +#include "base/thread_task_runner_handle.h" |
| +#include "base/time/time.h" |
| +#include "content/public/browser/user_metrics.h" |
| +#include "net/base/net_errors.h" |
| +#include "net/cert/cert_status_flags.h" |
| +#include "net/http/http_transaction_factory.h" |
| +#include "net/http/http_util.h" |
| +#include "net/ssl/ssl_info.h" |
| +#include "net/url_request/url_request.h" |
| +#include "net/url_request/url_request_context.h" |
| +#include "net/url_request/url_request_status.h" |
| + |
| +using base::TimeDelta; |
| +using base::TimeTicks; |
| + |
| +namespace content { |
| + |
| +namespace { |
| +// This matches the maximum allocation size of AsyncResourceHandler. |
| +const int kReadBufSize = 32 * 1024; |
| +const int kReadTimeoutSeconds = 5 * 60; |
| +} |
| + |
| +AsyncRevalidationKey::AsyncRevalidationKey( |
| + const ResourceContext* resource_context, |
| + const net::HttpCache* http_cache, |
| + const GURL& url) |
| + : resource_context(resource_context), |
| + http_cache(http_cache), |
| + url_key(net::HttpUtil::SpecForRequest(url)) {} |
| + |
| +AsyncRevalidationKey::AsyncRevalidationKey( |
| + const ResourceContext* resource_context) |
| + : resource_context(resource_context), http_cache(nullptr), url_key() {} |
| + |
| +AsyncRevalidationKey::AsyncRevalidationKey(const AsyncRevalidationKey& rhs) |
| + : resource_context(rhs.resource_context), |
| + http_cache(rhs.http_cache), |
| + url_key(rhs.url_key) {} |
| + |
| +AsyncRevalidationKey::~AsyncRevalidationKey() {} |
| + |
| +// The use of base::Unretained() in the initialisation of read_timer_ is safe |
| +// because base::Timer guarantees not to call the callback after being |
| +// destroyed. |
| +AsyncRevalidationDriver::AsyncRevalidationDriver( |
| + scoped_ptr<net::URLRequest> request, |
| + scoped_ptr<ResourceThrottle> throttle, |
| + const base::Closure& completion_callback) |
| + : read_timer_(FROM_HERE, |
| + TimeDelta::FromSeconds(kReadTimeoutSeconds), |
| + base::Bind(&AsyncRevalidationDriver::OnReadTimeout, |
| + base::Unretained(this)), |
| + false), |
| + request_(request.Pass()), |
| + throttle_(throttle.Pass()), |
| + completion_callback_(completion_callback), |
| + weak_ptr_factory_(this) { |
| + request_->set_delegate(this); |
| + throttle_->set_controller(this); |
| +} |
| + |
| +AsyncRevalidationDriver::~AsyncRevalidationDriver() { |
| + weak_ptr_factory_.InvalidateWeakPtrs(); |
| + // Run ResourceThrottle destructor before we tear-down the rest of our state |
| + // as the ResourceThrottle may want to inspect the URLRequest and other state. |
| + throttle_.reset(); |
| +} |
| + |
| +void AsyncRevalidationDriver::StartRequest() { |
| + RecordAction(base::UserMetricsAction("AsyncRevalidationCreated")); |
| + // Give the handler a chance to delay the URLRequest from being started. |
| + bool defer_start = false; |
| + throttle_->WillStartRequest(&defer_start); |
| + |
| + if (defer_start) { |
| + RecordDefer(DEFERRED_START); |
| + } else { |
| + StartRequestInternal(); |
| + } |
| +} |
| + |
| +void AsyncRevalidationDriver::CancelRequest() { |
| + CancelRequestInternal(net::ERR_ABORTED); |
| +} |
| + |
| +void AsyncRevalidationDriver::OnReceivedRedirect( |
| + net::URLRequest* unused, |
| + const net::RedirectInfo& redirect_info, |
| + bool* defer) { |
| + DCHECK_EQ(request_.get(), unused); |
| + |
| + DVLOG(1) << "OnReceivedRedirect: " << request_->url().spec(); |
| + RecordAction(base::UserMetricsAction("AsyncRevalidationRedirected")); |
| + CancelRequest(); |
| +} |
| + |
| +void AsyncRevalidationDriver::OnAuthRequired( |
| + net::URLRequest* unused, |
| + net::AuthChallengeInfo* auth_info) { |
| + DCHECK_EQ(request_.get(), unused); |
| + request_->CancelAuth(); |
|
davidben
2015/10/08 21:57:51
This needs to cancel the request; CancelAuth is to
Adam Rice
2015/10/13 22:53:16
Done.
|
| +} |
| + |
| +void AsyncRevalidationDriver::OnCertificateRequested( |
| + net::URLRequest* unused, |
| + net::SSLCertRequestInfo* cert_info) { |
| + DCHECK_EQ(request_.get(), unused); |
| + CancelRequestInternal(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED); |
|
davidben
2015/10/08 21:57:51
The default implementation already cancels.
Adam Rice
2015/10/13 22:53:16
Removed.
|
| +} |
| + |
| +void AsyncRevalidationDriver::OnSSLCertificateError( |
| + net::URLRequest* unused, |
| + const net::SSLInfo& ssl_info, |
| + bool fatal) { |
| + DCHECK_EQ(request_.get(), unused); |
| + CancelRequestInternal(net::MapCertStatusToNetError(ssl_info.cert_status)); |
|
davidben
2015/10/08 21:57:51
The default implementation already cancels.
Adam Rice
2015/10/13 22:53:16
Removed.
|
| +} |
| + |
| +void AsyncRevalidationDriver::OnBeforeNetworkStart(net::URLRequest* unused, |
| + bool* defer) { |
| + DCHECK_EQ(request_.get(), unused); |
| + |
| + // Give the ResourceThrottle a chance to delay the URLRequest from using the |
| + // network. |
| + throttle_->WillStartUsingNetwork(defer); |
| + if (*defer) |
| + RecordDefer(DEFERRED_NETWORK_START); |
| +} |
| + |
| +void AsyncRevalidationDriver::OnResponseStarted(net::URLRequest* unused) { |
| + DCHECK_EQ(request_.get(), unused); |
| + |
| + DVLOG(1) << "OnResponseStarted: " << request_->url().spec(); |
| + |
| + if (!request_->status().is_success()) { |
| + DCHECK(!has_already_failed_); |
| + has_already_failed_ = true; |
| + ResponseCompleted(); |
| + return; |
| + } |
| + |
| + bool defer = false; |
| + throttle_->WillProcessResponse(&defer); |
| + if (defer) { |
| + RecordDefer(DEFERRED_READ); // Read first chunk when resumed. |
| + return; |
| + } |
| + |
| + if (request_->status().is_success()) { |
| + StartReading(false); // Read the first chunk. |
| + } else { |
| + DCHECK(!has_already_failed_); |
| + has_already_failed_ = true; |
| + ResponseCompleted(); |
| + } |
| +} |
| + |
| +void AsyncRevalidationDriver::OnReadCompleted(net::URLRequest* unused, |
| + int bytes_read) { |
| + DCHECK_EQ(request_.get(), unused); |
| + DVLOG(1) << "OnReadCompleted: \"" << request_->url().spec() << "\"" |
| + << " bytes_read = " << bytes_read; |
| + |
| + // bytes_read == -1 always implies an error. |
| + if (bytes_read == -1 || !request_->status().is_success()) { |
| + if (!has_already_failed_) { |
| + has_already_failed_ = true; |
| + ResponseCompleted(); |
| + } |
| + return; |
| + } |
| + |
| + DCHECK_GE(bytes_read, 0); |
| + DCHECK(request_->status().is_success()); |
| + DCHECK(!is_deferred()); |
| + |
| + if (bytes_read > 0) { |
| + StartReading(true); // Read the next chunk. |
| + } else { |
| + // URLRequest reported an EOF. Call ResponseCompleted. |
| + DCHECK_EQ(0, bytes_read); |
| + ResponseCompleted(); |
| + } |
| +} |
| + |
| +void AsyncRevalidationDriver::Resume() { |
| + DeferredStage stage = deferred_stage_; |
| + deferred_stage_ = DEFERRED_NONE; |
| + switch (stage) { |
| + case DEFERRED_NONE: |
| + NOTREACHED(); |
| + break; |
| + case DEFERRED_START: |
| + StartRequestInternal(); |
| + break; |
| + case DEFERRED_NETWORK_START: |
| + request_->ResumeNetworkStart(); |
| + break; |
| + case DEFERRED_READ: |
| + base::ThreadTaskRunnerHandle::Get()->PostTask( |
| + FROM_HERE, base::Bind(&AsyncRevalidationDriver::ResumeReading, |
| + weak_ptr_factory_.GetWeakPtr())); |
| + break; |
| + } |
| +} |
| + |
| +void AsyncRevalidationDriver::Cancel() { |
| + NOTREACHED(); |
| +} |
| + |
| +void AsyncRevalidationDriver::CancelAndIgnore() { |
| + NOTREACHED(); |
| +} |
| + |
| +void AsyncRevalidationDriver::CancelWithError(int error_code) { |
| + NOTREACHED(); |
| +} |
| + |
| +void AsyncRevalidationDriver::StartRequestInternal() { |
| + DCHECK(!request_->is_pending()); |
| + |
| + // TODO(ricea): This seems like it will never call ResponseCompleted(). |
| + if (!request_->status().is_success()) |
| + return; |
|
davidben
2015/10/08 21:57:51
This isn't possible, unless you implement Cancel,
Adam Rice
2015/10/13 22:53:16
Thanks. Removed.
Adam Rice
2015/10/22 18:16:12
This turned out to be possible with the sequence o
|
| + |
| + request_->Start(); |
| +} |
| + |
| +void AsyncRevalidationDriver::CancelRequestInternal(int error) { |
| + DVLOG(1) << "CancelRequestInternal: " << request_->url().spec(); |
| + |
| + bool was_pending = request_->is_pending(); |
| + |
| + request_->CancelWithError(error); |
| + |
| + if (!was_pending) { |
| + // If the request isn't in flight, then we won't get an asynchronous |
| + // notification from the request, so we have to signal ourselves to finish |
| + // this request. |
| + base::ThreadTaskRunnerHandle::Get()->PostTask( |
| + FROM_HERE, base::Bind(&AsyncRevalidationDriver::ResponseCompleted, |
| + weak_ptr_factory_.GetWeakPtr())); |
| + } |
| +} |
| + |
| +void AsyncRevalidationDriver::CompleteResponseStarted() { |
| + bool defer = false; |
| + throttle_->WillProcessResponse(&defer); |
| + if (defer) |
| + RecordDefer(DEFERRED_READ); // Read first chunk when resumed. |
| +} |
| + |
| +void AsyncRevalidationDriver::StartReading(bool is_continuation) { |
| + int bytes_read = 0; |
| + ReadMore(&bytes_read); |
| + |
| + // If IO is pending, wait for the URLRequest to call OnReadCompleted. |
| + if (request_->status().is_io_pending()) |
| + return; |
| + |
| + if (!is_continuation || bytes_read <= 0) { |
| + OnReadCompleted(request_.get(), bytes_read); |
| + } else { |
| + // Else, trigger OnReadCompleted asynchronously to avoid starving the IO |
| + // thread in case the URLRequest can provide data synchronously. |
| + base::ThreadTaskRunnerHandle::Get()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&AsyncRevalidationDriver::OnReadCompleted, |
| + weak_ptr_factory_.GetWeakPtr(), request_.get(), bytes_read)); |
| + } |
| +} |
| + |
| +void AsyncRevalidationDriver::ResumeReading() { |
| + DCHECK(!is_deferred()); |
| + |
| + if (request_->status().is_success()) { |
| + StartReading(false); // Read the next chunk (OK to complete synchronously). |
| + } else { |
| + ResponseCompleted(); |
| + } |
| +} |
| + |
| +void AsyncRevalidationDriver::ReadMore(int* bytes_read) { |
| + DCHECK(!is_deferred()); |
| + |
| + if (!read_buffer_) |
| + read_buffer_ = new net::IOBuffer(kReadBufSize); |
| + |
| + read_timer_.Reset(); |
| + request_->Read(read_buffer_.get(), kReadBufSize, bytes_read); |
| + |
| + // No need to check the return value here as we'll detect errors by |
| + // inspecting the URLRequest's status. |
| +} |
| + |
| +void AsyncRevalidationDriver::ResponseCompleted() { |
| + DVLOG(1) << "ResponseCompleted: " << request_->url().spec(); |
| + completion_callback_.Run(); |
| +} |
| + |
| +void AsyncRevalidationDriver::OnReadTimeout() { |
| + RecordAction(base::UserMetricsAction("AsyncRevalidationTimeout")); |
| + CancelRequestInternal(net::ERR_TIMED_OUT); |
| +} |
| + |
| +void AsyncRevalidationDriver::RecordDefer(DeferredStage deferred_stage) { |
| + request_->LogBlockedBy(throttle_->GetNameForLogging()); |
| + DCHECK_EQ(DEFERRED_NONE, deferred_stage_); |
| + deferred_stage_ = deferred_stage; |
| +} |
| + |
| +} // namespace content |