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 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 |
OLD | NEW |