Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / browser / appcache / appcache_url_request_job.cc
blobfc628c8361371c78c0fbea995366d0a20e69abf6
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 "content/browser/appcache/appcache_url_request_job.h"
7 #include <vector>
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/profiler/scoped_tracker.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/stringprintf.h"
17 #include "content/browser/appcache/appcache.h"
18 #include "content/browser/appcache/appcache_group.h"
19 #include "content/browser/appcache/appcache_histograms.h"
20 #include "content/browser/appcache/appcache_host.h"
21 #include "content/browser/appcache/appcache_service_impl.h"
22 #include "net/base/io_buffer.h"
23 #include "net/base/net_errors.h"
24 #include "net/http/http_request_headers.h"
25 #include "net/http/http_response_headers.h"
26 #include "net/http/http_util.h"
27 #include "net/log/net_log.h"
28 #include "net/url_request/url_request.h"
29 #include "net/url_request/url_request_status.h"
31 namespace content {
33 AppCacheURLRequestJob::AppCacheURLRequestJob(
34 net::URLRequest* request,
35 net::NetworkDelegate* network_delegate,
36 AppCacheStorage* storage,
37 AppCacheHost* host,
38 bool is_main_resource)
39 : net::URLRequestJob(request, network_delegate),
40 host_(host),
41 storage_(storage),
42 has_been_started_(false), has_been_killed_(false),
43 delivery_type_(AWAITING_DELIVERY_ORDERS),
44 group_id_(0), cache_id_(kAppCacheNoCacheId), is_fallback_(false),
45 is_main_resource_(is_main_resource),
46 cache_entry_not_found_(false),
47 weak_factory_(this) {
48 DCHECK(storage_);
51 void AppCacheURLRequestJob::DeliverAppCachedResponse(
52 const GURL& manifest_url, int64 group_id, int64 cache_id,
53 const AppCacheEntry& entry, bool is_fallback) {
54 DCHECK(!has_delivery_orders());
55 DCHECK(entry.has_response_id());
56 delivery_type_ = APPCACHED_DELIVERY;
57 manifest_url_ = manifest_url;
58 group_id_ = group_id;
59 cache_id_ = cache_id;
60 entry_ = entry;
61 is_fallback_ = is_fallback;
62 MaybeBeginDelivery();
65 void AppCacheURLRequestJob::DeliverNetworkResponse() {
66 DCHECK(!has_delivery_orders());
67 delivery_type_ = NETWORK_DELIVERY;
68 storage_ = NULL; // not needed
69 MaybeBeginDelivery();
72 void AppCacheURLRequestJob::DeliverErrorResponse() {
73 DCHECK(!has_delivery_orders());
74 delivery_type_ = ERROR_DELIVERY;
75 storage_ = NULL; // not needed
76 MaybeBeginDelivery();
79 void AppCacheURLRequestJob::MaybeBeginDelivery() {
80 if (has_been_started() && has_delivery_orders()) {
81 // Start asynchronously so that all error reporting and data
82 // callbacks happen as they would for network requests.
83 base::MessageLoop::current()->PostTask(
84 FROM_HERE,
85 base::Bind(&AppCacheURLRequestJob::BeginDelivery,
86 weak_factory_.GetWeakPtr()));
90 void AppCacheURLRequestJob::BeginDelivery() {
91 DCHECK(has_delivery_orders() && has_been_started());
93 if (has_been_killed())
94 return;
96 switch (delivery_type_) {
97 case NETWORK_DELIVERY:
98 AppCacheHistograms::AddNetworkJobStartDelaySample(
99 base::TimeTicks::Now() - start_time_tick_);
100 // To fallthru to the network, we restart the request which will
101 // cause a new job to be created to retrieve the resource from the
102 // network. Our caller is responsible for arranging to not re-intercept
103 // the same request.
104 NotifyRestartRequired();
105 break;
107 case ERROR_DELIVERY:
108 AppCacheHistograms::AddErrorJobStartDelaySample(
109 base::TimeTicks::Now() - start_time_tick_);
110 request()->net_log().AddEvent(
111 net::NetLog::TYPE_APPCACHE_DELIVERING_ERROR_RESPONSE);
112 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED,
113 net::ERR_FAILED));
114 break;
116 case APPCACHED_DELIVERY:
117 if (entry_.IsExecutable()) {
118 BeginExecutableHandlerDelivery();
119 return;
121 AppCacheHistograms::AddAppCacheJobStartDelaySample(
122 base::TimeTicks::Now() - start_time_tick_);
123 request()->net_log().AddEvent(
124 is_fallback_ ?
125 net::NetLog::TYPE_APPCACHE_DELIVERING_FALLBACK_RESPONSE :
126 net::NetLog::TYPE_APPCACHE_DELIVERING_CACHED_RESPONSE);
127 storage_->LoadResponseInfo(
128 manifest_url_, group_id_, entry_.response_id(), this);
129 break;
131 default:
132 NOTREACHED();
133 break;
137 void AppCacheURLRequestJob::BeginExecutableHandlerDelivery() {
138 DCHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
139 kEnableExecutableHandlers));
140 if (!storage_->service()->handler_factory()) {
141 BeginErrorDelivery("missing handler factory");
142 return;
145 request()->net_log().AddEvent(
146 net::NetLog::TYPE_APPCACHE_DELIVERING_EXECUTABLE_RESPONSE);
148 // We defer job delivery until the executable handler is spun up and
149 // provides a response. The sequence goes like this...
151 // 1. First we load the cache.
152 // 2. Then if the handler is not spun up, we load the script resource which
153 // is needed to spin it up.
154 // 3. Then we ask then we ask the handler to compute a response.
155 // 4. Finally we deilver that response to the caller.
156 storage_->LoadCache(cache_id_, this);
159 void AppCacheURLRequestJob::OnCacheLoaded(AppCache* cache, int64 cache_id) {
160 DCHECK_EQ(cache_id_, cache_id);
161 DCHECK(!has_been_killed());
163 if (!cache) {
164 BeginErrorDelivery("cache load failed");
165 return;
168 // Keep references to ensure they don't go out of scope until job completion.
169 cache_ = cache;
170 group_ = cache->owning_group();
172 // If the handler is spun up, ask it to compute a response.
173 AppCacheExecutableHandler* handler =
174 cache->GetExecutableHandler(entry_.response_id());
175 if (handler) {
176 InvokeExecutableHandler(handler);
177 return;
180 // Handler is not spun up yet, load the script resource to do that.
181 // NOTE: This is not ideal since multiple jobs may be doing this,
182 // concurrently but close enough for now, the first to load the script
183 // will win.
185 // Read the script data, truncating if its too large.
186 // NOTE: we just issue one read and don't bother chaining if the resource
187 // is very (very) large, close enough for now.
188 const int64 kLimit = 500 * 1000;
189 handler_source_buffer_ = new net::GrowableIOBuffer();
190 handler_source_buffer_->SetCapacity(kLimit);
191 handler_source_reader_.reset(storage_->CreateResponseReader(
192 manifest_url_, group_id_, entry_.response_id()));
193 handler_source_reader_->ReadData(
194 handler_source_buffer_.get(),
195 kLimit,
196 base::Bind(&AppCacheURLRequestJob::OnExecutableSourceLoaded,
197 base::Unretained(this)));
200 void AppCacheURLRequestJob::OnExecutableSourceLoaded(int result) {
201 DCHECK(!has_been_killed());
202 handler_source_reader_.reset();
203 if (result < 0) {
204 BeginErrorDelivery("script source load failed");
205 return;
208 handler_source_buffer_->SetCapacity(result); // Free up some memory.
210 AppCacheExecutableHandler* handler = cache_->GetOrCreateExecutableHandler(
211 entry_.response_id(), handler_source_buffer_.get());
212 handler_source_buffer_ = NULL; // not needed anymore
213 if (handler) {
214 InvokeExecutableHandler(handler);
215 return;
218 BeginErrorDelivery("factory failed to produce a handler");
221 void AppCacheURLRequestJob::InvokeExecutableHandler(
222 AppCacheExecutableHandler* handler) {
223 handler->HandleRequest(
224 request(),
225 base::Bind(&AppCacheURLRequestJob::OnExecutableResponseCallback,
226 weak_factory_.GetWeakPtr()));
229 void AppCacheURLRequestJob::OnExecutableResponseCallback(
230 const AppCacheExecutableHandler::Response& response) {
231 DCHECK(!has_been_killed());
232 if (response.use_network) {
233 delivery_type_ = NETWORK_DELIVERY;
234 storage_ = NULL;
235 BeginDelivery();
236 return;
239 if (!response.cached_resource_url.is_empty()) {
240 AppCacheEntry* entry_ptr = cache_->GetEntry(response.cached_resource_url);
241 if (entry_ptr && !entry_ptr->IsExecutable()) {
242 entry_ = *entry_ptr;
243 BeginDelivery();
244 return;
248 if (!response.redirect_url.is_empty()) {
249 // TODO(michaeln): playback a redirect
250 // response_headers_(new HttpResponseHeaders(response_headers)),
251 // fallthru for now to deliver an error
254 // Otherwise, return an error.
255 BeginErrorDelivery("handler returned an invalid response");
258 void AppCacheURLRequestJob::BeginErrorDelivery(const char* message) {
259 if (host_)
260 host_->frontend()->OnLogMessage(host_->host_id(), APPCACHE_LOG_ERROR,
261 message);
262 delivery_type_ = ERROR_DELIVERY;
263 storage_ = NULL;
264 BeginDelivery();
267 AppCacheURLRequestJob::~AppCacheURLRequestJob() {
268 if (storage_)
269 storage_->CancelDelegateCallbacks(this);
272 void AppCacheURLRequestJob::OnResponseInfoLoaded(
273 AppCacheResponseInfo* response_info, int64 response_id) {
274 DCHECK(is_delivering_appcache_response());
275 scoped_refptr<AppCacheURLRequestJob> protect(this);
276 if (response_info) {
277 info_ = response_info;
278 reader_.reset(storage_->CreateResponseReader(
279 manifest_url_, group_id_, entry_.response_id()));
281 if (is_range_request())
282 SetupRangeResponse();
284 NotifyHeadersComplete();
285 } else {
286 if (storage_->service()->storage() == storage_) {
287 // A resource that is expected to be in the appcache is missing.
288 // See http://code.google.com/p/chromium/issues/detail?id=50657
289 // Instead of failing the request, we restart the request. The retry
290 // attempt will fallthru to the network instead of trying to load
291 // from the appcache.
292 storage_->service()->CheckAppCacheResponse(manifest_url_, cache_id_,
293 entry_.response_id());
294 AppCacheHistograms::CountResponseRetrieval(
295 false, is_main_resource_, manifest_url_.GetOrigin());
297 cache_entry_not_found_ = true;
298 NotifyRestartRequired();
302 const net::HttpResponseInfo* AppCacheURLRequestJob::http_info() const {
303 if (!info_.get())
304 return NULL;
305 if (range_response_info_)
306 return range_response_info_.get();
307 return info_->http_response_info();
310 void AppCacheURLRequestJob::SetupRangeResponse() {
311 DCHECK(is_range_request() && info_.get() && reader_.get() &&
312 is_delivering_appcache_response());
313 int resource_size = static_cast<int>(info_->response_data_size());
314 if (resource_size < 0 || !range_requested_.ComputeBounds(resource_size)) {
315 range_requested_ = net::HttpByteRange();
316 return;
319 DCHECK(range_requested_.IsValid());
320 int offset = static_cast<int>(range_requested_.first_byte_position());
321 int length = static_cast<int>(range_requested_.last_byte_position() -
322 range_requested_.first_byte_position() + 1);
324 // Tell the reader about the range to read.
325 reader_->SetReadRange(offset, length);
327 // Make a copy of the full response headers and fix them up
328 // for the range we'll be returning.
329 range_response_info_.reset(
330 new net::HttpResponseInfo(*info_->http_response_info()));
331 net::HttpResponseHeaders* headers = range_response_info_->headers.get();
332 headers->UpdateWithNewRange(
333 range_requested_, resource_size, true /* replace status line */);
336 void AppCacheURLRequestJob::OnReadComplete(int result) {
337 DCHECK(is_delivering_appcache_response());
338 if (result == 0) {
339 NotifyDone(net::URLRequestStatus());
340 AppCacheHistograms::CountResponseRetrieval(
341 true, is_main_resource_, manifest_url_.GetOrigin());
342 } else if (result < 0) {
343 if (storage_->service()->storage() == storage_) {
344 storage_->service()->CheckAppCacheResponse(manifest_url_, cache_id_,
345 entry_.response_id());
347 NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result));
348 AppCacheHistograms::CountResponseRetrieval(
349 false, is_main_resource_, manifest_url_.GetOrigin());
350 } else {
351 SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status
353 NotifyReadComplete(result);
356 // net::URLRequestJob overrides ------------------------------------------------
358 void AppCacheURLRequestJob::Start() {
359 DCHECK(!has_been_started());
360 has_been_started_ = true;
361 start_time_tick_ = base::TimeTicks::Now();
362 MaybeBeginDelivery();
365 void AppCacheURLRequestJob::Kill() {
366 if (!has_been_killed_) {
367 has_been_killed_ = true;
368 reader_.reset();
369 handler_source_reader_.reset();
370 if (storage_) {
371 storage_->CancelDelegateCallbacks(this);
372 storage_ = NULL;
374 host_ = NULL;
375 info_ = NULL;
376 cache_ = NULL;
377 group_ = NULL;
378 range_response_info_.reset();
379 net::URLRequestJob::Kill();
380 weak_factory_.InvalidateWeakPtrs();
384 net::LoadState AppCacheURLRequestJob::GetLoadState() const {
385 if (!has_been_started())
386 return net::LOAD_STATE_IDLE;
387 if (!has_delivery_orders())
388 return net::LOAD_STATE_WAITING_FOR_APPCACHE;
389 if (delivery_type_ != APPCACHED_DELIVERY)
390 return net::LOAD_STATE_IDLE;
391 if (!info_.get())
392 return net::LOAD_STATE_WAITING_FOR_APPCACHE;
393 if (reader_.get() && reader_->IsReadPending())
394 return net::LOAD_STATE_READING_RESPONSE;
395 return net::LOAD_STATE_IDLE;
398 bool AppCacheURLRequestJob::GetMimeType(std::string* mime_type) const {
399 if (!http_info())
400 return false;
401 return http_info()->headers->GetMimeType(mime_type);
404 bool AppCacheURLRequestJob::GetCharset(std::string* charset) {
405 if (!http_info())
406 return false;
407 return http_info()->headers->GetCharset(charset);
410 void AppCacheURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
411 if (!http_info())
412 return;
413 *info = *http_info();
416 int AppCacheURLRequestJob::GetResponseCode() const {
417 if (!http_info())
418 return -1;
419 return http_info()->headers->response_code();
422 bool AppCacheURLRequestJob::ReadRawData(net::IOBuffer* buf, int buf_size,
423 int *bytes_read) {
424 // TODO(vadimt): Remove ScopedTracker below once crbug.com/423948 is fixed.
425 tracked_objects::ScopedTracker tracking_profile(
426 FROM_HERE_WITH_EXPLICIT_FUNCTION(
427 "423948 AppCacheURLRequestJob::ReadRawData"));
429 DCHECK(is_delivering_appcache_response());
430 DCHECK_NE(buf_size, 0);
431 DCHECK(bytes_read);
432 DCHECK(!reader_->IsReadPending());
433 reader_->ReadData(
434 buf, buf_size, base::Bind(&AppCacheURLRequestJob::OnReadComplete,
435 base::Unretained(this)));
436 SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
437 return false;
440 void AppCacheURLRequestJob::SetExtraRequestHeaders(
441 const net::HttpRequestHeaders& headers) {
442 std::string value;
443 std::vector<net::HttpByteRange> ranges;
444 if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &value) ||
445 !net::HttpUtil::ParseRangeHeader(value, &ranges)) {
446 return;
449 // If multiple ranges are requested, we play dumb and
450 // return the entire response with 200 OK.
451 if (ranges.size() == 1U)
452 range_requested_ = ranges[0];
455 } // namespace content