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