blob: d5ef31e9d84bafcfc3b624c926924d67659defcb [file] [log] [blame]
// Copyright 2013 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 "components/precache/core/precache_fetcher.h"
#include <string>
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "components/precache/core/precache_switches.h"
#include "components/precache/core/proto/precache.pb.h"
#include "net/base/escape.h"
#include "net/base/load_flags.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request_status.h"
using net::URLFetcher;
namespace precache {
namespace {
GURL GetConfigURL() {
const CommandLine& command_line = *CommandLine::ForCurrentProcess();
if (command_line.HasSwitch(switches::kPrecacheConfigSettingsURL)) {
return GURL(
command_line.GetSwitchValueASCII(switches::kPrecacheConfigSettingsURL));
}
#if defined(PRECACHE_CONFIG_SETTINGS_URL)
return GURL(PRECACHE_CONFIG_SETTINGS_URL);
#else
// The precache config settings URL could not be determined, so return an
// empty, invalid GURL.
return GURL();
#endif
}
std::string GetManifestURLPrefix() {
const CommandLine& command_line = *CommandLine::ForCurrentProcess();
if (command_line.HasSwitch(switches::kPrecacheManifestURLPrefix)) {
return command_line.GetSwitchValueASCII(
switches::kPrecacheManifestURLPrefix);
}
#if defined(PRECACHE_MANIFEST_URL_PREFIX)
return PRECACHE_MANIFEST_URL_PREFIX;
#else
// The precache manifest URL prefix could not be determined, so return an
// empty string.
return std::string();
#endif
}
// Construct the URL of the precache manifest for the given starting URL.
// The server is expecting a request for a URL consisting of the manifest URL
// prefix followed by the doubly escaped starting URL.
GURL ConstructManifestURL(const GURL& starting_url) {
return GURL(
GetManifestURLPrefix() +
net::EscapeQueryParamValue(
net::EscapeQueryParamValue(starting_url.spec(), false), false));
}
// Attempts to parse a protobuf message from the response string of a
// URLFetcher. If parsing is successful, the message parameter will contain the
// parsed protobuf and this function will return true. Otherwise, returns false.
bool ParseProtoFromFetchResponse(const URLFetcher& source,
::google::protobuf::MessageLite* message) {
std::string response_string;
if (!source.GetStatus().is_success()) {
DLOG(WARNING) << "Fetch failed: " << source.GetOriginalURL().spec();
return false;
}
if (!source.GetResponseAsString(&response_string)) {
DLOG(WARNING) << "No response string present: "
<< source.GetOriginalURL().spec();
return false;
}
if (!message->ParseFromString(response_string)) {
DLOG(WARNING) << "Unable to parse proto served from "
<< source.GetOriginalURL().spec();
return false;
}
return true;
}
} // namespace
// Class that fetches a URL, and runs the specified callback when the fetch is
// complete. This class exists so that a different method can be run in
// response to different kinds of fetches, e.g. OnConfigFetchComplete when
// configuration settings are fetched, OnManifestFetchComplete when a manifest
// is fetched, etc.
class PrecacheFetcher::Fetcher : public net::URLFetcherDelegate {
public:
// Construct a new Fetcher. This will create and start a new URLFetcher for
// the specified URL using the specified request context.
Fetcher(net::URLRequestContextGetter* request_context, const GURL& url,
const base::Callback<void(const URLFetcher&)>& callback);
virtual ~Fetcher() {}
virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
private:
const base::Callback<void(const URLFetcher&)> callback_;
scoped_ptr<URLFetcher> url_fetcher_;
DISALLOW_COPY_AND_ASSIGN(Fetcher);
};
PrecacheFetcher::Fetcher::Fetcher(
net::URLRequestContextGetter* request_context, const GURL& url,
const base::Callback<void(const URLFetcher&)>& callback)
: callback_(callback) {
url_fetcher_.reset(URLFetcher::Create(url, URLFetcher::GET, this));
url_fetcher_->SetRequestContext(request_context);
url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_PROMPT_FOR_LOGIN);
url_fetcher_->Start();
}
void PrecacheFetcher::Fetcher::OnURLFetchComplete(const URLFetcher* source) {
callback_.Run(*source);
}
PrecacheFetcher::PrecacheFetcher(
const std::list<GURL>& starting_urls,
net::URLRequestContextGetter* request_context,
PrecacheFetcher::PrecacheDelegate* precache_delegate)
: starting_urls_(starting_urls),
request_context_(request_context),
precache_delegate_(precache_delegate) {
DCHECK(request_context_); // Request context must be non-NULL.
DCHECK(precache_delegate_); // Precache delegate must be non-NULL.
DCHECK_NE(GURL(), GetConfigURL())
<< "Could not determine the precache config settings URL.";
DCHECK_NE(std::string(), GetManifestURLPrefix())
<< "Could not determine the precache manifest URL prefix.";
}
PrecacheFetcher::~PrecacheFetcher() {
}
void PrecacheFetcher::Start() {
DCHECK(!fetcher_); // Start shouldn't be called repeatedly.
GURL config_url = GetConfigURL();
DCHECK(config_url.is_valid());
// Fetch the precache configuration settings from the server.
fetcher_.reset(new Fetcher(request_context_, config_url,
base::Bind(&PrecacheFetcher::OnConfigFetchComplete,
base::Unretained(this))));
}
void PrecacheFetcher::StartNextFetch() {
if (!resource_urls_to_fetch_.empty()) {
// Fetch the next resource URL.
fetcher_.reset(
new Fetcher(request_context_, resource_urls_to_fetch_.front(),
base::Bind(&PrecacheFetcher::OnResourceFetchComplete,
base::Unretained(this))));
resource_urls_to_fetch_.pop_front();
return;
}
if (!manifest_urls_to_fetch_.empty()) {
// Fetch the next manifest URL.
fetcher_.reset(
new Fetcher(request_context_, manifest_urls_to_fetch_.front(),
base::Bind(&PrecacheFetcher::OnManifestFetchComplete,
base::Unretained(this))));
manifest_urls_to_fetch_.pop_front();
return;
}
// There are no more URLs to fetch, so end the precache cycle.
precache_delegate_->OnDone();
// OnDone may have deleted this PrecacheFetcher, so don't do anything after it
// is called.
}
void PrecacheFetcher::OnConfigFetchComplete(const URLFetcher& source) {
PrecacheConfigurationSettings config;
if (ParseProtoFromFetchResponse(source, &config)) {
// Attempt to fetch manifests for starting URLs up to the maximum top sites
// count. If a manifest does not exist for a particular starting URL, then
// the fetch will fail, and that starting URL will be ignored.
int64 rank = 0;
for (std::list<GURL>::const_iterator it = starting_urls_.begin();
it != starting_urls_.end() && rank < config.top_sites_count();
++it, ++rank) {
manifest_urls_to_fetch_.push_back(ConstructManifestURL(*it));
}
}
StartNextFetch();
}
void PrecacheFetcher::OnManifestFetchComplete(const URLFetcher& source) {
PrecacheManifest manifest;
if (ParseProtoFromFetchResponse(source, &manifest)) {
for (int i = 0; i < manifest.resource_size(); ++i) {
if (manifest.resource(i).has_url()) {
resource_urls_to_fetch_.push_back(GURL(manifest.resource(i).url()));
}
}
}
StartNextFetch();
}
void PrecacheFetcher::OnResourceFetchComplete(const URLFetcher& source) {
// The resource has already been put in the cache during the fetch process, so
// nothing more needs to be done for the resource.
StartNextFetch();
}
} // namespace precache