blob: 0a45271126f17392e58b6295352d36f2758d192b [file] [log] [blame]
// Copyright (c) 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/appcache/appcache_url_loader_job.h"
#include "base/strings/string_number_conversions.h"
#include "content/browser/appcache/appcache_histograms.h"
#include "content/browser/appcache/appcache_request_handler.h"
#include "content/browser/appcache/appcache_subresource_url_factory.h"
#include "content/browser/appcache/appcache_url_loader_request.h"
#include "content/browser/url_loader_factory_getter.h"
#include "content/public/common/resource_type.h"
#include "net/http/http_status_code.h"
#include "services/network/public/cpp/net_adapters.h"
namespace content {
namespace {
// Max number of http redirects to follow. Same number as gecko.
// TODO(ananta/michaeln). Avoid duplicating logic from the n/w stack and figure
// out a way to use FollowRedirect() mechanism in the network URL loader.
const int kMaxRedirects = 20;
} // namespace
SubresourceLoadInfo::SubresourceLoadInfo()
: routing_id(-1),
request_id(-1),
options(0),
redirect_limit(kMaxRedirects) {}
SubresourceLoadInfo::~SubresourceLoadInfo() {}
AppCacheURLLoaderJob::~AppCacheURLLoaderJob() {
if (storage_.get())
storage_->CancelDelegateCallbacks(this);
}
void AppCacheURLLoaderJob::Kill() {}
bool AppCacheURLLoaderJob::IsStarted() const {
return delivery_type_ != AWAITING_DELIVERY_ORDERS;
}
void AppCacheURLLoaderJob::DeliverAppCachedResponse(const GURL& manifest_url,
int64_t cache_id,
const AppCacheEntry& entry,
bool is_fallback) {
if (!storage_.get()) {
DeliverErrorResponse();
return;
}
delivery_type_ = APPCACHED_DELIVERY;
// In tests we only care about the delivery_type_ state.
if (AppCacheRequestHandler::IsRunningInTests())
return;
load_timing_info_.request_start_time = base::Time::Now();
load_timing_info_.request_start = base::TimeTicks::Now();
AppCacheHistograms::AddAppCacheJobStartDelaySample(base::TimeTicks::Now() -
start_time_tick_);
manifest_url_ = manifest_url;
cache_id_ = cache_id;
entry_ = entry;
is_fallback_ = is_fallback;
// Handle range requests.
InitializeRangeRequestInfo(request_.headers);
// TODO(ananta)
// Implement the AppCacheServiceImpl::Observer interface or add weak pointer
// support to it.
storage_->LoadResponseInfo(manifest_url_, entry_.response_id(), this);
}
void AppCacheURLLoaderJob::DeliverNetworkResponse() {
delivery_type_ = NETWORK_DELIVERY;
// In tests we only care about the delivery_type_ state.
if (AppCacheRequestHandler::IsRunningInTests())
return;
AppCacheHistograms::AddNetworkJobStartDelaySample(base::TimeTicks::Now() -
start_time_tick_);
if (IsResourceTypeFrame(request_.resource_type)) {
DCHECK(!main_resource_loader_callback_.is_null());
// In network service land, if we are processing a navigation request, we
// need to inform the loader callback that we are not going to handle this
// request. The loader callback is valid only for navigation requests.
std::move(main_resource_loader_callback_).Run(StartLoaderCallback());
} else {
mojom::URLLoaderClientPtr client_ptr;
network_loader_client_binding_.Bind(mojo::MakeRequest(&client_ptr));
default_url_loader_factory_getter_->GetNetworkFactory()
->get()
->CreateLoaderAndStart(
mojo::MakeRequest(&network_loader_),
subresource_load_info_->routing_id,
subresource_load_info_->request_id, subresource_load_info_->options,
subresource_load_info_->request, std::move(client_ptr),
subresource_load_info_->traffic_annotation);
}
}
void AppCacheURLLoaderJob::DeliverErrorResponse() {
delivery_type_ = ERROR_DELIVERY;
// In tests we only care about the delivery_type_ state.
if (AppCacheRequestHandler::IsRunningInTests())
return;
// AppCacheURLRequestJob uses ERR_FAILED as the error code here. That seems
// to map to HTTP_INTERNAL_SERVER_ERROR.
std::string status("HTTP/1.1 ");
status.append(base::IntToString(net::HTTP_INTERNAL_SERVER_ERROR));
status.append(" ");
status.append(net::GetHttpReasonPhrase(net::HTTP_INTERNAL_SERVER_ERROR));
status.append("\0\0", 2);
ResourceResponseHead response;
response.headers = new net::HttpResponseHeaders(status);
client_->OnReceiveResponse(response, base::nullopt, nullptr);
NotifyCompleted(net::ERR_FAILED);
AppCacheHistograms::AddErrorJobStartDelaySample(base::TimeTicks::Now() -
start_time_tick_);
}
AppCacheURLLoaderJob* AppCacheURLLoaderJob::AsURLLoaderJob() {
return this;
}
void AppCacheURLLoaderJob::FollowRedirect() {
if (subresource_factory_.get()) {
if (storage_->usage_map()->find(
last_subresource_redirect_info_.new_url.GetOrigin()) !=
storage_->usage_map()->end()) {
// If we hit the redirect limit then attempt to load a fallback here.
// If that succeeds we are good. If not notify the client about the
// failure.
// TODO(ananta/michaeln)
// Avoid this logic and figure out a way to reuse the network loader's
// FollowRedirect mechanism.
subresource_load_info_->redirect_limit--;
if (subresource_load_info_->redirect_limit <= 0) {
HandleRedirectLimitHit();
return;
}
subresource_load_info_->client = std::move(client_);
subresource_load_info_->url_loader_request = binding_.Unbind();
subresource_factory_->Restart(last_subresource_redirect_info_,
std::move(sub_resource_handler_),
std::move(subresource_load_info_));
base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
return;
}
}
if (network_loader_)
network_loader_->FollowRedirect();
}
void AppCacheURLLoaderJob::SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) {
if (network_loader_)
network_loader_->SetPriority(priority, intra_priority_value);
}
void AppCacheURLLoaderJob::PauseReadingBodyFromNet() {
if (network_loader_)
network_loader_->PauseReadingBodyFromNet();
}
void AppCacheURLLoaderJob::ResumeReadingBodyFromNet() {
if (network_loader_)
network_loader_->ResumeReadingBodyFromNet();
}
void AppCacheURLLoaderJob::OnReceiveResponse(
const ResourceResponseHead& response_head,
const base::Optional<net::SSLInfo>& ssl_info,
mojom::DownloadedTempFilePtr downloaded_file) {
appcache_request_->set_response(response_head);
// The MaybeLoadFallbackForResponse() call below can pass a fallback
// response to us. Reset the delivery_type_ to ensure that we can
// receive it
delivery_type_ = AWAITING_DELIVERY_ORDERS;
received_response_ = true;
if (!sub_resource_handler_->MaybeLoadFallbackForResponse(nullptr)) {
client_->OnReceiveResponse(response_head, ssl_info,
std::move(downloaded_file));
} else {
// Disconnect from the network loader as we are delivering a fallback
// response to the client.
DisconnectFromNetworkLoader();
}
}
void AppCacheURLLoaderJob::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
const ResourceResponseHead& response_head) {
appcache_request_->set_response(response_head);
// The MaybeLoadFallbackForRedirect() call below can pass a fallback
// response to us. Reset the delivery_type_ to ensure that we can
// receive it
delivery_type_ = AWAITING_DELIVERY_ORDERS;
if (sub_resource_handler_->MaybeLoadFallbackForRedirect(
nullptr, redirect_info.new_url)) {
// Disconnect from the network loader as we are delivering a fallback
// response to the client.
DisconnectFromNetworkLoader();
return;
}
last_subresource_redirect_info_ = redirect_info;
client_->OnReceiveRedirect(redirect_info, response_head);
}
void AppCacheURLLoaderJob::OnDataDownloaded(int64_t data_len,
int64_t encoded_data_len) {
client_->OnDataDownloaded(data_len, encoded_data_len);
}
void AppCacheURLLoaderJob::OnUploadProgress(
int64_t current_position,
int64_t total_size,
OnUploadProgressCallback ack_callback) {
client_->OnUploadProgress(current_position, total_size,
std::move(ack_callback));
}
void AppCacheURLLoaderJob::OnReceiveCachedMetadata(
const std::vector<uint8_t>& data) {
client_->OnReceiveCachedMetadata(data);
}
void AppCacheURLLoaderJob::OnTransferSizeUpdated(int32_t transfer_size_diff) {
client_->OnTransferSizeUpdated(transfer_size_diff);
}
void AppCacheURLLoaderJob::OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) {
client_->OnStartLoadingResponseBody(std::move(body));
}
void AppCacheURLLoaderJob::OnComplete(
const ResourceRequestCompletionStatus& status) {
delivery_type_ = AWAITING_DELIVERY_ORDERS;
if (status.error_code != net::OK && !received_response_) {
if (sub_resource_handler_->MaybeLoadFallbackForResponse(nullptr)) {
// Disconnect from the network loader as we are delivering a fallback
// response to the client.
DisconnectFromNetworkLoader();
return;
}
}
client_->OnComplete(status);
}
void AppCacheURLLoaderJob::SetRequestHandlerAndFactory(
std::unique_ptr<AppCacheRequestHandler> handler,
AppCacheSubresourceURLFactory* subresource_factory) {
sub_resource_handler_ = std::move(handler);
subresource_factory_ = subresource_factory->GetWeakPtr();
}
void AppCacheURLLoaderJob::BindRequest(mojom::URLLoaderClientPtr client,
mojom::URLLoaderRequest request) {
DCHECK(!binding_.is_bound());
binding_.Bind(std::move(request));
client_ = std::move(client);
binding_.set_connection_error_handler(base::BindOnce(
&AppCacheURLLoaderJob::OnConnectionError, StaticAsWeakPtr(this)));
}
void AppCacheURLLoaderJob::Start(mojom::URLLoaderRequest request,
mojom::URLLoaderClientPtr client) {
BindRequest(std::move(client), std::move(request));
// Send the cached AppCacheResponse if any.
if (info_.get())
SendResponseInfo();
}
AppCacheURLLoaderJob::AppCacheURLLoaderJob(
const ResourceRequest& request,
AppCacheURLLoaderRequest* appcache_request,
AppCacheStorage* storage,
std::unique_ptr<SubresourceLoadInfo> subresource_load_info,
URLLoaderFactoryGetter* loader_factory_getter)
: request_(request),
storage_(storage->GetWeakPtr()),
start_time_tick_(base::TimeTicks::Now()),
cache_id_(kAppCacheNoCacheId),
is_fallback_(false),
binding_(this),
writable_handle_watcher_(FROM_HERE,
mojo::SimpleWatcher::ArmingPolicy::MANUAL),
network_loader_client_binding_(this),
appcache_request_(appcache_request),
received_response_(false) {
if (subresource_load_info.get()) {
DCHECK(loader_factory_getter);
subresource_load_info_ = std::move(subresource_load_info);
request_ = subresource_load_info_->request;
binding_.Bind(std::move(subresource_load_info_->url_loader_request));
binding_.set_connection_error_handler(base::BindOnce(
&AppCacheURLLoaderJob::OnConnectionError, StaticAsWeakPtr(this)));
client_ = std::move(subresource_load_info_->client);
default_url_loader_factory_getter_ = loader_factory_getter;
}
}
void AppCacheURLLoaderJob::OnResponseInfoLoaded(
AppCacheResponseInfo* response_info,
int64_t response_id) {
DCHECK(IsDeliveringAppCacheResponse());
if (!storage_.get()) {
DeliverErrorResponse();
return;
}
if (response_info) {
info_ = response_info;
reader_.reset(
storage_->CreateResponseReader(manifest_url_, entry_.response_id()));
if (is_range_request())
SetupRangeResponse();
if (IsResourceTypeFrame(request_.resource_type) &&
main_resource_loader_callback_) {
std::move(main_resource_loader_callback_)
.Run(base::BindOnce(&AppCacheURLLoaderJob::Start,
StaticAsWeakPtr(this)));
}
response_body_stream_ = std::move(data_pipe_.producer_handle);
// TODO(ananta)
// Move the asynchronous reading and mojo pipe handling code to a helper
// class. That would also need a change to BlobURLLoader.
// Wait for the data pipe to be ready to accept data.
writable_handle_watcher_.Watch(
response_body_stream_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
base::Bind(&AppCacheURLLoaderJob::OnResponseBodyStreamReady,
StaticAsWeakPtr(this)));
if (client_)
SendResponseInfo();
ReadMore();
} else {
// Error case here. We fallback to the network.
DeliverNetworkResponse();
AppCacheHistograms::CountResponseRetrieval(
false, IsResourceTypeFrame(request_.resource_type),
manifest_url_.GetOrigin());
cache_entry_not_found_ = true;
}
}
void AppCacheURLLoaderJob::OnReadComplete(int result) {
DLOG(WARNING) << "AppCache read completed with result: " << result;
if (result <= 0) {
writable_handle_watcher_.Cancel();
pending_write_->Complete(0);
pending_write_ = nullptr;
}
bool is_main_resource = IsResourceTypeFrame(request_.resource_type);
if (result == 0) {
NotifyCompleted(result);
AppCacheHistograms::CountResponseRetrieval(true, is_main_resource,
manifest_url_.GetOrigin());
return;
} else if (result < 0) {
// TODO(ananta)
// Populate the relevant fields of the ResourceRequestCompletionStatus
// structure.
NotifyCompleted(result);
AppCacheHistograms::CountResponseRetrieval(false, is_main_resource,
manifest_url_.GetOrigin());
return;
}
uint32_t bytes_written = static_cast<uint32_t>(result);
response_body_stream_ = pending_write_->Complete(bytes_written);
pending_write_ = nullptr;
ReadMore();
}
void AppCacheURLLoaderJob::OnConnectionError() {
if (storage_.get())
storage_->CancelDelegateCallbacks(this);
base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
}
void AppCacheURLLoaderJob::SendResponseInfo() {
DCHECK(client_);
// If this is null it means the response information was sent to the client.
if (!data_pipe_.consumer_handle.is_valid())
return;
const net::HttpResponseInfo* http_info = is_range_request()
? range_response_info_.get()
: info_->http_response_info();
ResourceResponseHead response_head;
response_head.headers = http_info->headers;
response_head.appcache_id = cache_id_;
response_head.appcache_manifest_url = manifest_url_;
http_info->headers->GetMimeType(&response_head.mime_type);
http_info->headers->GetCharset(&response_head.charset);
// TODO(ananta)
// Verify if the times sent here are correct.
response_head.request_time = http_info->request_time;
response_head.response_time = http_info->response_time;
response_head.content_length =
is_range_request() ? range_response_info_->headers->GetContentLength()
: info_->response_data_size();
response_head.connection_info = http_info->connection_info;
response_head.socket_address = http_info->socket_address;
response_head.was_fetched_via_spdy = http_info->was_fetched_via_spdy;
response_head.was_alpn_negotiated = http_info->was_alpn_negotiated;
response_head.alpn_negotiated_protocol = http_info->alpn_negotiated_protocol;
response_head.load_timing = load_timing_info_;
appcache_request_->set_response(response_head);
client_->OnReceiveResponse(response_head, http_info->ssl_info,
mojom::DownloadedTempFilePtr());
client_->OnStartLoadingResponseBody(std::move(data_pipe_.consumer_handle));
}
void AppCacheURLLoaderJob::ReadMore() {
DCHECK(!pending_write_.get());
uint32_t num_bytes;
// TODO: we should use the abstractions in MojoAsyncResourceHandler.
MojoResult result = network::NetToMojoPendingBuffer::BeginWrite(
&response_body_stream_, &pending_write_, &num_bytes);
if (result == MOJO_RESULT_SHOULD_WAIT) {
// The pipe is full. We need to wait for it to have more space.
writable_handle_watcher_.ArmOrNotify();
return;
} else if (result != MOJO_RESULT_OK) {
// The response body stream is in a bad state. Bail.
// TODO(ananta)
// Add proper error handling here.
NotifyCompleted(net::ERR_FAILED);
writable_handle_watcher_.Cancel();
response_body_stream_.reset();
return;
}
CHECK_GT(static_cast<uint32_t>(std::numeric_limits<int>::max()), num_bytes);
auto buffer =
base::MakeRefCounted<network::NetToMojoIOBuffer>(pending_write_.get());
uint32_t bytes_to_read =
std::min<uint32_t>(num_bytes, info_->response_data_size());
reader_->ReadData(
buffer.get(), bytes_to_read,
base::Bind(&AppCacheURLLoaderJob::OnReadComplete, StaticAsWeakPtr(this)));
}
void AppCacheURLLoaderJob::OnResponseBodyStreamReady(MojoResult result) {
// TODO(ananta)
// Add proper error handling here.
if (result != MOJO_RESULT_OK) {
DCHECK(false);
NotifyCompleted(net::ERR_FAILED);
}
ReadMore();
}
void AppCacheURLLoaderJob::NotifyCompleted(int error_code) {
if (storage_.get())
storage_->CancelDelegateCallbacks(this);
if (AppCacheRequestHandler::IsRunningInTests())
return;
const net::HttpResponseInfo* http_info =
is_range_request() ? range_response_info_.get()
: (info_ ? info_->http_response_info() : nullptr);
ResourceRequestCompletionStatus request_complete_data;
request_complete_data.error_code = error_code;
// TODO(ananta)
// Fill other details in the ResourceRequestCompletionStatus structure in
// case of an error.
if (!request_complete_data.error_code) {
request_complete_data.exists_in_cache = http_info->was_cached;
request_complete_data.completion_time = base::TimeTicks::Now();
request_complete_data.encoded_body_length =
is_range_request() ? range_response_info_->headers->GetContentLength()
: (info_ ? info_->response_data_size() : 0);
request_complete_data.decoded_body_length =
request_complete_data.encoded_body_length;
}
client_->OnComplete(request_complete_data);
}
void AppCacheURLLoaderJob::DisconnectFromNetworkLoader() {
// Close the pipe to the network loader as we are delivering a fallback
// response to the client.
network_loader_client_binding_.Close();
network_loader_ = nullptr;
}
void AppCacheURLLoaderJob::HandleRedirectLimitHit() {
DCHECK(sub_resource_handler_.get());
// TODO(ananta/michaeln)
// Fix the IsSuccess() function in the AppCacheURLLoaderRequest class.
// Currently presence of headers in the response is treated as success.
appcache_request_->set_response(ResourceResponseHead());
if (sub_resource_handler_->MaybeLoadFallbackForResponse(nullptr)) {
DisconnectFromNetworkLoader();
} else {
ResourceRequestCompletionStatus result;
result.error_code = net::ERR_FAILED;
client_->OnComplete(result);
}
}
} // namespace content