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 "webkit/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 "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"
32 AppCacheURLRequestJob::AppCacheURLRequestJob(
33 net::URLRequest
* request
,
34 net::NetworkDelegate
* network_delegate
,
35 AppCacheStorage
* storage
,
37 : net::URLRequestJob(request
, network_delegate
),
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),
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
;
58 is_fallback_
= is_fallback
;
62 void AppCacheURLRequestJob::DeliverNetworkResponse() {
63 DCHECK(!has_delivery_orders());
64 delivery_type_
= NETWORK_DELIVERY
;
65 storage_
= NULL
; // not needed
69 void AppCacheURLRequestJob::DeliverErrorResponse() {
70 DCHECK(!has_delivery_orders());
71 delivery_type_
= ERROR_DELIVERY
;
72 storage_
= NULL
; // not needed
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(
82 base::Bind(&AppCacheURLRequestJob::BeginDelivery
,
83 weak_factory_
.GetWeakPtr()));
87 void AppCacheURLRequestJob::BeginDelivery() {
88 DCHECK(has_delivery_orders() && has_been_started());
90 if (has_been_killed())
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
101 NotifyRestartRequired();
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
,
113 case APPCACHED_DELIVERY
:
114 if (entry_
.IsExecutable()) {
115 BeginExecutableHandlerDelivery();
118 AppCacheHistograms::AddAppCacheJobStartDelaySample(
119 base::TimeTicks::Now() - start_time_tick_
);
120 request()->net_log().AddEvent(
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);
134 void AppCacheURLRequestJob::BeginExecutableHandlerDelivery() {
135 DCHECK(CommandLine::ForCurrentProcess()->
136 HasSwitch(kEnableExecutableHandlers
));
137 if (!storage_
->service()->handler_factory()) {
138 BeginErrorDelivery("missing handler factory");
142 request()->net_log().AddEvent(
143 net::NetLog::TYPE_APPCACHE_DELIVERING_EXECUTABLE_RESPONSE
);
145 // We defer job delivery until the executable handler is spun up and
146 // provides a response. The sequence goes like this...
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);
156 void AppCacheURLRequestJob::OnCacheLoaded(AppCache
* cache
, int64 cache_id
) {
157 DCHECK_EQ(cache_id_
, cache_id
);
158 DCHECK(!has_been_killed());
161 BeginErrorDelivery("cache load failed");
165 // Keep references to ensure they don't go out of scope until job completion.
167 group_
= cache
->owning_group();
169 // If the handler is spun up, ask it to compute a response.
170 AppCacheExecutableHandler
* handler
=
171 cache
->GetExecutableHandler(entry_
.response_id());
173 InvokeExecutableHandler(handler
);
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
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(),
193 base::Bind(&AppCacheURLRequestJob::OnExecutableSourceLoaded
,
194 base::Unretained(this)));
197 void AppCacheURLRequestJob::OnExecutableSourceLoaded(int result
) {
198 DCHECK(!has_been_killed());
199 handler_source_reader_
.reset();
201 BeginErrorDelivery("script source load failed");
205 handler_source_buffer_
->SetCapacity(result
); // Free up some memory.
207 AppCacheExecutableHandler
* handler
= cache_
->GetOrCreateExecutableHandler(
208 entry_
.response_id(), handler_source_buffer_
.get());
209 handler_source_buffer_
= NULL
; // not needed anymore
211 InvokeExecutableHandler(handler
);
215 BeginErrorDelivery("factory failed to produce a handler");
218 void AppCacheURLRequestJob::InvokeExecutableHandler(
219 AppCacheExecutableHandler
* handler
) {
220 handler
->HandleRequest(
222 base::Bind(&AppCacheURLRequestJob::OnExecutableResponseCallback
,
223 weak_factory_
.GetWeakPtr()));
226 void AppCacheURLRequestJob::OnExecutableResponseCallback(
227 const AppCacheExecutableHandler::Response
& response
) {
228 DCHECK(!has_been_killed());
229 if (response
.use_network
) {
230 delivery_type_
= NETWORK_DELIVERY
;
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()) {
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
251 // Otherwise, return an error.
252 BeginErrorDelivery("handler returned an invalid response");
255 void AppCacheURLRequestJob::BeginErrorDelivery(const char* message
) {
257 host_
->frontend()->OnLogMessage(host_
->host_id(), LOG_ERROR
, message
);
258 delivery_type_
= ERROR_DELIVERY
;
263 AppCacheURLRequestJob::~AppCacheURLRequestJob() {
265 storage_
->CancelDelegateCallbacks(this);
268 void AppCacheURLRequestJob::OnResponseInfoLoaded(
269 AppCacheResponseInfo
* response_info
, int64 response_id
) {
270 DCHECK(is_delivering_appcache_response());
271 scoped_refptr
<AppCacheURLRequestJob
> protect(this);
273 info_
= response_info
;
274 reader_
.reset(storage_
->CreateResponseReader(
275 manifest_url_
, group_id_
, entry_
.response_id()));
277 if (is_range_request())
278 SetupRangeResponse();
280 NotifyHeadersComplete();
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();
294 const net::HttpResponseInfo
* AppCacheURLRequestJob::http_info() const {
297 if (range_response_info_
)
298 return range_response_info_
.get();
299 return info_
->http_response_info();
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();
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);
317 // Tell the reader about the range to read.
318 reader_
->SetReadRange(offset
, length
);
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
);
332 base::StringPrintf("%s: %d", kLengthHeader
, length
));
334 base::StringPrintf("%s: bytes %d-%d/%d",
341 void AppCacheURLRequestJob::OnReadComplete(int result
) {
342 DCHECK(is_delivering_appcache_response());
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
));
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);
374 net::URLRequestJob::Kill();
375 weak_factory_
.InvalidateWeakPtrs();
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
;
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
;
393 bool AppCacheURLRequestJob::GetMimeType(std::string
* mime_type
) const {
396 return http_info()->headers
->GetMimeType(mime_type
);
399 bool AppCacheURLRequestJob::GetCharset(std::string
* charset
) {
402 return http_info()->headers
->GetCharset(charset
);
405 void AppCacheURLRequestJob::GetResponseInfo(net::HttpResponseInfo
* info
) {
408 *info
= *http_info();
411 int AppCacheURLRequestJob::GetResponseCode() const {
414 return http_info()->headers
->response_code();
417 bool AppCacheURLRequestJob::ReadRawData(net::IOBuffer
* buf
, int buf_size
,
419 DCHECK(is_delivering_appcache_response());
420 DCHECK_NE(buf_size
, 0);
422 DCHECK(!reader_
->IsReadPending());
424 buf
, buf_size
, base::Bind(&AppCacheURLRequestJob::OnReadComplete
,
425 base::Unretained(this)));
426 SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING
, 0));
430 void AppCacheURLRequestJob::SetExtraRequestHeaders(
431 const net::HttpRequestHeaders
& headers
) {
433 std::vector
<net::HttpByteRange
> ranges
;
434 if (!headers
.GetHeader(net::HttpRequestHeaders::kRange
, &value
) ||
435 !net::HttpUtil::ParseRangeHeader(value
, &ranges
)) {
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];
445 } // namespace appcache