1 // Copyright 2014 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/service_worker/service_worker_url_request_job.h"
11 #include "base/bind.h"
12 #include "base/guid.h"
13 #include "base/strings/stringprintf.h"
14 #include "content/browser/service_worker/service_worker_fetch_dispatcher.h"
15 #include "content/browser/service_worker/service_worker_provider_host.h"
16 #include "content/common/resource_request_body.h"
17 #include "content/common/service_worker/service_worker_types.h"
18 #include "content/public/browser/blob_handle.h"
19 #include "content/public/browser/resource_request_info.h"
20 #include "content/public/common/page_transition_types.h"
21 #include "net/http/http_request_headers.h"
22 #include "net/http/http_response_headers.h"
23 #include "net/http/http_response_info.h"
24 #include "net/http/http_util.h"
25 #include "webkit/browser/blob/blob_data_handle.h"
26 #include "webkit/browser/blob/blob_storage_context.h"
27 #include "webkit/browser/blob/blob_url_request_job_factory.h"
31 ServiceWorkerURLRequestJob::ServiceWorkerURLRequestJob(
32 net::URLRequest
* request
,
33 net::NetworkDelegate
* network_delegate
,
34 base::WeakPtr
<ServiceWorkerProviderHost
> provider_host
,
35 base::WeakPtr
<storage::BlobStorageContext
> blob_storage_context
,
36 scoped_refptr
<ResourceRequestBody
> body
)
37 : net::URLRequestJob(request
, network_delegate
),
38 provider_host_(provider_host
),
39 response_type_(NOT_DETERMINED
),
41 blob_storage_context_(blob_storage_context
),
46 void ServiceWorkerURLRequestJob::FallbackToNetwork() {
47 DCHECK_EQ(NOT_DETERMINED
, response_type_
);
48 response_type_
= FALLBACK_TO_NETWORK
;
52 void ServiceWorkerURLRequestJob::ForwardToServiceWorker() {
53 DCHECK_EQ(NOT_DETERMINED
, response_type_
);
54 response_type_
= FORWARD_TO_SERVICE_WORKER
;
58 void ServiceWorkerURLRequestJob::Start() {
63 void ServiceWorkerURLRequestJob::Kill() {
64 net::URLRequestJob::Kill();
65 fetch_dispatcher_
.reset();
66 blob_request_
.reset();
67 weak_factory_
.InvalidateWeakPtrs();
70 net::LoadState
ServiceWorkerURLRequestJob::GetLoadState() const {
71 // TODO(kinuko): refine this for better debug.
72 return net::URLRequestJob::GetLoadState();
75 bool ServiceWorkerURLRequestJob::GetCharset(std::string
* charset
) {
78 return http_info()->headers
->GetCharset(charset
);
81 bool ServiceWorkerURLRequestJob::GetMimeType(std::string
* mime_type
) const {
84 return http_info()->headers
->GetMimeType(mime_type
);
87 void ServiceWorkerURLRequestJob::GetResponseInfo(net::HttpResponseInfo
* info
) {
93 int ServiceWorkerURLRequestJob::GetResponseCode() const {
96 return http_info()->headers
->response_code();
99 void ServiceWorkerURLRequestJob::SetExtraRequestHeaders(
100 const net::HttpRequestHeaders
& headers
) {
101 std::string range_header
;
102 std::vector
<net::HttpByteRange
> ranges
;
103 if (!headers
.GetHeader(net::HttpRequestHeaders::kRange
, &range_header
) ||
104 !net::HttpUtil::ParseRangeHeader(range_header
, &ranges
)) {
108 // We don't support multiple range requests in one single URL request.
109 if (ranges
.size() == 1U)
110 byte_range_
= ranges
[0];
113 bool ServiceWorkerURLRequestJob::ReadRawData(
114 net::IOBuffer
* buf
, int buf_size
, int *bytes_read
) {
115 if (!blob_request_
) {
120 blob_request_
->Read(buf
, buf_size
, bytes_read
);
121 net::URLRequestStatus status
= blob_request_
->status();
123 if (status
.is_io_pending())
125 return status
.is_success();
128 void ServiceWorkerURLRequestJob::OnReceivedRedirect(
129 net::URLRequest
* request
,
130 const net::RedirectInfo
& redirect_info
,
131 bool* defer_redirect
) {
135 void ServiceWorkerURLRequestJob::OnAuthRequired(
136 net::URLRequest
* request
,
137 net::AuthChallengeInfo
* auth_info
) {
141 void ServiceWorkerURLRequestJob::OnCertificateRequested(
142 net::URLRequest
* request
,
143 net::SSLCertRequestInfo
* cert_request_info
) {
147 void ServiceWorkerURLRequestJob::OnSSLCertificateError(
148 net::URLRequest
* request
,
149 const net::SSLInfo
& ssl_info
,
154 void ServiceWorkerURLRequestJob::OnBeforeNetworkStart(net::URLRequest
* request
,
159 void ServiceWorkerURLRequestJob::OnResponseStarted(net::URLRequest
* request
) {
160 // TODO(falken): Add Content-Length, Content-Type if they were not provided in
161 // the ServiceWorkerResponse.
162 CommitResponseHeader();
165 void ServiceWorkerURLRequestJob::OnReadCompleted(net::URLRequest
* request
,
167 SetStatus(request
->status());
168 if (!request
->status().is_success()) {
169 NotifyDone(request
->status());
172 NotifyReadComplete(bytes_read
);
174 NotifyDone(request
->status());
177 const net::HttpResponseInfo
* ServiceWorkerURLRequestJob::http_info() const {
178 if (!http_response_info_
)
180 if (range_response_info_
)
181 return range_response_info_
.get();
182 return http_response_info_
.get();
185 void ServiceWorkerURLRequestJob::GetExtraResponseInfo(
186 bool* was_fetched_via_service_worker
,
187 GURL
* original_url_via_service_worker
) const {
188 if (response_type_
!= FORWARD_TO_SERVICE_WORKER
) {
189 *was_fetched_via_service_worker
= false;
190 *original_url_via_service_worker
= GURL();
193 *was_fetched_via_service_worker
= true;
194 *original_url_via_service_worker
= response_url_
;
198 ServiceWorkerURLRequestJob::~ServiceWorkerURLRequestJob() {
201 void ServiceWorkerURLRequestJob::MaybeStartRequest() {
202 if (is_started_
&& response_type_
!= NOT_DETERMINED
) {
203 // Start asynchronously.
204 base::MessageLoop::current()->PostTask(
206 base::Bind(&ServiceWorkerURLRequestJob::StartRequest
,
207 weak_factory_
.GetWeakPtr()));
211 void ServiceWorkerURLRequestJob::StartRequest() {
212 switch (response_type_
) {
217 case FALLBACK_TO_NETWORK
:
218 // Restart the request to create a new job. Our request handler will
219 // return NULL, and the default job (which will hit network) should be
221 NotifyRestartRequired();
224 case FORWARD_TO_SERVICE_WORKER
:
225 DCHECK(provider_host_
&& provider_host_
->active_version());
226 DCHECK(!fetch_dispatcher_
);
227 // Send a fetch event to the ServiceWorker associated to the
229 fetch_dispatcher_
.reset(new ServiceWorkerFetchDispatcher(
230 CreateFetchRequest(),
231 provider_host_
->active_version(),
232 base::Bind(&ServiceWorkerURLRequestJob::DidPrepareFetchEvent
,
233 weak_factory_
.GetWeakPtr()),
234 base::Bind(&ServiceWorkerURLRequestJob::DidDispatchFetchEvent
,
235 weak_factory_
.GetWeakPtr())));
236 fetch_dispatcher_
->Run();
243 scoped_ptr
<ServiceWorkerFetchRequest
>
244 ServiceWorkerURLRequestJob::CreateFetchRequest() {
245 std::string blob_uuid
;
246 uint64 blob_size
= 0;
247 CreateRequestBodyBlob(&blob_uuid
, &blob_size
);
248 scoped_ptr
<ServiceWorkerFetchRequest
> request(
249 new ServiceWorkerFetchRequest());
251 request
->url
= request_
->url();
252 request
->method
= request_
->method();
253 const net::HttpRequestHeaders
& headers
= request_
->extra_request_headers();
254 for (net::HttpRequestHeaders::Iterator
it(headers
); it
.GetNext();)
255 request
->headers
[it
.name()] = it
.value();
256 request
->blob_uuid
= blob_uuid
;
257 request
->blob_size
= blob_size
;
258 request
->referrer
= GURL(request_
->referrer());
259 const ResourceRequestInfo
* info
= ResourceRequestInfo::ForRequest(request_
);
261 request
->is_reload
= PageTransitionCoreTypeIs(info
->GetPageTransition(),
262 PAGE_TRANSITION_RELOAD
);
264 return request
.Pass();
267 bool ServiceWorkerURLRequestJob::CreateRequestBodyBlob(std::string
* blob_uuid
,
269 if (!body_
.get() || !blob_storage_context_
)
271 const std::string
uuid(base::GenerateGUID());
273 std::vector
<const ResourceRequestBody::Element
*> resolved_elements
;
274 for (size_t i
= 0; i
< body_
->elements()->size(); ++i
) {
275 const ResourceRequestBody::Element
& element
= (*body_
->elements())[i
];
276 if (element
.type() != ResourceRequestBody::Element::TYPE_BLOB
) {
277 resolved_elements
.push_back(&element
);
280 scoped_ptr
<storage::BlobDataHandle
> handle
=
281 blob_storage_context_
->GetBlobDataFromUUID(element
.blob_uuid());
282 if (handle
->data()->items().empty())
284 for (size_t i
= 0; i
< handle
->data()->items().size(); ++i
) {
285 const storage::BlobData::Item
& item
= handle
->data()->items().at(i
);
286 DCHECK_NE(storage::BlobData::Item::TYPE_BLOB
, item
.type());
287 resolved_elements
.push_back(&item
);
290 scoped_refptr
<storage::BlobData
> blob_data
= new storage::BlobData(uuid
);
291 for (size_t i
= 0; i
< resolved_elements
.size(); ++i
) {
292 const ResourceRequestBody::Element
& element
= *resolved_elements
[i
];
293 size
+= element
.length();
294 switch (element
.type()) {
295 case ResourceRequestBody::Element::TYPE_BYTES
:
296 blob_data
->AppendData(element
.bytes(), element
.length());
298 case ResourceRequestBody::Element::TYPE_FILE
:
299 blob_data
->AppendFile(element
.path(),
302 element
.expected_modification_time());
304 case ResourceRequestBody::Element::TYPE_BLOB
:
305 // Blob elements should be resolved beforehand.
308 case ResourceRequestBody::Element::TYPE_FILE_FILESYSTEM
:
309 blob_data
->AppendFileSystemFile(element
.filesystem_url(),
312 element
.expected_modification_time());
319 request_body_blob_data_handle_
=
320 blob_storage_context_
->AddFinishedBlob(blob_data
.get());
326 void ServiceWorkerURLRequestJob::DidPrepareFetchEvent() {
327 // TODO(shimazu): Set the timestamp to measure the time to launch SW
328 // This is related to this (http://crbug.com/401389)
331 void ServiceWorkerURLRequestJob::DidDispatchFetchEvent(
332 ServiceWorkerStatusCode status
,
333 ServiceWorkerFetchEventResult fetch_result
,
334 const ServiceWorkerResponse
& response
) {
335 fetch_dispatcher_
.reset();
337 // Check if we're not orphaned.
341 if (status
!= SERVICE_WORKER_OK
) {
342 // Dispatching event has been failed, falling back to the network.
343 // (Tentative behavior described on github)
344 // TODO(kinuko): consider returning error if we've come here because
345 // unexpected worker termination etc (so that we could fix bugs).
346 // TODO(kinuko): Would be nice to log the error case.
347 response_type_
= FALLBACK_TO_NETWORK
;
348 NotifyRestartRequired();
352 if (fetch_result
== SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK
) {
353 // Change the response type and restart the request to fallback to
355 response_type_
= FALLBACK_TO_NETWORK
;
356 NotifyRestartRequired();
360 // We should have a response now.
361 DCHECK_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE
, fetch_result
);
363 // Set up a request for reading the blob.
364 if (!response
.blob_uuid
.empty() && blob_storage_context_
) {
365 scoped_ptr
<storage::BlobDataHandle
> blob_data_handle
=
366 blob_storage_context_
->GetBlobDataFromUUID(response
.blob_uuid
);
367 if (!blob_data_handle
) {
368 // The renderer gave us a bad blob UUID.
369 DeliverErrorResponse();
372 blob_request_
= storage::BlobProtocolHandler::CreateBlobRequest(
373 blob_data_handle
.Pass(), request()->context(), this);
374 blob_request_
->Start();
377 response_url_
= response
.url
;
378 CreateResponseHeader(
379 response
.status_code
, response
.status_text
, response
.headers
);
381 CommitResponseHeader();
384 void ServiceWorkerURLRequestJob::CreateResponseHeader(
386 const std::string
& status_text
,
387 const std::map
<std::string
, std::string
>& headers
) {
388 // TODO(kinuko): If the response has an identifier to on-disk cache entry,
389 // pull response header from the disk.
390 std::string
status_line(
391 base::StringPrintf("HTTP/1.1 %d %s", status_code
, status_text
.c_str()));
392 status_line
.push_back('\0');
393 http_response_headers_
= new net::HttpResponseHeaders(status_line
);
394 for (std::map
<std::string
, std::string
>::const_iterator it
= headers
.begin();
398 header
.reserve(it
->first
.size() + 2 + it
->second
.size());
399 header
.append(it
->first
);
401 header
.append(it
->second
);
402 http_response_headers_
->AddHeader(header
);
406 void ServiceWorkerURLRequestJob::CommitResponseHeader() {
407 http_response_info_
.reset(new net::HttpResponseInfo());
408 http_response_info_
->headers
.swap(http_response_headers_
);
409 NotifyHeadersComplete();
412 void ServiceWorkerURLRequestJob::DeliverErrorResponse() {
413 // TODO(falken): Print an error to the console of the ServiceWorker and of
414 // the requesting page.
415 CreateResponseHeader(500,
416 "Service Worker Response Error",
417 std::map
<std::string
, std::string
>());
418 CommitResponseHeader();
421 } // namespace content