// Copyright (c) 2009 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 "chrome/browser/net/url_fetcher.h" #include "base/compiler_specific.h" #include "base/string_util.h" #include "base/thread.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chrome_thread.h" #include "chrome/browser/net/url_request_context_getter.h" #include "googleurl/src/gurl.h" #include "net/base/load_flags.h" #include "net/base/io_buffer.h" #include "net/http/http_response_headers.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_context.h" static const int kBufferSize = 4096; bool URLFetcher::g_interception_enabled = false; class URLFetcher::Core : public base::RefCountedThreadSafe, public URLRequest::Delegate { public: // For POST requests, set |content_type| to the MIME type of the content // and set |content| to the data to upload. |flags| are flags to apply to // the load operation--these should be one or more of the LOAD_* flags // defined in url_request.h. Core(URLFetcher* fetcher, const GURL& original_url, RequestType request_type, URLFetcher::Delegate* d); // Starts the load. It's important that this not happen in the constructor // because it causes the IO thread to begin AddRef()ing and Release()ing // us. If our caller hasn't had time to fully construct us and take a // reference, the IO thread could interrupt things, run a task, Release() // us, and destroy us, leaving the caller with an already-destroyed object // when construction finishes. void Start(); // Stops any in-progress load and ensures no callback will happen. It is // safe to call this multiple times. void Stop(); // URLRequest::Delegate implementations virtual void OnResponseStarted(URLRequest* request); virtual void OnReadCompleted(URLRequest* request, int bytes_read); URLFetcher::Delegate* delegate() const { return delegate_; } private: friend class base::RefCountedThreadSafe; ~Core() {} // Wrapper functions that allow us to ensure actions happen on the right // thread. void StartURLRequest(); void CancelURLRequest(); void OnCompletedURLRequest(const URLRequestStatus& status); URLFetcher* fetcher_; // Corresponding fetcher object GURL original_url_; // The URL we were asked to fetch GURL url_; // The URL we eventually wound up at RequestType request_type_; // What type of request is this? URLFetcher::Delegate* delegate_; // Object to notify on completion MessageLoop* delegate_loop_; // Message loop of the creating thread URLRequest* request_; // The actual request this wraps int load_flags_; // Flags for the load operation int response_code_; // HTTP status code for the request std::string data_; // Results of the request scoped_refptr buffer_; // Read buffer scoped_refptr request_context_getter_; // Cookie/cache info for the request ResponseCookies cookies_; // Response cookies std::string extra_request_headers_;// Extra headers for the request, if any scoped_refptr response_headers_; std::string upload_content_; // HTTP POST payload std::string upload_content_type_; // MIME type of POST payload // The overload protection entry for this URL. This is used to // incrementally back off how rapidly we'll send requests to a particular // URL, to avoid placing too much demand on the remote resource. We update // this with the status of all requests as they return, and in turn use it // to determine how long to wait before making another request. URLFetcherProtectEntry* protect_entry_; // |num_retries_| indicates how many times we've failed to successfully // fetch this URL. Once this value exceeds the maximum number of retries // specified by the protection manager, we'll give up. int num_retries_; // Temporary member variable to test whether requests are being started // after they have already been cancelled. // TODO(eroman): Remove this after done investigating 27074. bool was_cancelled_; friend class URLFetcher; DISALLOW_COPY_AND_ASSIGN(Core); }; // static URLFetcher::Factory* URLFetcher::factory_ = NULL; URLFetcher::URLFetcher(const GURL& url, RequestType request_type, Delegate* d) : ALLOW_THIS_IN_INITIALIZER_LIST( core_(new Core(this, url, request_type, d))) { } URLFetcher::~URLFetcher() { core_->Stop(); } // static URLFetcher* URLFetcher::Create(int id, const GURL& url, RequestType request_type, Delegate* d) { return factory_ ? factory_->CreateURLFetcher(id, url, request_type, d) : new URLFetcher(url, request_type, d); } URLFetcher::Core::Core(URLFetcher* fetcher, const GURL& original_url, RequestType request_type, URLFetcher::Delegate* d) : fetcher_(fetcher), original_url_(original_url), request_type_(request_type), delegate_(d), delegate_loop_(MessageLoop::current()), request_(NULL), load_flags_(net::LOAD_NORMAL), response_code_(-1), buffer_(new net::IOBuffer(kBufferSize)), protect_entry_(URLFetcherProtectManager::GetInstance()->Register( original_url_.host())), num_retries_(0), was_cancelled_(false) { } void URLFetcher::Core::Start() { DCHECK(delegate_loop_); CHECK(request_context_getter_) << "We need an URLRequestContext!"; ChromeThread::PostDelayedTask( ChromeThread::IO, FROM_HERE, NewRunnableMethod(this, &Core::StartURLRequest), protect_entry_->UpdateBackoff(URLFetcherProtectEntry::SEND)); } void URLFetcher::Core::Stop() { DCHECK_EQ(MessageLoop::current(), delegate_loop_); delegate_ = NULL; ChromeThread::PostTask( ChromeThread::IO, FROM_HERE, NewRunnableMethod(this, &Core::CancelURLRequest)); } void URLFetcher::Core::OnResponseStarted(URLRequest* request) { DCHECK(request == request_); DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); if (request_->status().is_success()) { response_code_ = request_->GetResponseCode(); response_headers_ = request_->response_headers(); } int bytes_read = 0; // 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). if (request_->status().is_success() && (request_type_ != HEAD)) request_->Read(buffer_, kBufferSize, &bytes_read); OnReadCompleted(request_, bytes_read); } void URLFetcher::Core::OnReadCompleted(URLRequest* request, int bytes_read) { DCHECK(request == request_); DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); url_ = request->url(); do { if (!request_->status().is_success() || bytes_read <= 0) break; data_.append(buffer_->data(), bytes_read); } while (request_->Read(buffer_, kBufferSize, &bytes_read)); if (request_->status().is_success()) request_->GetResponseCookies(&cookies_); // See comments re: HEAD requests in OnResponseStarted(). if (!request_->status().is_io_pending() || (request_type_ == HEAD)) { delegate_loop_->PostTask(FROM_HERE, NewRunnableMethod( this, &Core::OnCompletedURLRequest, request_->status())); delete request_; request_ = NULL; } } void URLFetcher::Core::StartURLRequest() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); CHECK(!was_cancelled_); CHECK(request_context_getter_); CHECK(!request_); request_ = new URLRequest(original_url_, this); int flags = request_->load_flags() | load_flags_; if (!g_interception_enabled) { flags = flags | net::LOAD_DISABLE_INTERCEPT; } request_->set_load_flags(flags); request_->set_context(request_context_getter_->GetURLRequestContext()); switch (request_type_) { case GET: break; case POST: DCHECK(!upload_content_.empty()); DCHECK(!upload_content_type_.empty()); request_->set_method("POST"); if (!extra_request_headers_.empty()) extra_request_headers_ += "\r\n"; StringAppendF(&extra_request_headers_, "Content-Type: %s", upload_content_type_.c_str()); request_->AppendBytesToUpload(upload_content_.data(), static_cast(upload_content_.size())); break; case HEAD: request_->set_method("HEAD"); break; default: NOTREACHED(); } if (!extra_request_headers_.empty()) request_->SetExtraRequestHeaders(extra_request_headers_); request_->Start(); } void URLFetcher::Core::CancelURLRequest() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); if (request_) { request_->Cancel(); delete request_; request_ = NULL; } // 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; was_cancelled_ = true; } void URLFetcher::Core::OnCompletedURLRequest(const URLRequestStatus& status) { DCHECK(MessageLoop::current() == delegate_loop_); // Checks the response from server. if (response_code_ >= 500) { // When encountering a server error, we will send the request again // after backoff time. const int64 wait = protect_entry_->UpdateBackoff(URLFetcherProtectEntry::FAILURE); ++num_retries_; // Restarts the request if we still need to notify the delegate. if (delegate_) { if (num_retries_ <= protect_entry_->max_retries()) { ChromeThread::PostDelayedTask( ChromeThread::IO, FROM_HERE, NewRunnableMethod(this, &Core::StartURLRequest), wait); } else { delegate_->OnURLFetchComplete(fetcher_, url_, status, response_code_, cookies_, data_); } } } else { protect_entry_->UpdateBackoff(URLFetcherProtectEntry::SUCCESS); if (delegate_) delegate_->OnURLFetchComplete(fetcher_, url_, status, response_code_, cookies_, data_); } } void URLFetcher::set_upload_data(const std::string& upload_content_type, const std::string& upload_content) { core_->upload_content_type_ = upload_content_type; core_->upload_content_ = upload_content; } void URLFetcher::set_load_flags(int load_flags) { core_->load_flags_ = load_flags; } int URLFetcher::load_flags() const { return core_->load_flags_; } void URLFetcher::set_extra_request_headers( const std::string& extra_request_headers) { core_->extra_request_headers_ = extra_request_headers; } void URLFetcher::set_request_context( URLRequestContextGetter* request_context_getter) { core_->request_context_getter_ = request_context_getter; } net::HttpResponseHeaders* URLFetcher::response_headers() const { return core_->response_headers_; } void URLFetcher::Start() { core_->Start(); } const GURL& URLFetcher::url() const { return core_->url_; } URLFetcher::Delegate* URLFetcher::delegate() const { return core_->delegate(); }