| // Copyright 2010 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. |
| |
| #include <string> |
| |
| #include "apr_strings.h" |
| #include "apr_version.h" |
| #include "base/basictypes.h" |
| #include "base/scoped_ptr.h" |
| #include "base/string_util.h" |
| #include "net/instaweb/apache/header_util.h" |
| #include "net/instaweb/apache/log_message_handler.h" |
| #include "net/instaweb/apache/serf_url_async_fetcher.h" |
| #include "net/instaweb/apache/instaweb_context.h" |
| #include "net/instaweb/apache/instaweb_handler.h" |
| #include "net/instaweb/apache/mod_instaweb.h" |
| #include "net/instaweb/apache/apache_rewrite_driver_factory.h" |
| #include "net/instaweb/apache/apr_statistics.h" |
| #include "net/instaweb/public/version.h" |
| #include "net/instaweb/rewriter/public/rewrite_driver.h" |
| #include "net/instaweb/rewriter/public/rewrite_options.h" |
| #include "net/instaweb/util/public/google_message_handler.h" |
| #include "net/instaweb/util/public/google_url.h" |
| #include "net/instaweb/util/public/simple_meta_data.h" |
| #include "net/instaweb/util/public/query_params.h" |
| |
| // The httpd header must be after the pagepseed_server_context.h. Otherwise, |
| // the compiler will complain |
| // "strtoul_is_not_a_portable_function_use_strtol_instead". |
| #include "httpd.h" |
| #include "http_config.h" |
| #include "http_core.h" |
| // When HAVE_SYSLOG, syslog.h is included and #defined LOG_*, which conflicts |
| // with log_message_handler. |
| #undef HAVE_SYSLOG |
| #include "http_log.h" |
| #include "http_protocol.h" |
| #if USE_FIXUP_HOOK |
| #include "http_request.h" // NOLINT |
| #endif |
| |
| namespace net_instaweb { |
| |
| namespace { |
| |
| // Instaweb directive names -- these must match install/instaweb.conf.template. |
| const char* kModPagespeed = "ModPagespeed"; |
| const char* kModPagespeedUrlPrefix = "ModPagespeedUrlPrefix"; |
| const char* kModPagespeedFetchProxy = "ModPagespeedFetchProxy"; |
| const char* kModPagespeedGeneratedFilePrefix = |
| "ModPagespeedGeneratedFilePrefix"; |
| const char* kModPagespeedFileCachePath = "ModPagespeedFileCachePath"; |
| const char* kModPagespeedFileCacheSizeKb = "ModPagespeedFileCacheSizeKb"; |
| const char* kModPagespeedFileCacheCleanIntervalMs |
| = "ModPagespeedFileCacheCleanIntervalMs"; |
| const char* kModPagespeedLRUCacheKbPerProcess = |
| "ModPagespeedLRUCacheKbPerProcess"; |
| const char* kModPagespeedLRUCacheByteLimit = "ModPagespeedLRUCacheByteLimit"; |
| const char* kModPagespeedFetcherTimeoutMs = "ModPagespeedFetcherTimeOutMs"; |
| const char* kModPagespeedNumShards = "ModPagespeedNumShards"; |
| const char* kModPagespeedCssOutlineMinBytes = "ModPagespeedCssOutlineMinBytes"; |
| const char* kModPagespeedJsOutlineMinBytes = "ModPagespeedJsOutlineMinBytes"; |
| const char* kModPagespeedFilters = "ModPagespeedFilters"; |
| const char* kModPagespeedRewriteLevel = "ModPagespeedRewriteLevel"; |
| const char* kModPagespeedEnableFilters = "ModPagespeedEnableFilters"; |
| const char* kModPagespeedDisableFilters = "ModPagespeedDisableFilters"; |
| const char* kModPagespeedSlurpDirectory = "ModPagespeedSlurpDirectory"; |
| const char* kModPagespeedSlurpReadOnly = "ModPagespeedSlurpReadOnly"; |
| const char* kModPagespeedSlurpFlushLimit = "ModPagespeedSlurpFlushLimit"; |
| const char* kModPagespeedForceCaching = "ModPagespeedForceCaching"; |
| const char* kModPagespeedCssInlineMaxBytes = "ModPagespeedCssInlineMaxBytes"; |
| const char* kModPagespeedImgInlineMaxBytes = "ModPagespeedImgInlineMaxBytes"; |
| const char* kModPagespeedJsInlineMaxBytes = "ModPagespeedJsInlineMaxBytes"; |
| const char* kModPagespeedDomain = "ModPagespeedDomain"; |
| const char* kModPagespeedFilterName = "MOD_PAGESPEED_OUTPUT_FILTER"; |
| const char* kRepairHeadersFilterName = "MOD_PAGESPEED_REPAIR_HEADERS"; |
| const char* kModPagespeedBeaconUrl = "ModPagespeedBeaconUrl"; |
| |
| // TODO(jmarantz): determine the version-number from SVN at build time. |
| const char kModPagespeedVersion[] = MOD_PAGESPEED_VERSION_STRING "-" |
| LASTCHANGE_STRING; |
| const char kModPagespeedHeader[] = "X-Mod-Pagespeed"; |
| |
| enum RewriteOperation {REWRITE, FLUSH, FINISH}; |
| enum ConfigSwitch {CONFIG_ON, CONFIG_OFF, CONFIG_ERROR}; |
| |
| // Determine the resource type from a Content-Type string |
| bool is_html_content(const char* content_type) { |
| if (content_type != NULL && |
| StartsWithASCII(content_type, "text/html", false)) { |
| return true; |
| } |
| return false; |
| } |
| |
| // Check if pagespeed optimization rules applicable. |
| bool check_pagespeed_applicable(ap_filter_t* filter, apr_bucket_brigade* bb, |
| const QueryParams& query_params) { |
| request_rec* request = filter->r; |
| // We can't operate on Content-Ranges. |
| if (apr_table_get(request->headers_out, "Content-Range") != NULL) { |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, request, |
| "Content-Range is not available"); |
| return false; |
| } |
| |
| // Only rewrite text/html. |
| if (!is_html_content(request->content_type)) { |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, request, |
| "Content-Type=%s Host=%s Uri=%s", |
| request->content_type, request->hostname, |
| request->unparsed_uri); |
| return false; |
| } |
| |
| // Pass-through mode. |
| // TODO(jmarantz): strip the param from the URL. |
| CharStarVector v; |
| if (query_params.Lookup(kModPagespeed, &v) && |
| (v.size() == 1) && (strcasecmp(v[0], "off") == 0)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Create a new bucket from buf using HtmlRewriter. |
| // TODO(lsong): the content is copied multiple times. The buf is |
| // copied/processed to string output, then output is copied to new bucket. |
| apr_bucket* rewrite_html(ap_filter_t *filter, RewriteOperation operation, |
| const char* buf, int len) { |
| request_rec* request = filter->r; |
| InstawebContext* context = |
| static_cast<InstawebContext*>(filter->ctx); |
| if (context == NULL) { |
| LOG(DFATAL) << "Context is null"; |
| return NULL; |
| } |
| if (buf != NULL) { |
| context->Rewrite(buf, len); |
| } |
| if (operation == REWRITE) { |
| return NULL; |
| } else if (operation == FLUSH) { |
| context->Flush(); |
| } else if (operation == FINISH) { |
| context->Finish(); |
| } |
| |
| const std::string& output = context->output(); |
| if (output.empty()) { |
| return NULL; |
| } |
| // Use the rewritten content. Create in heap since output will |
| // be emptied for reuse. |
| apr_bucket* bucket = apr_bucket_heap_create( |
| output.data(), output.size(), NULL, |
| request->connection->bucket_alloc); |
| context->clear(); |
| return bucket; |
| } |
| |
| // To support query-specific rewriter sets, scan the query parameters to |
| // see whether we have any options we want to set. We will only allow |
| // a limited number of options to be set. In particular, some options are |
| // risky to set per query, such as image inline threshold, which exposes |
| // a DOS vulnerability and a risk of poisoning our internal cache. Domain |
| // adjustments can potentially introduce a security vulnerability. |
| // |
| // So we will check for explicit parameters we want to support. |
| bool ScanQueryParamsForRewriterOptions(RewriteDriverFactory* factory, |
| const QueryParams& query_params, |
| RewriteOptions* options) { |
| MessageHandler* handler = factory->message_handler(); |
| bool ret = true; |
| int option_count = 0; |
| for (int i = 0; i < query_params.size(); ++i) { |
| const char* name = query_params.name(i); |
| const char* value = query_params.value(i); |
| if (value == NULL) { |
| handler->Message(kWarning, "Empty value for %s", name); |
| ret = false; |
| } |
| int64 int_val; |
| // TODO(jmarantz): add js inlinine threshold, outline threshold. |
| if (strcmp(name, kModPagespeedCssInlineMaxBytes) == 0) { |
| if (StringToInt64(value, &int_val)) { |
| options->set_css_inline_max_bytes(int_val); |
| ++option_count; |
| } else { |
| handler->Message(kWarning, "Invalid integer value for %s: %s", |
| name, value); |
| ret = false; |
| } |
| } else if (strcmp(name, kModPagespeedFilters) == 0) { |
| // When using ModPagespeedFilters query param, only the |
| // specified filters should be enabled. |
| options->SetRewriteLevel(RewriteOptions::kPassThrough); |
| if (options->EnableFiltersByCommaSeparatedList(value, handler)) { |
| ++option_count; |
| } else { |
| ret = false; |
| } |
| } |
| } |
| return ret && (option_count > 0); |
| } |
| |
| apr_status_t instaweb_out_filter(ap_filter_t *filter, apr_bucket_brigade *bb) { |
| // Check if pagespeed is enabled. |
| request_rec* request = filter->r; |
| ApacheRewriteDriverFactory* factory = InstawebContext::Factory( |
| request->server); |
| if (!factory->enabled()) { |
| ap_remove_output_filter(filter); |
| return ap_pass_brigade(filter->next, bb); |
| } |
| |
| // Do nothing if there is nothing, and stop passing to other filters. |
| if (APR_BRIGADE_EMPTY(bb)) { |
| return APR_SUCCESS; |
| } |
| |
| QueryParams query_params; |
| query_params.Parse(request->parsed_uri.query); |
| |
| // Check if pagespeed optimization applicable and get the resource type. |
| if (!check_pagespeed_applicable(filter, bb, query_params)) { |
| ap_remove_output_filter(filter); |
| return ap_pass_brigade(filter->next, bb); |
| } |
| |
| InstawebContext* context = |
| static_cast<InstawebContext*>(filter->ctx); |
| |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, request, |
| "ModPagespeed OutputFilter called for request %s", |
| request->unparsed_uri); |
| |
| // Initialize per-request context structure. Note that instaweb_out_filter |
| // may get called multiple times per HTTP request, and this occurs only |
| // on the first call. |
| if (context == NULL) { |
| // Check if mod_instaweb has already rewritten the HTML. If the server is |
| // setup as both the original and the proxy server, mod_pagespeed filter may |
| // be applied twice. To avoid this, skip the content if it is already |
| // optimized by mod_pagespeed. |
| if (apr_table_get(request->headers_out, kModPagespeedHeader) != NULL) { |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, request, |
| "Already has x-mod-pagespeed"); |
| ap_remove_output_filter(filter); |
| return ap_pass_brigade(filter->next, bb); |
| } |
| |
| std::string absolute_url; |
| if (strncmp(request->unparsed_uri, "http://", 7) == 0) { |
| absolute_url = request->unparsed_uri; |
| } else { |
| absolute_url = ap_construct_url(request->pool, request->unparsed_uri, |
| request); |
| } |
| |
| RewriteOptions custom_options; |
| bool use_custom_options = ScanQueryParamsForRewriterOptions( |
| factory, query_params, &custom_options); |
| context = new InstawebContext(request, factory, absolute_url, |
| use_custom_options, custom_options); |
| filter->ctx = context; |
| |
| InstawebContext::ContentEncoding encoding = |
| context->content_encoding(); |
| if (encoding == InstawebContext::kGzip) { |
| // Unset the content encoding because the InstawebContext will decode the |
| // content before parsing. |
| apr_table_unset(request->headers_out, HttpAttributes::kContentEncoding); |
| apr_table_unset(request->err_headers_out, |
| HttpAttributes::kContentEncoding); |
| } else if (encoding == InstawebContext::kOther) { |
| // We don't know the encoding, so we cannot rewrite the HTML. |
| ap_remove_output_filter(filter); |
| return ap_pass_brigade(filter->next, bb); |
| } |
| |
| SimpleMetaData request_headers, response_headers; |
| ApacheHeaderToMetaData(request->headers_in, 0, |
| request->proto_num, &request_headers); |
| if ((request->filename != NULL) && |
| (strncmp(request->filename, "proxy:", 6) == 0)) { |
| absolute_url.assign(request->filename + 6, strlen(request->filename) - 6); |
| } |
| |
| apr_table_setn(request->headers_out, kModPagespeedHeader, |
| kModPagespeedVersion); |
| apr_table_unset(request->headers_out, HttpAttributes::kContentLength); |
| apr_table_unset(request->headers_out, "Content-MD5"); |
| apr_table_unset(request->headers_out, HttpAttributes::kContentEncoding); |
| |
| // Note that downstream output filters may further mutate the response |
| // headers, and this will not show those mutations. |
| ApacheHeaderToMetaData(request->headers_out, request->status, |
| request->proto_num, &response_headers); |
| |
| // Make sure compression is enabled for this response. |
| ap_add_output_filter("DEFLATE", NULL, request, request->connection); |
| } |
| |
| apr_bucket* new_bucket = NULL; |
| apr_bucket_brigade* context_bucket_brigade = context->bucket_brigade(); |
| while (!APR_BRIGADE_EMPTY(bb)) { |
| apr_bucket* bucket = APR_BRIGADE_FIRST(bb); |
| // Remove the bucket from the old brigade. We will create new bucket or |
| // reuse the bucket to insert into the new brigade. |
| APR_BUCKET_REMOVE(bucket); |
| if (!APR_BUCKET_IS_METADATA(bucket)) { |
| const char* buf = NULL; |
| size_t bytes = 0; |
| apr_status_t ret_code = |
| apr_bucket_read(bucket, &buf, &bytes, APR_BLOCK_READ); |
| if (ret_code == APR_SUCCESS) { |
| new_bucket = rewrite_html(filter, REWRITE, buf, bytes); |
| } else { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, ret_code, request, |
| "Reading bucket failed (rcode=%d)", ret_code); |
| apr_bucket_delete(bucket); |
| return ret_code; |
| } |
| // Processed the bucket, now delete it. |
| apr_bucket_delete(bucket); |
| if (new_bucket != NULL) { |
| APR_BRIGADE_INSERT_TAIL(context_bucket_brigade, new_bucket); |
| } |
| } else if (APR_BUCKET_IS_EOS(bucket)) { |
| new_bucket = rewrite_html(filter, FINISH, NULL, 0); |
| if (new_bucket != NULL) { |
| APR_BRIGADE_INSERT_TAIL(context_bucket_brigade, new_bucket); |
| } |
| // Insert the EOS bucket to the new brigade. |
| APR_BRIGADE_INSERT_TAIL(context_bucket_brigade, bucket); |
| // OK, we have seen the EOS. Time to pass it along down the chain. |
| return ap_pass_brigade(filter->next, context_bucket_brigade); |
| } else if (APR_BUCKET_IS_FLUSH(bucket)) { |
| new_bucket = rewrite_html(filter, FLUSH, NULL, 0); |
| if (new_bucket != NULL) { |
| APR_BRIGADE_INSERT_TAIL(context_bucket_brigade, new_bucket); |
| } |
| APR_BRIGADE_INSERT_TAIL(context_bucket_brigade, bucket); |
| // OK, Time to flush, pass it along down the chain. |
| apr_status_t ret_code = |
| ap_pass_brigade(filter->next, context_bucket_brigade); |
| if (ret_code != APR_SUCCESS) { |
| return ret_code; |
| } |
| } else { |
| // TODO(lsong): remove this log. |
| ap_log_rerror(APLOG_MARK, APLOG_INFO, APR_SUCCESS, request, |
| "Unknown meta data"); |
| APR_BRIGADE_INSERT_TAIL(context_bucket_brigade, bucket); |
| } |
| } |
| |
| apr_brigade_cleanup(bb); |
| return APR_SUCCESS; |
| } |
| |
| apr_status_t pagespeed_child_exit(void* data) { |
| ApacheRewriteDriverFactory* factory = |
| static_cast<ApacheRewriteDriverFactory*>(data); |
| delete factory; |
| return APR_SUCCESS; |
| } |
| |
| void pagespeed_child_init(apr_pool_t* pool, server_rec* server) { |
| // Create PageSpeed context used by instaweb rewrite-driver. This is |
| // per-process, so we initialize all the server's context by iterating the |
| // server lists in server->next. |
| server_rec* next_server = server; |
| while (next_server) { |
| ApacheRewriteDriverFactory* factory = InstawebContext::Factory(next_server); |
| if (factory->statistics()) { |
| factory->statistics()->InitVariables(pool, false); |
| } |
| next_server = next_server->next; |
| } |
| } |
| |
| int pagespeed_post_config(apr_pool_t* pool, apr_pool_t* plog, apr_pool_t* ptemp, |
| server_rec *server) { |
| AprStatistics* statistics = new AprStatistics(); |
| RewriteDriverFactory::Initialize(statistics); |
| SerfUrlAsyncFetcher::Initialize(statistics); |
| statistics->InitVariables(pool, true); |
| |
| server_rec* next_server = server; |
| while (next_server) { |
| ApacheRewriteDriverFactory* factory = InstawebContext::Factory(next_server); |
| if (factory->enabled()) { |
| factory->set_statistics(statistics); |
| if (factory->url_prefix().empty() || |
| factory->filename_prefix().empty() || |
| factory->file_cache_path().empty()) { |
| std::string buf("mod_pagespeed is enabled. "); |
| buf += "The following directives must not be NULL\n"; |
| buf += StrCat(kModPagespeedUrlPrefix, "=", factory->url_prefix(), "\n"); |
| buf += StrCat(kModPagespeedFileCachePath, "="); |
| buf += StrCat(factory->file_cache_path(), "\n"); |
| buf += StrCat(kModPagespeedGeneratedFilePrefix, "="); |
| buf += StrCat(factory->filename_prefix(), "\n"); |
| factory->message_handler()->Message(kError, "%s", buf.c_str()); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| // TODO(jmarantz): spew the rewriters |
| } |
| next_server = next_server->next; |
| } |
| return OK; |
| } |
| |
| // Here log transaction will wait for all the asynchronous resource fetchers to |
| // finish. |
| apr_status_t pagespeed_log_transaction(request_rec *request) { |
| server_rec* server = request->server; |
| ApacheRewriteDriverFactory* factory = InstawebContext::Factory(server); |
| if (factory == NULL) { |
| return DECLINED; |
| } |
| return DECLINED; |
| } |
| |
| // This function is a callback and it declares what |
| // other functions should be called for request |
| // processing and configuration requests. This |
| // callback function declares the Handlers for |
| // other events. |
| void mod_pagespeed_register_hooks(apr_pool_t *pool) { |
| // Enable logging using pagespeed style |
| InstallLogMessageHandler(pool); |
| |
| // Use instaweb to handle generated resources. |
| ap_hook_handler(instaweb_handler, NULL, NULL, -1); |
| ap_register_output_filter( |
| kModPagespeedFilterName, instaweb_out_filter, NULL, AP_FTYPE_RESOURCE); |
| // We need our repair headers filter to run after mod_headers. The |
| // mod_headers, which is the filter that is used to add the cache settings, is |
| // AP_FTYPE_CONTENT_SET. Using (AP_FTYPE_CONTENT_SET + 2) to make sure that we |
| // run after mod_headers. |
| ap_register_output_filter( |
| kRepairHeadersFilterName, repair_caching_header, NULL, |
| static_cast<ap_filter_type>(AP_FTYPE_CONTENT_SET + 2)); |
| ap_hook_post_config(pagespeed_post_config, NULL, NULL, APR_HOOK_MIDDLE); |
| ap_hook_child_init(pagespeed_child_init, NULL, NULL, APR_HOOK_LAST); |
| ap_hook_log_transaction(pagespeed_log_transaction, NULL, NULL, APR_HOOK_LAST); |
| } |
| |
| void* mod_pagespeed_create_server_config(apr_pool_t* pool, server_rec* server) { |
| ApacheRewriteDriverFactory* factory = InstawebContext::Factory(server); |
| if (factory == NULL) { |
| factory = new ApacheRewriteDriverFactory(pool, server); |
| |
| // To clean up the factory on process shutdown, we need to run |
| // pagespeed_child_exit *before* the pool is destroyed. If we run |
| // that hook with apr_pool_cleanup_register then the pool will |
| // already be destroyed, and the factory destruction will crash. |
| // The proper way to fix this is with: |
| // |
| // apr_pool_pre_cleanup_register(pool, factory, pagespeed_child_exit); |
| // |
| // However, this method was added in apr 1.3. We can do a compile-time |
| // check such as |
| // |
| // #if ((APR_MAJOR_VERSION > 1) || |
| // ((APR_MAJOR_VERSION == 1) && APR_MINOR_VERSION > 2)) |
| // |
| // However, the Apache include files that we depend on during the |
| // build process may not correspond to the Apache version into |
| // which that mod_pagespeed.so will be dynamically loaded. At |
| // some point in the future, we may be able to make apr 1.3 a |
| // minimum requirement for mod_pagespeed. In the meantime, we |
| // will not call the factory destructor and will instead rely on |
| // the process memory clean up to do what's necessary. |
| // |
| // TODO(jmarantz): Start employing apr_pool_pre_cleanup_register when |
| // it is generaly available |
| // |
| // TODO(jmarantz): Figure out how to segregate the pool-dependent |
| // cleanups (e.g. apr_mutex) from the pool-independent cleanups |
| // (e.g. memory allocated with new) so we can clean those up using |
| // apr_pool_cleanup_register. |
| } |
| return factory; |
| } |
| |
| typedef void (ApacheRewriteDriverFactory::*SetBoolFn)(bool val); |
| typedef void (ApacheRewriteDriverFactory::*SetInt64Fn)(int64 val); |
| typedef void (ApacheRewriteDriverFactory::*SetIntFn)(int val); |
| |
| const char* ParseBoolOption(cmd_parms* cmd, SetBoolFn fn, const char* arg) { |
| ApacheRewriteDriverFactory* factory = InstawebContext::Factory(cmd->server); |
| const char* ret = NULL; |
| if (strcasecmp(arg, "on") == 0) { |
| (factory->*fn)(true); |
| } else if (strcasecmp(arg, "off") == 0) { |
| (factory->*fn)(false); |
| } else { |
| ret = apr_pstrcat(cmd->pool, cmd->directive->directive, " on|off", NULL); |
| } |
| return ret; |
| } |
| |
| const char* ParseInt64Option(cmd_parms* cmd, SetInt64Fn fn, const char* arg) { |
| ApacheRewriteDriverFactory* factory = InstawebContext::Factory(cmd->server); |
| int64 val; |
| const char* ret = NULL; |
| if (StringToInt64(arg, &val)) { |
| (factory->*fn)(val); |
| } else { |
| ret = apr_pstrcat(cmd->pool, cmd->directive->directive, |
| " must specify a 64-bit integer", NULL); |
| } |
| return ret; |
| } |
| |
| const char* ParseIntOption(cmd_parms* cmd, SetIntFn fn, const char* arg) { |
| ApacheRewriteDriverFactory* factory = InstawebContext::Factory(cmd->server); |
| int val; |
| const char* ret = NULL; |
| if (StringToInt(arg, &val)) { |
| (factory->*fn)(val); |
| } else { |
| ret = apr_pstrcat(cmd->pool, cmd->directive->directive, |
| " must specify a 32-bit integer", NULL); |
| } |
| return ret; |
| } |
| |
| static const char* ParseDirective(cmd_parms* cmd, void* data, const char* arg) { |
| ApacheRewriteDriverFactory* factory = InstawebContext::Factory(cmd->server); |
| const char* directive = cmd->directive->directive; |
| const char* ret = NULL; |
| if (strcasecmp(directive, kModPagespeed) == 0) { |
| ret = ParseBoolOption(cmd, &ApacheRewriteDriverFactory::set_enabled, arg); |
| } else if (strcasecmp(directive, kModPagespeedUrlPrefix) == 0) { |
| factory->set_url_prefix(arg); |
| } else if (strcasecmp(directive, kModPagespeedFetchProxy) == 0) { |
| factory->set_fetcher_proxy(arg); |
| } else if (strcasecmp(directive, kModPagespeedGeneratedFilePrefix) == 0) { |
| factory->set_filename_prefix(arg); |
| } else if (strcasecmp(directive, kModPagespeedFileCachePath) == 0) { |
| factory->set_file_cache_path(arg); |
| } else if (strcasecmp(directive, kModPagespeedFileCacheSizeKb) == 0) { |
| ret = ParseInt64Option( |
| cmd, &ApacheRewriteDriverFactory::set_file_cache_clean_size_kb, arg); |
| } else if (strcasecmp(directive, |
| kModPagespeedFileCacheCleanIntervalMs) == 0) { |
| ret = ParseInt64Option( |
| cmd, &ApacheRewriteDriverFactory::set_file_cache_clean_interval_ms, |
| arg); |
| } else if (strcasecmp(directive, kModPagespeedFetcherTimeoutMs) == 0) { |
| ret = ParseInt64Option( |
| cmd, &ApacheRewriteDriverFactory::set_fetcher_time_out_ms, arg); |
| } else if (strcasecmp(directive, kModPagespeedNumShards) == 0) { |
| ret = ParseIntOption(cmd, &ApacheRewriteDriverFactory::set_num_shards, arg); |
| } else if (strcasecmp(directive, kModPagespeedCssOutlineMinBytes) == 0) { |
| ret = ParseInt64Option( |
| cmd, &ApacheRewriteDriverFactory::set_css_outline_min_bytes, arg); |
| } else if (strcasecmp(directive, kModPagespeedJsOutlineMinBytes) == 0) { |
| ret = ParseInt64Option( |
| cmd, &ApacheRewriteDriverFactory::set_js_outline_min_bytes, arg); |
| } else if (strcasecmp(directive, kModPagespeedImgInlineMaxBytes) == 0) { |
| ret = ParseInt64Option( |
| cmd, &ApacheRewriteDriverFactory::set_img_inline_max_bytes, arg); |
| } else if (strcasecmp(directive, kModPagespeedJsInlineMaxBytes) == 0) { |
| ret = ParseInt64Option( |
| cmd, &ApacheRewriteDriverFactory::set_js_inline_max_bytes, arg); |
| } else if (strcasecmp(directive, kModPagespeedCssInlineMaxBytes) == 0) { |
| ret = ParseInt64Option( |
| cmd, &ApacheRewriteDriverFactory::set_css_inline_max_bytes, arg); |
| } else if (strcasecmp(directive, kModPagespeedLRUCacheKbPerProcess) == 0) { |
| ret = ParseInt64Option( |
| cmd, &ApacheRewriteDriverFactory::set_lru_cache_kb_per_process, arg); |
| } else if (strcasecmp(directive, kModPagespeedLRUCacheByteLimit) == 0) { |
| ret = ParseInt64Option( |
| cmd, &ApacheRewriteDriverFactory::set_lru_cache_byte_limit, arg); |
| } else if (strcasecmp(directive, kModPagespeedEnableFilters) == 0) { |
| if (!factory->AddEnabledFilters(arg)) { |
| ret = "Failed to enable some filters."; |
| } |
| } else if (strcasecmp(directive, kModPagespeedDisableFilters) == 0) { |
| if (!factory->AddDisabledFilters(arg)) { |
| ret = "Failed to disable some filters."; |
| } |
| } else if (strcasecmp(directive, kModPagespeedRewriteLevel) == 0) { |
| RewriteOptions::RewriteLevel level = RewriteOptions::kPassThrough; |
| if (RewriteOptions::ParseRewriteLevel(arg, &level)) { |
| factory->SetRewriteLevel(level); |
| } else { |
| ret = "Failed to parse RewriteLevel."; |
| } |
| } else if (strcasecmp(directive, kModPagespeedSlurpDirectory) == 0) { |
| factory->set_slurp_directory(arg); |
| } else if (strcasecmp(directive, kModPagespeedSlurpReadOnly) == 0) { |
| ret = ParseBoolOption( |
| cmd, &ApacheRewriteDriverFactory::set_slurp_read_only, arg); |
| } else if (strcasecmp(directive, kModPagespeedSlurpFlushLimit) == 0) { |
| ret = ParseInt64Option( |
| cmd, &ApacheRewriteDriverFactory::set_slurp_flush_limit, arg); |
| } else if (strcasecmp(directive, kModPagespeedForceCaching) == 0) { |
| ret = ParseBoolOption( |
| cmd, &ApacheRewriteDriverFactory::set_force_caching, arg); |
| } else if (strcasecmp(directive, kModPagespeedBeaconUrl) == 0) { |
| factory->set_beacon_url(arg); |
| } else if (strcasecmp(directive, kModPagespeedDomain) == 0) { |
| factory->domain_lawyer()->AddDomain(arg, factory->message_handler()); |
| } else { |
| return "Unknown directive."; |
| } |
| |
| return ret; |
| } |
| |
| // Setting up Apache options is cumbersome for several reasons: |
| // |
| // 1. Apache appears to require the option table be entirely constructed |
| // using static data. So we cannot use helper functions to create the |
| // helper table, so that we can populate it from another table. |
| // 2. You have to fill in the table with a function pointer with a K&R |
| // C declaration that does not specify its argument types. There appears |
| // to be a type-correct union hidden behind an ifdef for |
| // AP_HAVE_DESIGNATED_INITIALIZER, but that doesn't work. It gives a |
| // syntax error; its comments indicate it is there for Doxygen. |
| // 3. Although you have to pre-declare all the options, you need to again |
| // dispatch based on the name of the options. You could, conceivably, |
| // provide a different function pointer for each call. This might look |
| // feasible with the 'mconfig' argument to AP_INIT_TAKE1, but mconfig |
| // must be specified in a static initializer. So it wouldn't be that easy |
| // to, say, create a C++ object for each config parameter. |
| // |
| // Googling for AP_MODULE_DECLARE_DATA didn't shed any light on how to do this |
| // using a style suitable for programming after 1980. So all we can do is make |
| // this a little less ugly with wrapper macros and helper functions. |
| #define APACHE_CONFIG_OPTION(name, help) \ |
| AP_INIT_TAKE1(name, reinterpret_cast<const char*(*)()>(ParseDirective), \ |
| NULL, RSRC_CONF, help) |
| |
| static const command_rec mod_pagespeed_filter_cmds[] = { |
| APACHE_CONFIG_OPTION(kModPagespeed, "Enable instaweb"), |
| APACHE_CONFIG_OPTION(kModPagespeedUrlPrefix, "Set the url prefix"), |
| APACHE_CONFIG_OPTION(kModPagespeedFetchProxy, "Set the fetch proxy"), |
| APACHE_CONFIG_OPTION(kModPagespeedGeneratedFilePrefix, |
| "Set generated file's prefix"), |
| APACHE_CONFIG_OPTION(kModPagespeedFileCachePath, |
| "Set the path for file cache"), |
| APACHE_CONFIG_OPTION(kModPagespeedFileCacheSizeKb, |
| "Set the target size (in kilobytes) for file cache"), |
| APACHE_CONFIG_OPTION(kModPagespeedFileCacheCleanIntervalMs, |
| "Set the interval (in ms) for cleaning the file cache"), |
| APACHE_CONFIG_OPTION(kModPagespeedFetcherTimeoutMs, |
| "Set internal fetcher timeout in milliseconds"), |
| APACHE_CONFIG_OPTION(kModPagespeedNumShards, "Set number of shards"), |
| APACHE_CONFIG_OPTION(kModPagespeedLRUCacheKbPerProcess, |
| "Set the total size, in KB, of the per-process " |
| "in-memory LRU cache"), |
| APACHE_CONFIG_OPTION(kModPagespeedLRUCacheByteLimit, |
| "Set the maximum byte size entry to store in the per-process " |
| "in-memory LRU cache"), |
| APACHE_CONFIG_OPTION(kModPagespeedRewriteLevel, |
| "Base level of rewriting (PassThrough, CoreFilters)"), |
| APACHE_CONFIG_OPTION(kModPagespeedEnableFilters, |
| "Comma-separated list of enabled filters"), |
| APACHE_CONFIG_OPTION(kModPagespeedDisableFilters, |
| "Comma-separated list of disabled filters"), |
| APACHE_CONFIG_OPTION(kModPagespeedSlurpDirectory, |
| "Directory from which to read slurped resources"), |
| APACHE_CONFIG_OPTION(kModPagespeedSlurpReadOnly, |
| "Only read from the slurped directory, fail to fetch " |
| "URLs not already in the slurped directory"), |
| APACHE_CONFIG_OPTION(kModPagespeedSlurpFlushLimit, |
| "Set the maximum byte size for the slurped content to hold before " |
| "a flush"), |
| APACHE_CONFIG_OPTION(kModPagespeedForceCaching, |
| "Ignore HTTP cache headers and TTLs"), |
| APACHE_CONFIG_OPTION(kModPagespeedCssOutlineMinBytes, |
| "Number of bytes above which inline " |
| "CSS resources will be outlined."), |
| APACHE_CONFIG_OPTION(kModPagespeedJsOutlineMinBytes, |
| "Number of bytes above which inline " |
| "Javascript resources will be outlined."), |
| APACHE_CONFIG_OPTION(kModPagespeedImgInlineMaxBytes, |
| "Number of bytes below which images will be inlined."), |
| APACHE_CONFIG_OPTION(kModPagespeedJsInlineMaxBytes, |
| "Number of bytes below which javascript will be inlined."), |
| APACHE_CONFIG_OPTION(kModPagespeedCssInlineMaxBytes, |
| "Number of bytes below which stylesheets will be inlined."), |
| APACHE_CONFIG_OPTION(kModPagespeedBeaconUrl, "URL for beacon callback" |
| " injected by add_instrumentation."), |
| APACHE_CONFIG_OPTION(kModPagespeedDomain, |
| "Authorize mod_pagespeed to rewrite resources in a domain."), |
| {NULL} |
| }; |
| |
| } // namespace |
| |
| } // namespace net_instaweb |
| |
| extern "C" { |
| // Export our module so Apache is able to load us. |
| // See http://gcc.gnu.org/wiki/Visibility for more information. |
| #if defined(__linux) |
| #pragma GCC visibility push(default) |
| #endif |
| |
| // Declare and populate the module's data structure. The |
| // name of this structure ('pagespeed_module') is important - it |
| // must match the name of the module. This structure is the |
| // only "glue" between the httpd core and the module. |
| module AP_MODULE_DECLARE_DATA pagespeed_module = { |
| // Only one callback function is provided. Real |
| // modules will need to declare callback functions for |
| // server/directory configuration, configuration merging |
| // and other tasks. |
| STANDARD20_MODULE_STUFF, |
| NULL, // create per-directory config structure |
| NULL, // merge per-directory config structures |
| net_instaweb::mod_pagespeed_create_server_config, |
| NULL, // merge per-server config structures |
| net_instaweb::mod_pagespeed_filter_cmds, |
| net_instaweb::mod_pagespeed_register_hooks, |
| }; |
| |
| #if defined(__linux) |
| #pragma GCC visibility pop |
| #endif |
| } // extern "C" |