blob: 9b0244f9490905ab35ad5a24e0b3b565ca326fe8 [file] [log] [blame]
// Copyright 2019 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/loader/fetcher_cache.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "cobalt/loader/loader_types.h"
namespace cobalt {
namespace loader {
namespace {
// Wraps a Fetcher::Handler and saves all data fetched and its associated
// information.
class CachedFetcherHandler : public Fetcher::Handler {
public:
typedef base::Callback<void(
const std::string& url,
const scoped_refptr<net::HttpResponseHeaders>& headers,
const Origin& last_url_origin, bool did_fail_from_transient_error,
std::string* data)>
SuccessCallback;
CachedFetcherHandler(const std::string& url, Fetcher::Handler* handler,
const SuccessCallback& on_success_callback)
: url_(url),
handler_(handler),
on_success_callback_(on_success_callback) {
DCHECK(handler_);
DCHECK(!on_success_callback_.is_null());
}
// Attach a wrapping fetcher so it can be used when forwarding the callbacks.
// This ensures that the underlying handler sees the same Fetcher object in
// the callback as the one returned by CreateCachedFetcher().
void AttachFetcher(Fetcher* wrapping_fetcher) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!wrapping_fetcher_);
DCHECK(wrapping_fetcher);
wrapping_fetcher_ = wrapping_fetcher;
}
private:
// From Fetcher::Handler.
LoadResponseType OnResponseStarted(
Fetcher*,
const scoped_refptr<net::HttpResponseHeaders>& headers) override {
// TODO: Respect HttpResponseHeaders::GetMaxAgeValue().
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(wrapping_fetcher_);
auto response = handler_->OnResponseStarted(wrapping_fetcher_, headers);
if (response == kLoadResponseContinue && headers) {
headers_ = headers;
auto content_length = headers_->GetContentLength();
if (content_length > 0) {
data_.reserve(static_cast<size_t>(content_length));
}
}
return response;
}
void OnReceived(Fetcher*, const char* data, size_t size) override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(wrapping_fetcher_);
data_.insert(data_.end(), data, data + size);
handler_->OnReceived(wrapping_fetcher_, data, size);
}
void OnReceivedPassed(Fetcher*, std::unique_ptr<std::string> data) override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(wrapping_fetcher_);
data_.insert(data_.end(), data->begin(), data->end());
handler_->OnReceivedPassed(wrapping_fetcher_, std::move(data));
}
void OnDone(Fetcher*) override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(wrapping_fetcher_);
handler_->OnDone(wrapping_fetcher_);
on_success_callback_.Run(
url_, headers_, wrapping_fetcher_->last_url_origin(),
wrapping_fetcher_->did_fail_from_transient_error(), &data_);
}
void OnError(Fetcher*, const std::string& error) override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(wrapping_fetcher_);
handler_->OnError(wrapping_fetcher_, error);
}
THREAD_CHECKER(thread_checker_);
const std::string url_;
Fetcher* wrapping_fetcher_ = nullptr;
Fetcher::Handler* const handler_;
const SuccessCallback on_success_callback_;
scoped_refptr<net::HttpResponseHeaders> headers_;
std::string data_;
};
// Wraps an underlying, real Fetcher for an ongoing request, so we can ensure
// that |handler_| is deleted when the Fetcher object is deleted.
class OngoingFetcher : public Fetcher {
public:
OngoingFetcher(std::unique_ptr<CachedFetcherHandler> handler,
const Loader::FetcherCreator& real_fetcher_creator)
: Fetcher(handler.get()), handler_(std::move(handler)) {
DCHECK(handler_);
handler_->AttachFetcher(this);
fetcher_ = real_fetcher_creator.Run(handler_.get());
}
~OngoingFetcher() override { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); }
Origin last_url_origin() const override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Some Fetchers may call callbacks inside its ctor, in such case |fetcher_|
// hasn't been assigned and a default value is returned.
return fetcher_ ? fetcher_->last_url_origin() : Origin();
}
bool did_fail_from_transient_error() const override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Some Fetchers may call callbacks inside its ctor, in such case |fetcher_|
// hasn't been assigned and a default value is returned.
return fetcher_ ? fetcher_->did_fail_from_transient_error() : false;
}
private:
THREAD_CHECKER(thread_checker_);
std::unique_ptr<CachedFetcherHandler> handler_;
std::unique_ptr<Fetcher> fetcher_;
};
// Fulfills the request directly using the data passed to ctor.
class CachedFetcher : public Fetcher {
public:
CachedFetcher(const GURL& url,
const scoped_refptr<net::HttpResponseHeaders>& headers,
const char* data, size_t size, const Origin& last_url_origin,
bool did_fail_from_transient_error, Handler* handler)
: Fetcher(handler),
last_url_origin_(last_url_origin),
did_fail_from_transient_error_(did_fail_from_transient_error) {
auto response_type = handler->OnResponseStarted(this, headers);
if (response_type == kLoadResponseAbort) {
std::string error_msg(base::StringPrintf(
"Handler::OnResponseStarted() aborted URL %s", url.spec().c_str()));
handler->OnError(this, error_msg.c_str());
return;
}
handler->OnReceived(this, data, size);
handler->OnDone(this);
}
Origin last_url_origin() const override { return last_url_origin_; }
bool did_fail_from_transient_error() const override {
return did_fail_from_transient_error_;
}
private:
Origin last_url_origin_;
bool did_fail_from_transient_error_;
};
} // namespace
class FetcherCache::CacheEntry {
public:
CacheEntry(const scoped_refptr<net::HttpResponseHeaders>& headers,
const Origin& last_url_origin, bool did_fail_from_transient_error,
std::string* data)
: headers_(headers),
last_url_origin_(last_url_origin),
did_fail_from_transient_error_(did_fail_from_transient_error) {
DCHECK(data);
data_.swap(*data);
}
const scoped_refptr<net::HttpResponseHeaders>& headers() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return headers_;
}
const Origin& last_url_origin() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return last_url_origin_;
}
bool did_fail_from_transient_error() const {
return did_fail_from_transient_error_;
}
const char* data() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return data_.data();
}
size_t size() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return data_.size();
}
size_t capacity() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return data_.capacity();
}
private:
THREAD_CHECKER(thread_checker_);
scoped_refptr<net::HttpResponseHeaders> headers_;
Origin last_url_origin_;
bool did_fail_from_transient_error_;
std::string data_;
};
FetcherCache::FetcherCache(const char* name, size_t capacity)
: capacity_(capacity),
memory_size_in_bytes_(
base::StringPrintf("Memory.%s.FetcherCache.Size", name), 0,
"Total number of bytes currently used by the cache."),
count_resources_cached_(
base::StringPrintf("Count.%s.FetcherCache.Cached", name), 0,
"The number of resources that are currently being cached.") {}
FetcherCache::~FetcherCache() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
while (!cache_entries_.empty()) {
delete cache_entries_.begin()->second;
cache_entries_.erase(cache_entries_.begin());
}
memory_size_in_bytes_ = 0;
count_resources_cached_ = 0;
}
Loader::FetcherCreator FetcherCache::GetFetcherCreator(
const GURL& url, const Loader::FetcherCreator& real_fetcher_creator) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!real_fetcher_creator.is_null());
return base::Bind(&FetcherCache::CreateCachedFetcher, base::Unretained(this),
url, real_fetcher_creator);
}
void FetcherCache::NotifyResourceRequested(const std::string& url) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
auto iter = cache_entries_.find(url);
if (iter != cache_entries_.end()) {
auto entry = iter->second;
cache_entries_.erase(iter);
cache_entries_.insert(std::make_pair(url, entry));
}
}
std::unique_ptr<Fetcher> FetcherCache::CreateCachedFetcher(
const GURL& url, const Loader::FetcherCreator& real_fetcher_creator,
Fetcher::Handler* handler) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!real_fetcher_creator.is_null());
DCHECK(handler);
auto iterator = cache_entries_.find(url.spec());
if (iterator != cache_entries_.end()) {
auto entry = iterator->second;
cache_entries_.erase(iterator);
cache_entries_.insert(std::make_pair(url.spec(), entry));
return std::unique_ptr<Fetcher>(
new CachedFetcher(url, entry->headers(), entry->data(), entry->size(),
entry->last_url_origin(),
entry->did_fail_from_transient_error(), handler));
}
std::unique_ptr<CachedFetcherHandler> cached_handler(new CachedFetcherHandler(
url.spec(), handler,
base::Bind(&FetcherCache::OnFetchSuccess, base::Unretained(this))));
return std::unique_ptr<Fetcher>(
new OngoingFetcher(std::move(cached_handler), real_fetcher_creator));
}
void FetcherCache::OnFetchSuccess(
const std::string& url,
const scoped_refptr<net::HttpResponseHeaders>& headers,
const Origin& last_url_origin, bool did_fail_from_transient_error,
std::string* data) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(data);
if (data->size() <= capacity_) {
auto entry = new CacheEntry(headers, last_url_origin,
did_fail_from_transient_error, data);
total_size_ += entry->capacity();
cache_entries_.insert(std::make_pair(url, entry));
while (total_size_ > capacity_) {
DCHECK(!cache_entries_.empty());
total_size_ -= cache_entries_.begin()->second->capacity();
delete cache_entries_.begin()->second;
cache_entries_.erase(cache_entries_.begin());
--count_resources_cached_;
}
++count_resources_cached_;
memory_size_in_bytes_ = total_size_;
}
}
} // namespace loader
} // namespace cobalt