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