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