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.
5 #include "webkit/browser/appcache/appcache_url_request_job.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"
32 AppCacheURLRequestJob::AppCacheURLRequestJob(
33 net::URLRequest* request,
34 net::NetworkDelegate* network_delegate,
35 AppCacheStorage* storage,
37 : net::URLRequestJob(request, network_delegate),
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),
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;
58 is_fallback_ = is_fallback;
62 void AppCacheURLRequestJob::DeliverNetworkResponse() {
63 DCHECK(!has_delivery_orders());
64 delivery_type_ = NETWORK_DELIVERY;
65 storage_ = NULL; // not needed
69 void AppCacheURLRequestJob::DeliverErrorResponse() {
70 DCHECK(!has_delivery_orders());
71 delivery_type_ = ERROR_DELIVERY;
72 storage_ = NULL; // not needed
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(
82 base::Bind(&AppCacheURLRequestJob::BeginDelivery,
83 weak_factory_.GetWeakPtr()));
87 void AppCacheURLRequestJob::BeginDelivery() {
88 DCHECK(has_delivery_orders() && has_been_started());
90 if (has_been_killed())
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
101 NotifyRestartRequired();
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,
113 case APPCACHED_DELIVERY:
114 if (entry_.IsExecutable()) {
115 BeginExecutableHandlerDelivery();
118 AppCacheHistograms::AddAppCacheJobStartDelaySample(
119 base::TimeTicks::Now() - start_time_tick_);
120 request()->net_log().AddEvent(
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);
134 void AppCacheURLRequestJob::BeginExecutableHandlerDelivery() {
135 DCHECK(CommandLine::ForCurrentProcess()->
136 HasSwitch(kEnableExecutableHandlers));
137 if (!storage_->service()->handler_factory()) {
138 BeginErrorDelivery("missing handler factory");
142 request()->net_log().AddEvent(
143 net::NetLog::TYPE_APPCACHE_DELIVERING_EXECUTABLE_RESPONSE);
145 // We defer job delivery until the executable handler is spun up and
146 // provides a response. The sequence goes like this...
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);
156 void AppCacheURLRequestJob::OnCacheLoaded(AppCache* cache, int64 cache_id) {
157 DCHECK_EQ(cache_id_, cache_id);
158 DCHECK(!has_been_killed());
161 BeginErrorDelivery("cache load failed");
165 // Keep references to ensure they don't go out of scope until job completion.
167 group_ = cache->owning_group();
169 // If the handler is spun up, ask it to compute a response.
170 AppCacheExecutableHandler* handler =
171 cache->GetExecutableHandler(entry_.response_id());
173 InvokeExecutableHandler(handler);
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
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(),
193 base::Bind(&AppCacheURLRequestJob::OnExecutableSourceLoaded,
194 base::Unretained(this)));
197 void AppCacheURLRequestJob::OnExecutableSourceLoaded(int result) {
198 DCHECK(!has_been_killed());
199 handler_source_reader_.reset();
201 BeginErrorDelivery("script source load failed");
205 handler_source_buffer_->SetCapacity(result); // Free up some memory.
207 AppCacheExecutableHandler* handler = cache_->GetOrCreateExecutableHandler(
208 entry_.response_id(), handler_source_buffer_.get());
209 handler_source_buffer_ = NULL; // not needed anymore
211 InvokeExecutableHandler(handler);
215 BeginErrorDelivery("factory failed to produce a handler");
218 void AppCacheURLRequestJob::InvokeExecutableHandler(
219 AppCacheExecutableHandler* handler) {
220 handler->HandleRequest(
222 base::Bind(&AppCacheURLRequestJob::OnExecutableResponseCallback,
223 weak_factory_.GetWeakPtr()));
226 void AppCacheURLRequestJob::OnExecutableResponseCallback(
227 const AppCacheExecutableHandler::Response& response) {
228 DCHECK(!has_been_killed());
229 if (response.use_network) {
230 delivery_type_ = NETWORK_DELIVERY;
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()) {
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
251 // Otherwise, return an error.
252 BeginErrorDelivery("handler returned an invalid response");
255 void AppCacheURLRequestJob::BeginErrorDelivery(const char* message) {
257 host_->frontend()->OnLogMessage(host_->host_id(), LOG_ERROR, message);
258 delivery_type_ = ERROR_DELIVERY;
263 AppCacheURLRequestJob::~AppCacheURLRequestJob() {
265 storage_->CancelDelegateCallbacks(this);
268 void AppCacheURLRequestJob::OnResponseInfoLoaded(
269 AppCacheResponseInfo* response_info, int64 response_id) {
270 DCHECK(is_delivering_appcache_response());
271 scoped_refptr<AppCacheURLRequestJob> protect(this);
273 info_ = response_info;
274 reader_.reset(storage_->CreateResponseReader(
275 manifest_url_, group_id_, entry_.response_id()));
277 if (is_range_request())
278 SetupRangeResponse();
280 NotifyHeadersComplete();
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());
291 cache_entry_not_found_ = true;
292 NotifyRestartRequired();
296 const net::HttpResponseInfo* AppCacheURLRequestJob::http_info() const {
299 if (range_response_info_)
300 return range_response_info_.get();
301 return info_->http_response_info();
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();
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);
319 // Tell the reader about the range to read.
320 reader_->SetReadRange(offset, length);
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);
334 base::StringPrintf("%s: %d", kLengthHeader, length));
336 base::StringPrintf("%s: bytes %d-%d/%d",
343 void AppCacheURLRequestJob::OnReadComplete(int result) {
344 DCHECK(is_delivering_appcache_response());
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());
352 NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result));
354 SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status
356 NotifyReadComplete(result);
359 // net::URLRequestJob overrides ------------------------------------------------
361 void AppCacheURLRequestJob::Start() {
362 DCHECK(!has_been_started());
363 has_been_started_ = true;
364 start_time_tick_ = base::TimeTicks::Now();
365 MaybeBeginDelivery();
368 void AppCacheURLRequestJob::Kill() {
369 if (!has_been_killed_) {
370 has_been_killed_ = true;
372 handler_source_reader_.reset();
374 storage_->CancelDelegateCallbacks(this);
381 range_response_info_.reset();
382 net::URLRequestJob::Kill();
383 weak_factory_.InvalidateWeakPtrs();
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;
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;
401 bool AppCacheURLRequestJob::GetMimeType(std::string* mime_type) const {
404 return http_info()->headers->GetMimeType(mime_type);
407 bool AppCacheURLRequestJob::GetCharset(std::string* charset) {
410 return http_info()->headers->GetCharset(charset);
413 void AppCacheURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
416 *info = *http_info();
419 int AppCacheURLRequestJob::GetResponseCode() const {
422 return http_info()->headers->response_code();
425 bool AppCacheURLRequestJob::ReadRawData(net::IOBuffer* buf, int buf_size,
427 DCHECK(is_delivering_appcache_response());
428 DCHECK_NE(buf_size, 0);
430 DCHECK(!reader_->IsReadPending());
432 buf, buf_size, base::Bind(&AppCacheURLRequestJob::OnReadComplete,
433 base::Unretained(this)));
434 SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
438 void AppCacheURLRequestJob::SetExtraRequestHeaders(
439 const net::HttpRequestHeaders& headers) {
441 std::vector<net::HttpByteRange> ranges;
442 if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &value) ||
443 !net::HttpUtil::ParseRangeHeader(value, &ranges)) {
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];
453 } // namespace appcache