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