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

Side by Side 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/download/download_request_model.h"
6
7 #include "base/bind.h"
8 #include "base/callback.h"
9 #include "base/format_macros.h"
10 #include "base/logging.h"
11 #include "base/message_loop/message_loop_proxy.h"
12 #include "base/metrics/histogram.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/stringprintf.h"
15 #include "content/browser/byte_stream.h"
16 #include "content/browser/child_process_security_policy_impl.h"
17 #include "content/browser/download/download_create_info.h"
18 #include "content/browser/download/download_interrupt_reasons_impl.h"
19 #include "content/browser/download/download_resource_handler.h"
20 #include "content/browser/download/download_stats.h"
21 #include "content/browser/fileapi/chrome_blob_storage_context.h"
22 #include "content/browser/net/referrer.h"
23 #include "content/browser/resource_context_impl.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/browser/download_url_parameters.h"
26 #include "content/public/browser/resource_context.h"
27 #include "content/public/common/url_constants.h"
28 #include "net/base/io_buffer.h"
29 #include "net/base/load_flags.h"
30 #include "net/base/upload_bytes_element_reader.h"
31 #include "net/base/upload_data_stream.h"
32 #include "net/http/http_response_headers.h"
33 #include "net/http/http_status_code.h"
34 #include "net/url_request/url_request.h"
35 #include "net/url_request/url_request_context.h"
36 #include "net/url_request/url_request_job_factory.h"
37 #include "webkit/browser/blob/blob_storage_context.h"
38 #include "webkit/browser/blob/blob_url_request_job_factory.h"
39
40 namespace content {
41
42 const int DownloadRequestModel::kDownloadByteStreamSize = 100 * 1024;
43
44 DownloadRequestModel::DownloadRequestModel(
45 net::URLRequest* request,
46 scoped_ptr<DownloadSaveInfo> save_info)
47 : request_(request),
48 save_info_(save_info.Pass()),
49 last_buffer_size_(0),
50 bytes_read_(0),
51 pause_count_(0),
52 last_interrupt_reason_(DOWNLOAD_INTERRUPT_REASON_NONE) {
53 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
54 // Both are required parameters.
55 DCHECK(request_);
56 DCHECK(save_info_);
57 RecordDownloadCount(UNTHROTTLED_COUNT);
58 }
59
60 DownloadRequestModel::~DownloadRequestModel() {}
61
62 // static
63 scoped_ptr<net::URLRequest> DownloadRequestModel::CreateRequest(
64 const DownloadUrlParameters& parameters,
65 net::URLRequest::Delegate* request_delegate) {
66 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
67
68 scoped_ptr<net::URLRequest> request;
69
70 if (!IsRequestAllowed(parameters.url(), parameters.render_process_host_id()))
71 return request.Pass();
72
73 // TODO(asanka): This method of getting the URLRequestContext is deprecated.
74 // Fix this to use the correct StorageParition.
75 net::URLRequestContext* request_context =
76 parameters.resource_context()->GetRequestContext();
77 if (!request_context ||
78 !request_context->job_factory()->IsHandledURL(parameters.url()))
79 return request.Pass();
80
81 request.reset(
82 request_context->CreateRequest(parameters.url(), request_delegate));
83
84 SetReferrerForRequest(request.get(), parameters.referrer());
85 if (parameters.url().SchemeIs(chrome::kBlobScheme)) {
86 ChromeBlobStorageContext* blob_context =
87 GetChromeBlobStorageContextForResourceContext(
88 parameters.resource_context());
89 webkit_blob::BlobProtocolHandler::SetRequestedBlobDataHandle(
90 request.get(),
91 blob_context->context()->GetBlobDataFromPublicURL(request->url()));
92 }
93
94 int extra_load_flags = net::LOAD_IS_DOWNLOAD;
95 if (parameters.prefer_cache()) {
96 if (request->get_upload() != NULL)
97 extra_load_flags |= net::LOAD_ONLY_FROM_CACHE;
98 else
99 extra_load_flags |= net::LOAD_PREFERRING_CACHE;
100 } else {
101 extra_load_flags |= net::LOAD_DISABLE_CACHE;
102 }
103 request->set_load_flags(request->load_flags() | parameters.load_flags() |
104 extra_load_flags);
105 request->set_method(parameters.method());
106 if (!parameters.post_body().empty()) {
107 const std::string& body = parameters.post_body();
108 scoped_ptr<net::UploadElementReader> reader(
109 net::UploadOwnedBytesElementReader::CreateWithString(body));
110 request->set_upload(make_scoped_ptr(
111 net::UploadDataStream::CreateWithReader(reader.Pass(), 0)));
112 }
113 if (parameters.post_id() >= 0) {
114 // The POST in this case does not have an actual body, and only works when
115 // retrieving data from cache. This is done because we don't want to do a
116 // re-POST without user consent, and currently don't have a good plan on how
117 // to display the UI for that.
118 DCHECK(parameters.prefer_cache());
119 DCHECK(parameters.method() == "POST");
120 ScopedVector<net::UploadElementReader> element_readers;
121 request->set_upload(make_scoped_ptr(
122 new net::UploadDataStream(&element_readers, parameters.post_id())));
123 }
124
125 if (parameters.offset() > 0) {
126 // If we've asked for a byte range, we want to make sure that we only get
127 // that range if our current copy of the information is good.
128 std::string range =
129 base::StringPrintf("bytes=%" PRId64 "-", parameters.offset());
130 request->SetExtraRequestHeaderByName("Range", range, true);
131
132 // We shouldn't be asked to continue if we don't have a verifier.
133 DCHECK(!parameters.last_modified().empty() || !parameters.etag().empty());
134 if (!parameters.last_modified().empty())
135 request->SetExtraRequestHeaderByName(
136 "If-Unmodified-Since", parameters.last_modified(), true);
137 if (!parameters.etag().empty())
138 request->SetExtraRequestHeaderByName("If-Match", parameters.etag(), true);
139 }
140
141 for (DownloadUrlParameters::RequestHeadersType::const_iterator iter =
142 parameters.request_headers_begin();
143 iter != parameters.request_headers_end();
144 ++iter) {
145 request->SetExtraRequestHeaderByName(
146 iter->first, iter->second, false /*overwrite*/);
147 }
148 return request.Pass();
149 }
150
151 // static
152 bool DownloadRequestModel::IsRequestAllowed(const GURL& url,
153 int child_process_id) {
154 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
155 return ChildProcessSecurityPolicyImpl::GetInstance()->CanRequestURL(
156 child_process_id, url);
157 }
158
159 void DownloadRequestModel::OnRequestRedirected(const GURL& url) {
160 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
161 // We treat a download as a main frame load, and thus update the policy URL
162 // on redirects.
163 request_->set_first_party_for_cookies(url);
164 }
165
166 scoped_ptr<DownloadCreateInfo> DownloadRequestModel::OnResponseStarted(
167 const std::string& sniffed_mime_type,
168 bool has_user_gesture,
169 PageTransition page_transition,
170 const ResumeRequestCallback& resume_callback) {
171 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
172 download_start_time_ = base::TimeTicks::Now();
173 DVLOG(20) << DebugString() << " OnResponseStarted";
174
175 // If it's a download, we don't want to poison the cache with it.
176 request_->StopCaching();
177
178 // Lower priority as well, so downloads don't contend for resources
179 // with main frames.
180 request_->SetPriority(net::IDLE);
181
182 // If the content-length header is not present (or contains something other
183 // than numbers), the incoming content_length is -1 (unknown size).
184 // Set the content length to 0 to indicate unknown size to DownloadManager.
185 int64 content_length = request_->GetExpectedContentSize();
186 if (content_length < 0)
187 content_length = 0;
188
189 // Deleted in DownloadManager.
190 scoped_ptr<DownloadCreateInfo> info(
191 new DownloadCreateInfo(base::Time::Now(),
192 content_length,
193 request_->net_log(),
194 has_user_gesture,
195 page_transition,
196 save_info_.Pass()));
197
198 // Create the ByteStream for sending data to the download sink.
199 CreateByteStream(
200 base::MessageLoopProxy::current(),
201 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE),
202 kDownloadByteStreamSize,
203 &stream_writer_,
204 &info->stream_reader);
205 stream_writer_->RegisterCallback(resume_callback);
206
207 info->url_chain = request_->url_chain();
208 info->referrer_url = GURL(request_->referrer());
209 info->mime_type = sniffed_mime_type;
210 info->remote_address = request_->GetSocketAddress().host();
211 request_->GetResponseHeaderByName("content-disposition",
212 &info->content_disposition);
213 RecordDownloadMimeType(info->mime_type);
214 RecordDownloadContentDisposition(info->content_disposition);
215
216 const net::HttpResponseHeaders* headers = request_->response_headers();
217 if (headers) {
218 if (headers->HasStrongValidators()) {
219 // If we don't have strong validators as per RFC 2616 section 13.3.3, then
220 // we neither store nor use them for range requests.
221 headers->EnumerateHeader(NULL, "Last-Modified", &info->last_modified);
222 headers->EnumerateHeader(NULL, "ETag", &info->etag);
223 }
224
225 int status = headers->response_code();
226 if (2 == status / 100 && status != net::HTTP_PARTIAL_CONTENT) {
227 // Success & not range response; if we asked for a range, we didn't
228 // get it--reset the file pointers to reflect that.
229 info->save_info->offset = 0;
230 info->save_info->hash_state = "";
231 }
232
233 headers->GetMimeType(&info->original_mime_type);
234 if (info->mime_type.empty())
235 info->mime_type = info->original_mime_type;
236 }
237
238 return info.Pass();
239 }
240
241 void DownloadRequestModel::OnWillRead(net::IOBuffer** buf,
242 int* buf_size,
243 int min_size) {
244 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
245 DCHECK(buf && buf_size);
246 DCHECK(!read_buffer_.get());
247
248 *buf_size = min_size < 0 ? kReadBufSize : min_size;
249 last_buffer_size_ = *buf_size;
250 read_buffer_ = new net::IOBuffer(*buf_size);
251 *buf = read_buffer_.get();
252 }
253
254 DownloadRequestModel::ReadState DownloadRequestModel::OnReadCompleted(
255 int bytes_read) {
256 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
257 DCHECK(read_buffer_.get());
258 DCHECK(last_stream_pause_time_.is_null());
259 DVLOG(20) << DebugString() << " OnReadCompleted bytes_read=" << bytes_read;
260
261 base::TimeTicks now(base::TimeTicks::Now());
262 if (!last_read_time_.is_null()) {
263 double seconds_since_last_read = (now - last_read_time_).InSecondsF();
264 if (now == last_read_time_)
265 // Use 1/10 ms as a "very small number" so that we avoid
266 // divide-by-zero error and still record a very high potential bandwidth.
267 seconds_since_last_read = 0.00001;
268
269 double actual_bandwidth = (bytes_read) / seconds_since_last_read;
270 double potential_bandwidth = last_buffer_size_ / seconds_since_last_read;
271 RecordBandwidth(actual_bandwidth, potential_bandwidth);
272 }
273 last_read_time_ = now;
274
275 if (!bytes_read)
276 return read_state();
277
278 bytes_read_ += bytes_read;
279 DCHECK(read_buffer_.get());
280
281 // Take the data ship it down the stream. If the stream is full, pause the
282 // request; the stream callback will resume it.
283 if (!stream_writer_->Write(read_buffer_, bytes_read)) {
284 OnPauseRequest();
285 last_stream_pause_time_ = now;
286 }
287
288 read_buffer_ = NULL; // Drop our reference.
289 return read_state();
290 }
291
292 DownloadInterruptReason DownloadRequestModel::OnResponseCompleted() {
293 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
294 const net::URLRequestStatus& status = request_->status();
295 int response_code = status.is_success() ? request_->GetResponseCode() : 0;
296 net::Error error_code = net::OK;
297 if (status.status() == net::URLRequestStatus::FAILED ||
298 // Note cancels as failures too.
299 status.status() == net::URLRequestStatus::CANCELED) {
300 error_code = static_cast<net::Error>(status.error()); // Normal case.
301 // Make sure that at least the fact of failure comes through.
302 if (error_code == net::OK)
303 error_code = net::ERR_FAILED;
304 }
305
306 // ERR_CONTENT_LENGTH_MISMATCH and ERR_INCOMPLETE_CHUNKED_ENCODING are
307 // allowed since a number of servers in the wild close the connection too
308 // early by mistake. Other browsers - IE9, Firefox 11.0, and Safari 5.1.4 -
309 // treat downloads as complete in both cases, so we follow their lead.
310 if (error_code == net::ERR_CONTENT_LENGTH_MISMATCH ||
311 error_code == net::ERR_INCOMPLETE_CHUNKED_ENCODING) {
312 error_code = net::OK;
313 }
314 DownloadInterruptReason reason = ConvertNetErrorToInterruptReason(
315 error_code, DOWNLOAD_INTERRUPT_FROM_NETWORK);
316
317 if (status.status() == net::URLRequestStatus::CANCELED &&
318 status.error() == net::ERR_ABORTED) {
319 // CANCELED + ERR_ABORTED == something outside of the network
320 // stack cancelled the request. There aren't that many things that
321 // could do this to a download request (whose lifetime is separated from
322 // the tab from which it came). We map this to USER_CANCELLED as the
323 // case we know about (system suspend because of laptop close) corresponds
324 // to a user action.
325 // TODO(ahendrickson) -- Find a better set of codes to use here, as
326 // CANCELED/ERR_ABORTED can occur for reasons other than user cancel.
327 reason = DOWNLOAD_INTERRUPT_REASON_USER_CANCELED;
328 }
329
330 // If an interrupt reason was set for this request by OnDownloadInterrupted,
331 // then use that instead.
332 if (last_interrupt_reason_ != DOWNLOAD_INTERRUPT_REASON_NONE)
333 reason = last_interrupt_reason_;
334
335 if (status.is_success() && reason == DOWNLOAD_INTERRUPT_REASON_NONE &&
336 request_->response_headers()) {
337 // Handle server's response codes.
338 switch (response_code) {
339 case -1: // Non-HTTP request.
340 case net::HTTP_OK:
341 case net::HTTP_CREATED:
342 case net::HTTP_ACCEPTED:
343 case net::HTTP_NON_AUTHORITATIVE_INFORMATION:
344 case net::HTTP_RESET_CONTENT:
345 case net::HTTP_PARTIAL_CONTENT:
346 // Expected successful codes.
347 break;
348 case net::HTTP_NO_CONTENT:
349 case net::HTTP_NOT_FOUND:
350 reason = DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT;
351 break;
352 case net::HTTP_PRECONDITION_FAILED:
353 // Failed our 'If-Unmodified-Since' or 'If-Match'; see
354 // download_manager_impl.cc BeginDownload()
355 reason = DOWNLOAD_INTERRUPT_REASON_SERVER_PRECONDITION;
356 break;
357 case net::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE:
358 // Retry by downloading from the start automatically:
359 // If we haven't received data when we get this error, we won't.
360 reason = DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE;
361 break;
362 default: // All other errors.
363 // Redirection and informational codes should have been handled earlier
364 // in the stack.
365 DCHECK_NE(3, response_code / 100);
366 DCHECK_NE(1, response_code / 100);
367 reason = DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED;
368 break;
369 }
370 }
371
372 std::string accept_ranges;
373 bool has_strong_validators = false;
374 if (request_->response_headers()) {
375 request_->response_headers()->EnumerateHeader(
376 NULL, "Accept-Ranges", &accept_ranges);
377 has_strong_validators = request_->response_headers()->HasStrongValidators();
378 }
379 RecordAcceptsRanges(accept_ranges, bytes_read_, has_strong_validators);
380 RecordNetworkBlockage(base::TimeTicks::Now() - download_start_time_,
381 total_pause_time_);
382
383 // Send the info down the stream. Conditional is in case we get
384 // OnResponseCompleted without OnResponseStarted.
385 if (stream_writer_)
386 stream_writer_->Close(reason);
387
388 // If the error mapped to something unknown, record it so that
389 // we can drill down.
390 if (reason == DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED)
391 RecordNetErrorForNetworkFailed(status.error());
392
393 stream_writer_.reset(); // We no longer need the stream.
394 read_buffer_ = NULL;
395
396 // TODO(asanka): Does this UMA make sense at all? Remove if not or fix it.
397 // This UMA used to measure the lifetime of a DownloadResourceHandler object.
398 if (reason == DOWNLOAD_INTERRUPT_REASON_NONE) {
399 UMA_HISTOGRAM_TIMES("SB2.DownloadDuration",
400 base::TimeTicks::Now() - download_start_time_);
401 }
402 DVLOG(20) << DebugString() << " OnResponseCompleted reason="
403 << DownloadInterruptReasonToString(reason);
404 return reason;
405 }
406
407 void DownloadRequestModel::OnPauseRequest() {
408 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
409 ++pause_count_;
410 }
411
412 DownloadRequestModel::ReadState DownloadRequestModel::OnResumeRequest() {
413 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
414 DCHECK_LT(0, pause_count_);
415
416 --pause_count_;
417
418 if (pause_count_ == 0 && !last_stream_pause_time_.is_null()) {
419 total_pause_time_ += (base::TimeTicks::Now() - last_stream_pause_time_);
420 last_stream_pause_time_ = base::TimeTicks();
421 }
422 return read_state();
423 }
424
425 void DownloadRequestModel::OnDownloadInterrupted(
426 DownloadInterruptReason interrupt_reason) {
427 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
428 last_interrupt_reason_ = interrupt_reason;
429 if (stream_writer_) {
430 stream_writer_->Close(interrupt_reason);
431 stream_writer_.reset();
432 }
433 }
434
435 std::string DownloadRequestModel::DebugString() const {
436 return base::StringPrintf("{ url = %s }", request_->url().spec().c_str());
437 }
438
439 DownloadRequestModel::ReadState DownloadRequestModel::read_state() const {
440 return pause_count_ > 0 ? WAIT_FOR_RESUME : READY_TO_READ;
441 }
442
443 } // namespace content
OLDNEW
« 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