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

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

Powered by Google App Engine
This is Rietveld 408576698