| // 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 |