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

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

Powered by Google App Engine
This is Rietveld 408576698