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