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

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: added MEDIA_BLINK_EXPORT 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 "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/resource_multibuffer.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;
38
39 ResourceMultiBufferDataProvider::ResourceMultiBufferDataProvider(
40 const MultiBufferBlockId& pos,
41 ResourceMultiBuffer* resource_multibuffer)
42 : pos_(pos),
43 resource_multibuffer_(resource_multibuffer),
44 retries_(0),
45 weak_factory_(this) {
46 url_data_ = pos_.url_data();
47 DCHECK(url_data_) << " pos = " << pos;
48 DCHECK_GE(pos.block_num(), 0);
49 original_url_data_ = url_data_;
50 }
51
52 void ResourceMultiBufferDataProvider::Start() {
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 resource_multibuffer_->frame_->setReferrerForRequest(request,
liberato (no reviews please) 2015/11/05 19:03:18 frame_ => frame() (and elsewhere)?
hubbe 2015/11/13 22:56:52 Done.
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 resource_multibuffer_->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 // MultiBufferDataProvider 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::SetAvailableCallback(
122 const base::Closure& cb) {
123 DCHECK(!Available());
124 cb_ = cb;
125 }
126
127 void ResourceMultiBufferDataProvider::SetDeferred(bool deferred) {
128 if (active_loader_) {
129 if (active_loader_->deferred() != deferred) {
130 active_loader_->SetDeferred(deferred);
131 }
132 }
133 }
134
135 /////////////////////////////////////////////////////////////////////////////
136 // WebURLLoaderClient implementation.
137
138 void ResourceMultiBufferDataProvider::willFollowRedirect(
139 WebURLLoader* loader,
140 WebURLRequest& newRequest,
141 const WebURLResponse& redirectResponse) {
142 GURL url(newRequest.url());
143 scoped_refptr<UrlData> new_url_data =
144 resource_multibuffer_->url_index()->GetByUrl(url, url_data_->cors_mode());
145 new_url_data->Use();
146 redirected_url_data_ = url_data_;
147 url_data_ = new_url_data;
148
149 redirected_url_data_->set_valid_until(
150 GetMemoryCacheValidUntil(redirectResponse));
151 }
152
153 void ResourceMultiBufferDataProvider::didSendData(
154 WebURLLoader* loader,
155 unsigned long long bytes_sent,
156 unsigned long long total_bytes_to_be_sent) {
157 NOTIMPLEMENTED();
158 }
159
160 void ResourceMultiBufferDataProvider::didReceiveResponse(
161 WebURLLoader* loader,
162 const WebURLResponse& response) {
163 DVLOG(1) << "didReceiveResponse: HTTP/"
164 << (response.httpVersion() == WebURLResponse::HTTP_0_9
165 ? "0.9"
166 : response.httpVersion() == WebURLResponse::HTTP_1_0
167 ? "1.0"
168 : response.httpVersion() == WebURLResponse::HTTP_1_1
169 ? "1.1"
170 : "Unknown")
171 << " " << response.httpStatusCode();
172 DCHECK(active_loader_);
173
174 // This test is vital for security!
175 if (original_url_data_->cors_mode() == UrlData::kUnspecified) {
176 // Unless we already passed a CORS check, make sure that
177 // we're on the same origin.
178 if (original_url_data_->url().GetOrigin() != url_data_->url().GetOrigin()) {
179 resource_multibuffer_->Fail(url_data_);
180 return;
181 }
182 }
183
184 base::Time last_modified;
185 if (base::Time::FromString(
186 response.httpHeaderField("Last-Modified").utf8().data(),
187 &last_modified)) {
188 scoped_refptr<UrlData> new_url_data =
189 url_data_->set_last_modified(last_modified);
190 new_url_data->Use();
191 if (new_url_data != url_data_) {
192 // Not technically a redirect, but we found a better URLData.
193 resource_multibuffer_->OnRedirect(url_data_, new_url_data);
194 url_data_ = new_url_data;
195 }
196 }
197
198 url_data_->set_valid_until(GetMemoryCacheValidUntil(response));
199
200 if (redirected_url_data_) {
201 redirected_url_data_->set_redirects_to(url_data_->url());
202 resource_multibuffer_->OnRedirect(redirected_url_data_, url_data_);
203 redirected_url_data_ = NULL;
204
205 scoped_ptr<MultiBuffer::DataProvider> self(
206 resource_multibuffer_->RemoveProvider(this));
207 pos_ = MultiBufferBlockId(url_data_, pos_.block_num());
208 resource_multibuffer_->AddProvider(self.Pass());
209 }
210
211 uint32 reasons = GetReasonsForUncacheability(response);
212 url_data_->set_cacheable(reasons == 0);
213 UMA_HISTOGRAM_BOOLEAN("Media.CacheUseful", reasons == 0);
214 int shift = 0;
215 int max_enum = base::bits::Log2Ceiling(kMaxReason);
216 while (reasons) {
217 DCHECK_LT(shift, max_enum); // Sanity check.
218 if (reasons & 0x1) {
219 UMA_HISTOGRAM_ENUMERATION("Media.UncacheableReason", shift,
220 max_enum); // PRESUBMIT_IGNORE_UMA_MAX
221 }
222
223 reasons >>= 1;
224 ++shift;
225 }
226
227 // Expected content length can be |kPositionNotSpecified|, in that case
228 // |content_length_| is not specified and this is a streaming response.
229 int64 content_length = response.expectedContentLength();
230
231 // We make a strong assumption that when we reach here we have either
232 // received a response from HTTP/HTTPS protocol or the request was
233 // successful (in particular range request). So we only verify the partial
234 // response for HTTP and HTTPS protocol.
235 if (url_data_->url().SchemeIsHTTPOrHTTPS()) {
236 bool partial_response = (response.httpStatusCode() == kHttpPartialContent);
237 bool ok_response = (response.httpStatusCode() == kHttpOK);
238
239 // Check to see whether the server supports byte ranges.
240 std::string accept_ranges =
241 response.httpHeaderField("Accept-Ranges").utf8();
242 if (accept_ranges.find("bytes") != std::string::npos)
243 url_data_->set_range_supported();
244
245 // If we have verified the partial response and it is correct, we will
246 // return kOk. It's also possible for a server to support range requests
247 // without advertising "Accept-Ranges: bytes".
248 if (partial_response && VerifyPartialResponse(response)) {
249 url_data_->set_range_supported();
250 } else if (ok_response && pos_.block_num() == 0) {
251 // We accept a 200 response for a Range:0- request, trusting the
252 // Accept-Ranges header, because Apache thinks that's a reasonable thing
253 // to return.
254 url_data_->set_length(content_length);
255 } else if (response.httpStatusCode() == 416) {
256 // Really, we should never request a range that doesn't exist, but
257 // if we do, let's handle it in a sane way.
258 // Unsatisfiable range
259 fifo_.push_back(DataBuffer::CreateEOSBuffer());
260 cb_.Run();
261 } else {
262 resource_multibuffer_->Fail(url_data_);
263 return;
264 }
265 } else {
266 if (content_length != kPositionNotSpecified) {
267 url_data_->set_length(content_length + byte_pos());
268 }
269 }
270 }
271
272 void ResourceMultiBufferDataProvider::didReceiveData(WebURLLoader* loader,
273 const char* data,
274 int data_length,
275 int encoded_data_length) {
276 DVLOG(1) << "didReceiveData: " << data_length << " bytes";
277 DCHECK(!Available());
278 DCHECK(active_loader_);
279 DCHECK_GT(data_length, 0);
280
281 // When we receive data, we allow more retries.
282 retries_ = 0;
283
284 while (data_length) {
285 if (fifo_.empty() || fifo_.back()->data_size() == block_size()) {
286 fifo_.push_back(new DataBuffer(block_size()));
287 fifo_.back()->set_data_size(0);
288 }
289 int to_append =
290 std::min<int>(data_length, block_size() - fifo_.back()->data_size());
291 DCHECK_GT(to_append, 0);
292 memcpy(fifo_.back()->writable_data() + fifo_.back()->data_size(), data,
293 to_append);
294 data += to_append;
295 fifo_.back()->set_data_size(fifo_.back()->data_size() + to_append);
296 data_length -= to_append;
297 }
298
299 if (Available())
300 cb_.Run();
301
302 // Beware, this object might be deleted here.
303 }
304
305 void ResourceMultiBufferDataProvider::didDownloadData(WebURLLoader* loader,
306 int dataLength,
307 int encoded_data_length) {
308 NOTIMPLEMENTED();
309 }
310
311 void ResourceMultiBufferDataProvider::didReceiveCachedMetadata(
312 WebURLLoader* loader,
313 const char* data,
314 int data_length) {
315 NOTIMPLEMENTED();
316 }
317
318 void ResourceMultiBufferDataProvider::didFinishLoading(
319 WebURLLoader* loader,
320 double finishTime,
321 int64_t total_encoded_data_length) {
322 DVLOG(1) << "didFinishLoading";
323 DCHECK(active_loader_.get());
324 DCHECK(!Available());
325
326 // We're done with the loader.
327 active_loader_.reset();
328
329 // If we didn't know the |instance_size_| we do now.
330 int64_t size = byte_pos();
331 if (!fifo_.empty())
332 size += fifo_.back()->data_size();
333
334 // This request reports something smaller than what we've seen in the past,
335 // Maybe it's transient error?
336 if (url_data_->length() != kPositionNotSpecified &&
337 size < url_data_->length()) {
338 if (retries_ < kMaxRetries) {
339 fifo_.clear();
340 retries_++;
341 base::MessageLoop::current()->PostDelayedTask(
342 FROM_HERE, base::Bind(&ResourceMultiBufferDataProvider::Start,
343 weak_factory_.GetWeakPtr()),
344 base::TimeDelta::FromMilliseconds(kLoaderFailedRetryDelayMs));
345 return;
346 } else {
347 scoped_ptr<ActiveLoader> active_loader = active_loader_.Pass();
348 resource_multibuffer_->Fail(url_data_);
349 return;
350 }
351 }
352
353 url_data_->set_length(size);
354 fifo_.push_back(DataBuffer::CreateEOSBuffer());
355
356 DCHECK(Available());
357 cb_.Run();
358
359 // Beware, this object might be deleted here.
360 }
361
362 void ResourceMultiBufferDataProvider::didFail(WebURLLoader* loader,
363 const WebURLError& error) {
364 DVLOG(1) << "didFail: reason=" << error.reason
365 << ", isCancellation=" << error.isCancellation
366 << ", domain=" << error.domain.utf8().data()
367 << ", localizedDescription="
368 << error.localizedDescription.utf8().data();
369 DCHECK(active_loader_.get());
370
371 if (retries_ < kMaxRetries) {
372 retries_++;
373 base::MessageLoop::current()->PostDelayedTask(
374 FROM_HERE, base::Bind(&ResourceMultiBufferDataProvider::Start,
375 weak_factory_.GetWeakPtr()),
376 base::TimeDelta::FromMilliseconds(kLoaderFailedRetryDelayMs));
377 } else {
378 // We don't need to continue loading after failure.
379 //
380 // Keep it alive until we exit this method so that |error| remains valid.
381 scoped_ptr<ActiveLoader> active_loader = active_loader_.Pass();
382 resource_multibuffer_->Fail(url_data_);
383 }
384 }
385
386 bool ResourceMultiBufferDataProvider::ParseContentRange(
387 const std::string& content_range_str,
388 int64* first_byte_position,
389 int64* last_byte_position,
390 int64* instance_size) {
391 const std::string kUpThroughBytesUnit = "bytes ";
392 if (content_range_str.find(kUpThroughBytesUnit) != 0)
393 return false;
394 std::string range_spec =
395 content_range_str.substr(kUpThroughBytesUnit.length());
396 size_t dash_offset = range_spec.find("-");
397 size_t slash_offset = range_spec.find("/");
398
399 if (dash_offset == std::string::npos || slash_offset == std::string::npos ||
400 slash_offset < dash_offset || slash_offset + 1 == range_spec.length()) {
401 return false;
402 }
403 if (!base::StringToInt64(range_spec.substr(0, dash_offset),
404 first_byte_position) ||
405 !base::StringToInt64(
406 range_spec.substr(dash_offset + 1, slash_offset - dash_offset - 1),
407 last_byte_position)) {
408 return false;
409 }
410 if (slash_offset == range_spec.length() - 2 &&
411 range_spec[slash_offset + 1] == '*') {
412 *instance_size = kPositionNotSpecified;
413 } else {
414 if (!base::StringToInt64(range_spec.substr(slash_offset + 1),
415 instance_size)) {
416 return false;
417 }
418 }
419 if (*last_byte_position < *first_byte_position ||
420 (*instance_size != kPositionNotSpecified &&
421 *last_byte_position >= *instance_size)) {
422 return false;
423 }
424
425 return true;
426 }
427
428 int64_t ResourceMultiBufferDataProvider::byte_pos() const {
429 int64_t ret = pos_.block_num();
430 return ret << resource_multibuffer_->block_size_shift();
431 }
432
433 int64_t ResourceMultiBufferDataProvider::block_size() const {
434 int64_t ret = 1;
435 return ret << resource_multibuffer_->block_size_shift();
436 }
437
438 bool ResourceMultiBufferDataProvider::VerifyPartialResponse(
439 const WebURLResponse& response) {
440 int64 first_byte_position, last_byte_position, instance_size;
441 if (!ParseContentRange(response.httpHeaderField("Content-Range").utf8(),
442 &first_byte_position, &last_byte_position,
443 &instance_size)) {
444 return false;
445 }
446
447 if (url_data_->length() == kPositionNotSpecified) {
448 url_data_->set_length(instance_size);
449 }
450
451 if (byte_pos() != first_byte_position) {
452 return false;
453 }
454
455 return true;
456 }
457
458 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698