Home | History | Annotate | Download | only in predictors
      1 // Copyright 2014 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 "chrome/browser/predictors/resource_prefetcher.h"
      6 
      7 #include <iterator>
      8 
      9 #include "base/stl_util.h"
     10 #include "content/public/browser/browser_thread.h"
     11 #include "net/base/io_buffer.h"
     12 #include "net/base/request_priority.h"
     13 #include "net/url_request/url_request_context.h"
     14 
     15 namespace {
     16 
     17 // The size of the buffer used to read the resource.
     18 static const size_t kResourceBufferSizeBytes = 50000;
     19 
     20 }  // namespace
     21 
     22 namespace predictors {
     23 
     24 ResourcePrefetcher::Request::Request(const GURL& i_resource_url)
     25     : resource_url(i_resource_url),
     26       prefetch_status(PREFETCH_STATUS_NOT_STARTED),
     27       usage_status(USAGE_STATUS_NOT_REQUESTED) {
     28 }
     29 
     30 ResourcePrefetcher::Request::Request(const Request& other)
     31     : resource_url(other.resource_url),
     32       prefetch_status(other.prefetch_status),
     33       usage_status(other.usage_status) {
     34 }
     35 
     36 ResourcePrefetcher::ResourcePrefetcher(
     37     Delegate* delegate,
     38     const ResourcePrefetchPredictorConfig& config,
     39     const NavigationID& navigation_id,
     40     PrefetchKeyType key_type,
     41     scoped_ptr<RequestVector> requests)
     42         : state_(INITIALIZED),
     43           delegate_(delegate),
     44           config_(config),
     45           navigation_id_(navigation_id),
     46           key_type_(key_type),
     47           request_vector_(requests.Pass()) {
     48   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
     49   DCHECK(request_vector_.get());
     50 
     51   std::copy(request_vector_->begin(), request_vector_->end(),
     52             std::back_inserter(request_queue_));
     53 }
     54 
     55 ResourcePrefetcher::~ResourcePrefetcher() {
     56   // Delete any pending net::URLRequests.
     57   STLDeleteContainerPairFirstPointers(inflight_requests_.begin(),
     58                                       inflight_requests_.end());
     59 }
     60 
     61 void ResourcePrefetcher::Start() {
     62   DCHECK(thread_checker_.CalledOnValidThread());
     63 
     64   CHECK_EQ(state_, INITIALIZED);
     65   state_ = RUNNING;
     66 
     67   TryToLaunchPrefetchRequests();
     68 }
     69 
     70 void ResourcePrefetcher::Stop() {
     71   DCHECK(thread_checker_.CalledOnValidThread());
     72 
     73   if (state_ == FINISHED)
     74     return;
     75 
     76   state_ = STOPPED;
     77 }
     78 
     79 void ResourcePrefetcher::TryToLaunchPrefetchRequests() {
     80   CHECK(state_ == RUNNING || state_ == STOPPED);
     81 
     82   // Try to launch new requests if the state is RUNNING.
     83   if (state_ == RUNNING) {
     84     bool request_available = true;
     85 
     86     // Loop through the requests while we are under the
     87     // max_prefetches_inflight_per_host_per_navigation limit, looking for a URL
     88     // for which the max_prefetches_inflight_per_host_per_navigation limit has
     89     // not been reached. Try to launch as many requests as possible.
     90     while ((inflight_requests_.size() <
     91                 config_.max_prefetches_inflight_per_navigation) &&
     92            request_available) {
     93       std::list<Request*>::iterator request_it = request_queue_.begin();
     94       for (; request_it != request_queue_.end(); ++request_it) {
     95         const std::string& host = (*request_it)->resource_url.host();
     96 
     97         std::map<std::string, size_t>::iterator host_it =
     98             host_inflight_counts_.find(host);
     99         if (host_it == host_inflight_counts_.end() ||
    100             host_it->second <
    101                 config_.max_prefetches_inflight_per_host_per_navigation)
    102           break;
    103       }
    104       request_available = request_it != request_queue_.end();
    105 
    106       if (request_available) {
    107         SendRequest(*request_it);
    108         request_queue_.erase(request_it);
    109       }
    110     }
    111   }
    112 
    113   // If the inflight_requests_ is empty, we cant launch any more. Finish.
    114   if (inflight_requests_.empty()) {
    115     CHECK(host_inflight_counts_.empty());
    116     CHECK(request_queue_.empty() || state_ == STOPPED);
    117 
    118     state_ = FINISHED;
    119     delegate_->ResourcePrefetcherFinished(this, request_vector_.release());
    120   }
    121 }
    122 
    123 void ResourcePrefetcher::SendRequest(Request* request) {
    124   request->prefetch_status = Request::PREFETCH_STATUS_STARTED;
    125 
    126   net::URLRequest* url_request =
    127       delegate_->GetURLRequestContext()->CreateRequest(
    128           request->resource_url, net::LOW, this, NULL).release();
    129 
    130   inflight_requests_[url_request] = request;
    131   host_inflight_counts_[url_request->original_url().host()] += 1;
    132 
    133   url_request->set_method("GET");
    134   url_request->set_first_party_for_cookies(navigation_id_.main_frame_url);
    135   url_request->SetReferrer(navigation_id_.main_frame_url.spec());
    136   StartURLRequest(url_request);
    137 }
    138 
    139 void ResourcePrefetcher::StartURLRequest(net::URLRequest* request) {
    140   request->Start();
    141 }
    142 
    143 void ResourcePrefetcher::FinishRequest(net::URLRequest* request,
    144                                        Request::PrefetchStatus status) {
    145   std::map<net::URLRequest*, Request*>::iterator request_it =
    146       inflight_requests_.find(request);
    147   CHECK(request_it != inflight_requests_.end());
    148 
    149   const std::string host = request->original_url().host();
    150   std::map<std::string, size_t>::iterator host_it = host_inflight_counts_.find(
    151       host);
    152   CHECK_GT(host_it->second, 0U);
    153   host_it->second -= 1;
    154   if (host_it->second == 0)
    155     host_inflight_counts_.erase(host);
    156 
    157   request_it->second->prefetch_status = status;
    158   inflight_requests_.erase(request_it);
    159 
    160   delete request;
    161 
    162   TryToLaunchPrefetchRequests();
    163 }
    164 
    165 void ResourcePrefetcher::ReadFullResponse(net::URLRequest* request) {
    166   bool status = true;
    167   while (status) {
    168     int bytes_read = 0;
    169     scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(
    170         kResourceBufferSizeBytes));
    171     status = request->Read(buffer.get(), kResourceBufferSizeBytes, &bytes_read);
    172 
    173     if (status) {
    174       status = ShouldContinueReadingRequest(request, bytes_read);
    175     } else if (request->status().error()) {
    176       FinishRequest(request, Request::PREFETCH_STATUS_FAILED);
    177       return;
    178     }
    179   }
    180 }
    181 
    182 bool ResourcePrefetcher::ShouldContinueReadingRequest(net::URLRequest* request,
    183                                                       int bytes_read) {
    184   if (bytes_read == 0) {  // When bytes_read == 0, no more data.
    185     if (request->was_cached())
    186       FinishRequest(request, Request::PREFETCH_STATUS_FROM_CACHE);
    187     else
    188       FinishRequest(request, Request::PREFETCH_STATUS_FROM_NETWORK);
    189     return false;
    190   }
    191 
    192   return true;
    193 }
    194 
    195 void ResourcePrefetcher::OnReceivedRedirect(
    196     net::URLRequest* request,
    197     const net::RedirectInfo& redirect_info,
    198     bool* defer_redirect) {
    199   FinishRequest(request, Request::PREFETCH_STATUS_REDIRECTED);
    200 }
    201 
    202 void ResourcePrefetcher::OnAuthRequired(net::URLRequest* request,
    203                                         net::AuthChallengeInfo* auth_info) {
    204   FinishRequest(request, Request::PREFETCH_STATUS_AUTH_REQUIRED);
    205 }
    206 
    207 void ResourcePrefetcher::OnCertificateRequested(
    208     net::URLRequest* request,
    209     net::SSLCertRequestInfo* cert_request_info) {
    210   FinishRequest(request, Request::PREFETCH_STATUS_CERT_REQUIRED);
    211 }
    212 
    213 void ResourcePrefetcher::OnSSLCertificateError(net::URLRequest* request,
    214                                                const net::SSLInfo& ssl_info,
    215                                                bool fatal) {
    216   FinishRequest(request, Request::PREFETCH_STATUS_CERT_ERROR);
    217 }
    218 
    219 void ResourcePrefetcher::OnResponseStarted(net::URLRequest* request) {
    220   if (request->status().error()) {
    221     FinishRequest(request, Request::PREFETCH_STATUS_FAILED);
    222     return;
    223   }
    224 
    225   // TODO(shishir): Do not read cached entries, or ones that are not cacheable.
    226   ReadFullResponse(request);
    227 }
    228 
    229 void ResourcePrefetcher::OnReadCompleted(net::URLRequest* request,
    230                                          int bytes_read) {
    231   if (request->status().error()) {
    232     FinishRequest(request, Request::PREFETCH_STATUS_FAILED);
    233     return;
    234   }
    235 
    236   if (ShouldContinueReadingRequest(request, bytes_read))
    237     ReadFullResponse(request);
    238 }
    239 
    240 }  // namespace predictors
    241