Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(45)

Unified Diff: content/browser/download/download_request_model.cc

Issue 23496076: WIP - Refactor programmatic downloads Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: content/browser/download/download_request_model.cc
diff --git a/content/browser/download/download_request_model.cc b/content/browser/download/download_request_model.cc
new file mode 100644
index 0000000000000000000000000000000000000000..98f5b76b112085e142e33f5cb58848f6a3f4f97a
--- /dev/null
+++ b/content/browser/download/download_request_model.cc
@@ -0,0 +1,443 @@
+// Copyright 2013 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/download/download_request_model.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/stringprintf.h"
+#include "content/browser/byte_stream.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/download/download_create_info.h"
+#include "content/browser/download/download_interrupt_reasons_impl.h"
+#include "content/browser/download/download_resource_handler.h"
+#include "content/browser/download/download_stats.h"
+#include "content/browser/fileapi/chrome_blob_storage_context.h"
+#include "content/browser/net/referrer.h"
+#include "content/browser/resource_context_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/download_url_parameters.h"
+#include "content/public/browser/resource_context.h"
+#include "content/public/common/url_constants.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job_factory.h"
+#include "webkit/browser/blob/blob_storage_context.h"
+#include "webkit/browser/blob/blob_url_request_job_factory.h"
+
+namespace content {
+
+const int DownloadRequestModel::kDownloadByteStreamSize = 100 * 1024;
+
+DownloadRequestModel::DownloadRequestModel(
+ net::URLRequest* request,
+ scoped_ptr<DownloadSaveInfo> save_info)
+ : request_(request),
+ save_info_(save_info.Pass()),
+ last_buffer_size_(0),
+ bytes_read_(0),
+ pause_count_(0),
+ last_interrupt_reason_(DOWNLOAD_INTERRUPT_REASON_NONE) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // Both are required parameters.
+ DCHECK(request_);
+ DCHECK(save_info_);
+ RecordDownloadCount(UNTHROTTLED_COUNT);
+}
+
+DownloadRequestModel::~DownloadRequestModel() {}
+
+// static
+scoped_ptr<net::URLRequest> DownloadRequestModel::CreateRequest(
+ const DownloadUrlParameters& parameters,
+ net::URLRequest::Delegate* request_delegate) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ scoped_ptr<net::URLRequest> request;
+
+ if (!IsRequestAllowed(parameters.url(), parameters.render_process_host_id()))
+ return request.Pass();
+
+ // TODO(asanka): This method of getting the URLRequestContext is deprecated.
+ // Fix this to use the correct StorageParition.
+ net::URLRequestContext* request_context =
+ parameters.resource_context()->GetRequestContext();
+ if (!request_context ||
+ !request_context->job_factory()->IsHandledURL(parameters.url()))
+ return request.Pass();
+
+ request.reset(
+ request_context->CreateRequest(parameters.url(), request_delegate));
+
+ SetReferrerForRequest(request.get(), parameters.referrer());
+ if (parameters.url().SchemeIs(chrome::kBlobScheme)) {
+ ChromeBlobStorageContext* blob_context =
+ GetChromeBlobStorageContextForResourceContext(
+ parameters.resource_context());
+ webkit_blob::BlobProtocolHandler::SetRequestedBlobDataHandle(
+ request.get(),
+ blob_context->context()->GetBlobDataFromPublicURL(request->url()));
+ }
+
+ int extra_load_flags = net::LOAD_IS_DOWNLOAD;
+ if (parameters.prefer_cache()) {
+ if (request->get_upload() != NULL)
+ extra_load_flags |= net::LOAD_ONLY_FROM_CACHE;
+ else
+ extra_load_flags |= net::LOAD_PREFERRING_CACHE;
+ } else {
+ extra_load_flags |= net::LOAD_DISABLE_CACHE;
+ }
+ request->set_load_flags(request->load_flags() | parameters.load_flags() |
+ extra_load_flags);
+ request->set_method(parameters.method());
+ if (!parameters.post_body().empty()) {
+ const std::string& body = parameters.post_body();
+ scoped_ptr<net::UploadElementReader> reader(
+ net::UploadOwnedBytesElementReader::CreateWithString(body));
+ request->set_upload(make_scoped_ptr(
+ net::UploadDataStream::CreateWithReader(reader.Pass(), 0)));
+ }
+ if (parameters.post_id() >= 0) {
+ // The POST in this case does not have an actual body, and only works when
+ // retrieving data from cache. This is done because we don't want to do a
+ // re-POST without user consent, and currently don't have a good plan on how
+ // to display the UI for that.
+ DCHECK(parameters.prefer_cache());
+ DCHECK(parameters.method() == "POST");
+ ScopedVector<net::UploadElementReader> element_readers;
+ request->set_upload(make_scoped_ptr(
+ new net::UploadDataStream(&element_readers, parameters.post_id())));
+ }
+
+ if (parameters.offset() > 0) {
+ // If we've asked for a byte range, we want to make sure that we only get
+ // that range if our current copy of the information is good.
+ std::string range =
+ base::StringPrintf("bytes=%" PRId64 "-", parameters.offset());
+ request->SetExtraRequestHeaderByName("Range", range, true);
+
+ // We shouldn't be asked to continue if we don't have a verifier.
+ DCHECK(!parameters.last_modified().empty() || !parameters.etag().empty());
+ if (!parameters.last_modified().empty())
+ request->SetExtraRequestHeaderByName(
+ "If-Unmodified-Since", parameters.last_modified(), true);
+ if (!parameters.etag().empty())
+ request->SetExtraRequestHeaderByName("If-Match", parameters.etag(), true);
+ }
+
+ for (DownloadUrlParameters::RequestHeadersType::const_iterator iter =
+ parameters.request_headers_begin();
+ iter != parameters.request_headers_end();
+ ++iter) {
+ request->SetExtraRequestHeaderByName(
+ iter->first, iter->second, false /*overwrite*/);
+ }
+ return request.Pass();
+}
+
+// static
+bool DownloadRequestModel::IsRequestAllowed(const GURL& url,
+ int child_process_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ return ChildProcessSecurityPolicyImpl::GetInstance()->CanRequestURL(
+ child_process_id, url);
+}
+
+void DownloadRequestModel::OnRequestRedirected(const GURL& url) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // We treat a download as a main frame load, and thus update the policy URL
+ // on redirects.
+ request_->set_first_party_for_cookies(url);
+}
+
+scoped_ptr<DownloadCreateInfo> DownloadRequestModel::OnResponseStarted(
+ const std::string& sniffed_mime_type,
+ bool has_user_gesture,
+ PageTransition page_transition,
+ const ResumeRequestCallback& resume_callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ download_start_time_ = base::TimeTicks::Now();
+ DVLOG(20) << DebugString() << " OnResponseStarted";
+
+ // If it's a download, we don't want to poison the cache with it.
+ request_->StopCaching();
+
+ // Lower priority as well, so downloads don't contend for resources
+ // with main frames.
+ request_->SetPriority(net::IDLE);
+
+ // If the content-length header is not present (or contains something other
+ // than numbers), the incoming content_length is -1 (unknown size).
+ // Set the content length to 0 to indicate unknown size to DownloadManager.
+ int64 content_length = request_->GetExpectedContentSize();
+ if (content_length < 0)
+ content_length = 0;
+
+ // Deleted in DownloadManager.
+ scoped_ptr<DownloadCreateInfo> info(
+ new DownloadCreateInfo(base::Time::Now(),
+ content_length,
+ request_->net_log(),
+ has_user_gesture,
+ page_transition,
+ save_info_.Pass()));
+
+ // Create the ByteStream for sending data to the download sink.
+ CreateByteStream(
+ base::MessageLoopProxy::current(),
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE),
+ kDownloadByteStreamSize,
+ &stream_writer_,
+ &info->stream_reader);
+ stream_writer_->RegisterCallback(resume_callback);
+
+ info->url_chain = request_->url_chain();
+ info->referrer_url = GURL(request_->referrer());
+ info->mime_type = sniffed_mime_type;
+ info->remote_address = request_->GetSocketAddress().host();
+ request_->GetResponseHeaderByName("content-disposition",
+ &info->content_disposition);
+ RecordDownloadMimeType(info->mime_type);
+ RecordDownloadContentDisposition(info->content_disposition);
+
+ const net::HttpResponseHeaders* headers = request_->response_headers();
+ if (headers) {
+ if (headers->HasStrongValidators()) {
+ // If we don't have strong validators as per RFC 2616 section 13.3.3, then
+ // we neither store nor use them for range requests.
+ headers->EnumerateHeader(NULL, "Last-Modified", &info->last_modified);
+ headers->EnumerateHeader(NULL, "ETag", &info->etag);
+ }
+
+ int status = headers->response_code();
+ if (2 == status / 100 && status != net::HTTP_PARTIAL_CONTENT) {
+ // Success & not range response; if we asked for a range, we didn't
+ // get it--reset the file pointers to reflect that.
+ info->save_info->offset = 0;
+ info->save_info->hash_state = "";
+ }
+
+ headers->GetMimeType(&info->original_mime_type);
+ if (info->mime_type.empty())
+ info->mime_type = info->original_mime_type;
+ }
+
+ return info.Pass();
+}
+
+void DownloadRequestModel::OnWillRead(net::IOBuffer** buf,
+ int* buf_size,
+ int min_size) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(buf && buf_size);
+ DCHECK(!read_buffer_.get());
+
+ *buf_size = min_size < 0 ? kReadBufSize : min_size;
+ last_buffer_size_ = *buf_size;
+ read_buffer_ = new net::IOBuffer(*buf_size);
+ *buf = read_buffer_.get();
+}
+
+DownloadRequestModel::ReadState DownloadRequestModel::OnReadCompleted(
+ int bytes_read) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(read_buffer_.get());
+ DCHECK(last_stream_pause_time_.is_null());
+ DVLOG(20) << DebugString() << " OnReadCompleted bytes_read=" << bytes_read;
+
+ base::TimeTicks now(base::TimeTicks::Now());
+ if (!last_read_time_.is_null()) {
+ double seconds_since_last_read = (now - last_read_time_).InSecondsF();
+ if (now == last_read_time_)
+ // Use 1/10 ms as a "very small number" so that we avoid
+ // divide-by-zero error and still record a very high potential bandwidth.
+ seconds_since_last_read = 0.00001;
+
+ double actual_bandwidth = (bytes_read) / seconds_since_last_read;
+ double potential_bandwidth = last_buffer_size_ / seconds_since_last_read;
+ RecordBandwidth(actual_bandwidth, potential_bandwidth);
+ }
+ last_read_time_ = now;
+
+ if (!bytes_read)
+ return read_state();
+
+ bytes_read_ += bytes_read;
+ DCHECK(read_buffer_.get());
+
+ // Take the data ship it down the stream. If the stream is full, pause the
+ // request; the stream callback will resume it.
+ if (!stream_writer_->Write(read_buffer_, bytes_read)) {
+ OnPauseRequest();
+ last_stream_pause_time_ = now;
+ }
+
+ read_buffer_ = NULL; // Drop our reference.
+ return read_state();
+}
+
+DownloadInterruptReason DownloadRequestModel::OnResponseCompleted() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ const net::URLRequestStatus& status = request_->status();
+ int response_code = status.is_success() ? request_->GetResponseCode() : 0;
+ net::Error error_code = net::OK;
+ if (status.status() == net::URLRequestStatus::FAILED ||
+ // Note cancels as failures too.
+ status.status() == net::URLRequestStatus::CANCELED) {
+ error_code = static_cast<net::Error>(status.error()); // Normal case.
+ // Make sure that at least the fact of failure comes through.
+ if (error_code == net::OK)
+ error_code = net::ERR_FAILED;
+ }
+
+ // ERR_CONTENT_LENGTH_MISMATCH and ERR_INCOMPLETE_CHUNKED_ENCODING are
+ // allowed since a number of servers in the wild close the connection too
+ // early by mistake. Other browsers - IE9, Firefox 11.0, and Safari 5.1.4 -
+ // treat downloads as complete in both cases, so we follow their lead.
+ if (error_code == net::ERR_CONTENT_LENGTH_MISMATCH ||
+ error_code == net::ERR_INCOMPLETE_CHUNKED_ENCODING) {
+ error_code = net::OK;
+ }
+ DownloadInterruptReason reason = ConvertNetErrorToInterruptReason(
+ error_code, DOWNLOAD_INTERRUPT_FROM_NETWORK);
+
+ if (status.status() == net::URLRequestStatus::CANCELED &&
+ status.error() == net::ERR_ABORTED) {
+ // CANCELED + ERR_ABORTED == something outside of the network
+ // stack cancelled the request. There aren't that many things that
+ // could do this to a download request (whose lifetime is separated from
+ // the tab from which it came). We map this to USER_CANCELLED as the
+ // case we know about (system suspend because of laptop close) corresponds
+ // to a user action.
+ // TODO(ahendrickson) -- Find a better set of codes to use here, as
+ // CANCELED/ERR_ABORTED can occur for reasons other than user cancel.
+ reason = DOWNLOAD_INTERRUPT_REASON_USER_CANCELED;
+ }
+
+ // If an interrupt reason was set for this request by OnDownloadInterrupted,
+ // then use that instead.
+ if (last_interrupt_reason_ != DOWNLOAD_INTERRUPT_REASON_NONE)
+ reason = last_interrupt_reason_;
+
+ if (status.is_success() && reason == DOWNLOAD_INTERRUPT_REASON_NONE &&
+ request_->response_headers()) {
+ // Handle server's response codes.
+ switch (response_code) {
+ case -1: // Non-HTTP request.
+ case net::HTTP_OK:
+ case net::HTTP_CREATED:
+ case net::HTTP_ACCEPTED:
+ case net::HTTP_NON_AUTHORITATIVE_INFORMATION:
+ case net::HTTP_RESET_CONTENT:
+ case net::HTTP_PARTIAL_CONTENT:
+ // Expected successful codes.
+ break;
+ case net::HTTP_NO_CONTENT:
+ case net::HTTP_NOT_FOUND:
+ reason = DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT;
+ break;
+ case net::HTTP_PRECONDITION_FAILED:
+ // Failed our 'If-Unmodified-Since' or 'If-Match'; see
+ // download_manager_impl.cc BeginDownload()
+ reason = DOWNLOAD_INTERRUPT_REASON_SERVER_PRECONDITION;
+ break;
+ case net::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE:
+ // Retry by downloading from the start automatically:
+ // If we haven't received data when we get this error, we won't.
+ reason = DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE;
+ break;
+ default: // All other errors.
+ // Redirection and informational codes should have been handled earlier
+ // in the stack.
+ DCHECK_NE(3, response_code / 100);
+ DCHECK_NE(1, response_code / 100);
+ reason = DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED;
+ break;
+ }
+ }
+
+ std::string accept_ranges;
+ bool has_strong_validators = false;
+ if (request_->response_headers()) {
+ request_->response_headers()->EnumerateHeader(
+ NULL, "Accept-Ranges", &accept_ranges);
+ has_strong_validators = request_->response_headers()->HasStrongValidators();
+ }
+ RecordAcceptsRanges(accept_ranges, bytes_read_, has_strong_validators);
+ RecordNetworkBlockage(base::TimeTicks::Now() - download_start_time_,
+ total_pause_time_);
+
+ // Send the info down the stream. Conditional is in case we get
+ // OnResponseCompleted without OnResponseStarted.
+ if (stream_writer_)
+ stream_writer_->Close(reason);
+
+ // If the error mapped to something unknown, record it so that
+ // we can drill down.
+ if (reason == DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED)
+ RecordNetErrorForNetworkFailed(status.error());
+
+ stream_writer_.reset(); // We no longer need the stream.
+ read_buffer_ = NULL;
+
+ // TODO(asanka): Does this UMA make sense at all? Remove if not or fix it.
+ // This UMA used to measure the lifetime of a DownloadResourceHandler object.
+ if (reason == DOWNLOAD_INTERRUPT_REASON_NONE) {
+ UMA_HISTOGRAM_TIMES("SB2.DownloadDuration",
+ base::TimeTicks::Now() - download_start_time_);
+ }
+ DVLOG(20) << DebugString() << " OnResponseCompleted reason="
+ << DownloadInterruptReasonToString(reason);
+ return reason;
+}
+
+void DownloadRequestModel::OnPauseRequest() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ ++pause_count_;
+}
+
+DownloadRequestModel::ReadState DownloadRequestModel::OnResumeRequest() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK_LT(0, pause_count_);
+
+ --pause_count_;
+
+ if (pause_count_ == 0 && !last_stream_pause_time_.is_null()) {
+ total_pause_time_ += (base::TimeTicks::Now() - last_stream_pause_time_);
+ last_stream_pause_time_ = base::TimeTicks();
+ }
+ return read_state();
+}
+
+void DownloadRequestModel::OnDownloadInterrupted(
+ DownloadInterruptReason interrupt_reason) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ last_interrupt_reason_ = interrupt_reason;
+ if (stream_writer_) {
+ stream_writer_->Close(interrupt_reason);
+ stream_writer_.reset();
+ }
+}
+
+std::string DownloadRequestModel::DebugString() const {
+ return base::StringPrintf("{ url = %s }", request_->url().spec().c_str());
+}
+
+DownloadRequestModel::ReadState DownloadRequestModel::read_state() const {
+ return pause_count_ > 0 ? WAIT_FOR_RESUME : READY_TO_READ;
+}
+
+} // namespace content
« no previous file with comments | « content/browser/download/download_request_model.h ('k') | content/browser/download/download_resource_handler.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698