• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "content/browser/service_worker/service_worker_url_request_job.h"
6 
7 #include <map>
8 #include <string>
9 #include <vector>
10 
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"
30 
31 namespace content {
32 
ServiceWorkerURLRequestJob(net::URLRequest * request,net::NetworkDelegate * network_delegate,base::WeakPtr<ServiceWorkerProviderHost> provider_host,base::WeakPtr<storage::BlobStorageContext> blob_storage_context,scoped_refptr<ResourceRequestBody> body)33 ServiceWorkerURLRequestJob::ServiceWorkerURLRequestJob(
34     net::URLRequest* request,
35     net::NetworkDelegate* network_delegate,
36     base::WeakPtr<ServiceWorkerProviderHost> provider_host,
37     base::WeakPtr<storage::BlobStorageContext> blob_storage_context,
38     scoped_refptr<ResourceRequestBody> body)
39     : net::URLRequestJob(request, network_delegate),
40       provider_host_(provider_host),
41       response_type_(NOT_DETERMINED),
42       is_started_(false),
43       blob_storage_context_(blob_storage_context),
44       body_(body),
45       weak_factory_(this) {
46 }
47 
FallbackToNetwork()48 void ServiceWorkerURLRequestJob::FallbackToNetwork() {
49   DCHECK_EQ(NOT_DETERMINED, response_type_);
50   response_type_ = FALLBACK_TO_NETWORK;
51   MaybeStartRequest();
52 }
53 
ForwardToServiceWorker()54 void ServiceWorkerURLRequestJob::ForwardToServiceWorker() {
55   DCHECK_EQ(NOT_DETERMINED, response_type_);
56   response_type_ = FORWARD_TO_SERVICE_WORKER;
57   MaybeStartRequest();
58 }
59 
Start()60 void ServiceWorkerURLRequestJob::Start() {
61   is_started_ = true;
62   MaybeStartRequest();
63 }
64 
Kill()65 void ServiceWorkerURLRequestJob::Kill() {
66   net::URLRequestJob::Kill();
67   fetch_dispatcher_.reset();
68   blob_request_.reset();
69   weak_factory_.InvalidateWeakPtrs();
70 }
71 
GetLoadState() const72 net::LoadState ServiceWorkerURLRequestJob::GetLoadState() const {
73   // TODO(kinuko): refine this for better debug.
74   return net::URLRequestJob::GetLoadState();
75 }
76 
GetCharset(std::string * charset)77 bool ServiceWorkerURLRequestJob::GetCharset(std::string* charset) {
78   if (!http_info())
79     return false;
80   return http_info()->headers->GetCharset(charset);
81 }
82 
GetMimeType(std::string * mime_type) const83 bool ServiceWorkerURLRequestJob::GetMimeType(std::string* mime_type) const {
84   if (!http_info())
85     return false;
86   return http_info()->headers->GetMimeType(mime_type);
87 }
88 
GetResponseInfo(net::HttpResponseInfo * info)89 void ServiceWorkerURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
90   if (!http_info())
91     return;
92   *info = *http_info();
93   info->response_time = response_time_;
94 }
95 
GetLoadTimingInfo(net::LoadTimingInfo * load_timing_info) const96 void ServiceWorkerURLRequestJob::GetLoadTimingInfo(
97     net::LoadTimingInfo* load_timing_info) const {
98   *load_timing_info = load_timing_info_;
99 }
100 
GetResponseCode() const101 int ServiceWorkerURLRequestJob::GetResponseCode() const {
102   if (!http_info())
103     return -1;
104   return http_info()->headers->response_code();
105 }
106 
SetExtraRequestHeaders(const net::HttpRequestHeaders & headers)107 void ServiceWorkerURLRequestJob::SetExtraRequestHeaders(
108     const net::HttpRequestHeaders& headers) {
109   std::string range_header;
110   std::vector<net::HttpByteRange> ranges;
111   if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header) ||
112       !net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
113     return;
114   }
115 
116   // We don't support multiple range requests in one single URL request.
117   if (ranges.size() == 1U)
118     byte_range_ = ranges[0];
119 }
120 
ReadRawData(net::IOBuffer * buf,int buf_size,int * bytes_read)121 bool ServiceWorkerURLRequestJob::ReadRawData(
122     net::IOBuffer* buf, int buf_size, int *bytes_read) {
123   if (!blob_request_) {
124     *bytes_read = 0;
125     return true;
126   }
127 
128   blob_request_->Read(buf, buf_size, bytes_read);
129   net::URLRequestStatus status = blob_request_->status();
130   SetStatus(status);
131   if (status.is_io_pending())
132     return false;
133   return status.is_success();
134 }
135 
OnReceivedRedirect(net::URLRequest * request,const net::RedirectInfo & redirect_info,bool * defer_redirect)136 void ServiceWorkerURLRequestJob::OnReceivedRedirect(
137     net::URLRequest* request,
138     const net::RedirectInfo& redirect_info,
139     bool* defer_redirect) {
140   NOTREACHED();
141 }
142 
OnAuthRequired(net::URLRequest * request,net::AuthChallengeInfo * auth_info)143 void ServiceWorkerURLRequestJob::OnAuthRequired(
144     net::URLRequest* request,
145     net::AuthChallengeInfo* auth_info) {
146   NOTREACHED();
147 }
148 
OnCertificateRequested(net::URLRequest * request,net::SSLCertRequestInfo * cert_request_info)149 void ServiceWorkerURLRequestJob::OnCertificateRequested(
150     net::URLRequest* request,
151     net::SSLCertRequestInfo* cert_request_info) {
152   NOTREACHED();
153 }
154 
OnSSLCertificateError(net::URLRequest * request,const net::SSLInfo & ssl_info,bool fatal)155 void ServiceWorkerURLRequestJob::OnSSLCertificateError(
156     net::URLRequest* request,
157     const net::SSLInfo& ssl_info,
158     bool fatal) {
159   NOTREACHED();
160 }
161 
OnBeforeNetworkStart(net::URLRequest * request,bool * defer)162 void ServiceWorkerURLRequestJob::OnBeforeNetworkStart(net::URLRequest* request,
163                                                       bool* defer) {
164   NOTREACHED();
165 }
166 
OnResponseStarted(net::URLRequest * request)167 void ServiceWorkerURLRequestJob::OnResponseStarted(net::URLRequest* request) {
168   // TODO(falken): Add Content-Length, Content-Type if they were not provided in
169   // the ServiceWorkerResponse.
170   response_time_ = base::Time::Now();
171   CommitResponseHeader();
172 }
173 
OnReadCompleted(net::URLRequest * request,int bytes_read)174 void ServiceWorkerURLRequestJob::OnReadCompleted(net::URLRequest* request,
175                                                  int bytes_read) {
176   SetStatus(request->status());
177   if (!request->status().is_success()) {
178     NotifyDone(request->status());
179     return;
180   }
181   NotifyReadComplete(bytes_read);
182   if (bytes_read == 0)
183     NotifyDone(request->status());
184 }
185 
http_info() const186 const net::HttpResponseInfo* ServiceWorkerURLRequestJob::http_info() const {
187   if (!http_response_info_)
188     return NULL;
189   if (range_response_info_)
190     return range_response_info_.get();
191   return http_response_info_.get();
192 }
193 
GetExtraResponseInfo(bool * was_fetched_via_service_worker,GURL * original_url_via_service_worker,base::TimeTicks * fetch_start_time,base::TimeTicks * fetch_ready_time,base::TimeTicks * fetch_end_time) const194 void ServiceWorkerURLRequestJob::GetExtraResponseInfo(
195     bool* was_fetched_via_service_worker,
196     GURL* original_url_via_service_worker,
197     base::TimeTicks* fetch_start_time,
198     base::TimeTicks* fetch_ready_time,
199     base::TimeTicks* fetch_end_time) const {
200   if (response_type_ != FORWARD_TO_SERVICE_WORKER) {
201     *was_fetched_via_service_worker = false;
202     *original_url_via_service_worker = GURL();
203     return;
204   }
205   *was_fetched_via_service_worker = true;
206   *original_url_via_service_worker = response_url_;
207   *fetch_start_time = fetch_start_time_;
208   *fetch_ready_time = fetch_ready_time_;
209   *fetch_end_time = fetch_end_time_;
210 }
211 
212 
~ServiceWorkerURLRequestJob()213 ServiceWorkerURLRequestJob::~ServiceWorkerURLRequestJob() {
214 }
215 
MaybeStartRequest()216 void ServiceWorkerURLRequestJob::MaybeStartRequest() {
217   if (is_started_ && response_type_ != NOT_DETERMINED) {
218     // Start asynchronously.
219     base::MessageLoop::current()->PostTask(
220         FROM_HERE,
221         base::Bind(&ServiceWorkerURLRequestJob::StartRequest,
222                    weak_factory_.GetWeakPtr()));
223   }
224 }
225 
StartRequest()226 void ServiceWorkerURLRequestJob::StartRequest() {
227   switch (response_type_) {
228     case NOT_DETERMINED:
229       NOTREACHED();
230       return;
231 
232     case FALLBACK_TO_NETWORK:
233       // Restart the request to create a new job. Our request handler will
234       // return NULL, and the default job (which will hit network) should be
235       // created.
236       NotifyRestartRequired();
237       return;
238 
239     case FORWARD_TO_SERVICE_WORKER:
240       DCHECK(provider_host_ && provider_host_->active_version());
241       DCHECK(!fetch_dispatcher_);
242       // Send a fetch event to the ServiceWorker associated to the
243       // provider_host.
244       fetch_dispatcher_.reset(new ServiceWorkerFetchDispatcher(
245           CreateFetchRequest(),
246           provider_host_->active_version(),
247           base::Bind(&ServiceWorkerURLRequestJob::DidPrepareFetchEvent,
248                      weak_factory_.GetWeakPtr()),
249           base::Bind(&ServiceWorkerURLRequestJob::DidDispatchFetchEvent,
250                      weak_factory_.GetWeakPtr())));
251       fetch_start_time_ = base::TimeTicks::Now();
252       load_timing_info_.send_start = fetch_start_time_;
253       fetch_dispatcher_->Run();
254       return;
255   }
256 
257   NOTREACHED();
258 }
259 
260 scoped_ptr<ServiceWorkerFetchRequest>
CreateFetchRequest()261 ServiceWorkerURLRequestJob::CreateFetchRequest() {
262   std::string blob_uuid;
263   uint64 blob_size = 0;
264   CreateRequestBodyBlob(&blob_uuid, &blob_size);
265   scoped_ptr<ServiceWorkerFetchRequest> request(
266       new ServiceWorkerFetchRequest());
267 
268   request->url = request_->url();
269   request->method = request_->method();
270   const net::HttpRequestHeaders& headers = request_->extra_request_headers();
271   for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext();)
272     request->headers[it.name()] = it.value();
273   request->blob_uuid = blob_uuid;
274   request->blob_size = blob_size;
275   request->referrer = GURL(request_->referrer());
276   const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request_);
277   if (info) {
278     request->is_reload = ui::PageTransitionCoreTypeIs(
279         info->GetPageTransition(), ui::PAGE_TRANSITION_RELOAD);
280   }
281   return request.Pass();
282 }
283 
CreateRequestBodyBlob(std::string * blob_uuid,uint64 * blob_size)284 bool ServiceWorkerURLRequestJob::CreateRequestBodyBlob(std::string* blob_uuid,
285                                                        uint64* blob_size) {
286   if (!body_.get() || !blob_storage_context_)
287     return false;
288 
289   std::vector<const ResourceRequestBody::Element*> resolved_elements;
290   for (size_t i = 0; i < body_->elements()->size(); ++i) {
291     const ResourceRequestBody::Element& element = (*body_->elements())[i];
292     if (element.type() != ResourceRequestBody::Element::TYPE_BLOB) {
293       resolved_elements.push_back(&element);
294       continue;
295     }
296     scoped_ptr<storage::BlobDataHandle> handle =
297         blob_storage_context_->GetBlobDataFromUUID(element.blob_uuid());
298     if (handle->data()->items().empty())
299       continue;
300     for (size_t i = 0; i < handle->data()->items().size(); ++i) {
301       const storage::BlobData::Item& item = handle->data()->items().at(i);
302       DCHECK_NE(storage::BlobData::Item::TYPE_BLOB, item.type());
303       resolved_elements.push_back(&item);
304     }
305   }
306 
307   const std::string uuid(base::GenerateGUID());
308   uint64 total_size = 0;
309   scoped_refptr<storage::BlobData> blob_data = new storage::BlobData(uuid);
310   for (size_t i = 0; i < resolved_elements.size(); ++i) {
311     const ResourceRequestBody::Element& element = *resolved_elements[i];
312     if (total_size != kuint64max && element.length() != kuint64max)
313       total_size += element.length();
314     else
315       total_size = kuint64max;
316     switch (element.type()) {
317       case ResourceRequestBody::Element::TYPE_BYTES:
318         blob_data->AppendData(element.bytes(), element.length());
319         break;
320       case ResourceRequestBody::Element::TYPE_FILE:
321         blob_data->AppendFile(element.path(),
322                               element.offset(),
323                               element.length(),
324                               element.expected_modification_time());
325         break;
326       case ResourceRequestBody::Element::TYPE_BLOB:
327         // Blob elements should be resolved beforehand.
328         NOTREACHED();
329         break;
330       case ResourceRequestBody::Element::TYPE_FILE_FILESYSTEM:
331         blob_data->AppendFileSystemFile(element.filesystem_url(),
332                                         element.offset(),
333                                         element.length(),
334                                         element.expected_modification_time());
335         break;
336       default:
337         NOTIMPLEMENTED();
338     }
339   }
340 
341   request_body_blob_data_handle_ =
342       blob_storage_context_->AddFinishedBlob(blob_data.get());
343   *blob_uuid = uuid;
344   *blob_size = total_size;
345   return true;
346 }
347 
DidPrepareFetchEvent()348 void ServiceWorkerURLRequestJob::DidPrepareFetchEvent() {
349   fetch_ready_time_ = base::TimeTicks::Now();
350 }
351 
DidDispatchFetchEvent(ServiceWorkerStatusCode status,ServiceWorkerFetchEventResult fetch_result,const ServiceWorkerResponse & response)352 void ServiceWorkerURLRequestJob::DidDispatchFetchEvent(
353     ServiceWorkerStatusCode status,
354     ServiceWorkerFetchEventResult fetch_result,
355     const ServiceWorkerResponse& response) {
356   fetch_dispatcher_.reset();
357 
358   // Check if we're not orphaned.
359   if (!request())
360     return;
361 
362   if (status != SERVICE_WORKER_OK) {
363     // Dispatching event has been failed, falling back to the network.
364     // (Tentative behavior described on github)
365     // TODO(kinuko): consider returning error if we've come here because
366     // unexpected worker termination etc (so that we could fix bugs).
367     // TODO(kinuko): Would be nice to log the error case.
368     response_type_ = FALLBACK_TO_NETWORK;
369     NotifyRestartRequired();
370     return;
371   }
372 
373   if (fetch_result == SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK) {
374     // Change the response type and restart the request to fallback to
375     // the network.
376     response_type_ = FALLBACK_TO_NETWORK;
377     NotifyRestartRequired();
378     return;
379   }
380 
381   // We should have a response now.
382   DCHECK_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, fetch_result);
383 
384   // Treat a response whose status is 0 as a Network Error.
385   if (response.status_code == 0) {
386     NotifyDone(
387         net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED));
388     return;
389   }
390 
391   fetch_end_time_ = base::TimeTicks::Now();
392   load_timing_info_.send_end = fetch_end_time_;
393 
394   // Set up a request for reading the blob.
395   if (!response.blob_uuid.empty() && blob_storage_context_) {
396     scoped_ptr<storage::BlobDataHandle> blob_data_handle =
397         blob_storage_context_->GetBlobDataFromUUID(response.blob_uuid);
398     if (!blob_data_handle) {
399       // The renderer gave us a bad blob UUID.
400       DeliverErrorResponse();
401       return;
402     }
403     blob_request_ = storage::BlobProtocolHandler::CreateBlobRequest(
404         blob_data_handle.Pass(), request()->context(), this);
405     blob_request_->Start();
406   }
407 
408   response_url_ = response.url;
409   CreateResponseHeader(
410       response.status_code, response.status_text, response.headers);
411   load_timing_info_.receive_headers_end = base::TimeTicks::Now();
412   if (!blob_request_)
413     CommitResponseHeader();
414 }
415 
CreateResponseHeader(int status_code,const std::string & status_text,const ServiceWorkerHeaderMap & headers)416 void ServiceWorkerURLRequestJob::CreateResponseHeader(
417     int status_code,
418     const std::string& status_text,
419     const ServiceWorkerHeaderMap& headers) {
420   // TODO(kinuko): If the response has an identifier to on-disk cache entry,
421   // pull response header from the disk.
422   std::string status_line(
423       base::StringPrintf("HTTP/1.1 %d %s", status_code, status_text.c_str()));
424   status_line.push_back('\0');
425   http_response_headers_ = new net::HttpResponseHeaders(status_line);
426   for (ServiceWorkerHeaderMap::const_iterator it = headers.begin();
427        it != headers.end();
428        ++it) {
429     std::string header;
430     header.reserve(it->first.size() + 2 + it->second.size());
431     header.append(it->first);
432     header.append(": ");
433     header.append(it->second);
434     http_response_headers_->AddHeader(header);
435   }
436 }
437 
CommitResponseHeader()438 void ServiceWorkerURLRequestJob::CommitResponseHeader() {
439   http_response_info_.reset(new net::HttpResponseInfo());
440   http_response_info_->headers.swap(http_response_headers_);
441   NotifyHeadersComplete();
442 }
443 
DeliverErrorResponse()444 void ServiceWorkerURLRequestJob::DeliverErrorResponse() {
445   // TODO(falken): Print an error to the console of the ServiceWorker and of
446   // the requesting page.
447   CreateResponseHeader(
448       500, "Service Worker Response Error", ServiceWorkerHeaderMap());
449   CommitResponseHeader();
450 }
451 
452 }  // namespace content
453