appcache_url_request_job.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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.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
30namespace appcache {
31
32AppCacheURLRequestJob::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
48void 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
62void AppCacheURLRequestJob::DeliverNetworkResponse() {
63  DCHECK(!has_delivery_orders());
64  delivery_type_ = NETWORK_DELIVERY;
65  storage_ = NULL;  // not needed
66  MaybeBeginDelivery();
67}
68
69void AppCacheURLRequestJob::DeliverErrorResponse() {
70  DCHECK(!has_delivery_orders());
71  delivery_type_ = ERROR_DELIVERY;
72  storage_ = NULL;  // not needed
73  MaybeBeginDelivery();
74}
75
76void 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
87void 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
134void 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
156void 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
197void 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
218void AppCacheURLRequestJob::InvokeExecutableHandler(
219    AppCacheExecutableHandler* handler) {
220  handler->HandleRequest(
221      request(),
222      base::Bind(&AppCacheURLRequestJob::OnExecutableResponseCallback,
223                 weak_factory_.GetWeakPtr()));
224}
225
226void 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
255void 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
263AppCacheURLRequestJob::~AppCacheURLRequestJob() {
264  if (storage_)
265    storage_->CancelDelegateCallbacks(this);
266}
267
268void 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
294const 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
302void 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
341void 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
357void AppCacheURLRequestJob::Start() {
358  DCHECK(!has_been_started());
359  has_been_started_ = true;
360  start_time_tick_ = base::TimeTicks::Now();
361  MaybeBeginDelivery();
362}
363
364void 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
379net::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
393bool AppCacheURLRequestJob::GetMimeType(std::string* mime_type) const {
394  if (!http_info())
395    return false;
396  return http_info()->headers->GetMimeType(mime_type);
397}
398
399bool AppCacheURLRequestJob::GetCharset(std::string* charset) {
400  if (!http_info())
401    return false;
402  return http_info()->headers->GetCharset(charset);
403}
404
405void AppCacheURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
406  if (!http_info())
407    return;
408  *info = *http_info();
409}
410
411int AppCacheURLRequestJob::GetResponseCode() const {
412  if (!http_info())
413    return -1;
414  return http_info()->headers->response_code();
415}
416
417bool 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
430void 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