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