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