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 "base/time/time.h"
15 #include "content/browser/service_worker/service_worker_fetch_dispatcher.h"
16 #include "content/browser/service_worker/service_worker_provider_host.h"
17 #include "content/common/resource_request_body.h"
18 #include "content/common/service_worker/service_worker_types.h"
19 #include "content/public/browser/blob_handle.h"
20 #include "content/public/browser/resource_request_info.h"
21 #include "net/base/net_errors.h"
22 #include "net/http/http_request_headers.h"
23 #include "net/http/http_response_headers.h"
24 #include "net/http/http_response_info.h"
25 #include "net/http/http_util.h"
26 #include "storage/browser/blob/blob_data_handle.h"
27 #include "storage/browser/blob/blob_storage_context.h"
28 #include "storage/browser/blob/blob_url_request_job_factory.h"
29 #include "ui/base/page_transition_types.h"
35 // Keep in sync with kDevToolsRequestInitiator and
36 // kDevToolsEmulateNetworkConditionsClientId defined in
37 // devtools_network_transaction.cc and InspectorResourceAgent.cpp.
38 const char kDevToolsRequestInitiator
[] = "X-DevTools-Request-Initiator";
39 const char kDevToolsEmulateNetworkConditionsClientId
[] =
40 "X-DevTools-Emulate-Network-Conditions-Client-Id";
44 ServiceWorkerURLRequestJob::ServiceWorkerURLRequestJob(
45 net::URLRequest
* request
,
46 net::NetworkDelegate
* network_delegate
,
47 base::WeakPtr
<ServiceWorkerProviderHost
> provider_host
,
48 base::WeakPtr
<storage::BlobStorageContext
> blob_storage_context
,
49 FetchRequestMode request_mode
,
50 FetchCredentialsMode credentials_mode
,
51 RequestContextType request_context_type
,
52 RequestContextFrameType frame_type
,
53 scoped_refptr
<ResourceRequestBody
> body
)
54 : net::URLRequestJob(request
, network_delegate
),
55 provider_host_(provider_host
),
56 response_type_(NOT_DETERMINED
),
58 service_worker_response_type_(blink::WebServiceWorkerResponseTypeDefault
),
59 blob_storage_context_(blob_storage_context
),
60 request_mode_(request_mode
),
61 credentials_mode_(credentials_mode
),
62 request_context_type_(request_context_type
),
63 frame_type_(frame_type
),
64 fall_back_required_(false),
69 void ServiceWorkerURLRequestJob::FallbackToNetwork() {
70 DCHECK_EQ(NOT_DETERMINED
, response_type_
);
71 response_type_
= FALLBACK_TO_NETWORK
;
75 void ServiceWorkerURLRequestJob::ForwardToServiceWorker() {
76 DCHECK_EQ(NOT_DETERMINED
, response_type_
);
77 response_type_
= FORWARD_TO_SERVICE_WORKER
;
81 void ServiceWorkerURLRequestJob::Start() {
86 void ServiceWorkerURLRequestJob::Kill() {
87 net::URLRequestJob::Kill();
88 fetch_dispatcher_
.reset();
89 blob_request_
.reset();
90 weak_factory_
.InvalidateWeakPtrs();
93 net::LoadState
ServiceWorkerURLRequestJob::GetLoadState() const {
94 // TODO(kinuko): refine this for better debug.
95 return net::URLRequestJob::GetLoadState();
98 bool ServiceWorkerURLRequestJob::GetCharset(std::string
* charset
) {
101 return http_info()->headers
->GetCharset(charset
);
104 bool ServiceWorkerURLRequestJob::GetMimeType(std::string
* mime_type
) const {
107 return http_info()->headers
->GetMimeType(mime_type
);
110 void ServiceWorkerURLRequestJob::GetResponseInfo(net::HttpResponseInfo
* info
) {
113 *info
= *http_info();
114 info
->response_time
= response_time_
;
117 void ServiceWorkerURLRequestJob::GetLoadTimingInfo(
118 net::LoadTimingInfo
* load_timing_info
) const {
119 *load_timing_info
= load_timing_info_
;
122 int ServiceWorkerURLRequestJob::GetResponseCode() const {
125 return http_info()->headers
->response_code();
128 void ServiceWorkerURLRequestJob::SetExtraRequestHeaders(
129 const net::HttpRequestHeaders
& headers
) {
130 std::string range_header
;
131 std::vector
<net::HttpByteRange
> ranges
;
132 if (!headers
.GetHeader(net::HttpRequestHeaders::kRange
, &range_header
) ||
133 !net::HttpUtil::ParseRangeHeader(range_header
, &ranges
)) {
137 // We don't support multiple range requests in one single URL request.
138 if (ranges
.size() == 1U)
139 byte_range_
= ranges
[0];
142 bool ServiceWorkerURLRequestJob::ReadRawData(
143 net::IOBuffer
* buf
, int buf_size
, int *bytes_read
) {
144 if (!blob_request_
) {
149 blob_request_
->Read(buf
, buf_size
, bytes_read
);
150 net::URLRequestStatus status
= blob_request_
->status();
152 if (status
.is_io_pending())
154 return status
.is_success();
157 void ServiceWorkerURLRequestJob::OnReceivedRedirect(
158 net::URLRequest
* request
,
159 const net::RedirectInfo
& redirect_info
,
160 bool* defer_redirect
) {
164 void ServiceWorkerURLRequestJob::OnAuthRequired(
165 net::URLRequest
* request
,
166 net::AuthChallengeInfo
* auth_info
) {
170 void ServiceWorkerURLRequestJob::OnCertificateRequested(
171 net::URLRequest
* request
,
172 net::SSLCertRequestInfo
* cert_request_info
) {
176 void ServiceWorkerURLRequestJob::OnSSLCertificateError(
177 net::URLRequest
* request
,
178 const net::SSLInfo
& ssl_info
,
183 void ServiceWorkerURLRequestJob::OnBeforeNetworkStart(net::URLRequest
* request
,
188 void ServiceWorkerURLRequestJob::OnResponseStarted(net::URLRequest
* request
) {
189 // TODO(falken): Add Content-Length, Content-Type if they were not provided in
190 // the ServiceWorkerResponse.
191 response_time_
= base::Time::Now();
192 CommitResponseHeader();
195 void ServiceWorkerURLRequestJob::OnReadCompleted(net::URLRequest
* request
,
197 SetStatus(request
->status());
198 if (!request
->status().is_success()) {
199 NotifyDone(request
->status());
202 NotifyReadComplete(bytes_read
);
204 NotifyDone(request
->status());
207 const net::HttpResponseInfo
* ServiceWorkerURLRequestJob::http_info() const {
208 if (!http_response_info_
)
210 if (range_response_info_
)
211 return range_response_info_
.get();
212 return http_response_info_
.get();
215 void ServiceWorkerURLRequestJob::GetExtraResponseInfo(
216 bool* was_fetched_via_service_worker
,
217 bool* was_fallback_required_by_service_worker
,
218 GURL
* original_url_via_service_worker
,
219 blink::WebServiceWorkerResponseType
* response_type_via_service_worker
,
220 base::TimeTicks
* fetch_start_time
,
221 base::TimeTicks
* fetch_ready_time
,
222 base::TimeTicks
* fetch_end_time
) const {
223 if (response_type_
!= FORWARD_TO_SERVICE_WORKER
) {
224 *was_fetched_via_service_worker
= false;
225 *was_fallback_required_by_service_worker
= false;
226 *original_url_via_service_worker
= GURL();
227 *response_type_via_service_worker
=
228 blink::WebServiceWorkerResponseTypeDefault
;
231 *was_fetched_via_service_worker
= true;
232 *was_fallback_required_by_service_worker
= fall_back_required_
;
233 *original_url_via_service_worker
= response_url_
;
234 *response_type_via_service_worker
= service_worker_response_type_
;
235 *fetch_start_time
= fetch_start_time_
;
236 *fetch_ready_time
= fetch_ready_time_
;
237 *fetch_end_time
= fetch_end_time_
;
241 ServiceWorkerURLRequestJob::~ServiceWorkerURLRequestJob() {
244 void ServiceWorkerURLRequestJob::MaybeStartRequest() {
245 if (is_started_
&& response_type_
!= NOT_DETERMINED
) {
246 // Start asynchronously.
247 base::MessageLoop::current()->PostTask(
249 base::Bind(&ServiceWorkerURLRequestJob::StartRequest
,
250 weak_factory_
.GetWeakPtr()));
254 void ServiceWorkerURLRequestJob::StartRequest() {
255 switch (response_type_
) {
260 case FALLBACK_TO_NETWORK
:
261 // Restart the request to create a new job. Our request handler will
262 // return NULL, and the default job (which will hit network) should be
264 NotifyRestartRequired();
267 case FORWARD_TO_SERVICE_WORKER
:
268 DCHECK(provider_host_
&& provider_host_
->active_version());
269 DCHECK(!fetch_dispatcher_
);
270 // Send a fetch event to the ServiceWorker associated to the
272 fetch_dispatcher_
.reset(new ServiceWorkerFetchDispatcher(
273 CreateFetchRequest(),
274 provider_host_
->active_version(),
275 base::Bind(&ServiceWorkerURLRequestJob::DidPrepareFetchEvent
,
276 weak_factory_
.GetWeakPtr()),
277 base::Bind(&ServiceWorkerURLRequestJob::DidDispatchFetchEvent
,
278 weak_factory_
.GetWeakPtr())));
279 fetch_start_time_
= base::TimeTicks::Now();
280 load_timing_info_
.send_start
= fetch_start_time_
;
281 fetch_dispatcher_
->Run();
288 scoped_ptr
<ServiceWorkerFetchRequest
>
289 ServiceWorkerURLRequestJob::CreateFetchRequest() {
290 std::string blob_uuid
;
291 uint64 blob_size
= 0;
292 CreateRequestBodyBlob(&blob_uuid
, &blob_size
);
293 scoped_ptr
<ServiceWorkerFetchRequest
> request(
294 new ServiceWorkerFetchRequest());
295 request
->mode
= request_mode_
;
296 request
->request_context_type
= request_context_type_
;
297 request
->frame_type
= frame_type_
;
298 request
->url
= request_
->url();
299 request
->method
= request_
->method();
300 const net::HttpRequestHeaders
& headers
= request_
->extra_request_headers();
301 for (net::HttpRequestHeaders::Iterator
it(headers
); it
.GetNext();) {
302 if (it
.name() == kDevToolsRequestInitiator
||
303 it
.name() == kDevToolsEmulateNetworkConditionsClientId
) {
306 request
->headers
[it
.name()] = it
.value();
308 request
->blob_uuid
= blob_uuid
;
309 request
->blob_size
= blob_size
;
310 request
->referrer
= GURL(request_
->referrer());
311 request
->credentials_mode
= credentials_mode_
;
312 const ResourceRequestInfo
* info
= ResourceRequestInfo::ForRequest(request_
);
314 request
->is_reload
= ui::PageTransitionCoreTypeIs(
315 info
->GetPageTransition(), ui::PAGE_TRANSITION_RELOAD
);
317 return request
.Pass();
320 bool ServiceWorkerURLRequestJob::CreateRequestBodyBlob(std::string
* blob_uuid
,
322 if (!body_
.get() || !blob_storage_context_
)
325 std::vector
<const ResourceRequestBody::Element
*> resolved_elements
;
326 for (size_t i
= 0; i
< body_
->elements()->size(); ++i
) {
327 const ResourceRequestBody::Element
& element
= (*body_
->elements())[i
];
328 if (element
.type() != ResourceRequestBody::Element::TYPE_BLOB
) {
329 resolved_elements
.push_back(&element
);
332 scoped_ptr
<storage::BlobDataHandle
> handle
=
333 blob_storage_context_
->GetBlobDataFromUUID(element
.blob_uuid());
334 if (handle
->data()->items().empty())
336 for (size_t i
= 0; i
< handle
->data()->items().size(); ++i
) {
337 const storage::BlobData::Item
& item
= handle
->data()->items().at(i
);
338 DCHECK_NE(storage::BlobData::Item::TYPE_BLOB
, item
.type());
339 resolved_elements
.push_back(&item
);
343 const std::string
uuid(base::GenerateGUID());
344 uint64 total_size
= 0;
345 scoped_refptr
<storage::BlobData
> blob_data
= new storage::BlobData(uuid
);
346 for (size_t i
= 0; i
< resolved_elements
.size(); ++i
) {
347 const ResourceRequestBody::Element
& element
= *resolved_elements
[i
];
348 if (total_size
!= kuint64max
&& element
.length() != kuint64max
)
349 total_size
+= element
.length();
351 total_size
= kuint64max
;
352 switch (element
.type()) {
353 case ResourceRequestBody::Element::TYPE_BYTES
:
354 blob_data
->AppendData(element
.bytes(), element
.length());
356 case ResourceRequestBody::Element::TYPE_FILE
:
357 blob_data
->AppendFile(element
.path(),
360 element
.expected_modification_time());
362 case ResourceRequestBody::Element::TYPE_BLOB
:
363 // Blob elements should be resolved beforehand.
366 case ResourceRequestBody::Element::TYPE_FILE_FILESYSTEM
:
367 blob_data
->AppendFileSystemFile(element
.filesystem_url(),
370 element
.expected_modification_time());
377 request_body_blob_data_handle_
=
378 blob_storage_context_
->AddFinishedBlob(blob_data
.get());
380 *blob_size
= total_size
;
384 void ServiceWorkerURLRequestJob::DidPrepareFetchEvent() {
385 fetch_ready_time_
= base::TimeTicks::Now();
388 void ServiceWorkerURLRequestJob::DidDispatchFetchEvent(
389 ServiceWorkerStatusCode status
,
390 ServiceWorkerFetchEventResult fetch_result
,
391 const ServiceWorkerResponse
& response
) {
392 fetch_dispatcher_
.reset();
394 // Check if we're not orphaned.
398 if (status
!= SERVICE_WORKER_OK
) {
399 // Dispatching event has been failed, falling back to the network.
400 // (Tentative behavior described on github)
401 // TODO(kinuko): consider returning error if we've come here because
402 // unexpected worker termination etc (so that we could fix bugs).
403 // TODO(kinuko): Would be nice to log the error case.
404 response_type_
= FALLBACK_TO_NETWORK
;
405 NotifyRestartRequired();
409 if (fetch_result
== SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK
) {
410 // When the request_mode is |CORS| or |CORS-with-forced-preflight| we can't
411 // simply fallback to the network in the browser process. It is because the
412 // CORS preflight logic is implemented in the renderer. So we returns a
413 // fall_back_required response to the renderer.
414 if (request_mode_
== FETCH_REQUEST_MODE_CORS
||
415 request_mode_
== FETCH_REQUEST_MODE_CORS_WITH_FORCED_PREFLIGHT
) {
416 fall_back_required_
= true;
417 CreateResponseHeader(
418 400, "Service Worker Fallback Required", ServiceWorkerHeaderMap());
419 CommitResponseHeader();
422 // Change the response type and restart the request to fallback to
424 response_type_
= FALLBACK_TO_NETWORK
;
425 NotifyRestartRequired();
429 // We should have a response now.
430 DCHECK_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE
, fetch_result
);
432 // Treat a response whose status is 0 as a Network Error.
433 if (response
.status_code
== 0) {
435 net::URLRequestStatus(net::URLRequestStatus::FAILED
, net::ERR_FAILED
));
439 fetch_end_time_
= base::TimeTicks::Now();
440 load_timing_info_
.send_end
= fetch_end_time_
;
442 // Set up a request for reading the blob.
443 if (!response
.blob_uuid
.empty() && blob_storage_context_
) {
444 scoped_ptr
<storage::BlobDataHandle
> blob_data_handle
=
445 blob_storage_context_
->GetBlobDataFromUUID(response
.blob_uuid
);
446 if (!blob_data_handle
) {
447 // The renderer gave us a bad blob UUID.
448 DeliverErrorResponse();
451 blob_request_
= storage::BlobProtocolHandler::CreateBlobRequest(
452 blob_data_handle
.Pass(), request()->context(), this);
453 blob_request_
->Start();
456 response_url_
= response
.url
;
457 service_worker_response_type_
= response
.response_type
;
458 CreateResponseHeader(
459 response
.status_code
, response
.status_text
, response
.headers
);
460 load_timing_info_
.receive_headers_end
= base::TimeTicks::Now();
462 CommitResponseHeader();
465 void ServiceWorkerURLRequestJob::CreateResponseHeader(
467 const std::string
& status_text
,
468 const ServiceWorkerHeaderMap
& headers
) {
469 // TODO(kinuko): If the response has an identifier to on-disk cache entry,
470 // pull response header from the disk.
471 std::string
status_line(
472 base::StringPrintf("HTTP/1.1 %d %s", status_code
, status_text
.c_str()));
473 status_line
.push_back('\0');
474 http_response_headers_
= new net::HttpResponseHeaders(status_line
);
475 for (ServiceWorkerHeaderMap::const_iterator it
= headers
.begin();
479 header
.reserve(it
->first
.size() + 2 + it
->second
.size());
480 header
.append(it
->first
);
482 header
.append(it
->second
);
483 http_response_headers_
->AddHeader(header
);
487 void ServiceWorkerURLRequestJob::CommitResponseHeader() {
488 http_response_info_
.reset(new net::HttpResponseInfo());
489 http_response_info_
->headers
.swap(http_response_headers_
);
490 NotifyHeadersComplete();
493 void ServiceWorkerURLRequestJob::DeliverErrorResponse() {
494 // TODO(falken): Print an error to the console of the ServiceWorker and of
495 // the requesting page.
496 CreateResponseHeader(
497 500, "Service Worker Response Error", ServiceWorkerHeaderMap());
498 CommitResponseHeader();
501 } // namespace content