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/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "content/browser/appcache/appcache.h"
17 #include "content/browser/appcache/appcache_group.h"
18 #include "content/browser/appcache/appcache_histograms.h"
19 #include "content/browser/appcache/appcache_host.h"
20 #include "content/browser/appcache/appcache_service_impl.h"
21 #include "net/base/io_buffer.h"
22 #include "net/base/net_errors.h"
23 #include "net/base/net_log.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/url_request/url_request.h"
28 #include "net/url_request/url_request_status.h"
32 AppCacheURLRequestJob::AppCacheURLRequestJob(
33 net::URLRequest
* request
,
34 net::NetworkDelegate
* network_delegate
,
35 AppCacheStorage
* storage
,
37 bool is_main_resource
)
38 : net::URLRequestJob(request
, network_delegate
),
41 has_been_started_(false), has_been_killed_(false),
42 delivery_type_(AWAITING_DELIVERY_ORDERS
),
43 group_id_(0), cache_id_(kAppCacheNoCacheId
), is_fallback_(false),
44 is_main_resource_(is_main_resource
),
45 cache_entry_not_found_(false),
50 void AppCacheURLRequestJob::DeliverAppCachedResponse(
51 const GURL
& manifest_url
, int64 group_id
, int64 cache_id
,
52 const AppCacheEntry
& entry
, bool is_fallback
) {
53 DCHECK(!has_delivery_orders());
54 DCHECK(entry
.has_response_id());
55 delivery_type_
= APPCACHED_DELIVERY
;
56 manifest_url_
= manifest_url
;
60 is_fallback_
= is_fallback
;
64 void AppCacheURLRequestJob::DeliverNetworkResponse() {
65 DCHECK(!has_delivery_orders());
66 delivery_type_
= NETWORK_DELIVERY
;
67 storage_
= NULL
; // not needed
71 void AppCacheURLRequestJob::DeliverErrorResponse() {
72 DCHECK(!has_delivery_orders());
73 delivery_type_
= ERROR_DELIVERY
;
74 storage_
= NULL
; // not needed
78 void AppCacheURLRequestJob::MaybeBeginDelivery() {
79 if (has_been_started() && has_delivery_orders()) {
80 // Start asynchronously so that all error reporting and data
81 // callbacks happen as they would for network requests.
82 base::MessageLoop::current()->PostTask(
84 base::Bind(&AppCacheURLRequestJob::BeginDelivery
,
85 weak_factory_
.GetWeakPtr()));
89 void AppCacheURLRequestJob::BeginDelivery() {
90 DCHECK(has_delivery_orders() && has_been_started());
92 if (has_been_killed())
95 switch (delivery_type_
) {
96 case NETWORK_DELIVERY
:
97 AppCacheHistograms::AddNetworkJobStartDelaySample(
98 base::TimeTicks::Now() - start_time_tick_
);
99 // To fallthru to the network, we restart the request which will
100 // cause a new job to be created to retrieve the resource from the
101 // network. Our caller is responsible for arranging to not re-intercept
103 NotifyRestartRequired();
107 AppCacheHistograms::AddErrorJobStartDelaySample(
108 base::TimeTicks::Now() - start_time_tick_
);
109 request()->net_log().AddEvent(
110 net::NetLog::TYPE_APPCACHE_DELIVERING_ERROR_RESPONSE
);
111 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED
,
115 case APPCACHED_DELIVERY
:
116 if (entry_
.IsExecutable()) {
117 BeginExecutableHandlerDelivery();
120 AppCacheHistograms::AddAppCacheJobStartDelaySample(
121 base::TimeTicks::Now() - start_time_tick_
);
122 request()->net_log().AddEvent(
124 net::NetLog::TYPE_APPCACHE_DELIVERING_FALLBACK_RESPONSE
:
125 net::NetLog::TYPE_APPCACHE_DELIVERING_CACHED_RESPONSE
);
126 storage_
->LoadResponseInfo(
127 manifest_url_
, group_id_
, entry_
.response_id(), this);
136 void AppCacheURLRequestJob::BeginExecutableHandlerDelivery() {
137 DCHECK(CommandLine::ForCurrentProcess()->
138 HasSwitch(kEnableExecutableHandlers
));
139 if (!storage_
->service()->handler_factory()) {
140 BeginErrorDelivery("missing handler factory");
144 request()->net_log().AddEvent(
145 net::NetLog::TYPE_APPCACHE_DELIVERING_EXECUTABLE_RESPONSE
);
147 // We defer job delivery until the executable handler is spun up and
148 // provides a response. The sequence goes like this...
150 // 1. First we load the cache.
151 // 2. Then if the handler is not spun up, we load the script resource which
152 // is needed to spin it up.
153 // 3. Then we ask then we ask the handler to compute a response.
154 // 4. Finally we deilver that response to the caller.
155 storage_
->LoadCache(cache_id_
, this);
158 void AppCacheURLRequestJob::OnCacheLoaded(AppCache
* cache
, int64 cache_id
) {
159 DCHECK_EQ(cache_id_
, cache_id
);
160 DCHECK(!has_been_killed());
163 BeginErrorDelivery("cache load failed");
167 // Keep references to ensure they don't go out of scope until job completion.
169 group_
= cache
->owning_group();
171 // If the handler is spun up, ask it to compute a response.
172 AppCacheExecutableHandler
* handler
=
173 cache
->GetExecutableHandler(entry_
.response_id());
175 InvokeExecutableHandler(handler
);
179 // Handler is not spun up yet, load the script resource to do that.
180 // NOTE: This is not ideal since multiple jobs may be doing this,
181 // concurrently but close enough for now, the first to load the script
184 // Read the script data, truncating if its too large.
185 // NOTE: we just issue one read and don't bother chaining if the resource
186 // is very (very) large, close enough for now.
187 const int64 kLimit
= 500 * 1000;
188 handler_source_buffer_
= new net::GrowableIOBuffer();
189 handler_source_buffer_
->SetCapacity(kLimit
);
190 handler_source_reader_
.reset(storage_
->CreateResponseReader(
191 manifest_url_
, group_id_
, entry_
.response_id()));
192 handler_source_reader_
->ReadData(
193 handler_source_buffer_
.get(),
195 base::Bind(&AppCacheURLRequestJob::OnExecutableSourceLoaded
,
196 base::Unretained(this)));
199 void AppCacheURLRequestJob::OnExecutableSourceLoaded(int result
) {
200 DCHECK(!has_been_killed());
201 handler_source_reader_
.reset();
203 BeginErrorDelivery("script source load failed");
207 handler_source_buffer_
->SetCapacity(result
); // Free up some memory.
209 AppCacheExecutableHandler
* handler
= cache_
->GetOrCreateExecutableHandler(
210 entry_
.response_id(), handler_source_buffer_
.get());
211 handler_source_buffer_
= NULL
; // not needed anymore
213 InvokeExecutableHandler(handler
);
217 BeginErrorDelivery("factory failed to produce a handler");
220 void AppCacheURLRequestJob::InvokeExecutableHandler(
221 AppCacheExecutableHandler
* handler
) {
222 handler
->HandleRequest(
224 base::Bind(&AppCacheURLRequestJob::OnExecutableResponseCallback
,
225 weak_factory_
.GetWeakPtr()));
228 void AppCacheURLRequestJob::OnExecutableResponseCallback(
229 const AppCacheExecutableHandler::Response
& response
) {
230 DCHECK(!has_been_killed());
231 if (response
.use_network
) {
232 delivery_type_
= NETWORK_DELIVERY
;
238 if (!response
.cached_resource_url
.is_empty()) {
239 AppCacheEntry
* entry_ptr
= cache_
->GetEntry(response
.cached_resource_url
);
240 if (entry_ptr
&& !entry_ptr
->IsExecutable()) {
247 if (!response
.redirect_url
.is_empty()) {
248 // TODO(michaeln): playback a redirect
249 // response_headers_(new HttpResponseHeaders(response_headers)),
250 // fallthru for now to deliver an error
253 // Otherwise, return an error.
254 BeginErrorDelivery("handler returned an invalid response");
257 void AppCacheURLRequestJob::BeginErrorDelivery(const char* message
) {
259 host_
->frontend()->OnLogMessage(host_
->host_id(), APPCACHE_LOG_ERROR
,
261 delivery_type_
= ERROR_DELIVERY
;
266 AppCacheURLRequestJob::~AppCacheURLRequestJob() {
268 storage_
->CancelDelegateCallbacks(this);
271 void AppCacheURLRequestJob::OnResponseInfoLoaded(
272 AppCacheResponseInfo
* response_info
, int64 response_id
) {
273 DCHECK(is_delivering_appcache_response());
274 scoped_refptr
<AppCacheURLRequestJob
> protect(this);
276 info_
= response_info
;
277 reader_
.reset(storage_
->CreateResponseReader(
278 manifest_url_
, group_id_
, entry_
.response_id()));
280 if (is_range_request())
281 SetupRangeResponse();
283 NotifyHeadersComplete();
285 if (storage_
->service()->storage() == storage_
) {
286 // A resource that is expected to be in the appcache is missing.
287 // See http://code.google.com/p/chromium/issues/detail?id=50657
288 // Instead of failing the request, we restart the request. The retry
289 // attempt will fallthru to the network instead of trying to load
290 // from the appcache.
291 storage_
->service()->CheckAppCacheResponse(manifest_url_
, cache_id_
,
292 entry_
.response_id());
293 AppCacheHistograms::CountResponseRetrieval(
294 false, is_main_resource_
, manifest_url_
.GetOrigin());
296 cache_entry_not_found_
= true;
297 NotifyRestartRequired();
301 const net::HttpResponseInfo
* AppCacheURLRequestJob::http_info() const {
304 if (range_response_info_
)
305 return range_response_info_
.get();
306 return info_
->http_response_info();
309 void AppCacheURLRequestJob::SetupRangeResponse() {
310 DCHECK(is_range_request() && info_
.get() && reader_
.get() &&
311 is_delivering_appcache_response());
312 int resource_size
= static_cast<int>(info_
->response_data_size());
313 if (resource_size
< 0 || !range_requested_
.ComputeBounds(resource_size
)) {
314 range_requested_
= net::HttpByteRange();
318 DCHECK(range_requested_
.IsValid());
319 int offset
= static_cast<int>(range_requested_
.first_byte_position());
320 int length
= static_cast<int>(range_requested_
.last_byte_position() -
321 range_requested_
.first_byte_position() + 1);
323 // Tell the reader about the range to read.
324 reader_
->SetReadRange(offset
, length
);
326 // Make a copy of the full response headers and fix them up
327 // for the range we'll be returning.
328 range_response_info_
.reset(
329 new net::HttpResponseInfo(*info_
->http_response_info()));
330 net::HttpResponseHeaders
* headers
= range_response_info_
->headers
.get();
331 headers
->UpdateWithNewRange(
332 range_requested_
, resource_size
, true /* replace status line */);
335 void AppCacheURLRequestJob::OnReadComplete(int result
) {
336 DCHECK(is_delivering_appcache_response());
338 NotifyDone(net::URLRequestStatus());
339 AppCacheHistograms::CountResponseRetrieval(
340 true, is_main_resource_
, manifest_url_
.GetOrigin());
341 } else if (result
< 0) {
342 if (storage_
->service()->storage() == storage_
) {
343 storage_
->service()->CheckAppCacheResponse(manifest_url_
, cache_id_
,
344 entry_
.response_id());
346 NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED
, result
));
347 AppCacheHistograms::CountResponseRetrieval(
348 false, is_main_resource_
, manifest_url_
.GetOrigin());
350 SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status
352 NotifyReadComplete(result
);
355 // net::URLRequestJob overrides ------------------------------------------------
357 void AppCacheURLRequestJob::Start() {
358 DCHECK(!has_been_started());
359 has_been_started_
= true;
360 start_time_tick_
= base::TimeTicks::Now();
361 MaybeBeginDelivery();
364 void AppCacheURLRequestJob::Kill() {
365 if (!has_been_killed_
) {
366 has_been_killed_
= true;
368 handler_source_reader_
.reset();
370 storage_
->CancelDelegateCallbacks(this);
377 range_response_info_
.reset();
378 net::URLRequestJob::Kill();
379 weak_factory_
.InvalidateWeakPtrs();
383 net::LoadState
AppCacheURLRequestJob::GetLoadState() const {
384 if (!has_been_started())
385 return net::LOAD_STATE_IDLE
;
386 if (!has_delivery_orders())
387 return net::LOAD_STATE_WAITING_FOR_APPCACHE
;
388 if (delivery_type_
!= APPCACHED_DELIVERY
)
389 return net::LOAD_STATE_IDLE
;
391 return net::LOAD_STATE_WAITING_FOR_APPCACHE
;
392 if (reader_
.get() && reader_
->IsReadPending())
393 return net::LOAD_STATE_READING_RESPONSE
;
394 return net::LOAD_STATE_IDLE
;
397 bool AppCacheURLRequestJob::GetMimeType(std::string
* mime_type
) const {
400 return http_info()->headers
->GetMimeType(mime_type
);
403 bool AppCacheURLRequestJob::GetCharset(std::string
* charset
) {
406 return http_info()->headers
->GetCharset(charset
);
409 void AppCacheURLRequestJob::GetResponseInfo(net::HttpResponseInfo
* info
) {
412 *info
= *http_info();
415 int AppCacheURLRequestJob::GetResponseCode() const {
418 return http_info()->headers
->response_code();
421 bool AppCacheURLRequestJob::ReadRawData(net::IOBuffer
* buf
, int buf_size
,
423 DCHECK(is_delivering_appcache_response());
424 DCHECK_NE(buf_size
, 0);
426 DCHECK(!reader_
->IsReadPending());
428 buf
, buf_size
, base::Bind(&AppCacheURLRequestJob::OnReadComplete
,
429 base::Unretained(this)));
430 SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING
, 0));
434 void AppCacheURLRequestJob::SetExtraRequestHeaders(
435 const net::HttpRequestHeaders
& headers
) {
437 std::vector
<net::HttpByteRange
> ranges
;
438 if (!headers
.GetHeader(net::HttpRequestHeaders::kRange
, &value
) ||
439 !net::HttpUtil::ParseRangeHeader(value
, &ranges
)) {
443 // If multiple ranges are requested, we play dumb and
444 // return the entire response with 200 OK.
445 if (ranges
.size() == 1U)
446 range_requested_
= ranges
[0];
449 } // namespace content