// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "webkit/browser/appcache/appcache_url_request_job.h" #include #include "base/bind.h" #include "base/bind_helpers.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/message_loop/message_loop.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/base/net_log.h" #include "net/http/http_request_headers.h" #include "net/http/http_response_headers.h" #include "net/http/http_util.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_status.h" #include "webkit/browser/appcache/appcache.h" #include "webkit/browser/appcache/appcache_group.h" #include "webkit/browser/appcache/appcache_histograms.h" #include "webkit/browser/appcache/appcache_host.h" #include "webkit/browser/appcache/appcache_service.h" namespace appcache { AppCacheURLRequestJob::AppCacheURLRequestJob( net::URLRequest* request, net::NetworkDelegate* network_delegate, AppCacheStorage* storage, AppCacheHost* host, bool is_main_resource) : net::URLRequestJob(request, network_delegate), host_(host), storage_(storage), has_been_started_(false), has_been_killed_(false), delivery_type_(AWAITING_DELIVERY_ORDERS), group_id_(0), cache_id_(kNoCacheId), is_fallback_(false), is_main_resource_(is_main_resource), cache_entry_not_found_(false), weak_factory_(this) { DCHECK(storage_); } void AppCacheURLRequestJob::DeliverAppCachedResponse( const GURL& manifest_url, int64 group_id, int64 cache_id, const AppCacheEntry& entry, bool is_fallback) { DCHECK(!has_delivery_orders()); DCHECK(entry.has_response_id()); delivery_type_ = APPCACHED_DELIVERY; manifest_url_ = manifest_url; group_id_ = group_id; cache_id_ = cache_id; entry_ = entry; is_fallback_ = is_fallback; MaybeBeginDelivery(); } void AppCacheURLRequestJob::DeliverNetworkResponse() { DCHECK(!has_delivery_orders()); delivery_type_ = NETWORK_DELIVERY; storage_ = NULL; // not needed MaybeBeginDelivery(); } void AppCacheURLRequestJob::DeliverErrorResponse() { DCHECK(!has_delivery_orders()); delivery_type_ = ERROR_DELIVERY; storage_ = NULL; // not needed MaybeBeginDelivery(); } void AppCacheURLRequestJob::MaybeBeginDelivery() { if (has_been_started() && has_delivery_orders()) { // Start asynchronously so that all error reporting and data // callbacks happen as they would for network requests. base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&AppCacheURLRequestJob::BeginDelivery, weak_factory_.GetWeakPtr())); } } void AppCacheURLRequestJob::BeginDelivery() { DCHECK(has_delivery_orders() && has_been_started()); if (has_been_killed()) return; switch (delivery_type_) { case NETWORK_DELIVERY: AppCacheHistograms::AddNetworkJobStartDelaySample( base::TimeTicks::Now() - start_time_tick_); // To fallthru to the network, we restart the request which will // cause a new job to be created to retrieve the resource from the // network. Our caller is responsible for arranging to not re-intercept // the same request. NotifyRestartRequired(); break; case ERROR_DELIVERY: AppCacheHistograms::AddErrorJobStartDelaySample( base::TimeTicks::Now() - start_time_tick_); request()->net_log().AddEvent( net::NetLog::TYPE_APPCACHE_DELIVERING_ERROR_RESPONSE); NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED)); break; case APPCACHED_DELIVERY: if (entry_.IsExecutable()) { BeginExecutableHandlerDelivery(); return; } AppCacheHistograms::AddAppCacheJobStartDelaySample( base::TimeTicks::Now() - start_time_tick_); request()->net_log().AddEvent( is_fallback_ ? net::NetLog::TYPE_APPCACHE_DELIVERING_FALLBACK_RESPONSE : net::NetLog::TYPE_APPCACHE_DELIVERING_CACHED_RESPONSE); storage_->LoadResponseInfo( manifest_url_, group_id_, entry_.response_id(), this); break; default: NOTREACHED(); break; } } void AppCacheURLRequestJob::BeginExecutableHandlerDelivery() { DCHECK(CommandLine::ForCurrentProcess()-> HasSwitch(kEnableExecutableHandlers)); if (!storage_->service()->handler_factory()) { BeginErrorDelivery("missing handler factory"); return; } request()->net_log().AddEvent( net::NetLog::TYPE_APPCACHE_DELIVERING_EXECUTABLE_RESPONSE); // We defer job delivery until the executable handler is spun up and // provides a response. The sequence goes like this... // // 1. First we load the cache. // 2. Then if the handler is not spun up, we load the script resource which // is needed to spin it up. // 3. Then we ask then we ask the handler to compute a response. // 4. Finally we deilver that response to the caller. storage_->LoadCache(cache_id_, this); } void AppCacheURLRequestJob::OnCacheLoaded(AppCache* cache, int64 cache_id) { DCHECK_EQ(cache_id_, cache_id); DCHECK(!has_been_killed()); if (!cache) { BeginErrorDelivery("cache load failed"); return; } // Keep references to ensure they don't go out of scope until job completion. cache_ = cache; group_ = cache->owning_group(); // If the handler is spun up, ask it to compute a response. AppCacheExecutableHandler* handler = cache->GetExecutableHandler(entry_.response_id()); if (handler) { InvokeExecutableHandler(handler); return; } // Handler is not spun up yet, load the script resource to do that. // NOTE: This is not ideal since multiple jobs may be doing this, // concurrently but close enough for now, the first to load the script // will win. // Read the script data, truncating if its too large. // NOTE: we just issue one read and don't bother chaining if the resource // is very (very) large, close enough for now. const int64 kLimit = 500 * 1000; handler_source_buffer_ = new net::GrowableIOBuffer(); handler_source_buffer_->SetCapacity(kLimit); handler_source_reader_.reset(storage_->CreateResponseReader( manifest_url_, group_id_, entry_.response_id())); handler_source_reader_->ReadData( handler_source_buffer_.get(), kLimit, base::Bind(&AppCacheURLRequestJob::OnExecutableSourceLoaded, base::Unretained(this))); } void AppCacheURLRequestJob::OnExecutableSourceLoaded(int result) { DCHECK(!has_been_killed()); handler_source_reader_.reset(); if (result < 0) { BeginErrorDelivery("script source load failed"); return; } handler_source_buffer_->SetCapacity(result); // Free up some memory. AppCacheExecutableHandler* handler = cache_->GetOrCreateExecutableHandler( entry_.response_id(), handler_source_buffer_.get()); handler_source_buffer_ = NULL; // not needed anymore if (handler) { InvokeExecutableHandler(handler); return; } BeginErrorDelivery("factory failed to produce a handler"); } void AppCacheURLRequestJob::InvokeExecutableHandler( AppCacheExecutableHandler* handler) { handler->HandleRequest( request(), base::Bind(&AppCacheURLRequestJob::OnExecutableResponseCallback, weak_factory_.GetWeakPtr())); } void AppCacheURLRequestJob::OnExecutableResponseCallback( const AppCacheExecutableHandler::Response& response) { DCHECK(!has_been_killed()); if (response.use_network) { delivery_type_ = NETWORK_DELIVERY; storage_ = NULL; BeginDelivery(); return; } if (!response.cached_resource_url.is_empty()) { AppCacheEntry* entry_ptr = cache_->GetEntry(response.cached_resource_url); if (entry_ptr && !entry_ptr->IsExecutable()) { entry_ = *entry_ptr; BeginDelivery(); return; } } if (!response.redirect_url.is_empty()) { // TODO(michaeln): playback a redirect // response_headers_(new HttpResponseHeaders(response_headers)), // fallthru for now to deliver an error } // Otherwise, return an error. BeginErrorDelivery("handler returned an invalid response"); } void AppCacheURLRequestJob::BeginErrorDelivery(const char* message) { if (host_) host_->frontend()->OnLogMessage(host_->host_id(), LOG_ERROR, message); delivery_type_ = ERROR_DELIVERY; storage_ = NULL; BeginDelivery(); } AppCacheURLRequestJob::~AppCacheURLRequestJob() { if (storage_) storage_->CancelDelegateCallbacks(this); } void AppCacheURLRequestJob::OnResponseInfoLoaded( AppCacheResponseInfo* response_info, int64 response_id) { DCHECK(is_delivering_appcache_response()); scoped_refptr protect(this); if (response_info) { info_ = response_info; reader_.reset(storage_->CreateResponseReader( manifest_url_, group_id_, entry_.response_id())); if (is_range_request()) SetupRangeResponse(); NotifyHeadersComplete(); } else { if (storage_->service()->storage() == storage_) { // A resource that is expected to be in the appcache is missing. // See http://code.google.com/p/chromium/issues/detail?id=50657 // Instead of failing the request, we restart the request. The retry // attempt will fallthru to the network instead of trying to load // from the appcache. storage_->service()->CheckAppCacheResponse(manifest_url_, cache_id_, entry_.response_id()); AppCacheHistograms::CountResponseRetrieval( false, is_main_resource_, manifest_url_.GetOrigin()); } cache_entry_not_found_ = true; NotifyRestartRequired(); } } const net::HttpResponseInfo* AppCacheURLRequestJob::http_info() const { if (!info_.get()) return NULL; if (range_response_info_) return range_response_info_.get(); return info_->http_response_info(); } void AppCacheURLRequestJob::SetupRangeResponse() { DCHECK(is_range_request() && info_.get() && reader_.get() && is_delivering_appcache_response()); int resource_size = static_cast(info_->response_data_size()); if (resource_size < 0 || !range_requested_.ComputeBounds(resource_size)) { range_requested_ = net::HttpByteRange(); return; } DCHECK(range_requested_.IsValid()); int offset = static_cast(range_requested_.first_byte_position()); int length = static_cast(range_requested_.last_byte_position() - range_requested_.first_byte_position() + 1); // Tell the reader about the range to read. reader_->SetReadRange(offset, length); // Make a copy of the full response headers and fix them up // for the range we'll be returning. range_response_info_.reset( new net::HttpResponseInfo(*info_->http_response_info())); net::HttpResponseHeaders* headers = range_response_info_->headers.get(); headers->UpdateWithNewRange( range_requested_, resource_size, true /* replace status line */); } void AppCacheURLRequestJob::OnReadComplete(int result) { DCHECK(is_delivering_appcache_response()); if (result == 0) { NotifyDone(net::URLRequestStatus()); AppCacheHistograms::CountResponseRetrieval( true, is_main_resource_, manifest_url_.GetOrigin()); } else if (result < 0) { if (storage_->service()->storage() == storage_) { storage_->service()->CheckAppCacheResponse(manifest_url_, cache_id_, entry_.response_id()); } NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result)); AppCacheHistograms::CountResponseRetrieval( false, is_main_resource_, manifest_url_.GetOrigin()); } else { SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status } NotifyReadComplete(result); } // net::URLRequestJob overrides ------------------------------------------------ void AppCacheURLRequestJob::Start() { DCHECK(!has_been_started()); has_been_started_ = true; start_time_tick_ = base::TimeTicks::Now(); MaybeBeginDelivery(); } void AppCacheURLRequestJob::Kill() { if (!has_been_killed_) { has_been_killed_ = true; reader_.reset(); handler_source_reader_.reset(); if (storage_) { storage_->CancelDelegateCallbacks(this); storage_ = NULL; } host_ = NULL; info_ = NULL; cache_ = NULL; group_ = NULL; range_response_info_.reset(); net::URLRequestJob::Kill(); weak_factory_.InvalidateWeakPtrs(); } } net::LoadState AppCacheURLRequestJob::GetLoadState() const { if (!has_been_started()) return net::LOAD_STATE_IDLE; if (!has_delivery_orders()) return net::LOAD_STATE_WAITING_FOR_APPCACHE; if (delivery_type_ != APPCACHED_DELIVERY) return net::LOAD_STATE_IDLE; if (!info_.get()) return net::LOAD_STATE_WAITING_FOR_APPCACHE; if (reader_.get() && reader_->IsReadPending()) return net::LOAD_STATE_READING_RESPONSE; return net::LOAD_STATE_IDLE; } bool AppCacheURLRequestJob::GetMimeType(std::string* mime_type) const { if (!http_info()) return false; return http_info()->headers->GetMimeType(mime_type); } bool AppCacheURLRequestJob::GetCharset(std::string* charset) { if (!http_info()) return false; return http_info()->headers->GetCharset(charset); } void AppCacheURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) { if (!http_info()) return; *info = *http_info(); } int AppCacheURLRequestJob::GetResponseCode() const { if (!http_info()) return -1; return http_info()->headers->response_code(); } bool AppCacheURLRequestJob::ReadRawData(net::IOBuffer* buf, int buf_size, int *bytes_read) { DCHECK(is_delivering_appcache_response()); DCHECK_NE(buf_size, 0); DCHECK(bytes_read); DCHECK(!reader_->IsReadPending()); reader_->ReadData( buf, buf_size, base::Bind(&AppCacheURLRequestJob::OnReadComplete, base::Unretained(this))); SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); return false; } void AppCacheURLRequestJob::SetExtraRequestHeaders( const net::HttpRequestHeaders& headers) { std::string value; std::vector ranges; if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &value) || !net::HttpUtil::ParseRangeHeader(value, &ranges)) { return; } // If multiple ranges are requested, we play dumb and // return the entire response with 200 OK. if (ranges.size() == 1U) range_requested_ = ranges[0]; } } // namespace appcache