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