service_worker_url_request_job.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
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/guid.h" 13#include "base/strings/stringprintf.h" 14#include "content/browser/service_worker/service_worker_fetch_dispatcher.h" 15#include "content/browser/service_worker/service_worker_provider_host.h" 16#include "content/common/resource_request_body.h" 17#include "content/common/service_worker/service_worker_types.h" 18#include "content/public/browser/blob_handle.h" 19#include "content/public/browser/resource_request_info.h" 20#include "content/public/common/page_transition_types.h" 21#include "net/http/http_request_headers.h" 22#include "net/http/http_response_headers.h" 23#include "net/http/http_response_info.h" 24#include "net/http/http_util.h" 25#include "webkit/browser/blob/blob_data_handle.h" 26#include "webkit/browser/blob/blob_storage_context.h" 27#include "webkit/browser/blob/blob_url_request_job_factory.h" 28 29namespace content { 30 31ServiceWorkerURLRequestJob::ServiceWorkerURLRequestJob( 32 net::URLRequest* request, 33 net::NetworkDelegate* network_delegate, 34 base::WeakPtr<ServiceWorkerProviderHost> provider_host, 35 base::WeakPtr<storage::BlobStorageContext> blob_storage_context, 36 scoped_refptr<ResourceRequestBody> body) 37 : net::URLRequestJob(request, network_delegate), 38 provider_host_(provider_host), 39 response_type_(NOT_DETERMINED), 40 is_started_(false), 41 blob_storage_context_(blob_storage_context), 42 body_(body), 43 weak_factory_(this) { 44} 45 46void ServiceWorkerURLRequestJob::FallbackToNetwork() { 47 DCHECK_EQ(NOT_DETERMINED, response_type_); 48 response_type_ = FALLBACK_TO_NETWORK; 49 MaybeStartRequest(); 50} 51 52void ServiceWorkerURLRequestJob::ForwardToServiceWorker() { 53 DCHECK_EQ(NOT_DETERMINED, response_type_); 54 response_type_ = FORWARD_TO_SERVICE_WORKER; 55 MaybeStartRequest(); 56} 57 58void ServiceWorkerURLRequestJob::Start() { 59 is_started_ = true; 60 MaybeStartRequest(); 61} 62 63void ServiceWorkerURLRequestJob::Kill() { 64 net::URLRequestJob::Kill(); 65 fetch_dispatcher_.reset(); 66 blob_request_.reset(); 67 weak_factory_.InvalidateWeakPtrs(); 68} 69 70net::LoadState ServiceWorkerURLRequestJob::GetLoadState() const { 71 // TODO(kinuko): refine this for better debug. 72 return net::URLRequestJob::GetLoadState(); 73} 74 75bool ServiceWorkerURLRequestJob::GetCharset(std::string* charset) { 76 if (!http_info()) 77 return false; 78 return http_info()->headers->GetCharset(charset); 79} 80 81bool ServiceWorkerURLRequestJob::GetMimeType(std::string* mime_type) const { 82 if (!http_info()) 83 return false; 84 return http_info()->headers->GetMimeType(mime_type); 85} 86 87void ServiceWorkerURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) { 88 if (!http_info()) 89 return; 90 *info = *http_info(); 91} 92 93int ServiceWorkerURLRequestJob::GetResponseCode() const { 94 if (!http_info()) 95 return -1; 96 return http_info()->headers->response_code(); 97} 98 99void ServiceWorkerURLRequestJob::SetExtraRequestHeaders( 100 const net::HttpRequestHeaders& headers) { 101 std::string range_header; 102 std::vector<net::HttpByteRange> ranges; 103 if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header) || 104 !net::HttpUtil::ParseRangeHeader(range_header, &ranges)) { 105 return; 106 } 107 108 // We don't support multiple range requests in one single URL request. 109 if (ranges.size() == 1U) 110 byte_range_ = ranges[0]; 111} 112 113bool ServiceWorkerURLRequestJob::ReadRawData( 114 net::IOBuffer* buf, int buf_size, int *bytes_read) { 115 if (!blob_request_) { 116 *bytes_read = 0; 117 return true; 118 } 119 120 blob_request_->Read(buf, buf_size, bytes_read); 121 net::URLRequestStatus status = blob_request_->status(); 122 SetStatus(status); 123 if (status.is_io_pending()) 124 return false; 125 return status.is_success(); 126} 127 128void ServiceWorkerURLRequestJob::OnReceivedRedirect( 129 net::URLRequest* request, 130 const net::RedirectInfo& redirect_info, 131 bool* defer_redirect) { 132 NOTREACHED(); 133} 134 135void ServiceWorkerURLRequestJob::OnAuthRequired( 136 net::URLRequest* request, 137 net::AuthChallengeInfo* auth_info) { 138 NOTREACHED(); 139} 140 141void ServiceWorkerURLRequestJob::OnCertificateRequested( 142 net::URLRequest* request, 143 net::SSLCertRequestInfo* cert_request_info) { 144 NOTREACHED(); 145} 146 147void ServiceWorkerURLRequestJob::OnSSLCertificateError( 148 net::URLRequest* request, 149 const net::SSLInfo& ssl_info, 150 bool fatal) { 151 NOTREACHED(); 152} 153 154void ServiceWorkerURLRequestJob::OnBeforeNetworkStart(net::URLRequest* request, 155 bool* defer) { 156 NOTREACHED(); 157} 158 159void ServiceWorkerURLRequestJob::OnResponseStarted(net::URLRequest* request) { 160 // TODO(falken): Add Content-Length, Content-Type if they were not provided in 161 // the ServiceWorkerResponse. 162 CommitResponseHeader(); 163} 164 165void ServiceWorkerURLRequestJob::OnReadCompleted(net::URLRequest* request, 166 int bytes_read) { 167 SetStatus(request->status()); 168 if (!request->status().is_success()) { 169 NotifyDone(request->status()); 170 return; 171 } 172 NotifyReadComplete(bytes_read); 173 if (bytes_read == 0) 174 NotifyDone(request->status()); 175} 176 177const net::HttpResponseInfo* ServiceWorkerURLRequestJob::http_info() const { 178 if (!http_response_info_) 179 return NULL; 180 if (range_response_info_) 181 return range_response_info_.get(); 182 return http_response_info_.get(); 183} 184 185void ServiceWorkerURLRequestJob::GetExtraResponseInfo( 186 bool* was_fetched_via_service_worker, 187 GURL* original_url_via_service_worker) const { 188 if (response_type_ != FORWARD_TO_SERVICE_WORKER) { 189 *was_fetched_via_service_worker = false; 190 *original_url_via_service_worker = GURL(); 191 return; 192 } 193 *was_fetched_via_service_worker = true; 194 *original_url_via_service_worker = response_url_; 195} 196 197 198ServiceWorkerURLRequestJob::~ServiceWorkerURLRequestJob() { 199} 200 201void ServiceWorkerURLRequestJob::MaybeStartRequest() { 202 if (is_started_ && response_type_ != NOT_DETERMINED) { 203 // Start asynchronously. 204 base::MessageLoop::current()->PostTask( 205 FROM_HERE, 206 base::Bind(&ServiceWorkerURLRequestJob::StartRequest, 207 weak_factory_.GetWeakPtr())); 208 } 209} 210 211void ServiceWorkerURLRequestJob::StartRequest() { 212 switch (response_type_) { 213 case NOT_DETERMINED: 214 NOTREACHED(); 215 return; 216 217 case FALLBACK_TO_NETWORK: 218 // Restart the request to create a new job. Our request handler will 219 // return NULL, and the default job (which will hit network) should be 220 // created. 221 NotifyRestartRequired(); 222 return; 223 224 case FORWARD_TO_SERVICE_WORKER: 225 DCHECK(provider_host_ && provider_host_->active_version()); 226 DCHECK(!fetch_dispatcher_); 227 // Send a fetch event to the ServiceWorker associated to the 228 // provider_host. 229 fetch_dispatcher_.reset(new ServiceWorkerFetchDispatcher( 230 CreateFetchRequest(), 231 provider_host_->active_version(), 232 base::Bind(&ServiceWorkerURLRequestJob::DidDispatchFetchEvent, 233 weak_factory_.GetWeakPtr()))); 234 fetch_dispatcher_->Run(); 235 return; 236 } 237 238 NOTREACHED(); 239} 240 241scoped_ptr<ServiceWorkerFetchRequest> 242ServiceWorkerURLRequestJob::CreateFetchRequest() { 243 std::string blob_uuid; 244 uint64 blob_size = 0; 245 CreateRequestBodyBlob(&blob_uuid, &blob_size); 246 scoped_ptr<ServiceWorkerFetchRequest> request( 247 new ServiceWorkerFetchRequest()); 248 249 request->url = request_->url(); 250 request->method = request_->method(); 251 const net::HttpRequestHeaders& headers = request_->extra_request_headers(); 252 for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext();) 253 request->headers[it.name()] = it.value(); 254 request->blob_uuid = blob_uuid; 255 request->blob_size = blob_size; 256 request->referrer = GURL(request_->referrer()); 257 const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request_); 258 if (info) { 259 request->is_reload = PageTransitionCoreTypeIs(info->GetPageTransition(), 260 PAGE_TRANSITION_RELOAD); 261 } 262 return request.Pass(); 263} 264 265bool ServiceWorkerURLRequestJob::CreateRequestBodyBlob(std::string* blob_uuid, 266 uint64* blob_size) { 267 if (!body_ || !blob_storage_context_) 268 return false; 269 const std::string uuid(base::GenerateGUID()); 270 uint64 size = 0; 271 std::vector<const ResourceRequestBody::Element*> resolved_elements; 272 for (size_t i = 0; i < body_->elements()->size(); ++i) { 273 const ResourceRequestBody::Element& element = (*body_->elements())[i]; 274 if (element.type() != ResourceRequestBody::Element::TYPE_BLOB) { 275 resolved_elements.push_back(&element); 276 continue; 277 } 278 scoped_ptr<storage::BlobDataHandle> handle = 279 blob_storage_context_->GetBlobDataFromUUID(element.blob_uuid()); 280 if (handle->data()->items().empty()) 281 continue; 282 for (size_t i = 0; i < handle->data()->items().size(); ++i) { 283 const storage::BlobData::Item& item = handle->data()->items().at(i); 284 DCHECK_NE(storage::BlobData::Item::TYPE_BLOB, item.type()); 285 resolved_elements.push_back(&item); 286 } 287 } 288 scoped_refptr<storage::BlobData> blob_data = new storage::BlobData(uuid); 289 for (size_t i = 0; i < resolved_elements.size(); ++i) { 290 const ResourceRequestBody::Element& element = *resolved_elements[i]; 291 size += element.length(); 292 switch (element.type()) { 293 case ResourceRequestBody::Element::TYPE_BYTES: 294 blob_data->AppendData(element.bytes(), element.length()); 295 break; 296 case ResourceRequestBody::Element::TYPE_FILE: 297 blob_data->AppendFile(element.path(), 298 element.offset(), 299 element.length(), 300 element.expected_modification_time()); 301 break; 302 case ResourceRequestBody::Element::TYPE_BLOB: 303 // Blob elements should be resolved beforehand. 304 NOTREACHED(); 305 break; 306 case ResourceRequestBody::Element::TYPE_FILE_FILESYSTEM: 307 blob_data->AppendFileSystemFile(element.filesystem_url(), 308 element.offset(), 309 element.length(), 310 element.expected_modification_time()); 311 break; 312 default: 313 NOTIMPLEMENTED(); 314 } 315 } 316 317 request_body_blob_data_handle_ = 318 blob_storage_context_->AddFinishedBlob(blob_data.get()); 319 *blob_uuid = uuid; 320 *blob_size = size; 321 return true; 322} 323 324void ServiceWorkerURLRequestJob::DidDispatchFetchEvent( 325 ServiceWorkerStatusCode status, 326 ServiceWorkerFetchEventResult fetch_result, 327 const ServiceWorkerResponse& response) { 328 fetch_dispatcher_.reset(); 329 330 // Check if we're not orphaned. 331 if (!request()) 332 return; 333 334 if (status != SERVICE_WORKER_OK) { 335 // Dispatching event has been failed, falling back to the network. 336 // (Tentative behavior described on github) 337 // TODO(kinuko): consider returning error if we've come here because 338 // unexpected worker termination etc (so that we could fix bugs). 339 // TODO(kinuko): Would be nice to log the error case. 340 response_type_ = FALLBACK_TO_NETWORK; 341 NotifyRestartRequired(); 342 return; 343 } 344 345 if (fetch_result == SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK) { 346 // Change the response type and restart the request to fallback to 347 // the network. 348 response_type_ = FALLBACK_TO_NETWORK; 349 NotifyRestartRequired(); 350 return; 351 } 352 353 // We should have a response now. 354 DCHECK_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, fetch_result); 355 356 // Set up a request for reading the blob. 357 if (!response.blob_uuid.empty() && blob_storage_context_) { 358 scoped_ptr<storage::BlobDataHandle> blob_data_handle = 359 blob_storage_context_->GetBlobDataFromUUID(response.blob_uuid); 360 if (!blob_data_handle) { 361 // The renderer gave us a bad blob UUID. 362 DeliverErrorResponse(); 363 return; 364 } 365 blob_request_ = storage::BlobProtocolHandler::CreateBlobRequest( 366 blob_data_handle.Pass(), request()->context(), this); 367 blob_request_->Start(); 368 } 369 370 response_url_ = response.url; 371 CreateResponseHeader( 372 response.status_code, response.status_text, response.headers); 373 if (!blob_request_) 374 CommitResponseHeader(); 375} 376 377void ServiceWorkerURLRequestJob::CreateResponseHeader( 378 int status_code, 379 const std::string& status_text, 380 const std::map<std::string, std::string>& headers) { 381 // TODO(kinuko): If the response has an identifier to on-disk cache entry, 382 // pull response header from the disk. 383 std::string status_line( 384 base::StringPrintf("HTTP/1.1 %d %s", status_code, status_text.c_str())); 385 status_line.push_back('\0'); 386 http_response_headers_ = new net::HttpResponseHeaders(status_line); 387 for (std::map<std::string, std::string>::const_iterator it = headers.begin(); 388 it != headers.end(); 389 ++it) { 390 std::string header; 391 header.reserve(it->first.size() + 2 + it->second.size()); 392 header.append(it->first); 393 header.append(": "); 394 header.append(it->second); 395 http_response_headers_->AddHeader(header); 396 } 397} 398 399void ServiceWorkerURLRequestJob::CommitResponseHeader() { 400 http_response_info_.reset(new net::HttpResponseInfo()); 401 http_response_info_->headers.swap(http_response_headers_); 402 NotifyHeadersComplete(); 403} 404 405void ServiceWorkerURLRequestJob::DeliverErrorResponse() { 406 // TODO(falken): Print an error to the console of the ServiceWorker and of 407 // the requesting page. 408 CreateResponseHeader(500, 409 "Service Worker Response Error", 410 std::map<std::string, std::string>()); 411 CommitResponseHeader(); 412} 413 414} // namespace content 415