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"
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"
33 AppCacheURLRequestJob::AppCacheURLRequestJob(
34 net::URLRequest
* request
,
35 net::NetworkDelegate
* network_delegate
,
36 AppCacheStorage
* storage
,
38 bool is_main_resource
)
39 : net::URLRequestJob(request
, network_delegate
),
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),
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
;
61 is_fallback_
= is_fallback
;
65 void AppCacheURLRequestJob::DeliverNetworkResponse() {
66 DCHECK(!has_delivery_orders());
67 delivery_type_
= NETWORK_DELIVERY
;
68 storage_
= NULL
; // not needed
72 void AppCacheURLRequestJob::DeliverErrorResponse() {
73 DCHECK(!has_delivery_orders());
74 delivery_type_
= ERROR_DELIVERY
;
75 storage_
= NULL
; // not needed
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(
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())
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
104 NotifyRestartRequired();
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
,
116 case APPCACHED_DELIVERY
:
117 if (entry_
.IsExecutable()) {
118 BeginExecutableHandlerDelivery();
121 AppCacheHistograms::AddAppCacheJobStartDelaySample(
122 base::TimeTicks::Now() - start_time_tick_
);
123 request()->net_log().AddEvent(
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);
137 void AppCacheURLRequestJob::BeginExecutableHandlerDelivery() {
138 DCHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
139 kEnableExecutableHandlers
));
140 if (!storage_
->service()->handler_factory()) {
141 BeginErrorDelivery("missing handler factory");
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());
164 BeginErrorDelivery("cache load failed");
168 // Keep references to ensure they don't go out of scope until job completion.
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());
176 InvokeExecutableHandler(handler
);
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
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(),
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();
204 BeginErrorDelivery("script source load failed");
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
214 InvokeExecutableHandler(handler
);
218 BeginErrorDelivery("factory failed to produce a handler");
221 void AppCacheURLRequestJob::InvokeExecutableHandler(
222 AppCacheExecutableHandler
* handler
) {
223 handler
->HandleRequest(
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
;
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()) {
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
) {
260 host_
->frontend()->OnLogMessage(host_
->host_id(), APPCACHE_LOG_ERROR
,
262 delivery_type_
= ERROR_DELIVERY
;
267 AppCacheURLRequestJob::~AppCacheURLRequestJob() {
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);
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();
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 {
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();
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());
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());
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;
369 handler_source_reader_
.reset();
371 storage_
->CancelDelegateCallbacks(this);
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
;
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 {
401 return http_info()->headers
->GetMimeType(mime_type
);
404 bool AppCacheURLRequestJob::GetCharset(std::string
* charset
) {
407 return http_info()->headers
->GetCharset(charset
);
410 void AppCacheURLRequestJob::GetResponseInfo(net::HttpResponseInfo
* info
) {
413 *info
= *http_info();
416 int AppCacheURLRequestJob::GetResponseCode() const {
419 return http_info()->headers
->response_code();
422 bool AppCacheURLRequestJob::ReadRawData(net::IOBuffer
* buf
, int buf_size
,
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);
432 DCHECK(!reader_
->IsReadPending());
434 buf
, buf_size
, base::Bind(&AppCacheURLRequestJob::OnReadComplete
,
435 base::Unretained(this)));
436 SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING
, 0));
440 void AppCacheURLRequestJob::SetExtraRequestHeaders(
441 const net::HttpRequestHeaders
& headers
) {
443 std::vector
<net::HttpByteRange
> ranges
;
444 if (!headers
.GetHeader(net::HttpRequestHeaders::kRange
, &value
) ||
445 !net::HttpUtil::ParseRangeHeader(value
, &ranges
)) {
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