service_worker_url_request_job.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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(net::URLRequest* request,
121                                                    const GURL& new_url,
122                                                    bool* defer_redirect) {
123  NOTREACHED();
124}
125
126void ServiceWorkerURLRequestJob::OnAuthRequired(
127    net::URLRequest* request,
128    net::AuthChallengeInfo* auth_info) {
129  NOTREACHED();
130}
131
132void ServiceWorkerURLRequestJob::OnCertificateRequested(
133    net::URLRequest* request,
134    net::SSLCertRequestInfo* cert_request_info) {
135  NOTREACHED();
136}
137
138void ServiceWorkerURLRequestJob::OnSSLCertificateError(
139    net::URLRequest* request,
140    const net::SSLInfo& ssl_info,
141    bool fatal) {
142  NOTREACHED();
143}
144
145void ServiceWorkerURLRequestJob::OnBeforeNetworkStart(net::URLRequest* request,
146                                                      bool* defer) {
147  NOTREACHED();
148}
149
150void ServiceWorkerURLRequestJob::OnResponseStarted(net::URLRequest* request) {
151  // TODO(falken): Add Content-Length, Content-Type if they were not provided in
152  // the ServiceWorkerResponse.
153  CommitResponseHeader();
154}
155
156void ServiceWorkerURLRequestJob::OnReadCompleted(net::URLRequest* request,
157                                                 int bytes_read) {
158  SetStatus(request->status());
159  if (!request->status().is_success()) {
160    NotifyDone(request->status());
161    return;
162  }
163  NotifyReadComplete(bytes_read);
164  if (bytes_read == 0)
165    NotifyDone(request->status());
166}
167
168const net::HttpResponseInfo* ServiceWorkerURLRequestJob::http_info() const {
169  if (!http_response_info_)
170    return NULL;
171  if (range_response_info_)
172    return range_response_info_.get();
173  return http_response_info_.get();
174}
175
176void ServiceWorkerURLRequestJob::GetExtraResponseInfo(
177    bool* was_fetched_via_service_worker,
178    GURL* original_url_via_service_worker) const {
179  if (response_type_ != FORWARD_TO_SERVICE_WORKER) {
180    *was_fetched_via_service_worker = false;
181    *original_url_via_service_worker = GURL();
182    return;
183  }
184  *was_fetched_via_service_worker = true;
185  *original_url_via_service_worker = response_url_;
186}
187
188
189ServiceWorkerURLRequestJob::~ServiceWorkerURLRequestJob() {
190}
191
192void ServiceWorkerURLRequestJob::MaybeStartRequest() {
193  if (is_started_ && response_type_ != NOT_DETERMINED) {
194    // Start asynchronously.
195    base::MessageLoop::current()->PostTask(
196        FROM_HERE,
197        base::Bind(&ServiceWorkerURLRequestJob::StartRequest,
198                   weak_factory_.GetWeakPtr()));
199  }
200}
201
202void ServiceWorkerURLRequestJob::StartRequest() {
203  switch (response_type_) {
204    case NOT_DETERMINED:
205      NOTREACHED();
206      return;
207
208    case FALLBACK_TO_NETWORK:
209      // Restart the request to create a new job. Our request handler will
210      // return NULL, and the default job (which will hit network) should be
211      // created.
212      NotifyRestartRequired();
213      return;
214
215    case FORWARD_TO_SERVICE_WORKER:
216      DCHECK(provider_host_ && provider_host_->active_version());
217      DCHECK(!fetch_dispatcher_);
218
219      // Send a fetch event to the ServiceWorker associated to the
220      // provider_host.
221      fetch_dispatcher_.reset(new ServiceWorkerFetchDispatcher(
222          request(), provider_host_->active_version(),
223          base::Bind(&ServiceWorkerURLRequestJob::DidDispatchFetchEvent,
224                     weak_factory_.GetWeakPtr())));
225      fetch_dispatcher_->Run();
226      return;
227  }
228
229  NOTREACHED();
230}
231
232void ServiceWorkerURLRequestJob::DidDispatchFetchEvent(
233    ServiceWorkerStatusCode status,
234    ServiceWorkerFetchEventResult fetch_result,
235    const ServiceWorkerResponse& response) {
236  fetch_dispatcher_.reset();
237
238  // Check if we're not orphaned.
239  if (!request())
240    return;
241
242  if (status != SERVICE_WORKER_OK) {
243    // Dispatching event has been failed, falling back to the network.
244    // (Tentative behavior described on github)
245    // TODO(kinuko): consider returning error if we've come here because
246    // unexpected worker termination etc (so that we could fix bugs).
247    // TODO(kinuko): Would be nice to log the error case.
248    response_type_ = FALLBACK_TO_NETWORK;
249    NotifyRestartRequired();
250    return;
251  }
252
253  if (fetch_result == SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK) {
254    // Change the response type and restart the request to fallback to
255    // the network.
256    response_type_ = FALLBACK_TO_NETWORK;
257    NotifyRestartRequired();
258    return;
259  }
260
261  // We should have a response now.
262  DCHECK_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, fetch_result);
263
264  // Set up a request for reading the blob.
265  if (!response.blob_uuid.empty() && blob_storage_context_) {
266    scoped_ptr<webkit_blob::BlobDataHandle> blob_data_handle =
267        blob_storage_context_->GetBlobDataFromUUID(response.blob_uuid);
268    if (!blob_data_handle) {
269      // The renderer gave us a bad blob UUID.
270      DeliverErrorResponse();
271      return;
272    }
273    blob_request_ = webkit_blob::BlobProtocolHandler::CreateBlobRequest(
274        blob_data_handle.Pass(), request()->context(), this);
275    blob_request_->Start();
276  }
277
278  response_url_ = response.url;
279  CreateResponseHeader(
280      response.status_code, response.status_text, response.headers);
281  if (!blob_request_)
282    CommitResponseHeader();
283}
284
285void ServiceWorkerURLRequestJob::CreateResponseHeader(
286    int status_code,
287    const std::string& status_text,
288    const std::map<std::string, std::string>& headers) {
289  // TODO(kinuko): If the response has an identifier to on-disk cache entry,
290  // pull response header from the disk.
291  std::string status_line(
292      base::StringPrintf("HTTP/1.1 %d %s", status_code, status_text.c_str()));
293  status_line.push_back('\0');
294  http_response_headers_ = new net::HttpResponseHeaders(status_line);
295  for (std::map<std::string, std::string>::const_iterator it = headers.begin();
296       it != headers.end();
297       ++it) {
298    std::string header;
299    header.reserve(it->first.size() + 2 + it->second.size());
300    header.append(it->first);
301    header.append(": ");
302    header.append(it->second);
303    http_response_headers_->AddHeader(header);
304  }
305}
306
307void ServiceWorkerURLRequestJob::CommitResponseHeader() {
308  http_response_info_.reset(new net::HttpResponseInfo());
309  http_response_info_->headers.swap(http_response_headers_);
310  NotifyHeadersComplete();
311}
312
313void ServiceWorkerURLRequestJob::DeliverErrorResponse() {
314  // TODO(falken): Print an error to the console of the ServiceWorker and of
315  // the requesting page.
316  CreateResponseHeader(500,
317                       "Service Worker Response Error",
318                       std::map<std::string, std::string>());
319  CommitResponseHeader();
320}
321
322}  // namespace content
323