service_worker_url_request_job.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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
16namespace content {
17
18ServiceWorkerURLRequestJob::ServiceWorkerURLRequestJob(
19    net::URLRequest* request,
20    net::NetworkDelegate* network_delegate,
21    base::WeakPtr<ServiceWorkerProviderHost> provider_host)
22    : net::URLRequestJob(request, network_delegate),
23      provider_host_(provider_host),
24      response_type_(NOT_DETERMINED),
25      is_started_(false),
26      weak_factory_(this) {
27}
28
29void ServiceWorkerURLRequestJob::FallbackToNetwork() {
30  DCHECK_EQ(NOT_DETERMINED, response_type_);
31  response_type_ = FALLBACK_TO_NETWORK;
32  MaybeStartRequest();
33}
34
35void ServiceWorkerURLRequestJob::ForwardToServiceWorker() {
36  DCHECK_EQ(NOT_DETERMINED, response_type_);
37  response_type_ = FORWARD_TO_SERVICE_WORKER;
38  MaybeStartRequest();
39}
40
41void ServiceWorkerURLRequestJob::Start() {
42  is_started_ = true;
43  MaybeStartRequest();
44}
45
46void ServiceWorkerURLRequestJob::Kill() {
47  net::URLRequestJob::Kill();
48  fetch_dispatcher_.reset();
49  weak_factory_.InvalidateWeakPtrs();
50}
51
52net::LoadState ServiceWorkerURLRequestJob::GetLoadState() const {
53  // TODO(kinuko): refine this for better debug.
54  return net::URLRequestJob::GetLoadState();
55}
56
57bool ServiceWorkerURLRequestJob::GetCharset(std::string* charset) {
58  if (!http_info())
59    return false;
60  return http_info()->headers->GetCharset(charset);
61}
62
63bool ServiceWorkerURLRequestJob::GetMimeType(std::string* mime_type) const {
64  if (!http_info())
65    return false;
66  return http_info()->headers->GetMimeType(mime_type);
67}
68
69void ServiceWorkerURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
70  if (!http_info())
71    return;
72  *info = *http_info();
73}
74
75int ServiceWorkerURLRequestJob::GetResponseCode() const {
76  if (!http_info())
77    return -1;
78  return http_info()->headers->response_code();
79}
80
81void ServiceWorkerURLRequestJob::SetExtraRequestHeaders(
82    const net::HttpRequestHeaders& headers) {
83  std::string range_header;
84  std::vector<net::HttpByteRange> ranges;
85  if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header) ||
86      !net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
87    return;
88  }
89
90  // We don't support multiple range requests in one single URL request.
91  if (ranges.size() == 1U)
92    byte_range_ = ranges[0];
93}
94
95bool ServiceWorkerURLRequestJob::ReadRawData(
96    net::IOBuffer* buf, int buf_size, int *bytes_read) {
97  // TODO(kinuko): Implement this.
98  // If the response returned from ServiceWorker had an
99  // identifier to on-disk data (e.g. blob or cache entry) we'll need to
100  // pull the body from disk.
101  NOTIMPLEMENTED();
102  *bytes_read = 0;
103  return true;
104}
105
106const net::HttpResponseInfo* ServiceWorkerURLRequestJob::http_info() const {
107  if (!http_response_info_)
108    return NULL;
109  if (range_response_info_)
110    return range_response_info_.get();
111  return http_response_info_.get();
112}
113
114ServiceWorkerURLRequestJob::~ServiceWorkerURLRequestJob() {
115}
116
117void ServiceWorkerURLRequestJob::MaybeStartRequest() {
118  if (is_started_ && response_type_ != NOT_DETERMINED) {
119    // Start asynchronously.
120    base::MessageLoop::current()->PostTask(
121        FROM_HERE,
122        base::Bind(&ServiceWorkerURLRequestJob::StartRequest,
123                   weak_factory_.GetWeakPtr()));
124  }
125}
126
127void ServiceWorkerURLRequestJob::StartRequest() {
128  switch (response_type_) {
129    case NOT_DETERMINED:
130      NOTREACHED();
131      return;
132
133    case FALLBACK_TO_NETWORK:
134      // Restart the request to create a new job. Our request handler will
135      // return NULL, and the default job (which will hit network) should be
136      // created.
137      NotifyRestartRequired();
138      return;
139
140    case FORWARD_TO_SERVICE_WORKER:
141      DCHECK(provider_host_ && provider_host_->associated_version());
142      DCHECK(!fetch_dispatcher_);
143
144      // Send a fetch event to the ServiceWorker associated to the
145      // provider_host.
146      fetch_dispatcher_.reset(new ServiceWorkerFetchDispatcher(
147          request(), provider_host_->associated_version(),
148          base::Bind(&ServiceWorkerURLRequestJob::DidDispatchFetchEvent,
149                     weak_factory_.GetWeakPtr())));
150      fetch_dispatcher_->Run();
151      return;
152  }
153
154  NOTREACHED();
155}
156
157void ServiceWorkerURLRequestJob::DidDispatchFetchEvent(
158    ServiceWorkerStatusCode status,
159    ServiceWorkerFetchEventResult fetch_result,
160    const ServiceWorkerResponse& response) {
161  fetch_dispatcher_.reset();
162
163  // Check if we're not orphaned.
164  if (!request())
165    return;
166
167  if (status != SERVICE_WORKER_OK) {
168    // Dispatching event has been failed, falling back to the network.
169    // (Tentative behavior described on github)
170    // TODO(kinuko): consider returning error if we've come here because
171    // unexpected worker termination etc (so that we could fix bugs).
172    // TODO(kinuko): Would be nice to log the error case.
173    response_type_ = FALLBACK_TO_NETWORK;
174    NotifyRestartRequired();
175  }
176
177  if (fetch_result == SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK) {
178    // Change the response type and restart the request to fallback to
179    // the network.
180    response_type_ = FALLBACK_TO_NETWORK;
181    NotifyRestartRequired();
182    return;
183  }
184
185  // We should have response now.
186  DCHECK_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, fetch_result);
187
188  CreateResponseHeader(response);
189  NotifyHeadersComplete();
190}
191
192void ServiceWorkerURLRequestJob::CreateResponseHeader(
193    const ServiceWorkerResponse& response) {
194  // TODO(kinuko): If the response has an identifier to on-disk cache entry,
195  // pull response header from the disk.
196  std::string status_line(base::StringPrintf("HTTP/1.1 %d %s",
197                                             response.status_code,
198                                             response.status_text.c_str()));
199  status_line.push_back('\0');
200  scoped_refptr<net::HttpResponseHeaders> headers(
201      new net::HttpResponseHeaders(status_line));
202  for (std::map<std::string, std::string>::const_iterator it =
203           response.headers.begin();
204       it != response.headers.end(); ++it) {
205    std::string header;
206    header.reserve(it->first.size() + 2 + it->second.size());
207    header.append(it->first);
208    header.append(": ");
209    header.append(it->second);
210    headers->AddHeader(header);
211  }
212
213  http_response_info_.reset(new net::HttpResponseInfo());
214  http_response_info_->headers = headers;
215}
216
217}  // namespace content
218