// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "webkit/glue/media/buffered_resource_loader.h" #include "base/format_macros.h" #include "base/string_util.h" #include "net/base/net_errors.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebKit.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebKitClient.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebURLError.h" #include "webkit/glue/multipart_response_delegate.h" #include "webkit/glue/webkit_glue.h" using WebKit::WebFrame; using WebKit::WebString; using WebKit::WebURLError; using WebKit::WebURLLoader; using WebKit::WebURLRequest; using WebKit::WebURLResponse; using webkit_glue::MultipartResponseDelegate; namespace webkit_glue { static const int kHttpOK = 200; static const int kHttpPartialContent = 206; // Define the number of bytes in a megabyte. static const size_t kMegabyte = 1024 * 1024; // Backward capacity of the buffer, by default 2MB. static const size_t kBackwardCapcity = 2 * kMegabyte; // Forward capacity of the buffer, by default 10MB. static const size_t kForwardCapacity = 10 * kMegabyte; // The threshold of bytes that we should wait until the data arrives in the // future instead of restarting a new connection. This number is defined in the // number of bytes, we should determine this value from typical connection speed // and amount of time for a suitable wait. Now I just make a guess for this // number to be 2MB. // TODO(hclam): determine a better value for this. static const int kForwardWaitThreshold = 2 * kMegabyte; BufferedResourceLoader::BufferedResourceLoader( const GURL& url, int64 first_byte_position, int64 last_byte_position) : buffer_(new media::SeekableBuffer(kBackwardCapcity, kForwardCapacity)), deferred_(false), defer_strategy_(kReadThenDefer), completed_(false), range_requested_(false), range_supported_(false), url_(url), first_byte_position_(first_byte_position), last_byte_position_(last_byte_position), single_origin_(true), start_callback_(NULL), offset_(0), content_length_(kPositionNotSpecified), instance_size_(kPositionNotSpecified), read_callback_(NULL), read_position_(0), read_size_(0), read_buffer_(NULL), first_offset_(0), last_offset_(0), keep_test_loader_(false) { } BufferedResourceLoader::~BufferedResourceLoader() { if (!completed_ && url_loader_.get()) url_loader_->cancel(); } void BufferedResourceLoader::Start(net::CompletionCallback* start_callback, NetworkEventCallback* event_callback, WebFrame* frame) { // Make sure we have not started. DCHECK(!start_callback_.get()); DCHECK(!event_callback_.get()); DCHECK(start_callback); DCHECK(event_callback); CHECK(frame); start_callback_.reset(start_callback); event_callback_.reset(event_callback); if (first_byte_position_ != kPositionNotSpecified) { // TODO(hclam): server may not support range request so |offset_| may not // equal to |first_byte_position_|. offset_ = first_byte_position_; } // Increment the reference count right before we start the request. This // reference will be release when this request has ended. AddRef(); // Prepare the request. WebURLRequest request(url_); request.setTargetType(WebURLRequest::TargetIsMedia); if (IsRangeRequest()) { range_requested_ = true; request.setHTTPHeaderField(WebString::fromUTF8("Range"), WebString::fromUTF8(GenerateHeaders( first_byte_position_, last_byte_position_))); } frame->setReferrerForRequest(request, WebKit::WebURL()); // This flag is for unittests as we don't want to reset |url_loader| if (!keep_test_loader_) url_loader_.reset(frame->createAssociatedURLLoader()); // Start the resource loading. url_loader_->loadAsynchronously(request, this); } void BufferedResourceLoader::Stop() { // Reset callbacks. start_callback_.reset(); event_callback_.reset(); read_callback_.reset(); // Use the internal buffer to signal that we have been stopped. // TODO(hclam): Not so pretty to do this. if (!buffer_.get()) return; // Destroy internal buffer. buffer_.reset(); if (url_loader_.get()) { if (deferred_) url_loader_->setDefersLoading(false); deferred_ = false; if (!completed_) { url_loader_->cancel(); completed_ = true; } } } void BufferedResourceLoader::Read(int64 position, int read_size, uint8* buffer, net::CompletionCallback* read_callback) { DCHECK(!read_callback_.get()); DCHECK(buffer_.get()); DCHECK(read_callback); DCHECK(buffer); // Save the parameter of reading. read_callback_.reset(read_callback); read_position_ = position; read_size_ = read_size; read_buffer_ = buffer; // If read position is beyond the instance size, we cannot read there. if (instance_size_ != kPositionNotSpecified && instance_size_ <= read_position_) { DoneRead(0); return; } // Make sure |offset_| and |read_position_| does not differ by a large // amount. if (read_position_ > offset_ + kint32max || read_position_ < offset_ + kint32min) { DoneRead(net::ERR_CACHE_MISS); return; } // Prepare the parameters. first_offset_ = static_cast<int>(read_position_ - offset_); last_offset_ = first_offset_ + read_size_; // If we can serve the request now, do the actual read. if (CanFulfillRead()) { ReadInternal(); UpdateDeferBehavior(); return; } // If you're deferred and you can't fulfill the read because you don't have // enough data, you will never fulfill the read. // Update defer behavior to re-enable deferring if need be. UpdateDeferBehavior(); // If we expected the read request to be fulfilled later, returns // immediately and let more data to flow in. if (WillFulfillRead()) return; // Make a callback to report failure. DoneRead(net::ERR_CACHE_MISS); } int64 BufferedResourceLoader::GetBufferedPosition() { if (buffer_.get()) return offset_ + static_cast<int>(buffer_->forward_bytes()) - 1; return kPositionNotSpecified; } int64 BufferedResourceLoader::content_length() { return content_length_; } int64 BufferedResourceLoader::instance_size() { return instance_size_; } bool BufferedResourceLoader::range_supported() { return range_supported_; } bool BufferedResourceLoader::network_activity() { return !completed_ && !deferred_; } const GURL& BufferedResourceLoader::url() { return url_; } void BufferedResourceLoader::SetURLLoaderForTest(WebURLLoader* mock_loader) { url_loader_.reset(mock_loader); keep_test_loader_ = true; } ///////////////////////////////////////////////////////////////////////////// // WebKit::WebURLLoaderClient implementation. void BufferedResourceLoader::willSendRequest( WebURLLoader* loader, WebURLRequest& newRequest, const WebURLResponse& redirectResponse) { // The load may have been stopped and |start_callback| is destroyed. // In this case we shouldn't do anything. if (!start_callback_.get()) { // Set the url in the request to an invalid value (empty url). newRequest.setURL(WebKit::WebURL()); return; } // Only allow |single_origin_| if we haven't seen a different origin yet. if (single_origin_) single_origin_ = url_.GetOrigin() == GURL(newRequest.url()).GetOrigin(); if (!IsProtocolSupportedForMedia(newRequest.url())) { // Set the url in the request to an invalid value (empty url). newRequest.setURL(WebKit::WebURL()); DoneStart(net::ERR_ADDRESS_INVALID); return; } url_ = newRequest.url(); } void BufferedResourceLoader::didSendData( WebURLLoader* loader, unsigned long long bytes_sent, unsigned long long total_bytes_to_be_sent) { NOTIMPLEMENTED(); } void BufferedResourceLoader::didReceiveResponse( WebURLLoader* loader, const WebURLResponse& response) { // The loader may have been stopped and |start_callback| is destroyed. // In this case we shouldn't do anything. if (!start_callback_.get()) return; bool partial_response = false; // We make a strong assumption that when we reach here we have either // received a response from HTTP/HTTPS protocol or the request was // successful (in particular range request). So we only verify the partial // response for HTTP and HTTPS protocol. if (url_.SchemeIs(kHttpScheme) || url_.SchemeIs(kHttpsScheme)) { int error = net::OK; // Check to see whether the server supports byte ranges. std::string accept_ranges = response.httpHeaderField("Accept-Ranges").utf8(); range_supported_ = (accept_ranges.find("bytes") != std::string::npos); partial_response = (response.httpStatusCode() == kHttpPartialContent); if (range_requested_) { // If we have verified the partial response and it is correct, we will // return net::OK. It's also possible for a server to support range // requests without advertising Accept-Ranges: bytes. if (partial_response && VerifyPartialResponse(response)) range_supported_ = true; else error = net::ERR_INVALID_RESPONSE; } else if (response.httpStatusCode() != kHttpOK) { // We didn't request a range but server didn't reply with "200 OK". error = net::ERR_FAILED; } if (error != net::OK) { DoneStart(error); Stop(); return; } } else { // For any protocol other than HTTP and HTTPS, assume range request is // always fulfilled. partial_response = range_requested_; } // Expected content length can be |kPositionNotSpecified|, in that case // |content_length_| is not specified and this is a streaming response. content_length_ = response.expectedContentLength(); // If we have not requested a range, then the size of the instance is equal // to the content length. if (!partial_response) instance_size_ = content_length_; // Calls with a successful response. DoneStart(net::OK); } void BufferedResourceLoader::didReceiveData( WebURLLoader* loader, const char* data, int data_length, int encoded_data_length) { DCHECK(!completed_); DCHECK_GT(data_length, 0); // If this loader has been stopped, |buffer_| would be destroyed. // In this case we shouldn't do anything. if (!buffer_.get()) return; // Writes more data to |buffer_|. buffer_->Append(reinterpret_cast<const uint8*>(data), data_length); // If there is an active read request, try to fulfill the request. if (HasPendingRead() && CanFulfillRead()) ReadInternal(); // At last see if the buffer is full and we need to defer the downloading. UpdateDeferBehavior(); // Consume excess bytes from our in-memory buffer if necessary. if (buffer_->forward_bytes() > buffer_->forward_capacity()) { size_t excess = buffer_->forward_bytes() - buffer_->forward_capacity(); bool success = buffer_->Seek(excess); DCHECK(success); offset_ += first_offset_ + excess; } // Notify that we have received some data. NotifyNetworkEvent(); } void BufferedResourceLoader::didDownloadData( WebKit::WebURLLoader* loader, int dataLength) { NOTIMPLEMENTED(); } void BufferedResourceLoader::didReceiveCachedMetadata( WebURLLoader* loader, const char* data, int data_length) { NOTIMPLEMENTED(); } void BufferedResourceLoader::didFinishLoading( WebURLLoader* loader, double finishTime) { DCHECK(!completed_); completed_ = true; // If we didn't know the |instance_size_| we do now. if (instance_size_ == kPositionNotSpecified) { instance_size_ = offset_ + buffer_->forward_bytes(); } // If there is a start callback, calls it. if (start_callback_.get()) { DoneStart(net::OK); } // If there is a pending read but the request has ended, returns with what // we have. if (HasPendingRead()) { // Make sure we have a valid buffer before we satisfy a read request. DCHECK(buffer_.get()); // Try to fulfill with what is in the buffer. if (CanFulfillRead()) ReadInternal(); else DoneRead(net::ERR_CACHE_MISS); } // There must not be any outstanding read request. DCHECK(!HasPendingRead()); // Notify that network response is completed. NotifyNetworkEvent(); url_loader_.reset(); Release(); } void BufferedResourceLoader::didFail( WebURLLoader* loader, const WebURLError& error) { DCHECK(!completed_); completed_ = true; // If there is a start callback, calls it. if (start_callback_.get()) { DoneStart(error.reason); } // If there is a pending read but the request failed, return with the // reason for the error. if (HasPendingRead()) { DoneRead(error.reason); } // Notify that network response is completed. NotifyNetworkEvent(); url_loader_.reset(); Release(); } bool BufferedResourceLoader::HasSingleOrigin() const { return single_origin_; } ///////////////////////////////////////////////////////////////////////////// // Helper methods. void BufferedResourceLoader::UpdateDeferBehavior() { if (!url_loader_.get() || !buffer_.get()) return; if ((deferred_ && ShouldDisableDefer()) || (!deferred_ && ShouldEnableDefer())) { bool eventOccurred = ToggleDeferring(); if (eventOccurred) NotifyNetworkEvent(); } } void BufferedResourceLoader::UpdateDeferStrategy(DeferStrategy strategy) { defer_strategy_ = strategy; UpdateDeferBehavior(); } bool BufferedResourceLoader::ShouldEnableDefer() { // If we're already deferring, then enabling makes no sense. if (deferred_) return false; switch(defer_strategy_) { // Never defer at all, so never enable defer. case kNeverDefer: return false; // Defer if nothing is being requested. case kReadThenDefer: return !read_callback_.get(); // Defer if we've reached the max capacity of the threshold. case kThresholdDefer: return buffer_->forward_bytes() >= buffer_->forward_capacity(); } // Otherwise don't enable defer. return false; } bool BufferedResourceLoader::ShouldDisableDefer() { // If we're not deferring, then disabling makes no sense. if (!deferred_) return false; switch(defer_strategy_) { // Always disable deferring. case kNeverDefer: return true; // We have an outstanding read request, and we have not buffered enough // yet to fulfill the request; disable defer to get more data. case kReadThenDefer: { size_t amount_buffered = buffer_->forward_bytes(); size_t amount_to_read = static_cast<size_t>(read_size_); return read_callback_.get() && amount_buffered < amount_to_read; } // We have less than half the capacity of our threshold, so // disable defer to get more data. case kThresholdDefer: { size_t amount_buffered = buffer_->forward_bytes(); size_t half_capacity = buffer_->forward_capacity() / 2; return amount_buffered < half_capacity; } } // Otherwise keep deferring. return false; } bool BufferedResourceLoader::ToggleDeferring() { deferred_ = !deferred_; if (url_loader_.get()) { url_loader_->setDefersLoading(deferred_); return true; } return false; } bool BufferedResourceLoader::CanFulfillRead() { // If we are reading too far in the backward direction. if (first_offset_ < 0 && first_offset_ + static_cast<int>(buffer_->backward_bytes()) < 0) return false; // If the start offset is too far ahead. if (first_offset_ >= static_cast<int>(buffer_->forward_bytes())) return false; // At the point, we verified that first byte requested is within the buffer. // If the request has completed, then just returns with what we have now. if (completed_) return true; // If the resource request is still active, make sure the whole requested // range is covered. if (last_offset_ > static_cast<int>(buffer_->forward_bytes())) return false; return true; } bool BufferedResourceLoader::WillFulfillRead() { // Reading too far in the backward direction. if (first_offset_ < 0 && first_offset_ + static_cast<int>(buffer_->backward_bytes()) < 0) return false; // Try to read too far ahead. if (last_offset_ > kForwardWaitThreshold) return false; // The resource request has completed, there's no way we can fulfill the // read request. if (completed_) return false; return true; } void BufferedResourceLoader::ReadInternal() { // Seek to the first byte requested. bool ret = buffer_->Seek(first_offset_); DCHECK(ret); // Then do the read. int read = static_cast<int>(buffer_->Read(read_buffer_, read_size_)); offset_ += first_offset_ + read; // And report with what we have read. DoneRead(read); } bool BufferedResourceLoader::VerifyPartialResponse( const WebURLResponse& response) { int first_byte_position, last_byte_position, instance_size; if (!MultipartResponseDelegate::ReadContentRanges(response, &first_byte_position, &last_byte_position, &instance_size)) { return false; } if (instance_size != kPositionNotSpecified) { instance_size_ = instance_size; } if (first_byte_position_ != kPositionNotSpecified && first_byte_position_ != first_byte_position) { return false; } // TODO(hclam): I should also check |last_byte_position|, but since // we will never make such a request that it is ok to leave it unimplemented. return true; } std::string BufferedResourceLoader::GenerateHeaders( int64 first_byte_position, int64 last_byte_position) { // Construct the value for the range header. std::string header; if (first_byte_position > kPositionNotSpecified && last_byte_position > kPositionNotSpecified) { if (first_byte_position <= last_byte_position) { header = base::StringPrintf("bytes=%" PRId64 "-%" PRId64, first_byte_position, last_byte_position); } } else if (first_byte_position > kPositionNotSpecified) { header = base::StringPrintf("bytes=%" PRId64 "-", first_byte_position); } else if (last_byte_position > kPositionNotSpecified) { NOTIMPLEMENTED() << "Suffix range not implemented"; } return header; } void BufferedResourceLoader::DoneRead(int error) { read_callback_->RunWithParams(Tuple1<int>(error)); read_callback_.reset(); read_position_ = 0; read_size_ = 0; read_buffer_ = NULL; first_offset_ = 0; last_offset_ = 0; } void BufferedResourceLoader::DoneStart(int error) { start_callback_->RunWithParams(Tuple1<int>(error)); start_callback_.reset(); } void BufferedResourceLoader::NotifyNetworkEvent() { if (event_callback_.get()) event_callback_->Run(); } bool BufferedResourceLoader::IsRangeRequest() const { return first_byte_position_ != kPositionNotSpecified; } } // namespace webkit_glue