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

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: compile fixes 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 static const int kHttpOK = 200;
36 static const int kHttpPartialContent = 206;
37 static const int kMaxRetries = 3;
xhwang 2015/11/19 23:34:17 "static" not needed, also be consistent with l.33
hubbe 2015/11/20 23:24:23 Done.
38
39 ResourceMultiBufferDataProvider::ResourceMultiBufferDataProvider(
40 UrlData* url_data,
41 MultiBufferBlockId pos)
42 : pos_(pos),
43 url_data_(url_data),
44 retries_(0),
45 cors_mode_(url_data->cors_mode()),
46 origin_(url_data->url().GetOrigin()),
47 weak_factory_(this) {
48 DCHECK(url_data_) << " pos = " << pos;
49 DCHECK_GE(pos, 0);
50 }
51
52 void ResourceMultiBufferDataProvider::Start() {
xhwang 2015/11/19 23:34:17 definition order doesn't match declaration order
hubbe 2015/11/20 23:24:23 Done.
53 // In the case of a re-start, throw away any half-finished blocks.
54 fifo_.clear();
55 // Prepare the request.
56 WebURLRequest request(url_data_->url());
57 // TODO(mkwst): Split this into video/audio.
58 request.setRequestContext(WebURLRequest::RequestContextVideo);
59
60 request.setHTTPHeaderField(
61 WebString::fromUTF8(net::HttpRequestHeaders::kRange),
62 WebString::fromUTF8(
63 net::HttpByteRange::RightUnbounded(byte_pos()).GetHeaderValue()));
64
65 url_data_->multibuffer()->frame()->setReferrerForRequest(request,
66 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_->multibuffer()->frame()->createAssociatedURLLoader(options));
xhwang 2015/11/19 23:34:17 It seems we plumb the |frame| all the way here thr
hubbe 2015/11/20 23:24:23 To me, it makes sense to have "creating a loader"
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 // MultiBufferDataProvider implementation.
xhwang 2015/11/19 23:34:17 MultiBuffer::DataProvider
hubbe 2015/11/20 23:24:23 Done.
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_) {
xhwang 2015/11/19 23:34:17 nit: We like to return early if (!active_loader)
hubbe 2015/11/20 23:24:23 I tend to prefer non-negative if-statements over r
123 if (active_loader_->deferred() != deferred) {
124 active_loader_->SetDeferred(deferred);
125 }
126 }
127 }
128
129 /////////////////////////////////////////////////////////////////////////////
130 // WebURLLoaderClient implementation.
131
132 void ResourceMultiBufferDataProvider::willFollowRedirect(
133 WebURLLoader* loader,
134 WebURLRequest& newRequest,
135 const WebURLResponse& redirectResponse) {
136 redirects_to_ = newRequest.url();
137 url_data_->set_valid_until(GetMemoryCacheValidUntil(redirectResponse));
138
139 // This test is vital for security!
140 if (cors_mode_ == UrlData::kUnspecified) {
141 if (origin_ != redirects_to_.GetOrigin()) {
142 url_data_->Fail();
143 }
144 }
145 }
146
147 void ResourceMultiBufferDataProvider::didSendData(
148 WebURLLoader* loader,
149 unsigned long long bytes_sent,
150 unsigned long long total_bytes_to_be_sent) {
151 NOTIMPLEMENTED();
152 }
153
154 void ResourceMultiBufferDataProvider::didReceiveResponse(
155 WebURLLoader* loader,
156 const WebURLResponse& response) {
157 DVLOG(1) << "didReceiveResponse: HTTP/"
158 << (response.httpVersion() == WebURLResponse::HTTPVersion_0_9
159 ? "0.9"
160 : response.httpVersion() == WebURLResponse::HTTPVersion_1_0
161 ? "1.0"
162 : response.httpVersion() ==
163 WebURLResponse::HTTPVersion_1_1
164 ? "1.1"
165 : response.httpVersion() ==
166 WebURLResponse::HTTPVersion_2_0
167 ? "2.0"
168 : "Unknown")
xhwang 2015/11/19 23:34:17 Have a helper function to convert WebURLResponse::
hubbe 2015/11/20 23:24:23 Helper function seems overkill since it's just for
xhwang 2015/11/23 23:09:21 Acknowledged.
169 << " " << response.httpStatusCode();
170 DCHECK(active_loader_);
171
172 scoped_refptr<UrlData> destination_url_data(url_data_);
173
174 base::Time last_modified;
175 if (base::Time::FromString(
176 response.httpHeaderField("Last-Modified").utf8().data(),
177 &last_modified)) {
178 url_data_->set_last_modified(last_modified);
179 }
180
181 url_data_->set_valid_until(GetMemoryCacheValidUntil(response));
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 uint32 reasons = GetReasonsForUncacheability(response);
197 url_data_->set_cacheable(reasons == 0);
198 UMA_HISTOGRAM_BOOLEAN("Media.CacheUseful", reasons == 0);
199 int shift = 0;
200 int max_enum = base::bits::Log2Ceiling(kMaxReason);
201 while (reasons) {
202 DCHECK_LT(shift, max_enum); // Sanity check.
203 if (reasons & 0x1) {
204 UMA_HISTOGRAM_ENUMERATION("Media.UncacheableReason", shift,
205 max_enum); // PRESUBMIT_IGNORE_UMA_MAX
206 }
207
208 reasons >>= 1;
209 ++shift;
210 }
211
212 // Expected content length can be |kPositionNotSpecified|, in that case
213 // |content_length_| is not specified and this is a streaming response.
214 int64 content_length = response.expectedContentLength();
215
216 // We make a strong assumption that when we reach here we have either
217 // received a response from HTTP/HTTPS protocol or the request was
218 // successful (in particular range request). So we only verify the partial
219 // response for HTTP and HTTPS protocol.
220 if (url_data_->url().SchemeIsHTTPOrHTTPS()) {
221 bool partial_response = (response.httpStatusCode() == kHttpPartialContent);
222 bool ok_response = (response.httpStatusCode() == kHttpOK);
223
224 // Check to see whether the server supports byte ranges.
225 std::string accept_ranges =
226 response.httpHeaderField("Accept-Ranges").utf8();
227 if (accept_ranges.find("bytes") != std::string::npos)
228 url_data_->set_range_supported();
xhwang 2015/11/19 23:34:17 We do similar operations in multiple places. Can w
hubbe 2015/11/20 23:24:23 A helper function that calls url_data_->set_range_
xhwang 2015/11/23 23:09:21 Sorry for not being clear. A helper function that
hubbe 2015/11/24 22:55:10 Doesn't seem to be a lot of re-usable code here. J
229
230 // If we have verified the partial response and it is correct, we will
231 // return kOk. It's also possible for a server to support range requests
xhwang 2015/11/19 23:34:17 I don't see where "kOk" is returned.
hubbe 2015/11/20 23:24:23 Removed reference to kOk.
232 // without advertising "Accept-Ranges: bytes".
233 if (partial_response && VerifyPartialResponse(response)) {
234 url_data_->set_range_supported();
235 } else if (ok_response && pos_ == 0) {
236 // We accept a 200 response for a Range:0- request, trusting the
237 // Accept-Ranges header, because Apache thinks that's a reasonable thing
238 // to return.
239 url_data_->set_length(content_length);
240 } else if (response.httpStatusCode() == 416) {
xhwang 2015/11/19 23:34:17 We don't like hardcoded numbers, define a const si
hubbe 2015/11/20 23:24:23 Done.
241 // Really, we should never request a range that doesn't exist, but
242 // if we do, let's handle it in a sane way.
243 // Unsatisfiable range
244 fifo_.push_back(DataBuffer::CreateEOSBuffer());
245 url_data_->multibuffer()->DataProviderEvent(this);
xhwang 2015/11/19 23:34:17 We are using the multibuffer a lot, I kinda feel w
hubbe 2015/11/20 23:24:23 To me, using url_data_ seems more questionable tha
246 return;
247 } else {
248 url_data_->Fail();
249 return;
250 }
251 } else {
252 url_data_->set_range_supported();
253 if (content_length != kPositionNotSpecified) {
254 url_data_->set_length(content_length + byte_pos());
255 }
256 }
257
258 if (url_index) {
259 destination_url_data = url_index->TryInsert(destination_url_data);
260 }
261
262 if (destination_url_data != url_data_) {
263 scoped_refptr<UrlData> old_url_data(url_data_);
264 destination_url_data->Use();
265
266 // Take ownership of ourselves.
267 scoped_ptr<DataProvider> self(
268 url_data_->multibuffer()->RemoveProvider(this));
269 url_data_ = destination_url_data.get();
xhwang 2015/11/19 23:34:17 Can we remove the .get() part?
hubbe 2015/11/20 23:24:23 No.
270 url_data_->multibuffer()->AddProvider(self.Pass());
271
272 // Transfer data, writers, readers, to new UrlData.
273 old_url_data->RedirectTo(destination_url_data);
xhwang 2015/11/19 23:34:17 These operations are confusing. Can we wrap 263-27
hubbe 2015/11/20 23:24:23 I decided to add a bunch of comments instead. Let
274 }
275 }
276
277 void ResourceMultiBufferDataProvider::didReceiveData(WebURLLoader* loader,
278 const char* data,
279 int data_length,
280 int encoded_data_length) {
281 DVLOG(1) << "didReceiveData: " << data_length << " bytes";
282 DCHECK(!Available());
283 DCHECK(active_loader_);
284 DCHECK_GT(data_length, 0);
285
286 // When we receive data, we allow more retries.
287 retries_ = 0;
288
289 while (data_length) {
290 if (fifo_.empty() || fifo_.back()->data_size() == block_size()) {
xhwang 2015/11/19 23:34:17 nit: fifo_.back()->data_size() appeared 4 times, w
hubbe 2015/11/20 23:24:23 Done (but only for 3 out of the 4 cases.)
291 fifo_.push_back(new DataBuffer(block_size()));
292 fifo_.back()->set_data_size(0);
293 }
294 int to_append =
295 std::min<int>(data_length, block_size() - fifo_.back()->data_size());
296 DCHECK_GT(to_append, 0);
297 memcpy(fifo_.back()->writable_data() + fifo_.back()->data_size(), data,
298 to_append);
299 data += to_append;
300 fifo_.back()->set_data_size(fifo_.back()->data_size() + to_append);
301 data_length -= to_append;
302 }
303
304 if (Available())
305 url_data_->multibuffer()->DataProviderEvent(this);
306
307 // Beware, this object might be deleted here.
308 }
309
310 void ResourceMultiBufferDataProvider::didDownloadData(WebURLLoader* loader,
311 int dataLength,
312 int encoded_data_length) {
313 NOTIMPLEMENTED();
314 }
315
316 void ResourceMultiBufferDataProvider::didReceiveCachedMetadata(
317 WebURLLoader* loader,
318 const char* data,
319 int data_length) {
320 NOTIMPLEMENTED();
321 }
322
323 void ResourceMultiBufferDataProvider::didFinishLoading(
324 WebURLLoader* loader,
325 double finishTime,
326 int64_t total_encoded_data_length) {
327 DVLOG(1) << "didFinishLoading";
328 DCHECK(active_loader_.get());
329 DCHECK(!Available());
330
331 // We're done with the loader.
332 active_loader_.reset();
333
334 // If we didn't know the |instance_size_| we do now.
335 int64_t size = byte_pos();
336 if (!fifo_.empty())
337 size += fifo_.back()->data_size();
338
339 // This request reports something smaller than what we've seen in the past,
340 // Maybe it's transient error?
341 if (url_data_->length() != kPositionNotSpecified &&
342 size < url_data_->length()) {
343 if (retries_ < kMaxRetries) {
344 fifo_.clear();
345 retries_++;
346 base::MessageLoop::current()->PostDelayedTask(
347 FROM_HERE, base::Bind(&ResourceMultiBufferDataProvider::Start,
348 weak_factory_.GetWeakPtr()),
349 base::TimeDelta::FromMilliseconds(kLoaderFailedRetryDelayMs));
350 return;
351 } else {
352 scoped_ptr<ActiveLoader> active_loader = active_loader_.Pass();
353 url_data_->Fail();
354 return;
355 }
356 }
357
358 url_data_->set_length(size);
359 fifo_.push_back(DataBuffer::CreateEOSBuffer());
360
361 DCHECK(Available());
362 url_data_->multibuffer()->DataProviderEvent(this);
363
364 // Beware, this object might be deleted here.
365 }
366
367 void ResourceMultiBufferDataProvider::didFail(WebURLLoader* loader,
368 const WebURLError& error) {
369 DVLOG(1) << "didFail: reason=" << error.reason
370 << ", isCancellation=" << error.isCancellation
371 << ", domain=" << error.domain.utf8().data()
372 << ", localizedDescription="
373 << error.localizedDescription.utf8().data();
374 DCHECK(active_loader_.get());
375
376 if (retries_ < kMaxRetries) {
377 retries_++;
378 base::MessageLoop::current()->PostDelayedTask(
379 FROM_HERE, base::Bind(&ResourceMultiBufferDataProvider::Start,
380 weak_factory_.GetWeakPtr()),
381 base::TimeDelta::FromMilliseconds(kLoaderFailedRetryDelayMs));
382 } else {
383 // We don't need to continue loading after failure.
384 //
385 // Keep it alive until we exit this method so that |error| remains valid.
386 scoped_ptr<ActiveLoader> active_loader = active_loader_.Pass();
387 url_data_->Fail();
388 }
389 }
390
391 bool ResourceMultiBufferDataProvider::ParseContentRange(
392 const std::string& content_range_str,
393 int64* first_byte_position,
394 int64* last_byte_position,
395 int64* instance_size) {
396 const std::string kUpThroughBytesUnit = "bytes ";
397 if (content_range_str.find(kUpThroughBytesUnit) != 0)
398 return false;
399 std::string range_spec =
400 content_range_str.substr(kUpThroughBytesUnit.length());
401 size_t dash_offset = range_spec.find("-");
402 size_t slash_offset = range_spec.find("/");
403
404 if (dash_offset == std::string::npos || slash_offset == std::string::npos ||
405 slash_offset < dash_offset || slash_offset + 1 == range_spec.length()) {
406 return false;
407 }
408 if (!base::StringToInt64(range_spec.substr(0, dash_offset),
409 first_byte_position) ||
410 !base::StringToInt64(
411 range_spec.substr(dash_offset + 1, slash_offset - dash_offset - 1),
412 last_byte_position)) {
413 return false;
414 }
415 if (slash_offset == range_spec.length() - 2 &&
416 range_spec[slash_offset + 1] == '*') {
417 *instance_size = kPositionNotSpecified;
418 } else {
419 if (!base::StringToInt64(range_spec.substr(slash_offset + 1),
420 instance_size)) {
421 return false;
422 }
423 }
424 if (*last_byte_position < *first_byte_position ||
425 (*instance_size != kPositionNotSpecified &&
426 *last_byte_position >= *instance_size)) {
427 return false;
428 }
429
430 return true;
431 }
432
433 int64_t ResourceMultiBufferDataProvider::byte_pos() const {
434 int64_t ret = pos_;
435 return ret << url_data_->multibuffer()->block_size_shift();
436 }
437
438 int64_t ResourceMultiBufferDataProvider::block_size() const {
439 int64_t ret = 1;
440 return ret << url_data_->multibuffer()->block_size_shift();
441 }
442
443 bool ResourceMultiBufferDataProvider::VerifyPartialResponse(
444 const WebURLResponse& response) {
445 int64 first_byte_position, last_byte_position, instance_size;
446 if (!ParseContentRange(response.httpHeaderField("Content-Range").utf8(),
447 &first_byte_position, &last_byte_position,
448 &instance_size)) {
449 return false;
450 }
451
452 if (url_data_->length() == kPositionNotSpecified) {
453 url_data_->set_length(instance_size);
454 }
455
456 if (byte_pos() != first_byte_position) {
457 return false;
458 }
459
460 return true;
461 }
462
463 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698