service_worker_write_to_cache_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_write_to_cache_job.h"
6
7#include "content/browser/service_worker/service_worker_context_core.h"
8#include "content/browser/service_worker/service_worker_disk_cache.h"
9#include "content/browser/service_worker/service_worker_metrics.h"
10#include "net/base/io_buffer.h"
11#include "net/base/net_errors.h"
12#include "net/http/http_request_headers.h"
13#include "net/http/http_response_headers.h"
14#include "net/http/http_util.h"
15#include "net/url_request/url_request.h"
16#include "net/url_request/url_request_context.h"
17#include "net/url_request/url_request_status.h"
18
19namespace content {
20
21ServiceWorkerWriteToCacheJob::ServiceWorkerWriteToCacheJob(
22    net::URLRequest* request,
23    net::NetworkDelegate* network_delegate,
24    ResourceType resource_type,
25    base::WeakPtr<ServiceWorkerContextCore> context,
26    ServiceWorkerVersion* version,
27    int64 response_id)
28    : net::URLRequestJob(request, network_delegate),
29      resource_type_(resource_type),
30      context_(context),
31      url_(request->url()),
32      response_id_(response_id),
33      version_(version),
34      has_been_killed_(false),
35      did_notify_started_(false),
36      did_notify_finished_(false),
37      weak_factory_(this) {
38  InitNetRequest();
39}
40
41ServiceWorkerWriteToCacheJob::~ServiceWorkerWriteToCacheJob() {
42  DCHECK_EQ(did_notify_started_, did_notify_finished_);
43}
44
45void ServiceWorkerWriteToCacheJob::Start() {
46  if (!context_) {
47    NotifyStartError(net::URLRequestStatus(
48        net::URLRequestStatus::FAILED, net::ERR_FAILED));
49    return;
50  }
51  version_->script_cache_map()->NotifyStartedCaching(
52      url_, response_id_);
53  did_notify_started_ = true;
54  StartNetRequest();
55}
56
57void ServiceWorkerWriteToCacheJob::Kill() {
58  if (has_been_killed_)
59    return;
60  weak_factory_.InvalidateWeakPtrs();
61  has_been_killed_ = true;
62  net_request_.reset();
63  if (did_notify_started_ && !did_notify_finished_) {
64    version_->script_cache_map()->NotifyFinishedCaching(
65        url_, false);
66    did_notify_finished_ = true;
67  }
68  writer_.reset();
69  context_.reset();
70  net::URLRequestJob::Kill();
71}
72
73net::LoadState ServiceWorkerWriteToCacheJob::GetLoadState() const {
74  if (writer_ && writer_->IsWritePending())
75    return net::LOAD_STATE_WAITING_FOR_APPCACHE;
76  if (net_request_)
77    return net_request_->GetLoadState().state;
78  return net::LOAD_STATE_IDLE;
79}
80
81bool ServiceWorkerWriteToCacheJob::GetCharset(std::string* charset) {
82  if (!http_info())
83    return false;
84  return http_info()->headers->GetCharset(charset);
85}
86
87bool ServiceWorkerWriteToCacheJob::GetMimeType(std::string* mime_type) const {
88  if (!http_info())
89    return false;
90  return http_info()->headers->GetMimeType(mime_type);
91}
92
93void ServiceWorkerWriteToCacheJob::GetResponseInfo(
94    net::HttpResponseInfo* info) {
95  if (!http_info())
96    return;
97  *info = *http_info();
98}
99
100int ServiceWorkerWriteToCacheJob::GetResponseCode() const {
101  if (!http_info())
102    return -1;
103  return http_info()->headers->response_code();
104}
105
106void ServiceWorkerWriteToCacheJob::SetExtraRequestHeaders(
107      const net::HttpRequestHeaders& headers) {
108  std::string value;
109  DCHECK(!headers.GetHeader(net::HttpRequestHeaders::kRange, &value));
110  net_request_->SetExtraRequestHeaders(headers);
111}
112
113bool ServiceWorkerWriteToCacheJob::ReadRawData(
114    net::IOBuffer* buf,
115    int buf_size,
116    int *bytes_read) {
117  net::URLRequestStatus status = ReadNetData(buf, buf_size, bytes_read);
118  SetStatus(status);
119  if (status.is_io_pending())
120    return false;
121
122  // No more data to process, the job is complete.
123  io_buffer_ = NULL;
124  version_->script_cache_map()->NotifyFinishedCaching(
125      url_, status.is_success());
126  did_notify_finished_ = true;
127  return status.is_success();
128}
129
130const net::HttpResponseInfo* ServiceWorkerWriteToCacheJob::http_info() const {
131  return http_info_.get();
132}
133
134void ServiceWorkerWriteToCacheJob::InitNetRequest() {
135  DCHECK(request());
136  net_request_ = request()->context()->CreateRequest(
137      request()->url(),
138      request()->priority(),
139      this,
140      this->GetCookieStore());
141  net_request_->set_first_party_for_cookies(
142      request()->first_party_for_cookies());
143  net_request_->SetReferrer(request()->referrer());
144
145  if (resource_type_ == RESOURCE_TYPE_SERVICE_WORKER) {
146    // This will get copied into net_request_ when URLRequest::StartJob calls
147    // ServiceWorkerWriteToCacheJob::SetExtraRequestHeaders.
148    request()->SetExtraRequestHeaderByName("Service-Worker", "script", true);
149  }
150}
151
152void ServiceWorkerWriteToCacheJob::StartNetRequest() {
153  net_request_->Start();  // We'll continue in OnResponseStarted.
154}
155
156net::URLRequestStatus ServiceWorkerWriteToCacheJob::ReadNetData(
157    net::IOBuffer* buf,
158    int buf_size,
159    int *bytes_read) {
160  DCHECK_GT(buf_size, 0);
161  DCHECK(bytes_read);
162
163  *bytes_read = 0;
164  io_buffer_ = buf;
165  int net_bytes_read = 0;
166  if (!net_request_->Read(buf, buf_size, &net_bytes_read)) {
167    if (net_request_->status().is_io_pending())
168      return net_request_->status();
169    DCHECK(!net_request_->status().is_success());
170    return net_request_->status();
171  }
172
173  if (net_bytes_read != 0) {
174    WriteDataToCache(net_bytes_read);
175    DCHECK(GetStatus().is_io_pending());
176    return GetStatus();
177  }
178
179  DCHECK(net_request_->status().is_success());
180  return net_request_->status();
181}
182
183void ServiceWorkerWriteToCacheJob::WriteHeadersToCache() {
184  if (!context_) {
185    AsyncNotifyDoneHelper(net::URLRequestStatus(
186        net::URLRequestStatus::FAILED, net::ERR_FAILED));
187    return;
188  }
189  writer_ = context_->storage()->CreateResponseWriter(response_id_);
190  info_buffer_ = new HttpResponseInfoIOBuffer(
191      new net::HttpResponseInfo(net_request_->response_info()));
192  writer_->WriteInfo(
193      info_buffer_,
194      base::Bind(&ServiceWorkerWriteToCacheJob::OnWriteHeadersComplete,
195                 weak_factory_.GetWeakPtr()));
196  SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
197}
198
199void ServiceWorkerWriteToCacheJob::OnWriteHeadersComplete(int result) {
200  SetStatus(net::URLRequestStatus());  // Clear the IO_PENDING status
201  if (result < 0) {
202    ServiceWorkerMetrics::CountWriteResponseResult(
203        ServiceWorkerMetrics::WRITE_HEADERS_ERROR);
204    AsyncNotifyDoneHelper(net::URLRequestStatus(
205        net::URLRequestStatus::FAILED, result));
206    return;
207  }
208  http_info_.reset(info_buffer_->http_info.release());
209  info_buffer_ = NULL;
210  NotifyHeadersComplete();
211}
212
213void ServiceWorkerWriteToCacheJob::WriteDataToCache(int amount_to_write) {
214  DCHECK_NE(0, amount_to_write);
215  SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
216  writer_->WriteData(
217      io_buffer_, amount_to_write,
218      base::Bind(&ServiceWorkerWriteToCacheJob::OnWriteDataComplete,
219                 weak_factory_.GetWeakPtr()));
220}
221
222void ServiceWorkerWriteToCacheJob::OnWriteDataComplete(int result) {
223  DCHECK_NE(0, result);
224  io_buffer_ = NULL;
225  if (!context_) {
226    AsyncNotifyDoneHelper(net::URLRequestStatus(
227        net::URLRequestStatus::FAILED, net::ERR_FAILED));
228    return;
229  }
230  if (result < 0) {
231    ServiceWorkerMetrics::CountWriteResponseResult(
232        ServiceWorkerMetrics::WRITE_DATA_ERROR);
233    AsyncNotifyDoneHelper(net::URLRequestStatus(
234        net::URLRequestStatus::FAILED, result));
235    return;
236  }
237  ServiceWorkerMetrics::CountWriteResponseResult(
238      ServiceWorkerMetrics::WRITE_OK);
239  SetStatus(net::URLRequestStatus());  // Clear the IO_PENDING status
240  NotifyReadComplete(result);
241}
242
243void ServiceWorkerWriteToCacheJob::OnReceivedRedirect(
244    net::URLRequest* request,
245    const GURL& new_url,
246    bool* defer_redirect) {
247  DCHECK_EQ(net_request_, request);
248  // Script resources can't redirect.
249  AsyncNotifyDoneHelper(net::URLRequestStatus(
250      net::URLRequestStatus::FAILED, net::ERR_FAILED));
251}
252
253void ServiceWorkerWriteToCacheJob::OnAuthRequired(
254    net::URLRequest* request,
255    net::AuthChallengeInfo* auth_info) {
256  DCHECK_EQ(net_request_, request);
257  // TODO(michaeln): Pass this thru to our jobs client.
258  AsyncNotifyDoneHelper(net::URLRequestStatus(
259      net::URLRequestStatus::FAILED, net::ERR_FAILED));
260}
261
262void ServiceWorkerWriteToCacheJob::OnCertificateRequested(
263    net::URLRequest* request,
264    net::SSLCertRequestInfo* cert_request_info) {
265  DCHECK_EQ(net_request_, request);
266  // TODO(michaeln): Pass this thru to our jobs client.
267  // see NotifyCertificateRequested.
268  AsyncNotifyDoneHelper(net::URLRequestStatus(
269      net::URLRequestStatus::FAILED, net::ERR_FAILED));
270}
271
272void ServiceWorkerWriteToCacheJob:: OnSSLCertificateError(
273    net::URLRequest* request,
274    const net::SSLInfo& ssl_info,
275    bool fatal) {
276  DCHECK_EQ(net_request_, request);
277  // TODO(michaeln): Pass this thru to our jobs client,
278  // see NotifySSLCertificateError.
279  AsyncNotifyDoneHelper(net::URLRequestStatus(
280      net::URLRequestStatus::FAILED, net::ERR_FAILED));
281}
282
283void ServiceWorkerWriteToCacheJob::OnBeforeNetworkStart(
284    net::URLRequest* request,
285    bool* defer) {
286  DCHECK_EQ(net_request_, request);
287  NotifyBeforeNetworkStart(defer);
288}
289
290void ServiceWorkerWriteToCacheJob::OnResponseStarted(
291    net::URLRequest* request) {
292  DCHECK_EQ(net_request_, request);
293  if (!request->status().is_success()) {
294    AsyncNotifyDoneHelper(request->status());
295    return;
296  }
297  if (request->GetResponseCode() / 100 != 2) {
298    AsyncNotifyDoneHelper(net::URLRequestStatus(
299        net::URLRequestStatus::FAILED, net::ERR_FAILED));
300    // TODO(michaeln): Instead of error'ing immediately, send the net
301    // response to our consumer, just don't cache it?
302    return;
303  }
304  WriteHeadersToCache();
305}
306
307void ServiceWorkerWriteToCacheJob::OnReadCompleted(
308    net::URLRequest* request,
309    int bytes_read) {
310  DCHECK_EQ(net_request_, request);
311  if (!request->status().is_success()) {
312    AsyncNotifyDoneHelper(request->status());
313    return;
314  }
315  if (bytes_read > 0) {
316    WriteDataToCache(bytes_read);
317    return;
318  }
319  // We're done with all.
320  AsyncNotifyDoneHelper(request->status());
321  return;
322}
323
324void ServiceWorkerWriteToCacheJob::AsyncNotifyDoneHelper(
325    const net::URLRequestStatus& status) {
326  DCHECK(!status.is_io_pending());
327  version_->script_cache_map()->NotifyFinishedCaching(
328      url_, status.is_success());
329  did_notify_finished_ = true;
330  SetStatus(status);
331  NotifyDone(status);
332}
333
334}  // namespace content
335