service_worker_url_request_job.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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 "base/bind.h"
8#include "base/strings/stringprintf.h"
9#include "content/browser/service_worker/service_worker_fetch_dispatcher.h"
10#include "content/browser/service_worker/service_worker_provider_host.h"
11#include "net/http/http_request_headers.h"
12#include "net/http/http_response_headers.h"
13#include "net/http/http_response_info.h"
14#include "net/http/http_util.h"
15#include "webkit/browser/blob/blob_data_handle.h"
16#include "webkit/browser/blob/blob_storage_context.h"
17#include "webkit/browser/blob/blob_url_request_job_factory.h"
18
19namespace content {
20
21ServiceWorkerURLRequestJob::ServiceWorkerURLRequestJob(
22    net::URLRequest* request,
23    net::NetworkDelegate* network_delegate,
24    base::WeakPtr<ServiceWorkerProviderHost> provider_host,
25    base::WeakPtr<webkit_blob::BlobStorageContext> blob_storage_context)
26    : net::URLRequestJob(request, network_delegate),
27      provider_host_(provider_host),
28      response_type_(NOT_DETERMINED),
29      is_started_(false),
30      blob_storage_context_(blob_storage_context),
31      weak_factory_(this) {
32}
33
34void ServiceWorkerURLRequestJob::FallbackToNetwork() {
35  DCHECK_EQ(NOT_DETERMINED, response_type_);
36  response_type_ = FALLBACK_TO_NETWORK;
37  MaybeStartRequest();
38}
39
40void ServiceWorkerURLRequestJob::ForwardToServiceWorker() {
41  DCHECK_EQ(NOT_DETERMINED, response_type_);
42  response_type_ = FORWARD_TO_SERVICE_WORKER;
43  MaybeStartRequest();
44}
45
46void ServiceWorkerURLRequestJob::Start() {
47  is_started_ = true;
48  MaybeStartRequest();
49}
50
51void ServiceWorkerURLRequestJob::Kill() {
52  net::URLRequestJob::Kill();
53  fetch_dispatcher_.reset();
54  blob_request_.reset();
55  weak_factory_.InvalidateWeakPtrs();
56}
57
58net::LoadState ServiceWorkerURLRequestJob::GetLoadState() const {
59  // TODO(kinuko): refine this for better debug.
60  return net::URLRequestJob::GetLoadState();
61}
62
63bool ServiceWorkerURLRequestJob::GetCharset(std::string* charset) {
64  if (!http_info())
65    return false;
66  return http_info()->headers->GetCharset(charset);
67}
68
69bool ServiceWorkerURLRequestJob::GetMimeType(std::string* mime_type) const {
70  if (!http_info())
71    return false;
72  return http_info()->headers->GetMimeType(mime_type);
73}
74
75void ServiceWorkerURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
76  if (!http_info())
77    return;
78  *info = *http_info();
79}
80
81int ServiceWorkerURLRequestJob::GetResponseCode() const {
82  if (!http_info())
83    return -1;
84  return http_info()->headers->response_code();
85}
86
87void ServiceWorkerURLRequestJob::SetExtraRequestHeaders(
88    const net::HttpRequestHeaders& headers) {
89  std::string range_header;
90  std::vector<net::HttpByteRange> ranges;
91  if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header) ||
92      !net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
93    return;
94  }
95
96  // We don't support multiple range requests in one single URL request.
97  if (ranges.size() == 1U)
98    byte_range_ = ranges[0];
99}
100
101bool ServiceWorkerURLRequestJob::ReadRawData(
102    net::IOBuffer* buf, int buf_size, int *bytes_read) {
103  if (!blob_request_) {
104    *bytes_read = 0;
105    return true;
106  }
107
108  blob_request_->Read(buf, buf_size, bytes_read);
109  net::URLRequestStatus status = blob_request_->status();
110  SetStatus(status);
111  if (status.is_io_pending())
112    return false;
113  return status.is_success();
114}
115
116void ServiceWorkerURLRequestJob::OnReceivedRedirect(net::URLRequest* request,
117                                                    const GURL& new_url,
118                                                    bool* defer_redirect) {
119  NOTREACHED();
120}
121
122void ServiceWorkerURLRequestJob::OnAuthRequired(
123    net::URLRequest* request,
124    net::AuthChallengeInfo* auth_info) {
125  NOTREACHED();
126}
127
128void ServiceWorkerURLRequestJob::OnCertificateRequested(
129    net::URLRequest* request,
130    net::SSLCertRequestInfo* cert_request_info) {
131  NOTREACHED();
132}
133
134void ServiceWorkerURLRequestJob::OnSSLCertificateError(
135    net::URLRequest* request,
136    const net::SSLInfo& ssl_info,
137    bool fatal) {
138  NOTREACHED();
139}
140
141void ServiceWorkerURLRequestJob::OnBeforeNetworkStart(net::URLRequest* request,
142                                                      bool* defer) {
143  NOTREACHED();
144}
145
146void ServiceWorkerURLRequestJob::OnResponseStarted(net::URLRequest* request) {
147  // TODO(falken): Add Content-Length, Content-Type if they were not provided in
148  // the ServiceWorkerResponse.
149  CommitResponseHeader();
150}
151
152void ServiceWorkerURLRequestJob::OnReadCompleted(net::URLRequest* request,
153                                                 int bytes_read) {
154  SetStatus(request->status());
155  if (!request->status().is_success()) {
156    NotifyDone(request->status());
157    return;
158  }
159  NotifyReadComplete(bytes_read);
160  if (bytes_read == 0)
161    NotifyDone(request->status());
162}
163
164const net::HttpResponseInfo* ServiceWorkerURLRequestJob::http_info() const {
165  if (!http_response_info_)
166    return NULL;
167  if (range_response_info_)
168    return range_response_info_.get();
169  return http_response_info_.get();
170}
171
172ServiceWorkerURLRequestJob::~ServiceWorkerURLRequestJob() {
173}
174
175void ServiceWorkerURLRequestJob::MaybeStartRequest() {
176  if (is_started_ && response_type_ != NOT_DETERMINED) {
177    // Start asynchronously.
178    base::MessageLoop::current()->PostTask(
179        FROM_HERE,
180        base::Bind(&ServiceWorkerURLRequestJob::StartRequest,
181                   weak_factory_.GetWeakPtr()));
182  }
183}
184
185void ServiceWorkerURLRequestJob::StartRequest() {
186  switch (response_type_) {
187    case NOT_DETERMINED:
188      NOTREACHED();
189      return;
190
191    case FALLBACK_TO_NETWORK:
192      // Restart the request to create a new job. Our request handler will
193      // return NULL, and the default job (which will hit network) should be
194      // created.
195      NotifyRestartRequired();
196      return;
197
198    case FORWARD_TO_SERVICE_WORKER:
199      DCHECK(provider_host_ && provider_host_->active_version());
200      DCHECK(!fetch_dispatcher_);
201
202      // Send a fetch event to the ServiceWorker associated to the
203      // provider_host.
204      fetch_dispatcher_.reset(new ServiceWorkerFetchDispatcher(
205          request(), provider_host_->active_version(),
206          base::Bind(&ServiceWorkerURLRequestJob::DidDispatchFetchEvent,
207                     weak_factory_.GetWeakPtr())));
208      fetch_dispatcher_->Run();
209      return;
210  }
211
212  NOTREACHED();
213}
214
215void ServiceWorkerURLRequestJob::DidDispatchFetchEvent(
216    ServiceWorkerStatusCode status,
217    ServiceWorkerFetchEventResult fetch_result,
218    const ServiceWorkerResponse& response) {
219  fetch_dispatcher_.reset();
220
221  // Check if we're not orphaned.
222  if (!request())
223    return;
224
225  if (status != SERVICE_WORKER_OK) {
226    // Dispatching event has been failed, falling back to the network.
227    // (Tentative behavior described on github)
228    // TODO(kinuko): consider returning error if we've come here because
229    // unexpected worker termination etc (so that we could fix bugs).
230    // TODO(kinuko): Would be nice to log the error case.
231    response_type_ = FALLBACK_TO_NETWORK;
232    NotifyRestartRequired();
233    return;
234  }
235
236  if (fetch_result == SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK) {
237    // Change the response type and restart the request to fallback to
238    // the network.
239    response_type_ = FALLBACK_TO_NETWORK;
240    NotifyRestartRequired();
241    return;
242  }
243
244  // We should have a response now.
245  DCHECK_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, fetch_result);
246
247  // Set up a request for reading the blob.
248  if (!response.blob_uuid.empty() && blob_storage_context_) {
249    scoped_ptr<webkit_blob::BlobDataHandle> blob_data_handle =
250        blob_storage_context_->GetBlobDataFromUUID(response.blob_uuid);
251    if (!blob_data_handle) {
252      // The renderer gave us a bad blob UUID.
253      DeliverErrorResponse();
254      return;
255    }
256    blob_request_ = webkit_blob::BlobProtocolHandler::CreateBlobRequest(
257        blob_data_handle.Pass(), request()->context(), this);
258    blob_request_->Start();
259  }
260
261  CreateResponseHeader(
262      response.status_code, response.status_text, response.headers);
263  if (!blob_request_)
264    CommitResponseHeader();
265}
266
267void ServiceWorkerURLRequestJob::CreateResponseHeader(
268    int status_code,
269    const std::string& status_text,
270    const std::map<std::string, std::string>& headers) {
271  // TODO(kinuko): If the response has an identifier to on-disk cache entry,
272  // pull response header from the disk.
273  std::string status_line(
274      base::StringPrintf("HTTP/1.1 %d %s", status_code, status_text.c_str()));
275  status_line.push_back('\0');
276  http_response_headers_ = new net::HttpResponseHeaders(status_line);
277  // TODO(gavinp,michaeln): This header should not be exposed to content; we can
278  // either remove it for content or move this data out of a header and in to
279  // the http_response_info_.
280  http_response_headers_->AddHeader("Service-Worker: generated");
281  for (std::map<std::string, std::string>::const_iterator it = headers.begin();
282       it != headers.end();
283       ++it) {
284    std::string header;
285    header.reserve(it->first.size() + 2 + it->second.size());
286    header.append(it->first);
287    header.append(": ");
288    header.append(it->second);
289    http_response_headers_->AddHeader(header);
290  }
291}
292
293void ServiceWorkerURLRequestJob::CommitResponseHeader() {
294  http_response_info_.reset(new net::HttpResponseInfo());
295  http_response_info_->headers.swap(http_response_headers_);
296  NotifyHeadersComplete();
297}
298
299void ServiceWorkerURLRequestJob::DeliverErrorResponse() {
300  // TODO(falken): Print an error to the console of the ServiceWorker and of
301  // the requesting page.
302  CreateResponseHeader(500,
303                       "Service Worker Response Error",
304                       std::map<std::string, std::string>());
305  CommitResponseHeader();
306}
307
308}  // namespace content
309