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     // A resource that is expected to be in the appcache is missing.
    283     // See http://code.google.com/p/chromium/issues/detail?id=50657
    284     // Instead of failing the request, we restart the request. The retry
    285     // attempt will fallthru to the network instead of trying to load
    286     // from the appcache.
    287     storage_->service()->CheckAppCacheResponse(manifest_url_, cache_id_,
    288                                                entry_.response_id());
    289     cache_entry_not_found_ = true;
    290     NotifyRestartRequired();
    291   }
    292 }
    293 
    294 const net::HttpResponseInfo* AppCacheURLRequestJob::http_info() const {
    295   if (!info_.get())
    296     return NULL;
    297   if (range_response_info_)
    298     return range_response_info_.get();
    299   return info_->http_response_info();
    300 }
    301 
    302 void AppCacheURLRequestJob::SetupRangeResponse() {
    303   DCHECK(is_range_request() && info_.get() && reader_.get() &&
    304          is_delivering_appcache_response());
    305   int resource_size = static_cast<int>(info_->response_data_size());
    306   if (resource_size < 0 || !range_requested_.ComputeBounds(resource_size)) {
    307     range_requested_ = net::HttpByteRange();
    308     return;
    309   }
    310 
    311   DCHECK(range_requested_.HasFirstBytePosition() &&
    312          range_requested_.HasLastBytePosition());
    313   int offset = static_cast<int>(range_requested_.first_byte_position());
    314   int length = static_cast<int>(range_requested_.last_byte_position() -
    315                                 range_requested_.first_byte_position() + 1);
    316 
    317   // Tell the reader about the range to read.
    318   reader_->SetReadRange(offset, length);
    319 
    320   // Make a copy of the full response headers and fix them up
    321   // for the range we'll be returning.
    322   const char kLengthHeader[] = "Content-Length";
    323   const char kRangeHeader[] = "Content-Range";
    324   const char kPartialStatusLine[] = "HTTP/1.1 206 Partial Content";
    325   range_response_info_.reset(
    326       new net::HttpResponseInfo(*info_->http_response_info()));
    327   net::HttpResponseHeaders* headers = range_response_info_->headers.get();
    328   headers->RemoveHeader(kLengthHeader);
    329   headers->RemoveHeader(kRangeHeader);
    330   headers->ReplaceStatusLine(kPartialStatusLine);
    331   headers->AddHeader(
    332       base::StringPrintf("%s: %d", kLengthHeader, length));
    333   headers->AddHeader(
    334       base::StringPrintf("%s: bytes %d-%d/%d",
    335                          kRangeHeader,
    336                          offset,
    337                          offset + length - 1,
    338                          resource_size));
    339 }
    340 
    341 void AppCacheURLRequestJob::OnReadComplete(int result) {
    342   DCHECK(is_delivering_appcache_response());
    343   if (result == 0) {
    344     NotifyDone(net::URLRequestStatus());
    345   } else if (result < 0) {
    346     storage_->service()->CheckAppCacheResponse(manifest_url_, cache_id_,
    347                                                entry_.response_id());
    348     NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result));
    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     net::URLRequestJob::Kill();
    375     weak_factory_.InvalidateWeakPtrs();
    376   }
    377 }
    378 
    379 net::LoadState AppCacheURLRequestJob::GetLoadState() const {
    380   if (!has_been_started())
    381     return net::LOAD_STATE_IDLE;
    382   if (!has_delivery_orders())
    383     return net::LOAD_STATE_WAITING_FOR_APPCACHE;
    384   if (delivery_type_ != APPCACHED_DELIVERY)
    385     return net::LOAD_STATE_IDLE;
    386   if (!info_.get())
    387     return net::LOAD_STATE_WAITING_FOR_APPCACHE;
    388   if (reader_.get() && reader_->IsReadPending())
    389     return net::LOAD_STATE_READING_RESPONSE;
    390   return net::LOAD_STATE_IDLE;
    391 }
    392 
    393 bool AppCacheURLRequestJob::GetMimeType(std::string* mime_type) const {
    394   if (!http_info())
    395     return false;
    396   return http_info()->headers->GetMimeType(mime_type);
    397 }
    398 
    399 bool AppCacheURLRequestJob::GetCharset(std::string* charset) {
    400   if (!http_info())
    401     return false;
    402   return http_info()->headers->GetCharset(charset);
    403 }
    404 
    405 void AppCacheURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
    406   if (!http_info())
    407     return;
    408   *info = *http_info();
    409 }
    410 
    411 int AppCacheURLRequestJob::GetResponseCode() const {
    412   if (!http_info())
    413     return -1;
    414   return http_info()->headers->response_code();
    415 }
    416 
    417 bool AppCacheURLRequestJob::ReadRawData(net::IOBuffer* buf, int buf_size,
    418                                         int *bytes_read) {
    419   DCHECK(is_delivering_appcache_response());
    420   DCHECK_NE(buf_size, 0);
    421   DCHECK(bytes_read);
    422   DCHECK(!reader_->IsReadPending());
    423   reader_->ReadData(
    424       buf, buf_size, base::Bind(&AppCacheURLRequestJob::OnReadComplete,
    425                                 base::Unretained(this)));
    426   SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
    427   return false;
    428 }
    429 
    430 void AppCacheURLRequestJob::SetExtraRequestHeaders(
    431     const net::HttpRequestHeaders& headers) {
    432   std::string value;
    433   std::vector<net::HttpByteRange> ranges;
    434   if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &value) ||
    435       !net::HttpUtil::ParseRangeHeader(value, &ranges)) {
    436     return;
    437   }
    438 
    439   // If multiple ranges are requested, we play dumb and
    440   // return the entire response with 200 OK.
    441   if (ranges.size() == 1U)
    442     range_requested_ = ranges[0];
    443 }
    444 
    445 }  // namespace appcache
    446