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

Side by Side Diff: media/blink/resource_multibuffer_data_provider.cc

Issue 1399603003: Tie multibuffers to URLs (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@media_cache
Patch Set: removed VLOG(0), oops Created 5 years 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
OLDNEW
(Empty)
1 // Copyright 2015 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 "media/blink/resource_multibuffer_data_provider.h"
6
7 #include "base/bind.h"
8 #include "base/bits.h"
9 #include "base/callback_helpers.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/metrics/histogram.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "media/blink/active_loader.h"
14 #include "media/blink/cache_util.h"
15 #include "media/blink/media_blink_export.h"
16 #include "media/blink/url_index.h"
17 #include "net/http/http_byte_range.h"
18 #include "net/http/http_request_headers.h"
19 #include "third_party/WebKit/public/platform/WebURLError.h"
20 #include "third_party/WebKit/public/platform/WebURLResponse.h"
21
22 using blink::WebFrame;
23 using blink::WebString;
24 using blink::WebURLError;
25 using blink::WebURLLoader;
26 using blink::WebURLLoaderOptions;
27 using blink::WebURLRequest;
28 using blink::WebURLResponse;
29
30 namespace media {
31
32 // The number of milliseconds to wait before retrying a failed load.
33 const int kLoaderFailedRetryDelayMs = 250;
34
35 const int kHttpOK = 200;
36 const int kHttpPartialContent = 206;
37 const int kHttpRangeNotSatisfiable = 416;
38 const int kMaxRetries = 3;
39
40 ResourceMultiBufferDataProvider::ResourceMultiBufferDataProvider(
41 UrlData* url_data,
42 MultiBufferBlockId pos)
43 : pos_(pos),
44 url_data_(url_data),
45 retries_(0),
46 cors_mode_(url_data->cors_mode()),
47 origin_(url_data->url().GetOrigin()),
48 weak_factory_(this) {
49 DCHECK(url_data_) << " pos = " << pos;
50 DCHECK_GE(pos, 0);
51 }
52
53 void ResourceMultiBufferDataProvider::Start() {
54 // In the case of a re-start, throw away any half-finished blocks.
55 fifo_.clear();
56 // Prepare the request.
57 WebURLRequest request(url_data_->url());
58 // TODO(mkwst): Split this into video/audio.
59 request.setRequestContext(WebURLRequest::RequestContextVideo);
60
61 request.setHTTPHeaderField(
62 WebString::fromUTF8(net::HttpRequestHeaders::kRange),
63 WebString::fromUTF8(
64 net::HttpByteRange::RightUnbounded(byte_pos()).GetHeaderValue()));
65
66 url_data_->frame()->setReferrerForRequest(request, blink::WebURL());
67
68 // Disable compression, compression for audio/video doesn't make sense...
69 request.setHTTPHeaderField(
70 WebString::fromUTF8(net::HttpRequestHeaders::kAcceptEncoding),
71 WebString::fromUTF8("identity;q=1, *;q=0"));
72
73 // Check for our test WebURLLoader.
74 scoped_ptr<WebURLLoader> loader;
75 if (test_loader_) {
76 loader = test_loader_.Pass();
77 } else {
78 WebURLLoaderOptions options;
79 if (url_data_->cors_mode() == UrlData::CORS_UNSPECIFIED) {
80 options.allowCredentials = true;
81 options.crossOriginRequestPolicy =
82 WebURLLoaderOptions::CrossOriginRequestPolicyAllow;
83 } else {
84 options.exposeAllResponseHeaders = true;
85 // The author header set is empty, no preflight should go ahead.
86 options.preflightPolicy = WebURLLoaderOptions::PreventPreflight;
87 options.crossOriginRequestPolicy =
88 WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl;
89 if (url_data_->cors_mode() == UrlData::CORS_USE_CREDENTIALS)
90 options.allowCredentials = true;
91 }
92 loader.reset(url_data_->frame()->createAssociatedURLLoader(options));
93 }
94
95 // Start the resource loading.
96 loader->loadAsynchronously(request, this);
97 active_loader_.reset(new ActiveLoader(loader.Pass()));
98 }
99
100 ResourceMultiBufferDataProvider::~ResourceMultiBufferDataProvider() {}
101
102 /////////////////////////////////////////////////////////////////////////////
103 // MultiBuffer::DataProvider implementation.
104 MultiBufferBlockId ResourceMultiBufferDataProvider::Tell() const {
105 return pos_;
106 }
107
108 bool ResourceMultiBufferDataProvider::Available() const {
109 if (fifo_.empty())
110 return false;
111 if (fifo_.back()->end_of_stream())
112 return true;
113 if (fifo_.front()->data_size() == block_size())
114 return true;
115 return false;
116 }
117
118 scoped_refptr<DataBuffer> ResourceMultiBufferDataProvider::Read() {
119 DCHECK(Available());
120 scoped_refptr<DataBuffer> ret = fifo_.front();
121 fifo_.pop_front();
122 ++pos_;
123 return ret;
124 }
125
126 void ResourceMultiBufferDataProvider::SetDeferred(bool deferred) {
127 if (!active_loader_ || active_loader_->deferred() == deferred)
128 return;
129 active_loader_->SetDeferred(deferred);
130 }
131
132 /////////////////////////////////////////////////////////////////////////////
133 // WebURLLoaderClient implementation.
134
135 void ResourceMultiBufferDataProvider::willFollowRedirect(
136 WebURLLoader* loader,
137 WebURLRequest& newRequest,
138 const WebURLResponse& redirectResponse) {
139 redirects_to_ = newRequest.url();
140 url_data_->set_valid_until(GetCacheValidUntil(redirectResponse));
141
142 // This test is vital for security!
143 if (cors_mode_ == UrlData::CORS_UNSPECIFIED) {
144 if (origin_ != redirects_to_.GetOrigin()) {
145 url_data_->Fail();
146 }
147 }
148 }
149
150 void ResourceMultiBufferDataProvider::didSendData(
151 WebURLLoader* loader,
152 unsigned long long bytes_sent,
153 unsigned long long total_bytes_to_be_sent) {
154 NOTIMPLEMENTED();
155 }
156
157 void ResourceMultiBufferDataProvider::didReceiveResponse(
158 WebURLLoader* loader,
159 const WebURLResponse& response) {
160 #if ENABLE_DLOG
161 string version;
162 switch (response.httpVersion()) {
163 case WebURLResponse::HTTPVersion_0_9:
164 version = "0.9";
165 break;
166 case WebURLResponse::HTTPVersion_1_0:
167 version = "1.0";
168 break;
169 case WebURLResponse::HTTPVersion_1_1:
170 version = "1.1";
171 break;
172 case WebURLResponse::HTTPVersion_2_0:
173 version = "2.1";
174 break;
175 }
176 DVLOG(1) << "didReceiveResponse: HTTP/" << version << " "
177 << response.httpStatusCode();
178 #endif
179 DCHECK(active_loader_);
180
181 scoped_refptr<UrlData> destination_url_data(url_data_);
182
183 UrlIndex* url_index = url_data_->url_index();
184
185 if (!redirects_to_.is_empty()) {
186 if (!url_index) {
187 // We've been disconnected from the url index.
188 // That means the url_index_ has been destroyed, which means we do not
189 // need to do anything clever.
190 return;
191 }
192 destination_url_data = url_index->GetByUrl(redirects_to_, cors_mode_);
193 redirects_to_ = GURL();
194 }
195
196 base::Time last_modified;
197 if (base::Time::FromString(
198 response.httpHeaderField("Last-Modified").utf8().data(),
199 &last_modified)) {
200 destination_url_data->set_last_modified(last_modified);
201 }
202
203 destination_url_data->set_valid_until(GetCacheValidUntil(response));
204
205 uint32 reasons = GetReasonsForUncacheability(response);
206 destination_url_data->set_cacheable(reasons == 0);
207 UMA_HISTOGRAM_BOOLEAN("Media.CacheUseful", reasons == 0);
208 int shift = 0;
209 int max_enum = base::bits::Log2Ceiling(kMaxReason);
210 while (reasons) {
211 DCHECK_LT(shift, max_enum); // Sanity check.
212 if (reasons & 0x1) {
213 UMA_HISTOGRAM_ENUMERATION("Media.UncacheableReason", shift,
214 max_enum); // PRESUBMIT_IGNORE_UMA_MAX
215 }
216
217 reasons >>= 1;
218 ++shift;
219 }
220
221 // Expected content length can be |kPositionNotSpecified|, in that case
222 // |content_length_| is not specified and this is a streaming response.
223 int64 content_length = response.expectedContentLength();
224
225 // We make a strong assumption that when we reach here we have either
226 // received a response from HTTP/HTTPS protocol or the request was
227 // successful (in particular range request). So we only verify the partial
228 // response for HTTP and HTTPS protocol.
229 if (destination_url_data->url().SchemeIsHTTPOrHTTPS()) {
230 bool partial_response = (response.httpStatusCode() == kHttpPartialContent);
231 bool ok_response = (response.httpStatusCode() == kHttpOK);
232
233 // Check to see whether the server supports byte ranges.
234 std::string accept_ranges =
235 response.httpHeaderField("Accept-Ranges").utf8();
236 if (accept_ranges.find("bytes") != std::string::npos)
237 destination_url_data->set_range_supported();
238
239 // If we have verified the partial response and it is correct.
240 // It's also possible for a server to support range requests
241 // without advertising "Accept-Ranges: bytes".
242 if (partial_response && VerifyPartialResponse(response)) {
243 destination_url_data->set_range_supported();
244 } else if (ok_response && pos_ == 0) {
245 // We accept a 200 response for a Range:0- request, trusting the
246 // Accept-Ranges header, because Apache thinks that's a reasonable thing
247 // to return.
248 destination_url_data->set_length(content_length);
249 } else if (response.httpStatusCode() == kHttpRangeNotSatisfiable) {
250 // Really, we should never request a range that doesn't exist, but
251 // if we do, let's handle it in a sane way.
252 // Unsatisfiable range
253 fifo_.push_back(DataBuffer::CreateEOSBuffer());
254 destination_url_data->multibuffer()->OnDataProviderEvent(this);
255 return;
256 } else {
257 destination_url_data->Fail();
258 return;
259 }
260 } else {
261 destination_url_data->set_range_supported();
262 if (content_length != kPositionNotSpecified) {
263 destination_url_data->set_length(content_length + byte_pos());
264 }
265 }
266
267 if (url_index) {
268 destination_url_data = url_index->TryInsert(destination_url_data);
269 }
270
271 if (destination_url_data != url_data_) {
272 // At this point, we've encountered a redirect, or found a better url data
273 // instance for the data that we're about to download.
274
275 // First, let's take a ref on the current url data.
276 scoped_refptr<UrlData> old_url_data(url_data_);
277 destination_url_data->Use();
278
279 // Take ownership of ourselves. (From the multibuffer)
280 scoped_ptr<DataProvider> self(
281 url_data_->multibuffer()->RemoveProvider(this));
282 url_data_ = destination_url_data.get();
283 // Give the ownership to our new owner.
284 url_data_->multibuffer()->AddProvider(self.Pass());
285
286 // Call callback to let upstream users know about the transfer.
287 // This will merge the data from the two multibuffers and
288 // cause clients to start using the new UrlData.
289 old_url_data->RedirectTo(destination_url_data);
290 }
291 }
292
293 void ResourceMultiBufferDataProvider::didReceiveData(WebURLLoader* loader,
294 const char* data,
295 int data_length,
296 int encoded_data_length) {
297 DVLOG(1) << "didReceiveData: " << data_length << " bytes";
298 DCHECK(!Available());
299 DCHECK(active_loader_);
300 DCHECK_GT(data_length, 0);
301
302 // When we receive data, we allow more retries.
303 retries_ = 0;
304
305 while (data_length) {
306 if (fifo_.empty() || fifo_.back()->data_size() == block_size()) {
307 fifo_.push_back(new DataBuffer(block_size()));
308 fifo_.back()->set_data_size(0);
309 }
310 int last_block_size = fifo_.back()->data_size();
311 int to_append = std::min<int>(data_length, block_size() - last_block_size);
312 DCHECK_GT(to_append, 0);
313 memcpy(fifo_.back()->writable_data() + last_block_size, data, to_append);
314 data += to_append;
315 fifo_.back()->set_data_size(last_block_size + to_append);
316 data_length -= to_append;
317 }
318
319 if (Available())
320 url_data_->multibuffer()->OnDataProviderEvent(this);
321
322 // Beware, this object might be deleted here.
323 }
324
325 void ResourceMultiBufferDataProvider::didDownloadData(WebURLLoader* loader,
326 int dataLength,
327 int encoded_data_length) {
328 NOTIMPLEMENTED();
329 }
330
331 void ResourceMultiBufferDataProvider::didReceiveCachedMetadata(
332 WebURLLoader* loader,
333 const char* data,
334 int data_length) {
335 NOTIMPLEMENTED();
336 }
337
338 void ResourceMultiBufferDataProvider::didFinishLoading(
339 WebURLLoader* loader,
340 double finishTime,
341 int64_t total_encoded_data_length) {
342 DVLOG(1) << "didFinishLoading";
343 DCHECK(active_loader_.get());
344 DCHECK(!Available());
345
346 // We're done with the loader.
347 active_loader_.reset();
348
349 // If we didn't know the |instance_size_| we do now.
350 int64_t size = byte_pos();
351 if (!fifo_.empty())
352 size += fifo_.back()->data_size();
353
354 // This request reports something smaller than what we've seen in the past,
355 // Maybe it's transient error?
356 if (url_data_->length() != kPositionNotSpecified &&
357 size < url_data_->length()) {
358 if (retries_ < kMaxRetries) {
359 fifo_.clear();
360 retries_++;
361 base::MessageLoop::current()->PostDelayedTask(
362 FROM_HERE, base::Bind(&ResourceMultiBufferDataProvider::Start,
363 weak_factory_.GetWeakPtr()),
364 base::TimeDelta::FromMilliseconds(kLoaderFailedRetryDelayMs));
365 return;
366 } else {
367 scoped_ptr<ActiveLoader> active_loader = active_loader_.Pass();
368 url_data_->Fail();
369 return;
370 }
371 }
372
373 url_data_->set_length(size);
374 fifo_.push_back(DataBuffer::CreateEOSBuffer());
375
376 DCHECK(Available());
377 url_data_->multibuffer()->OnDataProviderEvent(this);
378
379 // Beware, this object might be deleted here.
380 }
381
382 void ResourceMultiBufferDataProvider::didFail(WebURLLoader* loader,
383 const WebURLError& error) {
384 DVLOG(1) << "didFail: reason=" << error.reason
385 << ", isCancellation=" << error.isCancellation
386 << ", domain=" << error.domain.utf8().data()
387 << ", localizedDescription="
388 << error.localizedDescription.utf8().data();
389 DCHECK(active_loader_.get());
390
391 if (retries_ < kMaxRetries) {
392 retries_++;
393 base::MessageLoop::current()->PostDelayedTask(
394 FROM_HERE, base::Bind(&ResourceMultiBufferDataProvider::Start,
395 weak_factory_.GetWeakPtr()),
396 base::TimeDelta::FromMilliseconds(kLoaderFailedRetryDelayMs));
397 } else {
398 // We don't need to continue loading after failure.
399 //
400 // Keep it alive until we exit this method so that |error| remains valid.
401 scoped_ptr<ActiveLoader> active_loader = active_loader_.Pass();
402 url_data_->Fail();
403 }
404 }
405
406 bool ResourceMultiBufferDataProvider::ParseContentRange(
407 const std::string& content_range_str,
408 int64* first_byte_position,
409 int64* last_byte_position,
410 int64* instance_size) {
411 const std::string kUpThroughBytesUnit = "bytes ";
412 if (content_range_str.find(kUpThroughBytesUnit) != 0)
413 return false;
414 std::string range_spec =
415 content_range_str.substr(kUpThroughBytesUnit.length());
416 size_t dash_offset = range_spec.find("-");
417 size_t slash_offset = range_spec.find("/");
418
419 if (dash_offset == std::string::npos || slash_offset == std::string::npos ||
420 slash_offset < dash_offset || slash_offset + 1 == range_spec.length()) {
421 return false;
422 }
423 if (!base::StringToInt64(range_spec.substr(0, dash_offset),
424 first_byte_position) ||
425 !base::StringToInt64(
426 range_spec.substr(dash_offset + 1, slash_offset - dash_offset - 1),
427 last_byte_position)) {
428 return false;
429 }
430 if (slash_offset == range_spec.length() - 2 &&
431 range_spec[slash_offset + 1] == '*') {
432 *instance_size = kPositionNotSpecified;
433 } else {
434 if (!base::StringToInt64(range_spec.substr(slash_offset + 1),
435 instance_size)) {
436 return false;
437 }
438 }
439 if (*last_byte_position < *first_byte_position ||
440 (*instance_size != kPositionNotSpecified &&
441 *last_byte_position >= *instance_size)) {
442 return false;
443 }
444
445 return true;
446 }
447
448 int64_t ResourceMultiBufferDataProvider::byte_pos() const {
449 int64_t ret = pos_;
450 return ret << url_data_->multibuffer()->block_size_shift();
451 }
452
453 int64_t ResourceMultiBufferDataProvider::block_size() const {
454 int64_t ret = 1;
455 return ret << url_data_->multibuffer()->block_size_shift();
456 }
457
458 bool ResourceMultiBufferDataProvider::VerifyPartialResponse(
459 const WebURLResponse& response) {
460 int64 first_byte_position, last_byte_position, instance_size;
461 if (!ParseContentRange(response.httpHeaderField("Content-Range").utf8(),
462 &first_byte_position, &last_byte_position,
463 &instance_size)) {
464 return false;
465 }
466
467 if (url_data_->length() == kPositionNotSpecified) {
468 url_data_->set_length(instance_size);
469 }
470
471 if (byte_pos() != first_byte_position) {
472 return false;
473 }
474
475 return true;
476 }
477
478 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698