blob: 2f1f6b5839d8298e374d8bc726b9dc6123950b2f [file] [log] [blame]
// Copyright 2013 Google Inc.
//
// 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.
//
// Author: jmarantz@google.com (Joshua Marantz)
#include "pagespeed/system/system_rewrite_options.h"
#include "base/logging.h"
#include "pagespeed/system/serf_url_async_fetcher.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/timer.h"
namespace net_instaweb {
class ThreadSystem;
namespace {
const int64 kDefaultCacheFlushIntervalSec = 5;
const char kFetchHttps[] = "FetchHttps";
} // namespace
const char SystemRewriteOptions::kStaticAssetCDN[] = "StaticAssetCDN";
RewriteOptions::Properties* SystemRewriteOptions::system_properties_ = NULL;
void SystemRewriteOptions::Initialize() {
if (Properties::Initialize(&system_properties_)) {
RewriteOptions::Initialize();
AddProperties();
}
}
void SystemRewriteOptions::Terminate() {
if (Properties::Terminate(&system_properties_)) {
RewriteOptions::Terminate();
}
}
SystemRewriteOptions::SystemRewriteOptions(ThreadSystem* thread_system)
: RewriteOptions(thread_system) {
DCHECK(system_properties_ != NULL)
<< "Call SystemRewriteOptions::Initialize() before construction";
InitializeOptions(system_properties_);
}
SystemRewriteOptions::SystemRewriteOptions(const StringPiece& description,
ThreadSystem* thread_system)
: RewriteOptions(thread_system),
description_(description.data(), description.size()) {
DCHECK(system_properties_ != NULL)
<< "Call SystemRewriteOptions::Initialize() before construction";
InitializeOptions(system_properties_);
}
SystemRewriteOptions::~SystemRewriteOptions() {
}
void SystemRewriteOptions::AddProperties() {
AddSystemProperty("", &SystemRewriteOptions::fetcher_proxy_, "afp",
RewriteOptions::kFetcherProxy,
"Set the fetch proxy", false);
AddSystemProperty("", &SystemRewriteOptions::file_cache_path_, "afcp",
RewriteOptions::kFileCachePath,
"Set the path for file cache", false);
AddSystemProperty("", &SystemRewriteOptions::log_dir_, "ald",
RewriteOptions::kLogDir,
"Directory to store logs in.", false);
AddSystemProperty("", &SystemRewriteOptions::memcached_servers_, "ams",
RewriteOptions::kMemcachedServers,
"Comma-separated list of servers e.g. "
"host1:port1,host2:port2", false);
AddSystemProperty(1, &SystemRewriteOptions::memcached_threads_, "amt",
RewriteOptions::kMemcachedThreads,
"Number of background threads to use to run "
"memcached fetches", true);
AddSystemProperty(500 * Timer::kMsUs, // half a second
&SystemRewriteOptions::memcached_timeout_us_, "amo",
RewriteOptions::kMemcachedTimeoutUs,
"Maximum time in microseconds to allow for memcached "
"transactions", true);
AddSystemProperty(50 * Timer::kMsUs, // 50 ms
&SystemRewriteOptions::slow_file_latency_threshold_us_,
"asflt", "SlowFileLatencyUs",
"Maximum time in microseconds to allow for file operations "
"before logging and bumping a stat", true);
AddSystemProperty(true, &SystemRewriteOptions::statistics_enabled_, "ase",
RewriteOptions::kStatisticsEnabled,
"Whether to collect cross-process statistics.", true);
AddSystemProperty("", &SystemRewriteOptions::statistics_logging_charts_css_,
"aslcc", RewriteOptions::kStatisticsLoggingChartsCSS,
"Where to find an offline copy of the Google Charts Tools "
"API CSS.", false);
AddSystemProperty("", &SystemRewriteOptions::statistics_logging_charts_js_,
"aslcj", RewriteOptions::kStatisticsLoggingChartsJS,
"Where to find an offline copy of the Google Charts Tools "
"API JS.", false);
AddSystemProperty(false, &SystemRewriteOptions::statistics_logging_enabled_,
"asle", RewriteOptions::kStatisticsLoggingEnabled,
"Whether to log statistics if they're being collected.",
true);
AddSystemProperty(10 * Timer::kMinuteMs,
&SystemRewriteOptions::statistics_logging_interval_ms_,
"asli", RewriteOptions::kStatisticsLoggingIntervalMs,
"How often to log statistics, in milliseconds.", true);
// 2 Weeks of data w/ 10 minute intervals.
// Takes about 0.1s to parse 1MB file for modpagespeed.com/pagespeed_console
// TODO(sligocki): Increase once we have a better method for reading
// historical data.
AddSystemProperty(1 * 1024 /* 1 Megabytes */,
&SystemRewriteOptions::statistics_logging_max_file_size_kb_,
"aslfs", RewriteOptions::kStatisticsLoggingMaxFileSizeKb,
"Max size for statistics logging file.", false);
AddSystemProperty(true, &SystemRewriteOptions::use_shared_mem_locking_,
"ausml", RewriteOptions::kUseSharedMemLocking,
"Use shared memory for internal named lock service", true);
AddSystemProperty(
Timer::kHourMs, &SystemRewriteOptions::file_cache_clean_interval_ms_,
"afcci", RewriteOptions::kFileCacheCleanIntervalMs,
"Set the interval (in ms) for cleaning the file cache, -1 to disable "
"cleaning", true);
AddSystemProperty(100 * 1024 /* 100 megabytes */,
&SystemRewriteOptions::file_cache_clean_size_kb_,
"afc", RewriteOptions::kFileCacheCleanSizeKb,
"Set the target size (in kilobytes) for file cache", true);
// Default to no inode limit so that existing installations are not affected.
// pagespeed.conf.template contains suggested limit for new installations.
// TODO(morlovich): Inject this as an argument, since we want a different
// default for ngx_pagespeed?
AddSystemProperty(0, &SystemRewriteOptions::file_cache_clean_inode_limit_,
"afcl", RewriteOptions::kFileCacheCleanInodeLimit,
"Set the target number of inodes for the file cache; 0 "
"means no limit", true);
AddSystemProperty(0, &SystemRewriteOptions::lru_cache_byte_limit_, "alcb",
RewriteOptions::kLruCacheByteLimit,
"Set the maximum byte size entry to store in the "
"per-process in-memory LRU cache", true);
AddSystemProperty(0, &SystemRewriteOptions::lru_cache_kb_per_process_, "alcp",
RewriteOptions::kLruCacheKbPerProcess,
"Set the total size, in KB, of the per-process in-memory "
"LRU cache", true);
AddSystemProperty("", &SystemRewriteOptions::cache_flush_filename_, "acff",
RewriteOptions::kCacheFlushFilename,
"Name of file to check for timestamp updates used to flush "
"cache. This file will be relative to the "
"ModPagespeedFileCachePath if it does not begin with a "
"slash.", false);
AddSystemProperty(kDefaultCacheFlushIntervalSec,
&SystemRewriteOptions::cache_flush_poll_interval_sec_,
"acfpi", RewriteOptions::kCacheFlushPollIntervalSec,
"Number of seconds to wait between polling for cache-flush "
"requests", true);
AddSystemProperty(true,
&SystemRewriteOptions::compress_metadata_cache_,
"cc", RewriteOptions::kCompressMetadataCache,
"Whether to compress cache entries before writing them to "
"memory or disk.", true);
AddSystemProperty("enable", &SystemRewriteOptions::https_options_, "fhs",
kFetchHttps, "Controls direct fetching of HTTPS resources."
" Value is comma-separated list of keywords: "
SERF_HTTPS_KEYWORDS, false);
AddSystemProperty("", &SystemRewriteOptions::ssl_cert_directory_, "assld",
RewriteOptions::kSslCertDirectory,
"Directory to find SSL certificates.", false);
AddSystemProperty("", &SystemRewriteOptions::ssl_cert_file_, "asslf",
RewriteOptions::kSslCertFile,
"File with SSL certificates.", false);
AddSystemProperty("", &SystemRewriteOptions::slurp_directory_, "asd",
RewriteOptions::kSlurpDirectory,
"Directory from which to read slurped resources", false);
AddSystemProperty(false, &SystemRewriteOptions::test_proxy_, "atp",
RewriteOptions::kTestProxy,
"Direct non-PageSpeed URLs to a fetcher, acting as a "
"simple proxy. Meant for test use only", false);
AddSystemProperty("", &SystemRewriteOptions::test_proxy_slurp_, "atps",
RewriteOptions::kTestProxySlurp,
"If set, the fetcher used by the TestProxy mode will be a "
"readonly slurp fetcher from the given directory", false);
AddSystemProperty(false, &SystemRewriteOptions::slurp_read_only_, "asro",
RewriteOptions::kSlurpReadOnly,
"Only read from the slurped directory, fail to fetch "
"URLs not already in the slurped directory", false);
AddSystemProperty(true,
&SystemRewriteOptions::rate_limit_background_fetches_,
"rlbf",
RewriteOptions::kRateLimitBackgroundFetches,
"Rate-limit the number of background HTTP fetches done at "
"once", true);
AddSystemProperty(0, &SystemRewriteOptions::slurp_flush_limit_, "asfl",
RewriteOptions::kSlurpFlushLimit,
"Set the maximum byte size for the slurped content to hold "
"before a flush", false);
AddSystemProperty(false, &SystemRewriteOptions::disable_loopback_routing_,
"adlr",
"DangerPermitFetchFromUnknownHosts",
kProcessScopeStrict,
"Disable security checks that prohibit fetching from "
"hostnames mod_pagespeed does not know about", false);
AddSystemProperty(false, &SystemRewriteOptions::fetch_with_gzip_, "afg",
"FetchWithGzip", kProcessScope,
"Request http content from origin servers using gzip",
true);
AddSystemProperty(1024 * 1024 * 10, /* 10 Megabytes */
&SystemRewriteOptions::ipro_max_response_bytes_,
"imrb", "IproMaxResponseBytes", kProcessScope,
"Limit allowed size of IPRO responses. "
"Set to 0 for unlimited.", true);
AddSystemProperty(10,
&SystemRewriteOptions::ipro_max_concurrent_recordings_,
"imcr", "IproMaxConcurrentRecordings", kProcessScope,
"Limit allowed number of IPRO recordings", true);
AddSystemProperty(1024 * 50, /* 50 Megabytes */
&SystemRewriteOptions::default_shared_memory_cache_kb_,
"dsmc", "DefaultSharedMemoryCacheKB", kProcessScope,
"Size of the default shared memory cache used by all "
"virtual hosts that don't use "
"CreateSharedMemoryMetadataCache. "
"Set to 0 to turn off the default shared memory cache.",
false);
AddSystemProperty("",
&SystemRewriteOptions::purge_method_,
"pm", "PurgeMethod", kServerScope,
"HTTP method used for Cache Purge requests. Typically "
"this is set to PURGE, but you must ensure that only "
"authorized clients have access to this method.", false);
AddSystemProperty("",
&SystemRewriteOptions::static_assets_to_cdn_,
"sacdn", kStaticAssetCDN, kProcessScopeStrict,
"Configures serving of helper scripts from external "
"URLs rather than from compiled-in versions via static "
"handler.", true);
MergeSubclassProperties(system_properties_);
// We allow a special instantiation of the options with a null thread system
// because we are only updating the static properties on process startup; we
// won't have a thread-system yet or multiple threads.
//
// Leave slurp_read_only out of the signature as (a) we don't actually change
// this spontaneously, and (b) it's useful to keep the metadata cache between
// slurping read-only and slurp read/write.
SystemRewriteOptions config("dummy_options", NULL);
config.slurp_read_only_.DoNotUseForSignatureComputation();
// This one shouldn't be changed live either nor control any cache keys.
config.static_assets_to_cdn_.DoNotUseForSignatureComputation();
}
SystemRewriteOptions* SystemRewriteOptions::Clone() const {
SystemRewriteOptions* options = NewOptions();
options->Merge(*this);
return options;
}
SystemRewriteOptions* SystemRewriteOptions::NewOptions() const {
return new SystemRewriteOptions("new_options", thread_system());
}
const SystemRewriteOptions* SystemRewriteOptions::DynamicCast(
const RewriteOptions* instance) {
const SystemRewriteOptions* config =
dynamic_cast<const SystemRewriteOptions*>(instance);
DCHECK(config != NULL);
return config;
}
SystemRewriteOptions* SystemRewriteOptions::DynamicCast(
RewriteOptions* instance) {
SystemRewriteOptions* config = dynamic_cast<SystemRewriteOptions*>(instance);
DCHECK(config != NULL);
return config;
}
bool SystemRewriteOptions::HttpsOptions::SetFromString(
StringPiece value, GoogleString* error_detail) {
bool success = SerfUrlAsyncFetcher::ValidateHttpsOptions(value, error_detail);
if (success) {
set(value.as_string());
}
return success;
}
bool SystemRewriteOptions::StaticAssetCDNOptions::SetFromString(
StringPiece value, GoogleString* error_detail) {
StringPieceVector args;
SplitStringPieceToVector(value, ",", &args, true);
if (args.size() < 2) {
*error_detail = "Not enough arguments.";
return false;
}
StaticAssetSet* new_set = static_assets_to_cdn_.MakeWriteable();
new_set->clear();
for (int i = 1, n = args.size(); i < n; ++i) {
StaticAssetEnum::StaticAsset value;
TrimWhitespace(&args[i]);
if (StaticAssetEnum::StaticAsset_Parse(args[i].as_string(), &value)) {
new_set->insert(value);
} else {
*error_detail = StrCat("Invalid static asset label: ", args[i]);
return false;
}
}
args[0].CopyToString(&mutable_value());
return true;
}
GoogleString SystemRewriteOptions::StaticAssetCDNOptions::Signature(
const Hasher* hasher) const {
LOG(DFATAL) << "StaticAssetCDNOptions shouldn't be in signature computation?";
return "";
}
GoogleString SystemRewriteOptions::StaticAssetCDNOptions::ToString() const {
GoogleString result = value();
for (StaticAssetSet::const_iterator i = static_assets_to_cdn_->begin();
i != static_assets_to_cdn_->end(); ++i) {
StrAppend(&result, "&", StaticAssetEnum::StaticAsset_Name(*i));
}
return result;
}
void SystemRewriteOptions::StaticAssetCDNOptions::Merge(const OptionBase* src) {
const SystemRewriteOptions::StaticAssetCDNOptions* cdn_src =
dynamic_cast<const SystemRewriteOptions::StaticAssetCDNOptions*>(src);
CHECK(cdn_src != NULL);
if (cdn_src->was_set()) {
mutable_value() = cdn_src->value();
static_assets_to_cdn_ = cdn_src->static_assets_to_cdn_;
}
}
void SystemRewriteOptions::FillInStaticAssetCDNConf(
StaticAssetConfig* out_conf) const {
const SystemRewriteOptions::StaticAssetSet& assets_to_enable =
static_assets_to_cdn();
for (SystemRewriteOptions::StaticAssetSet::const_iterator i =
assets_to_enable.begin();
i != assets_to_enable.end(); ++i) {
StaticAssetEnum::StaticAsset role = *i;
GoogleString name = StaticAssetEnum::StaticAsset_Name(role);
StaticAssetConfig::Asset* asset_out = out_conf->add_asset();
asset_out->set_role(role);
// For file base name, we just lowercase the enum and convert
// the last _ into . Combined with prefixes set below, this mostly produces
// sensible filenames, like opt-blank.gif, dbg-mobilize_xhr.js, as the last
// word in the enum tends to be the extension. A few cases get a bit weird
// (client_domain.rewriter, defer.iframe), but they don't seem worth
// worrying about for a developer-targeted feature.
LowerString(&name);
size_t last_under = name.find_last_of('_');
if (last_under != GoogleString::npos) {
name[last_under] = '.';
}
asset_out->set_name(name);
asset_out->set_debug_hash("dbg");
asset_out->set_opt_hash("opt");
}
}
void SystemRewriteOptions::Merge(const RewriteOptions& src) {
RewriteOptions::Merge(src);
const SystemRewriteOptions* ssrc = DynamicCast(&src);
CHECK(ssrc != NULL);
statistics_domains_.MergeOrShare(ssrc->statistics_domains_);
global_statistics_domains_.MergeOrShare(ssrc->global_statistics_domains_);
messages_domains_.MergeOrShare(ssrc->messages_domains_);
console_domains_.MergeOrShare(ssrc->console_domains_);
admin_domains_.MergeOrShare(ssrc->admin_domains_);
global_admin_domains_.MergeOrShare(ssrc->global_admin_domains_);
}
RewriteOptions::OptionSettingResult
SystemRewriteOptions::ParseAndSetOptionFromName2(
StringPiece name, StringPiece arg1, StringPiece arg2,
GoogleString* msg, MessageHandler* handler) {
CopyOnWrite<FastWildcardGroup>* wildcard_group = NULL;
if (StringCaseEqual(name, "StatisticsDomains")) {
wildcard_group = &statistics_domains_;
} else if (StringCaseEqual(name, "GlobalStatisticsDomains")) {
wildcard_group = &global_statistics_domains_;
} else if (StringCaseEqual(name, "MessagesDomains")) {
wildcard_group = &messages_domains_;
} else if (StringCaseEqual(name, "ConsoleDomains")) {
wildcard_group = &console_domains_;
} else if (StringCaseEqual(name, "AdminDomains")) {
wildcard_group = &admin_domains_;
} else if (StringCaseEqual(name, "GlobalAdminDomains")) {
wildcard_group = &global_admin_domains_;
}
if (wildcard_group != NULL) {
FastWildcardGroup* mutable_wildcard_group = wildcard_group->MakeWriteable();
if (StringCaseEqual(arg1, "allow")) {
mutable_wildcard_group->Allow(arg2);
} else if (StringCaseEqual(arg1, "disallow")) {
mutable_wildcard_group->Disallow(arg2);
} else {
*msg = StrCat("expected 'allow' or 'disallow', got '", arg1, "'");
return RewriteOptions::kOptionValueInvalid;
}
return RewriteOptions::kOptionOk;
}
return RewriteOptions::ParseAndSetOptionFromName2(
name, arg1, arg2, msg, handler);
}
GoogleString SystemRewriteOptions::SubclassSignatureLockHeld() {
GoogleString out;
StrAppend(&out, "_", "SD:", statistics_domains_->Signature());
StrAppend(&out, "_", "GSD:", global_statistics_domains_->Signature());
StrAppend(&out, "_", "MD:", messages_domains_->Signature());
StrAppend(&out, "_", "CD:", console_domains_->Signature());
StrAppend(&out, "_", "AD:", admin_domains_->Signature());
StrAppend(&out, "_", "GAD:", global_admin_domains_->Signature());
return out;
}
bool SystemRewriteOptions::AllowDomain(
const GoogleUrl& url, const FastWildcardGroup& wildcard_group) const {
StringPiece host = url.Host();
if (host.empty()) {
DCHECK(false);
return false;
}
if (wildcard_group.empty()) {
return true; // Allow unless they disallowed anything.
}
// Otherwise, allow only if this host is whitelisted.
return wildcard_group.Match(host.as_string(), false /* default deny */);
}
} // namespace net_instaweb