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

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: formatted 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(base::Time::Now() +
141 GetCacheValidUntil(redirectResponse));
142
143 // This test is vital for security!
144 if (cors_mode_ == UrlData::CORS_UNSPECIFIED) {
145 if (origin_ != redirects_to_.GetOrigin()) {
146 url_data_->Fail();
147 }
148 }
149 }
150
151 void ResourceMultiBufferDataProvider::didSendData(
152 WebURLLoader* loader,
153 unsigned long long bytes_sent,
154 unsigned long long total_bytes_to_be_sent) {
155 NOTIMPLEMENTED();
156 }
157
158 void ResourceMultiBufferDataProvider::didReceiveResponse(
159 WebURLLoader* loader,
160 const WebURLResponse& response) {
161 #if ENABLE_DLOG
162 string version;
163 switch (response.httpVersion()) {
164 case WebURLResponse::HTTPVersion_0_9:
165 version = "0.9";
166 break;
167 case WebURLResponse::HTTPVersion_1_0:
168 version = "1.0";
169 break;
170 case WebURLResponse::HTTPVersion_1_1:
171 version = "1.1";
172 break;
173 case WebURLResponse::HTTPVersion_2_0:
174 version = "2.1";
175 break;
176 }
177 DVLOG(1) << "didReceiveResponse: HTTP/" << version << " "
178 << response.httpStatusCode();
179 #endif
180 DCHECK(active_loader_);
181
182 scoped_refptr<UrlData> destination_url_data(url_data_);
183
184 UrlIndex* url_index = url_data_->url_index();
185
186 if (!redirects_to_.is_empty()) {
187 if (!url_index) {
188 // We've been disconnected from the url index.
189 // That means the url_index_ has been destroyed, which means we do not
190 // need to do anything clever.
191 return;
192 }
193 destination_url_data = url_index->GetByUrl(redirects_to_, cors_mode_);
194 redirects_to_ = GURL();
195 }
196
197 base::Time last_modified;
198 if (base::Time::FromString(
199 response.httpHeaderField("Last-Modified").utf8().data(),
200 &last_modified)) {
201 destination_url_data->set_last_modified(last_modified);
202 }
203
204 destination_url_data->set_valid_until(base::Time::Now() +
205 GetCacheValidUntil(response));
206
207 uint32 reasons = GetReasonsForUncacheability(response);
208 destination_url_data->set_cacheable(reasons == 0);
209 UMA_HISTOGRAM_BOOLEAN("Media.CacheUseful", reasons == 0);
210 int shift = 0;
211 int max_enum = base::bits::Log2Ceiling(kMaxReason);
212 while (reasons) {
213 DCHECK_LT(shift, max_enum); // Sanity check.
214 if (reasons & 0x1) {
215 UMA_HISTOGRAM_ENUMERATION("Media.UncacheableReason", shift,
216 max_enum); // PRESUBMIT_IGNORE_UMA_MAX
217 }
218
219 reasons >>= 1;
220 ++shift;
221 }
222
223 // Expected content length can be |kPositionNotSpecified|, in that case
224 // |content_length_| is not specified and this is a streaming response.
225 int64 content_length = response.expectedContentLength();
226
227 // We make a strong assumption that when we reach here we have either
228 // received a response from HTTP/HTTPS protocol or the request was
229 // successful (in particular range request). So we only verify the partial
230 // response for HTTP and HTTPS protocol.
231 if (destination_url_data->url().SchemeIsHTTPOrHTTPS()) {
232 bool partial_response = (response.httpStatusCode() == kHttpPartialContent);
233 bool ok_response = (response.httpStatusCode() == kHttpOK);
234
235 // Check to see whether the server supports byte ranges.
236 std::string accept_ranges =
237 response.httpHeaderField("Accept-Ranges").utf8();
238 if (accept_ranges.find("bytes") != std::string::npos)
239 destination_url_data->set_range_supported();
240
241 // If we have verified the partial response and it is correct.
242 // It's also possible for a server to support range requests
243 // without advertising "Accept-Ranges: bytes".
244 if (partial_response && VerifyPartialResponse(response)) {
245 destination_url_data->set_range_supported();
246 } else if (ok_response && pos_ == 0) {
247 // We accept a 200 response for a Range:0- request, trusting the
248 // Accept-Ranges header, because Apache thinks that's a reasonable thing
249 // to return.
250 destination_url_data->set_length(content_length);
251 } else if (response.httpStatusCode() == kHttpRangeNotSatisfiable) {
252 // Really, we should never request a range that doesn't exist, but
253 // if we do, let's handle it in a sane way.
254 // Unsatisfiable range
255 fifo_.push_back(DataBuffer::CreateEOSBuffer());
256 destination_url_data->multibuffer()->OnDataProviderEvent(this);
257 return;
258 } else {
259 destination_url_data->Fail();
260 return;
261 }
262 } else {
263 destination_url_data->set_range_supported();
264 if (content_length != kPositionNotSpecified) {
265 destination_url_data->set_length(content_length + byte_pos());
266 }
267 }
268
269 if (url_index) {
270 destination_url_data = url_index->TryInsert(destination_url_data);
271 }
272
273 if (destination_url_data != url_data_) {
274 // At this point, we've encountered a redirect, or found a better url data
275 // instance for the data that we're about to download.
276
277 // First, let's take a ref on the current url data.
278 scoped_refptr<UrlData> old_url_data(url_data_);
279 destination_url_data->Use();
280
281 // Take ownership of ourselves. (From the multibuffer)
282 scoped_ptr<DataProvider> self(
283 url_data_->multibuffer()->RemoveProvider(this));
284 url_data_ = destination_url_data.get();
285 // Give the ownership to our new owner.
286 url_data_->multibuffer()->AddProvider(self.Pass());
287
288 // Call callback to let upstream users know about the transfer.
289 // This will merge the data from the two multibuffers and
290 // cause clients to start using the new UrlData.
291 old_url_data->RedirectTo(destination_url_data);
292 }
293 }
294
295 void ResourceMultiBufferDataProvider::didReceiveData(WebURLLoader* loader,
296 const char* data,
297 int data_length,
298 int encoded_data_length) {
299 DVLOG(1) << "didReceiveData: " << data_length << " bytes";
300 DCHECK(!Available());
301 DCHECK(active_loader_);
302 DCHECK_GT(data_length, 0);
303
304 // When we receive data, we allow more retries.
305 retries_ = 0;
306
307 while (data_length) {
308 if (fifo_.empty() || fifo_.back()->data_size() == block_size()) {
309 fifo_.push_back(new DataBuffer(block_size()));
310 fifo_.back()->set_data_size(0);
311 }
312 int last_block_size = fifo_.back()->data_size();
313 int to_append = std::min<int>(data_length, block_size() - last_block_size);
314 DCHECK_GT(to_append, 0);
315 memcpy(fifo_.back()->writable_data() + last_block_size, data, to_append);
316 data += to_append;
317 fifo_.back()->set_data_size(last_block_size + to_append);
318 data_length -= to_append;
319 }
320
321 if (Available())
322 url_data_->multibuffer()->OnDataProviderEvent(this);
323
324 // Beware, this object might be deleted here.
325 }
326
327 void ResourceMultiBufferDataProvider::didDownloadData(WebURLLoader* loader,
328 int dataLength,
329 int encoded_data_length) {
330 NOTIMPLEMENTED();
331 }
332
333 void ResourceMultiBufferDataProvider::didReceiveCachedMetadata(
334 WebURLLoader* loader,
335 const char* data,
336 int data_length) {
337 NOTIMPLEMENTED();
338 }
339
340 void ResourceMultiBufferDataProvider::didFinishLoading(
341 WebURLLoader* loader,
342 double finishTime,
343 int64_t total_encoded_data_length) {
344 DVLOG(1) << "didFinishLoading";
345 DCHECK(active_loader_.get());
346 DCHECK(!Available());
347
348 // We're done with the loader.
349 active_loader_.reset();
350
351 // If we didn't know the |instance_size_| we do now.
352 int64_t size = byte_pos();
353 if (!fifo_.empty())
354 size += fifo_.back()->data_size();
355
356 // This request reports something smaller than what we've seen in the past,
357 // Maybe it's transient error?
358 if (url_data_->length() != kPositionNotSpecified &&
359 size < url_data_->length()) {
360 if (retries_ < kMaxRetries) {
361 fifo_.clear();
362 retries_++;
363 base::MessageLoop::current()->PostDelayedTask(
364 FROM_HERE, base::Bind(&ResourceMultiBufferDataProvider::Start,
365 weak_factory_.GetWeakPtr()),
366 base::TimeDelta::FromMilliseconds(kLoaderFailedRetryDelayMs));
367 return;
368 } else {
369 scoped_ptr<ActiveLoader> active_loader = active_loader_.Pass();
370 url_data_->Fail();
371 return;
372 }
373 }
374
375 url_data_->set_length(size);
376 fifo_.push_back(DataBuffer::CreateEOSBuffer());
377
378 DCHECK(Available());
379 url_data_->multibuffer()->OnDataProviderEvent(this);
380
381 // Beware, this object might be deleted here.
382 }
383
384 void ResourceMultiBufferDataProvider::didFail(WebURLLoader* loader,
385 const WebURLError& error) {
386 DVLOG(1) << "didFail: reason=" << error.reason
387 << ", isCancellation=" << error.isCancellation
388 << ", domain=" << error.domain.utf8().data()
389 << ", localizedDescription="
390 << error.localizedDescription.utf8().data();
391 DCHECK(active_loader_.get());
392
393 if (retries_ < kMaxRetries) {
394 retries_++;
395 base::MessageLoop::current()->PostDelayedTask(
396 FROM_HERE, base::Bind(&ResourceMultiBufferDataProvider::Start,
397 weak_factory_.GetWeakPtr()),
398 base::TimeDelta::FromMilliseconds(kLoaderFailedRetryDelayMs));
399 } else {
400 // We don't need to continue loading after failure.
401 //
402 // Keep it alive until we exit this method so that |error| remains valid.
403 scoped_ptr<ActiveLoader> active_loader = active_loader_.Pass();
404 url_data_->Fail();
405 }
406 }
407
408 bool ResourceMultiBufferDataProvider::ParseContentRange(
409 const std::string& content_range_str,
410 int64* first_byte_position,
411 int64* last_byte_position,
412 int64* instance_size) {
413 const std::string kUpThroughBytesUnit = "bytes ";
414 if (content_range_str.find(kUpThroughBytesUnit) != 0)
415 return false;
416 std::string range_spec =
417 content_range_str.substr(kUpThroughBytesUnit.length());
418 size_t dash_offset = range_spec.find("-");
419 size_t slash_offset = range_spec.find("/");
420
421 if (dash_offset == std::string::npos || slash_offset == std::string::npos ||
422 slash_offset < dash_offset || slash_offset + 1 == range_spec.length()) {
423 return false;
424 }
425 if (!base::StringToInt64(range_spec.substr(0, dash_offset),
426 first_byte_position) ||
427 !base::StringToInt64(
428 range_spec.substr(dash_offset + 1, slash_offset - dash_offset - 1),
429 last_byte_position)) {
430 return false;
431 }
432 if (slash_offset == range_spec.length() - 2 &&
433 range_spec[slash_offset + 1] == '*') {
434 *instance_size = kPositionNotSpecified;
435 } else {
436 if (!base::StringToInt64(range_spec.substr(slash_offset + 1),
437 instance_size)) {
438 return false;
439 }
440 }
441 if (*last_byte_position < *first_byte_position ||
442 (*instance_size != kPositionNotSpecified &&
443 *last_byte_position >= *instance_size)) {
444 return false;
445 }
446
447 return true;
448 }
449
450 int64_t ResourceMultiBufferDataProvider::byte_pos() const {
451 int64_t ret = pos_;
452 return ret << url_data_->multibuffer()->block_size_shift();
453 }
454
455 int64_t ResourceMultiBufferDataProvider::block_size() const {
456 int64_t ret = 1;
457 return ret << url_data_->multibuffer()->block_size_shift();
458 }
459
460 bool ResourceMultiBufferDataProvider::VerifyPartialResponse(
461 const WebURLResponse& response) {
462 int64 first_byte_position, last_byte_position, instance_size;
463 if (!ParseContentRange(response.httpHeaderField("Content-Range").utf8(),
464 &first_byte_position, &last_byte_position,
465 &instance_size)) {
466 return false;
467 }
468
469 if (url_data_->length() == kPositionNotSpecified) {
470 url_data_->set_length(instance_size);
471 }
472
473 if (byte_pos() != first_byte_position) {
474 return false;
475 }
476
477 return true;
478 }
479
480 } // namespace media
OLDNEW
« no previous file with comments | « media/blink/resource_multibuffer_data_provider.h ('k') | media/blink/resource_multibuffer_data_provider_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698