// Copyright (c) 2012 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 "net/url_request/url_fetcher_core.h" #include "base/bind.h" #include "base/logging.h" #include "base/metrics/histogram.h" #include "base/sequenced_task_runner.h" #include "base/single_thread_task_runner.h" #include "base/stl_util.h" #include "base/thread_task_runner_handle.h" #include "base/tracked_objects.h" #include "net/base/io_buffer.h" #include "net/base/load_flags.h" #include "net/base/net_errors.h" #include "net/base/request_priority.h" #include "net/base/upload_bytes_element_reader.h" #include "net/base/upload_data_stream.h" #include "net/base/upload_file_element_reader.h" #include "net/http/http_response_headers.h" #include "net/url_request/url_fetcher_delegate.h" #include "net/url_request/url_fetcher_response_writer.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_getter.h" #include "net/url_request/url_request_throttler_manager.h" namespace { const int kBufferSize = 4096; const int kUploadProgressTimerInterval = 100; bool g_interception_enabled = false; bool g_ignore_certificate_requests = false; void EmptyCompletionCallback(int result) {} } // namespace namespace net { // URLFetcherCore::Registry --------------------------------------------------- URLFetcherCore::Registry::Registry() {} URLFetcherCore::Registry::~Registry() {} void URLFetcherCore::Registry::AddURLFetcherCore(URLFetcherCore* core) { DCHECK(!ContainsKey(fetchers_, core)); fetchers_.insert(core); } void URLFetcherCore::Registry::RemoveURLFetcherCore(URLFetcherCore* core) { DCHECK(ContainsKey(fetchers_, core)); fetchers_.erase(core); } void URLFetcherCore::Registry::CancelAll() { while (!fetchers_.empty()) (*fetchers_.begin())->CancelURLRequest(ERR_ABORTED); } // URLFetcherCore ------------------------------------------------------------- // static base::LazyInstance URLFetcherCore::g_registry = LAZY_INSTANCE_INITIALIZER; URLFetcherCore::URLFetcherCore(URLFetcher* fetcher, const GURL& original_url, URLFetcher::RequestType request_type, URLFetcherDelegate* d) : fetcher_(fetcher), original_url_(original_url), request_type_(request_type), delegate_(d), delegate_task_runner_(base::ThreadTaskRunnerHandle::Get()), load_flags_(LOAD_NORMAL), response_code_(URLFetcher::RESPONSE_CODE_INVALID), buffer_(new IOBuffer(kBufferSize)), url_request_data_key_(NULL), was_fetched_via_proxy_(false), upload_content_set_(false), upload_range_offset_(0), upload_range_length_(0), referrer_policy_( URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE), is_chunked_upload_(false), was_cancelled_(false), stop_on_redirect_(false), stopped_on_redirect_(false), automatically_retry_on_5xx_(true), num_retries_on_5xx_(0), max_retries_on_5xx_(0), num_retries_on_network_changes_(0), max_retries_on_network_changes_(0), current_upload_bytes_(-1), current_response_bytes_(0), total_response_bytes_(-1) { CHECK(original_url_.is_valid()); } void URLFetcherCore::Start() { DCHECK(delegate_task_runner_.get()); DCHECK(request_context_getter_.get()) << "We need an URLRequestContext!"; if (network_task_runner_.get()) { DCHECK_EQ(network_task_runner_, request_context_getter_->GetNetworkTaskRunner()); } else { network_task_runner_ = request_context_getter_->GetNetworkTaskRunner(); } DCHECK(network_task_runner_.get()) << "We need an IO task runner"; network_task_runner_->PostTask( FROM_HERE, base::Bind(&URLFetcherCore::StartOnIOThread, this)); } void URLFetcherCore::Stop() { if (delegate_task_runner_.get()) // May be NULL in tests. DCHECK(delegate_task_runner_->BelongsToCurrentThread()); delegate_ = NULL; fetcher_ = NULL; if (!network_task_runner_.get()) return; if (network_task_runner_->RunsTasksOnCurrentThread()) { CancelURLRequest(ERR_ABORTED); } else { network_task_runner_->PostTask( FROM_HERE, base::Bind(&URLFetcherCore::CancelURLRequest, this, ERR_ABORTED)); } } void URLFetcherCore::SetUploadData(const std::string& upload_content_type, const std::string& upload_content) { DCHECK(!is_chunked_upload_); DCHECK(!upload_content_set_); DCHECK(upload_content_.empty()); DCHECK(upload_file_path_.empty()); DCHECK(upload_content_type_.empty()); // Empty |upload_content_type| is allowed iff the |upload_content| is empty. DCHECK(upload_content.empty() || !upload_content_type.empty()); upload_content_type_ = upload_content_type; upload_content_ = upload_content; upload_content_set_ = true; } void URLFetcherCore::SetUploadFilePath( const std::string& upload_content_type, const base::FilePath& file_path, uint64 range_offset, uint64 range_length, scoped_refptr file_task_runner) { DCHECK(!is_chunked_upload_); DCHECK(!upload_content_set_); DCHECK(upload_content_.empty()); DCHECK(upload_file_path_.empty()); DCHECK_EQ(upload_range_offset_, 0ULL); DCHECK_EQ(upload_range_length_, 0ULL); DCHECK(upload_content_type_.empty()); DCHECK(!upload_content_type.empty()); upload_content_type_ = upload_content_type; upload_file_path_ = file_path; upload_range_offset_ = range_offset; upload_range_length_ = range_length; upload_file_task_runner_ = file_task_runner; upload_content_set_ = true; } void URLFetcherCore::SetChunkedUpload(const std::string& content_type) { DCHECK(is_chunked_upload_ || (upload_content_type_.empty() && upload_content_.empty())); // Empty |content_type| is not allowed here, because it is impossible // to ensure non-empty upload content as it is not yet supplied. DCHECK(!content_type.empty()); upload_content_type_ = content_type; upload_content_.clear(); is_chunked_upload_ = true; } void URLFetcherCore::AppendChunkToUpload(const std::string& content, bool is_last_chunk) { DCHECK(delegate_task_runner_.get()); DCHECK(network_task_runner_.get()); network_task_runner_->PostTask( FROM_HERE, base::Bind(&URLFetcherCore::CompleteAddingUploadDataChunk, this, content, is_last_chunk)); } void URLFetcherCore::SetLoadFlags(int load_flags) { load_flags_ = load_flags; } int URLFetcherCore::GetLoadFlags() const { return load_flags_; } void URLFetcherCore::SetReferrer(const std::string& referrer) { referrer_ = referrer; } void URLFetcherCore::SetReferrerPolicy( URLRequest::ReferrerPolicy referrer_policy) { referrer_policy_ = referrer_policy; } void URLFetcherCore::SetExtraRequestHeaders( const std::string& extra_request_headers) { extra_request_headers_.Clear(); extra_request_headers_.AddHeadersFromString(extra_request_headers); } void URLFetcherCore::AddExtraRequestHeader(const std::string& header_line) { extra_request_headers_.AddHeaderFromString(header_line); } void URLFetcherCore::SetRequestContext( URLRequestContextGetter* request_context_getter) { DCHECK(!request_context_getter_.get()); DCHECK(request_context_getter); request_context_getter_ = request_context_getter; } void URLFetcherCore::SetFirstPartyForCookies( const GURL& first_party_for_cookies) { DCHECK(first_party_for_cookies_.is_empty()); first_party_for_cookies_ = first_party_for_cookies; } void URLFetcherCore::SetURLRequestUserData( const void* key, const URLFetcher::CreateDataCallback& create_data_callback) { DCHECK(key); DCHECK(!create_data_callback.is_null()); url_request_data_key_ = key; url_request_create_data_callback_ = create_data_callback; } void URLFetcherCore::SetStopOnRedirect(bool stop_on_redirect) { stop_on_redirect_ = stop_on_redirect; } void URLFetcherCore::SetAutomaticallyRetryOn5xx(bool retry) { automatically_retry_on_5xx_ = retry; } void URLFetcherCore::SetMaxRetriesOn5xx(int max_retries) { max_retries_on_5xx_ = max_retries; } int URLFetcherCore::GetMaxRetriesOn5xx() const { return max_retries_on_5xx_; } base::TimeDelta URLFetcherCore::GetBackoffDelay() const { return backoff_delay_; } void URLFetcherCore::SetAutomaticallyRetryOnNetworkChanges(int max_retries) { max_retries_on_network_changes_ = max_retries; } void URLFetcherCore::SaveResponseToFileAtPath( const base::FilePath& file_path, scoped_refptr file_task_runner) { DCHECK(delegate_task_runner_->BelongsToCurrentThread()); SaveResponseWithWriter(scoped_ptr( new URLFetcherFileWriter(file_task_runner, file_path))); } void URLFetcherCore::SaveResponseToTemporaryFile( scoped_refptr file_task_runner) { DCHECK(delegate_task_runner_->BelongsToCurrentThread()); SaveResponseWithWriter(scoped_ptr( new URLFetcherFileWriter(file_task_runner, base::FilePath()))); } void URLFetcherCore::SaveResponseWithWriter( scoped_ptr response_writer) { DCHECK(delegate_task_runner_->BelongsToCurrentThread()); response_writer_ = response_writer.Pass(); } HttpResponseHeaders* URLFetcherCore::GetResponseHeaders() const { return response_headers_.get(); } // TODO(panayiotis): socket_address_ is written in the IO thread, // if this is accessed in the UI thread, this could result in a race. // Same for response_headers_ above and was_fetched_via_proxy_ below. HostPortPair URLFetcherCore::GetSocketAddress() const { return socket_address_; } bool URLFetcherCore::WasFetchedViaProxy() const { return was_fetched_via_proxy_; } const GURL& URLFetcherCore::GetOriginalURL() const { return original_url_; } const GURL& URLFetcherCore::GetURL() const { return url_; } const URLRequestStatus& URLFetcherCore::GetStatus() const { return status_; } int URLFetcherCore::GetResponseCode() const { return response_code_; } const ResponseCookies& URLFetcherCore::GetCookies() const { return cookies_; } void URLFetcherCore::ReceivedContentWasMalformed() { DCHECK(delegate_task_runner_->BelongsToCurrentThread()); if (network_task_runner_.get()) { network_task_runner_->PostTask( FROM_HERE, base::Bind(&URLFetcherCore::NotifyMalformedContent, this)); } } bool URLFetcherCore::GetResponseAsString( std::string* out_response_string) const { URLFetcherStringWriter* string_writer = response_writer_ ? response_writer_->AsStringWriter() : NULL; if (!string_writer) return false; *out_response_string = string_writer->data(); UMA_HISTOGRAM_MEMORY_KB("UrlFetcher.StringResponseSize", (string_writer->data().length() / 1024)); return true; } bool URLFetcherCore::GetResponseAsFilePath(bool take_ownership, base::FilePath* out_response_path) { DCHECK(delegate_task_runner_->BelongsToCurrentThread()); URLFetcherFileWriter* file_writer = response_writer_ ? response_writer_->AsFileWriter() : NULL; if (!file_writer) return false; *out_response_path = file_writer->file_path(); if (take_ownership) { // Intentionally calling a file_writer_ method directly without posting // the task to network_task_runner_. // // This is for correctly handling the case when file_writer_->DisownFile() // is soon followed by URLFetcherCore::Stop(). We have to make sure that // DisownFile takes effect before Stop deletes file_writer_. // // This direct call should be thread-safe, since DisownFile itself does no // file operation. It just flips the state to be referred in destruction. file_writer->DisownFile(); } return true; } void URLFetcherCore::OnReceivedRedirect(URLRequest* request, const GURL& new_url, bool* defer_redirect) { DCHECK_EQ(request, request_.get()); DCHECK(network_task_runner_->BelongsToCurrentThread()); if (stop_on_redirect_) { stopped_on_redirect_ = true; url_ = new_url; response_code_ = request_->GetResponseCode(); was_fetched_via_proxy_ = request_->was_fetched_via_proxy(); request->Cancel(); OnReadCompleted(request, 0); } } void URLFetcherCore::OnResponseStarted(URLRequest* request) { DCHECK_EQ(request, request_.get()); DCHECK(network_task_runner_->BelongsToCurrentThread()); if (request_->status().is_success()) { response_code_ = request_->GetResponseCode(); response_headers_ = request_->response_headers(); socket_address_ = request_->GetSocketAddress(); was_fetched_via_proxy_ = request_->was_fetched_via_proxy(); total_response_bytes_ = request_->GetExpectedContentSize(); } ReadResponse(); } void URLFetcherCore::OnCertificateRequested( URLRequest* request, SSLCertRequestInfo* cert_request_info) { DCHECK_EQ(request, request_.get()); DCHECK(network_task_runner_->BelongsToCurrentThread()); if (g_ignore_certificate_requests) { request->ContinueWithCertificate(NULL); } else { request->Cancel(); } } void URLFetcherCore::OnReadCompleted(URLRequest* request, int bytes_read) { DCHECK(request == request_); DCHECK(network_task_runner_->BelongsToCurrentThread()); if (!stopped_on_redirect_) url_ = request->url(); URLRequestThrottlerManager* throttler_manager = request->context()->throttler_manager(); if (throttler_manager) { url_throttler_entry_ = throttler_manager->RegisterRequestUrl(url_); } do { if (!request_->status().is_success() || bytes_read <= 0) break; current_response_bytes_ += bytes_read; InformDelegateDownloadProgress(); const int result = WriteBuffer(new DrainableIOBuffer(buffer_.get(), bytes_read)); if (result < 0) { // Write failed or waiting for write completion. return; } } while (request_->Read(buffer_.get(), kBufferSize, &bytes_read)); const URLRequestStatus status = request_->status(); if (status.is_success()) request_->GetResponseCookies(&cookies_); // See comments re: HEAD requests in ReadResponse(). if (!status.is_io_pending() || request_type_ == URLFetcher::HEAD) { status_ = status; ReleaseRequest(); // No more data to write. const int result = response_writer_->Finish( base::Bind(&URLFetcherCore::DidFinishWriting, this)); if (result != ERR_IO_PENDING) DidFinishWriting(result); } } void URLFetcherCore::CancelAll() { g_registry.Get().CancelAll(); } int URLFetcherCore::GetNumFetcherCores() { return g_registry.Get().size(); } void URLFetcherCore::SetEnableInterceptionForTests(bool enabled) { g_interception_enabled = enabled; } void URLFetcherCore::SetIgnoreCertificateRequests(bool ignored) { g_ignore_certificate_requests = ignored; } URLFetcherCore::~URLFetcherCore() { // |request_| should be NULL. If not, it's unsafe to delete it here since we // may not be on the IO thread. DCHECK(!request_.get()); } void URLFetcherCore::StartOnIOThread() { DCHECK(network_task_runner_->BelongsToCurrentThread()); if (!response_writer_) response_writer_.reset(new URLFetcherStringWriter); const int result = response_writer_->Initialize( base::Bind(&URLFetcherCore::DidInitializeWriter, this)); if (result != ERR_IO_PENDING) DidInitializeWriter(result); } void URLFetcherCore::StartURLRequest() { DCHECK(network_task_runner_->BelongsToCurrentThread()); if (was_cancelled_) { // Since StartURLRequest() is posted as a *delayed* task, it may // run after the URLFetcher was already stopped. return; } DCHECK(request_context_getter_.get()); DCHECK(!request_.get()); g_registry.Get().AddURLFetcherCore(this); current_response_bytes_ = 0; request_ = request_context_getter_->GetURLRequestContext()->CreateRequest( original_url_, DEFAULT_PRIORITY, this, NULL); request_->set_stack_trace(stack_trace_); int flags = request_->load_flags() | load_flags_; if (!g_interception_enabled) flags = flags | LOAD_DISABLE_INTERCEPT; if (is_chunked_upload_) request_->EnableChunkedUpload(); request_->SetLoadFlags(flags); request_->SetReferrer(referrer_); request_->set_referrer_policy(referrer_policy_); request_->set_first_party_for_cookies(first_party_for_cookies_.is_empty() ? original_url_ : first_party_for_cookies_); if (url_request_data_key_ && !url_request_create_data_callback_.is_null()) { request_->SetUserData(url_request_data_key_, url_request_create_data_callback_.Run()); } switch (request_type_) { case URLFetcher::GET: break; case URLFetcher::POST: case URLFetcher::PUT: case URLFetcher::PATCH: // Upload content must be set. DCHECK(is_chunked_upload_ || upload_content_set_); request_->set_method( request_type_ == URLFetcher::POST ? "POST" : request_type_ == URLFetcher::PUT ? "PUT" : "PATCH"); if (!upload_content_type_.empty()) { extra_request_headers_.SetHeader(HttpRequestHeaders::kContentType, upload_content_type_); } if (!upload_content_.empty()) { scoped_ptr reader(new UploadBytesElementReader( upload_content_.data(), upload_content_.size())); request_->set_upload(make_scoped_ptr( UploadDataStream::CreateWithReader(reader.Pass(), 0))); } else if (!upload_file_path_.empty()) { scoped_ptr reader( new UploadFileElementReader(upload_file_task_runner_.get(), upload_file_path_, upload_range_offset_, upload_range_length_, base::Time())); request_->set_upload(make_scoped_ptr( UploadDataStream::CreateWithReader(reader.Pass(), 0))); } current_upload_bytes_ = -1; // TODO(kinaba): http://crbug.com/118103. Implement upload callback in the // layer and avoid using timer here. upload_progress_checker_timer_.reset( new base::RepeatingTimer()); upload_progress_checker_timer_->Start( FROM_HERE, base::TimeDelta::FromMilliseconds(kUploadProgressTimerInterval), this, &URLFetcherCore::InformDelegateUploadProgress); break; case URLFetcher::HEAD: request_->set_method("HEAD"); break; case URLFetcher::DELETE_REQUEST: request_->set_method("DELETE"); break; default: NOTREACHED(); } if (!extra_request_headers_.IsEmpty()) request_->SetExtraRequestHeaders(extra_request_headers_); request_->Start(); } void URLFetcherCore::DidInitializeWriter(int result) { if (result != OK) { CancelURLRequest(result); delegate_task_runner_->PostTask( FROM_HERE, base::Bind(&URLFetcherCore::InformDelegateFetchIsComplete, this)); return; } StartURLRequestWhenAppropriate(); } void URLFetcherCore::StartURLRequestWhenAppropriate() { DCHECK(network_task_runner_->BelongsToCurrentThread()); if (was_cancelled_) return; DCHECK(request_context_getter_.get()); int64 delay = 0LL; if (original_url_throttler_entry_.get() == NULL) { URLRequestThrottlerManager* manager = request_context_getter_->GetURLRequestContext()->throttler_manager(); if (manager) { original_url_throttler_entry_ = manager->RegisterRequestUrl(original_url_); } } if (original_url_throttler_entry_.get() != NULL) { delay = original_url_throttler_entry_->ReserveSendingTimeForNextRequest( GetBackoffReleaseTime()); } if (delay == 0) { StartURLRequest(); } else { base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::Bind(&URLFetcherCore::StartURLRequest, this), base::TimeDelta::FromMilliseconds(delay)); } } void URLFetcherCore::CancelURLRequest(int error) { DCHECK(network_task_runner_->BelongsToCurrentThread()); if (request_.get()) { request_->CancelWithError(error); ReleaseRequest(); } // Set the error manually. // Normally, calling URLRequest::CancelWithError() results in calling // OnReadCompleted() with bytes_read = -1 via an asynchronous task posted by // URLRequestJob::NotifyDone(). But, because the request was released // immediately after being canceled, the request could not call // OnReadCompleted() which overwrites |status_| with the error status. status_.set_status(URLRequestStatus::CANCELED); status_.set_error(error); // Release the reference to the request context. There could be multiple // references to URLFetcher::Core at this point so it may take a while to // delete the object, but we cannot delay the destruction of the request // context. request_context_getter_ = NULL; first_party_for_cookies_ = GURL(); url_request_data_key_ = NULL; url_request_create_data_callback_.Reset(); was_cancelled_ = true; } void URLFetcherCore::OnCompletedURLRequest( base::TimeDelta backoff_delay) { DCHECK(delegate_task_runner_->BelongsToCurrentThread()); // Save the status and backoff_delay so that delegates can read it. if (delegate_) { backoff_delay_ = backoff_delay; InformDelegateFetchIsComplete(); } } void URLFetcherCore::InformDelegateFetchIsComplete() { DCHECK(delegate_task_runner_->BelongsToCurrentThread()); if (delegate_) delegate_->OnURLFetchComplete(fetcher_); } void URLFetcherCore::NotifyMalformedContent() { DCHECK(network_task_runner_->BelongsToCurrentThread()); if (url_throttler_entry_.get() != NULL) { int status_code = response_code_; if (status_code == URLFetcher::RESPONSE_CODE_INVALID) { // The status code will generally be known by the time clients // call the |ReceivedContentWasMalformed()| function (which ends up // calling the current function) but if it's not, we need to assume // the response was successful so that the total failure count // used to calculate exponential back-off goes up. status_code = 200; } url_throttler_entry_->ReceivedContentWasMalformed(status_code); } } void URLFetcherCore::DidFinishWriting(int result) { if (result != OK) { CancelURLRequest(result); delegate_task_runner_->PostTask( FROM_HERE, base::Bind(&URLFetcherCore::InformDelegateFetchIsComplete, this)); return; } // If the file was successfully closed, then the URL request is complete. RetryOrCompleteUrlFetch(); } void URLFetcherCore::RetryOrCompleteUrlFetch() { DCHECK(network_task_runner_->BelongsToCurrentThread()); base::TimeDelta backoff_delay; // Checks the response from server. if (response_code_ >= 500 || status_.error() == ERR_TEMPORARILY_THROTTLED) { // When encountering a server error, we will send the request again // after backoff time. ++num_retries_on_5xx_; // Note that backoff_delay may be 0 because (a) the // URLRequestThrottlerManager and related code does not // necessarily back off on the first error, (b) it only backs off // on some of the 5xx status codes, (c) not all URLRequestContexts // have a throttler manager. base::TimeTicks backoff_release_time = GetBackoffReleaseTime(); backoff_delay = backoff_release_time - base::TimeTicks::Now(); if (backoff_delay < base::TimeDelta()) backoff_delay = base::TimeDelta(); if (automatically_retry_on_5xx_ && num_retries_on_5xx_ <= max_retries_on_5xx_) { StartOnIOThread(); return; } } else { backoff_delay = base::TimeDelta(); } // Retry if the request failed due to network changes. if (status_.error() == ERR_NETWORK_CHANGED && num_retries_on_network_changes_ < max_retries_on_network_changes_) { ++num_retries_on_network_changes_; // Retry soon, after flushing all the current tasks which may include // further network change observers. network_task_runner_->PostTask( FROM_HERE, base::Bind(&URLFetcherCore::StartOnIOThread, this)); return; } request_context_getter_ = NULL; first_party_for_cookies_ = GURL(); url_request_data_key_ = NULL; url_request_create_data_callback_.Reset(); bool posted = delegate_task_runner_->PostTask( FROM_HERE, base::Bind(&URLFetcherCore::OnCompletedURLRequest, this, backoff_delay)); // If the delegate message loop does not exist any more, then the delegate // should be gone too. DCHECK(posted || !delegate_); } void URLFetcherCore::ReleaseRequest() { upload_progress_checker_timer_.reset(); request_.reset(); g_registry.Get().RemoveURLFetcherCore(this); } base::TimeTicks URLFetcherCore::GetBackoffReleaseTime() { DCHECK(network_task_runner_->BelongsToCurrentThread()); if (original_url_throttler_entry_.get()) { base::TimeTicks original_url_backoff = original_url_throttler_entry_->GetExponentialBackoffReleaseTime(); base::TimeTicks destination_url_backoff; if (url_throttler_entry_.get() != NULL && original_url_throttler_entry_.get() != url_throttler_entry_.get()) { destination_url_backoff = url_throttler_entry_->GetExponentialBackoffReleaseTime(); } return original_url_backoff > destination_url_backoff ? original_url_backoff : destination_url_backoff; } else { return base::TimeTicks(); } } void URLFetcherCore::CompleteAddingUploadDataChunk( const std::string& content, bool is_last_chunk) { if (was_cancelled_) { // Since CompleteAddingUploadDataChunk() is posted as a *delayed* task, it // may run after the URLFetcher was already stopped. return; } DCHECK(is_chunked_upload_); DCHECK(request_.get()); DCHECK(!content.empty()); request_->AppendChunkToUpload(content.data(), static_cast(content.length()), is_last_chunk); } int URLFetcherCore::WriteBuffer(scoped_refptr data) { while (data->BytesRemaining() > 0) { const int result = response_writer_->Write( data.get(), data->BytesRemaining(), base::Bind(&URLFetcherCore::DidWriteBuffer, this, data)); if (result < 0) { if (result != ERR_IO_PENDING) DidWriteBuffer(data, result); return result; } data->DidConsume(result); } return OK; } void URLFetcherCore::DidWriteBuffer(scoped_refptr data, int result) { if (result < 0) { // Handle errors. CancelURLRequest(result); response_writer_->Finish(base::Bind(&EmptyCompletionCallback)); delegate_task_runner_->PostTask( FROM_HERE, base::Bind(&URLFetcherCore::InformDelegateFetchIsComplete, this)); return; } // Continue writing. data->DidConsume(result); if (WriteBuffer(data) < 0) return; // Finished writing buffer_. Read some more, unless the request has been // cancelled and deleted. DCHECK_EQ(0, data->BytesRemaining()); if (request_.get()) ReadResponse(); } void URLFetcherCore::ReadResponse() { // Some servers may treat HEAD requests as GET requests. To free up the // network connection as soon as possible, signal that the request has // completed immediately, without trying to read any data back (all we care // about is the response code and headers, which we already have). int bytes_read = 0; if (request_->status().is_success() && (request_type_ != URLFetcher::HEAD)) request_->Read(buffer_.get(), kBufferSize, &bytes_read); OnReadCompleted(request_.get(), bytes_read); } void URLFetcherCore::InformDelegateUploadProgress() { DCHECK(network_task_runner_->BelongsToCurrentThread()); if (request_.get()) { int64 current = request_->GetUploadProgress().position(); if (current_upload_bytes_ != current) { current_upload_bytes_ = current; int64 total = -1; if (!is_chunked_upload_) { total = static_cast(request_->GetUploadProgress().size()); // Total may be zero if the UploadDataStream::Init has not been called // yet. Don't send the upload progress until the size is initialized. if (!total) return; } delegate_task_runner_->PostTask( FROM_HERE, base::Bind( &URLFetcherCore::InformDelegateUploadProgressInDelegateThread, this, current, total)); } } } void URLFetcherCore::InformDelegateUploadProgressInDelegateThread( int64 current, int64 total) { DCHECK(delegate_task_runner_->BelongsToCurrentThread()); if (delegate_) delegate_->OnURLFetchUploadProgress(fetcher_, current, total); } void URLFetcherCore::InformDelegateDownloadProgress() { DCHECK(network_task_runner_->BelongsToCurrentThread()); delegate_task_runner_->PostTask( FROM_HERE, base::Bind( &URLFetcherCore::InformDelegateDownloadProgressInDelegateThread, this, current_response_bytes_, total_response_bytes_)); } void URLFetcherCore::InformDelegateDownloadProgressInDelegateThread( int64 current, int64 total) { DCHECK(delegate_task_runner_->BelongsToCurrentThread()); if (delegate_) delegate_->OnURLFetchDownloadProgress(fetcher_, current, total); } } // namespace net