Home | History | Annotate | Download | only in appcache
      1 // Copyright (c) 2012 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 "content/browser/appcache/appcache_url_request_job.h"
      6 
      7 #include <vector>
      8 
      9 #include "base/bind.h"
     10 #include "base/bind_helpers.h"
     11 #include "base/command_line.h"
     12 #include "base/compiler_specific.h"
     13 #include "base/message_loop/message_loop.h"
     14 #include "base/strings/string_util.h"
     15 #include "base/strings/stringprintf.h"
     16 #include "content/browser/appcache/appcache.h"
     17 #include "content/browser/appcache/appcache_group.h"
     18 #include "content/browser/appcache/appcache_histograms.h"
     19 #include "content/browser/appcache/appcache_host.h"
     20 #include "content/browser/appcache/appcache_service_impl.h"
     21 #include "net/base/io_buffer.h"
     22 #include "net/base/net_errors.h"
     23 #include "net/base/net_log.h"
     24 #include "net/http/http_request_headers.h"
     25 #include "net/http/http_response_headers.h"
     26 #include "net/http/http_util.h"
     27 #include "net/url_request/url_request.h"
     28 #include "net/url_request/url_request_status.h"
     29 
     30 namespace content {
     31 
     32 AppCacheURLRequestJob::AppCacheURLRequestJob(
     33     net::URLRequest* request,
     34     net::NetworkDelegate* network_delegate,
     35     AppCacheStorage* storage,
     36     AppCacheHost* host,
     37     bool is_main_resource)
     38     : net::URLRequestJob(request, network_delegate),
     39       host_(host),
     40       storage_(storage),
     41       has_been_started_(false), has_been_killed_(false),
     42       delivery_type_(AWAITING_DELIVERY_ORDERS),
     43       group_id_(0), cache_id_(kAppCacheNoCacheId), is_fallback_(false),
     44       is_main_resource_(is_main_resource),
     45       cache_entry_not_found_(false),
     46       weak_factory_(this) {
     47   DCHECK(storage_);
     48 }
     49 
     50 void AppCacheURLRequestJob::DeliverAppCachedResponse(
     51     const GURL& manifest_url, int64 group_id, int64 cache_id,
     52     const AppCacheEntry& entry, bool is_fallback) {
     53   DCHECK(!has_delivery_orders());
     54   DCHECK(entry.has_response_id());
     55   delivery_type_ = APPCACHED_DELIVERY;
     56   manifest_url_ = manifest_url;
     57   group_id_ = group_id;
     58   cache_id_ = cache_id;
     59   entry_ = entry;
     60   is_fallback_ = is_fallback;
     61   MaybeBeginDelivery();
     62 }
     63 
     64 void AppCacheURLRequestJob::DeliverNetworkResponse() {
     65   DCHECK(!has_delivery_orders());
     66   delivery_type_ = NETWORK_DELIVERY;
     67   storage_ = NULL;  // not needed
     68   MaybeBeginDelivery();
     69 }
     70 
     71 void AppCacheURLRequestJob::DeliverErrorResponse() {
     72   DCHECK(!has_delivery_orders());
     73   delivery_type_ = ERROR_DELIVERY;
     74   storage_ = NULL;  // not needed
     75   MaybeBeginDelivery();
     76 }
     77 
     78 void AppCacheURLRequestJob::MaybeBeginDelivery() {
     79   if (has_been_started() && has_delivery_orders()) {
     80     // Start asynchronously so that all error reporting and data
     81     // callbacks happen as they would for network requests.
     82     base::MessageLoop::current()->PostTask(
     83         FROM_HERE,
     84         base::Bind(&AppCacheURLRequestJob::BeginDelivery,
     85                    weak_factory_.GetWeakPtr()));
     86   }
     87 }
     88 
     89 void AppCacheURLRequestJob::BeginDelivery() {
     90   DCHECK(has_delivery_orders() && has_been_started());
     91 
     92   if (has_been_killed())
     93     return;
     94 
     95   switch (delivery_type_) {
     96     case NETWORK_DELIVERY:
     97       AppCacheHistograms::AddNetworkJobStartDelaySample(
     98           base::TimeTicks::Now() - start_time_tick_);
     99       // To fallthru to the network, we restart the request which will
    100       // cause a new job to be created to retrieve the resource from the
    101       // network. Our caller is responsible for arranging to not re-intercept
    102       // the same request.
    103       NotifyRestartRequired();
    104       break;
    105 
    106     case ERROR_DELIVERY:
    107       AppCacheHistograms::AddErrorJobStartDelaySample(
    108           base::TimeTicks::Now() - start_time_tick_);
    109       request()->net_log().AddEvent(
    110           net::NetLog::TYPE_APPCACHE_DELIVERING_ERROR_RESPONSE);
    111       NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED,
    112                                              net::ERR_FAILED));
    113       break;
    114 
    115     case APPCACHED_DELIVERY:
    116       if (entry_.IsExecutable()) {
    117         BeginExecutableHandlerDelivery();
    118         return;
    119       }
    120       AppCacheHistograms::AddAppCacheJobStartDelaySample(
    121           base::TimeTicks::Now() - start_time_tick_);
    122       request()->net_log().AddEvent(
    123           is_fallback_ ?
    124               net::NetLog::TYPE_APPCACHE_DELIVERING_FALLBACK_RESPONSE :
    125               net::NetLog::TYPE_APPCACHE_DELIVERING_CACHED_RESPONSE);
    126       storage_->LoadResponseInfo(
    127           manifest_url_, group_id_, entry_.response_id(), this);
    128       break;
    129 
    130     default:
    131       NOTREACHED();
    132       break;
    133   }
    134 }
    135 
    136 void AppCacheURLRequestJob::BeginExecutableHandlerDelivery() {
    137   DCHECK(CommandLine::ForCurrentProcess()->
    138             HasSwitch(kEnableExecutableHandlers));
    139   if (!storage_->service()->handler_factory()) {
    140     BeginErrorDelivery("missing handler factory");
    141     return;
    142   }
    143 
    144   request()->net_log().AddEvent(
    145       net::NetLog::TYPE_APPCACHE_DELIVERING_EXECUTABLE_RESPONSE);
    146 
    147   // We defer job delivery until the executable handler is spun up and
    148   // provides a response. The sequence goes like this...
    149   //
    150   // 1. First we load the cache.
    151   // 2. Then if the handler is not spun up, we load the script resource which
    152   //    is needed to spin it up.
    153   // 3. Then we ask then we ask the handler to compute a response.
    154   // 4. Finally we deilver that response to the caller.
    155   storage_->LoadCache(cache_id_, this);
    156 }
    157 
    158 void AppCacheURLRequestJob::OnCacheLoaded(AppCache* cache, int64 cache_id) {
    159   DCHECK_EQ(cache_id_, cache_id);
    160   DCHECK(!has_been_killed());
    161 
    162   if (!cache) {
    163     BeginErrorDelivery("cache load failed");
    164     return;
    165   }
    166 
    167   // Keep references to ensure they don't go out of scope until job completion.
    168   cache_ = cache;
    169   group_ = cache->owning_group();
    170 
    171   // If the handler is spun up, ask it to compute a response.
    172   AppCacheExecutableHandler* handler =
    173       cache->GetExecutableHandler(entry_.response_id());
    174   if (handler) {
    175     InvokeExecutableHandler(handler);
    176     return;
    177   }
    178 
    179   // Handler is not spun up yet, load the script resource to do that.
    180   // NOTE: This is not ideal since multiple jobs may be doing this,
    181   // concurrently but close enough for now, the first to load the script
    182   // will win.
    183 
    184   // Read the script data, truncating if its too large.
    185   // NOTE: we just issue one read and don't bother chaining if the resource
    186   // is very (very) large, close enough for now.
    187   const int64 kLimit = 500 * 1000;
    188   handler_source_buffer_ = new net::GrowableIOBuffer();
    189   handler_source_buffer_->SetCapacity(kLimit);
    190   handler_source_reader_.reset(storage_->CreateResponseReader(
    191       manifest_url_, group_id_, entry_.response_id()));
    192   handler_source_reader_->ReadData(
    193       handler_source_buffer_.get(),
    194       kLimit,
    195       base::Bind(&AppCacheURLRequestJob::OnExecutableSourceLoaded,
    196                  base::Unretained(this)));
    197 }
    198 
    199 void AppCacheURLRequestJob::OnExecutableSourceLoaded(int result) {
    200   DCHECK(!has_been_killed());
    201   handler_source_reader_.reset();
    202   if (result < 0) {
    203     BeginErrorDelivery("script source load failed");
    204     return;
    205   }
    206 
    207   handler_source_buffer_->SetCapacity(result);  // Free up some memory.
    208 
    209   AppCacheExecutableHandler* handler = cache_->GetOrCreateExecutableHandler(
    210       entry_.response_id(), handler_source_buffer_.get());
    211   handler_source_buffer_ = NULL;  // not needed anymore
    212   if (handler) {
    213     InvokeExecutableHandler(handler);
    214     return;
    215   }
    216 
    217   BeginErrorDelivery("factory failed to produce a handler");
    218 }
    219 
    220 void AppCacheURLRequestJob::InvokeExecutableHandler(
    221     AppCacheExecutableHandler* handler) {
    222   handler->HandleRequest(
    223       request(),
    224       base::Bind(&AppCacheURLRequestJob::OnExecutableResponseCallback,
    225                  weak_factory_.GetWeakPtr()));
    226 }
    227 
    228 void AppCacheURLRequestJob::OnExecutableResponseCallback(
    229     const AppCacheExecutableHandler::Response& response) {
    230   DCHECK(!has_been_killed());
    231   if (response.use_network) {
    232     delivery_type_ = NETWORK_DELIVERY;
    233     storage_ = NULL;
    234     BeginDelivery();
    235     return;
    236   }
    237 
    238   if (!response.cached_resource_url.is_empty()) {
    239     AppCacheEntry* entry_ptr = cache_->GetEntry(response.cached_resource_url);
    240     if (entry_ptr && !entry_ptr->IsExecutable()) {
    241       entry_ = *entry_ptr;
    242       BeginDelivery();
    243       return;
    244     }
    245   }
    246 
    247   if (!response.redirect_url.is_empty()) {
    248     // TODO(michaeln): playback a redirect
    249     // response_headers_(new HttpResponseHeaders(response_headers)),
    250     // fallthru for now to deliver an error
    251   }
    252 
    253   // Otherwise, return an error.
    254   BeginErrorDelivery("handler returned an invalid response");
    255 }
    256 
    257 void AppCacheURLRequestJob::BeginErrorDelivery(const char* message) {
    258   if (host_)
    259     host_->frontend()->OnLogMessage(host_->host_id(), APPCACHE_LOG_ERROR,
    260                                     message);
    261   delivery_type_ = ERROR_DELIVERY;
    262   storage_ = NULL;
    263   BeginDelivery();
    264 }
    265 
    266 AppCacheURLRequestJob::~AppCacheURLRequestJob() {
    267   if (storage_)
    268     storage_->CancelDelegateCallbacks(this);
    269 }
    270 
    271 void AppCacheURLRequestJob::OnResponseInfoLoaded(
    272       AppCacheResponseInfo* response_info, int64 response_id) {
    273   DCHECK(is_delivering_appcache_response());
    274   scoped_refptr<AppCacheURLRequestJob> protect(this);
    275   if (response_info) {
    276     info_ = response_info;
    277     reader_.reset(storage_->CreateResponseReader(
    278         manifest_url_, group_id_, entry_.response_id()));
    279 
    280     if (is_range_request())
    281       SetupRangeResponse();
    282 
    283     NotifyHeadersComplete();
    284   } else {
    285     if (storage_->service()->storage() == storage_) {
    286       // A resource that is expected to be in the appcache is missing.
    287       // See http://code.google.com/p/chromium/issues/detail?id=50657
    288       // Instead of failing the request, we restart the request. The retry
    289       // attempt will fallthru to the network instead of trying to load
    290       // from the appcache.
    291       storage_->service()->CheckAppCacheResponse(manifest_url_, cache_id_,
    292                                                  entry_.response_id());
    293       AppCacheHistograms::CountResponseRetrieval(
    294           false, is_main_resource_, manifest_url_.GetOrigin());
    295     }
    296     cache_entry_not_found_ = true;
    297     NotifyRestartRequired();
    298   }
    299 }
    300 
    301 const net::HttpResponseInfo* AppCacheURLRequestJob::http_info() const {
    302   if (!info_.get())
    303     return NULL;
    304   if (range_response_info_)
    305     return range_response_info_.get();
    306   return info_->http_response_info();
    307 }
    308 
    309 void AppCacheURLRequestJob::SetupRangeResponse() {
    310   DCHECK(is_range_request() && info_.get() && reader_.get() &&
    311          is_delivering_appcache_response());
    312   int resource_size = static_cast<int>(info_->response_data_size());
    313   if (resource_size < 0 || !range_requested_.ComputeBounds(resource_size)) {
    314     range_requested_ = net::HttpByteRange();
    315     return;
    316   }
    317 
    318   DCHECK(range_requested_.IsValid());
    319   int offset = static_cast<int>(range_requested_.first_byte_position());
    320   int length = static_cast<int>(range_requested_.last_byte_position() -
    321                                 range_requested_.first_byte_position() + 1);
    322 
    323   // Tell the reader about the range to read.
    324   reader_->SetReadRange(offset, length);
    325 
    326   // Make a copy of the full response headers and fix them up
    327   // for the range we'll be returning.
    328   range_response_info_.reset(
    329       new net::HttpResponseInfo(*info_->http_response_info()));
    330   net::HttpResponseHeaders* headers = range_response_info_->headers.get();
    331   headers->UpdateWithNewRange(
    332       range_requested_, resource_size, true /* replace status line */);
    333 }
    334 
    335 void AppCacheURLRequestJob::OnReadComplete(int result) {
    336   DCHECK(is_delivering_appcache_response());
    337   if (result == 0) {
    338     NotifyDone(net::URLRequestStatus());
    339     AppCacheHistograms::CountResponseRetrieval(
    340         true, is_main_resource_, manifest_url_.GetOrigin());
    341   } else if (result < 0) {
    342     if (storage_->service()->storage() == storage_) {
    343       storage_->service()->CheckAppCacheResponse(manifest_url_, cache_id_,
    344                                                  entry_.response_id());
    345     }
    346     NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result));
    347     AppCacheHistograms::CountResponseRetrieval(
    348         false, is_main_resource_, manifest_url_.GetOrigin());
    349   } else {
    350     SetStatus(net::URLRequestStatus());  // Clear the IO_PENDING status
    351   }
    352   NotifyReadComplete(result);
    353 }
    354 
    355 // net::URLRequestJob overrides ------------------------------------------------
    356 
    357 void AppCacheURLRequestJob::Start() {
    358   DCHECK(!has_been_started());
    359   has_been_started_ = true;
    360   start_time_tick_ = base::TimeTicks::Now();
    361   MaybeBeginDelivery();
    362 }
    363 
    364 void AppCacheURLRequestJob::Kill() {
    365   if (!has_been_killed_) {
    366     has_been_killed_ = true;
    367     reader_.reset();
    368     handler_source_reader_.reset();
    369     if (storage_) {
    370       storage_->CancelDelegateCallbacks(this);
    371       storage_ = NULL;
    372     }
    373     host_ = NULL;
    374     info_ = NULL;
    375     cache_ = NULL;
    376     group_ = NULL;
    377     range_response_info_.reset();
    378     net::URLRequestJob::Kill();
    379     weak_factory_.InvalidateWeakPtrs();
    380   }
    381 }
    382 
    383 net::LoadState AppCacheURLRequestJob::GetLoadState() const {
    384   if (!has_been_started())
    385     return net::LOAD_STATE_IDLE;
    386   if (!has_delivery_orders())
    387     return net::LOAD_STATE_WAITING_FOR_APPCACHE;
    388   if (delivery_type_ != APPCACHED_DELIVERY)
    389     return net::LOAD_STATE_IDLE;
    390   if (!info_.get())
    391     return net::LOAD_STATE_WAITING_FOR_APPCACHE;
    392   if (reader_.get() && reader_->IsReadPending())
    393     return net::LOAD_STATE_READING_RESPONSE;
    394   return net::LOAD_STATE_IDLE;
    395 }
    396 
    397 bool AppCacheURLRequestJob::GetMimeType(std::string* mime_type) const {
    398   if (!http_info())
    399     return false;
    400   return http_info()->headers->GetMimeType(mime_type);
    401 }
    402 
    403 bool AppCacheURLRequestJob::GetCharset(std::string* charset) {
    404   if (!http_info())
    405     return false;
    406   return http_info()->headers->GetCharset(charset);
    407 }
    408 
    409 void AppCacheURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
    410   if (!http_info())
    411     return;
    412   *info = *http_info();
    413 }
    414 
    415 int AppCacheURLRequestJob::GetResponseCode() const {
    416   if (!http_info())
    417     return -1;
    418   return http_info()->headers->response_code();
    419 }
    420 
    421 bool AppCacheURLRequestJob::ReadRawData(net::IOBuffer* buf, int buf_size,
    422                                         int *bytes_read) {
    423   DCHECK(is_delivering_appcache_response());
    424   DCHECK_NE(buf_size, 0);
    425   DCHECK(bytes_read);
    426   DCHECK(!reader_->IsReadPending());
    427   reader_->ReadData(
    428       buf, buf_size, base::Bind(&AppCacheURLRequestJob::OnReadComplete,
    429                                 base::Unretained(this)));
    430   SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
    431   return false;
    432 }
    433 
    434 void AppCacheURLRequestJob::SetExtraRequestHeaders(
    435     const net::HttpRequestHeaders& headers) {
    436   std::string value;
    437   std::vector<net::HttpByteRange> ranges;
    438   if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &value) ||
    439       !net::HttpUtil::ParseRangeHeader(value, &ranges)) {
    440     return;
    441   }
    442 
    443   // If multiple ranges are requested, we play dumb and
    444   // return the entire response with 200 OK.
    445   if (ranges.size() == 1U)
    446     range_requested_ = ranges[0];
    447 }
    448 
    449 }  // namespace content
    450