From: Graham Leggett Date: Tue, 30 Apr 2013 16:10:02 +0000 (+0000) Subject: mod_cache_socache: New cache implementation backed by mod_socache X-Git-Tag: 2.4.5~342 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=c06ddb094ac088e949c616e49d4d12023068539a;p=apache mod_cache_socache: New cache implementation backed by mod_socache that replaces mod_mem_cache removed from httpd v2.2. Add backported files, missed by the backport process in r1477652. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1477708 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/docs/manual/mod/mod_cache_socache.xml b/docs/manual/mod/mod_cache_socache.xml new file mode 100644 index 0000000000..94d00a4fbc --- /dev/null +++ b/docs/manual/mod/mod_cache_socache.xml @@ -0,0 +1,243 @@ + + + + + + + + + +mod_cache_socache +Shared object cache (socache) based storage module for the +HTTP caching filter. +Extension +mod_cache_socache.c +cache_socache_module + + +

mod_cache_socache implements a shared object cache + (socache) based storage manager for mod_cache.

+ +

The headers and bodies of cached responses are combined, and stored + underneath a single key in the shared object cache. A + number of implementations of shared object + caches are available to choose from.

+ +

Multiple content negotiated responses can be stored concurrently, + however the caching of partial content is not yet supported by this + module.

+ + +# Turn on caching +CacheSocache shmcb +CacheSocacheMaxSize 102400 +<Location /foo> + CacheEnable socache +</Location> + +# Fall back to the disk cache +CacheSocache shmcb +CacheSocacheMaxSize 102400 +<Location /foo> + CacheEnable socache + CacheEnable disk +</Location> + + + Note: +

mod_cache_socache requires the services of + mod_cache, which must be loaded before + mod_cache_socache.

+
+
+mod_cache +mod_cache_disk +Caching Guide + + +CacheSocache +The directory root under which cache files are +stored +CacheSocache type[:args] +server configvirtual host + +Available in Apache 2.5 and later + + +

The CacheSocache directive defines the name of + the shared object cache implementation to use, followed by optional + arguments for that implementation. A number of + implementations of shared object caches are available to choose + from.

+ + + CacheSocache shmcb + +
+
+ + +CacheSocacheMaxTime +The maximum time (in seconds) for a document to be placed in the +cache +CacheSocacheMaxTime seconds +CacheSocacheMaxTime 86400 +server config + virtual host + directory + .htaccess + +Available in Apache 2.5 and later + + +

The CacheSocacheMaxTime directive sets the + maximum freshness lifetime, in seconds, for a document to be stored in + the cache. This value overrides the freshness lifetime defined for the + document by the HTTP protocol.

+ + + CacheSocacheMaxTime 86400 + +
+
+ + +CacheSocacheMinTime +The maximum time (in seconds) for a document to be placed in the +cache +CacheSocacheMinTime seconds +CacheSocacheMinTime 600 +server config + virtual host + directory + .htaccess + +Available in Apache 2.5 and later + + +

The CacheSocacheMinTime directive sets the + amount of seconds beyond the freshness lifetime of the response that the + response should be cached for in the shared object cache. If a response is + only stored for its freshness lifetime, there will be no opportunity to + revalidate the response to make it fresh again.

+ + + CacheSocacheMinTime 600 + +
+
+ + +CacheSocacheMaxSize +The maximum size (in bytes) of an entry to be placed in the +cache +CacheSocacheMaxSize bytes +CacheSocacheMaxSize 102400 +server config + virtual host + directory + .htaccess + +Available in Apache 2.5 and later + + +

The CacheSocacheMaxSize directive sets the + maximum size, in bytes, for the combined headers and body of a document + to be considered for storage in the cache. The larger the headers that + are stored alongside the body, the smaller the body may be.

+ +

The mod_cache_socache module will only attempt to + cache responses that have an explicit content length, or that are small + enough to be written in one pass. This is done to allow the + mod_cache_disk module to have an opportunity to cache + responses larger than those cacheable within + mod_cache_socache.

+ + + CacheSocacheMaxSize 102400 + +
+
+ + +CacheSocacheReadSize +The minimum size (in bytes) of the document to read and be cached + before sending the data downstream +CacheSocacheReadSize bytes +CacheSocacheReadSize 0 +server config + virtual host + directory + .htaccess + +Available in Apache 2.5 and later + + +

The CacheSocacheReadSize directive sets the + minimum amount of data, in bytes, to be read from the backend before the + data is sent to the client. The default of zero causes all data read of + any size to be passed downstream to the client immediately as it arrives. + Setting this to a higher value causes the disk cache to buffer at least + this amount before sending the result to the client. This can improve + performance when caching content from a slow reverse proxy.

+ +

This directive only takes effect when the data is being saved to the + cache, as opposed to data being served from the cache.

+ + + CacheReadSize 102400 + +
+
+ + +CacheSocacheReadTime +The minimum time (in milliseconds) that should elapse while reading + before data is sent downstream +CacheSocacheReadTime milliseconds +CacheSocacheReadTime 0 +server config + virtual host + directory + .htaccess + +Available in Apache 2.5 and later + + +

The CacheSocacheReadTime directive sets the minimum amount + of elapsed time that should pass before making an attempt to send data + downstream to the client. During the time period, data will be buffered + before sending the result to the client. This can improve performance when + caching content from a reverse proxy.

+ +

The default of zero disables this option.

+ +

This directive only takes effect when the data is being saved to the + cache, as opposed to data being served from the cache. It is recommended + that this option be used alongside the + CacheSocacheReadSize directive + to ensure that the server does not buffer excessively should data arrive faster + than expected.

+ + + CacheSocacheReadTime 1000 + +
+
+ +
diff --git a/modules/cache/NWGNUcach_socache b/modules/cache/NWGNUcach_socache new file mode 100644 index 0000000000..f7ed0e43de --- /dev/null +++ b/modules/cache/NWGNUcach_socache @@ -0,0 +1,262 @@ +# +# Declare the sub-directories to be built here +# + +SUBDIRS = \ + $(EOLIST) + +# +# Get the 'head' of the build environment. This includes default targets and +# paths to tools +# + +include $(AP_WORK)/build/NWGNUhead.inc + +# +# build this level's files +# +# Make sure all needed macro's are defined +# + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(APR)/include \ + $(APRUTIL)/include \ + $(SRC)/include \ + $(SERVER)/mpm/netware \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = cach_socache + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache $(VERSION_STR) Cache Socache Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = cach_socache + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)/build/NWGNUenvironment.inc +# +NLM_VERSION = + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 65536 + + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = + +# +# If this is specified it will be used by the link '-check' directive +# +NLM_CHECK_SYM = + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# If this is specified it will be linked in with the XDCData option in the def +# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled +# by setting APACHE_UNIPROC in the environment +# +XDCDATA = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/$(NLM_NAME).nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_cache_socache.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(PRELUDE) \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + Apache2 \ + Libc \ + mod_cach \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @libc.imp \ + @aprlib.imp \ + @httpd.imp \ + @mod_cache.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + cache_socache_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(APBUILD)/NWGNUtail.inc + + diff --git a/modules/cache/cache_socache_common.h b/modules/cache/cache_socache_common.h new file mode 100644 index 0000000000..3ee3d0da34 --- /dev/null +++ b/modules/cache/cache_socache_common.h @@ -0,0 +1,57 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @file cache_socache_common.h + * @brief Common Shared Object Cache vars/structs + * + * @defgroup Cache_cache Cache Functions + * @ingroup MOD_SOCACHE_CACHE + * @{ + */ + +#ifndef CACHE_SOCACHE_COMMON_H +#define CACHE_SOCACHE_COMMON_H + +#include "apr_time.h" + +#include "cache_common.h" + +#define CACHE_SOCACHE_VARY_FORMAT_VERSION 1 +#define CACHE_SOCACHE_DISK_FORMAT_VERSION 2 + +typedef struct { + /* Indicates the format of the header struct stored on-disk. */ + apr_uint32_t format; + /* The HTTP status code returned for this response. */ + int status; + /* The size of the entity name that follows. */ + apr_size_t name_len; + /* The number of times we've cached this entity. */ + apr_size_t entity_version; + /* Miscellaneous time values. */ + apr_time_t date; + apr_time_t expire; + apr_time_t request_time; + apr_time_t response_time; + /* Does this cached request have a body? */ + unsigned int header_only:1; + /* The parsed cache control header */ + cache_control_t control; +} cache_socache_info_t; + +#endif /* CACHE_SOCACHE_COMMON_H */ +/** @} */ diff --git a/modules/cache/mod_cache_socache.c b/modules/cache/mod_cache_socache.c new file mode 100644 index 0000000000..9632db123e --- /dev/null +++ b/modules/cache/mod_cache_socache.c @@ -0,0 +1,1491 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 "apr_lib.h" +#include "apr_file_io.h" +#include "apr_strings.h" +#include "apr_buckets.h" +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" +#include "http_core.h" +#include "ap_provider.h" +#include "ap_socache.h" +#include "util_filter.h" +#include "util_script.h" +#include "util_charset.h" +#include "util_mutex.h" + +#include "mod_cache.h" + +#include "cache_socache_common.h" + +/* + * mod_cache_socache: Shared Object Cache Based HTTP 1.1 Cache. + * + * Flow to Find the entry: + * Incoming client requests URI /foo/bar/baz + * Fetch URI key (may contain Format #1 or Format #2) + * If format #1 (Contains a list of Vary Headers): + * Use each header name (from .header) with our request values (headers_in) to + * regenerate key using HeaderName+HeaderValue+.../foo/bar/baz + * re-read in key (must be format #2) + * + * Format #1: + * apr_uint32_t format; + * apr_time_t expire; + * apr_array_t vary_headers (delimited by CRLF) + * + * Format #2: + * cache_socache_info_t (first sizeof(apr_uint32_t) bytes is the format) + * entity name (sobj->name) [length is in cache_socache_info_t->name_len] + * r->headers_out (delimited by CRLF) + * CRLF + * r->headers_in (delimited by CRLF) + * CRLF + */ + +module AP_MODULE_DECLARE_DATA cache_socache_module; + +/* + * cache_socache_object_t + * Pointed to by cache_object_t::vobj + */ +typedef struct cache_socache_object_t +{ + apr_pool_t *pool; /* pool */ + unsigned char *buffer; /* the cache buffer */ + apr_size_t buffer_len; /* size of the buffer */ + apr_bucket_brigade *body; /* brigade containing the body, if any */ + apr_table_t *headers_in; /* Input headers to save */ + apr_table_t *headers_out; /* Output headers to save */ + cache_socache_info_t socache_info; /* Header information. */ + apr_size_t body_offset; /* offset to the start of the body */ + unsigned int newbody :1; /* whether a new body is present */ + apr_time_t expire; /* when to expire the entry */ + + const char *name; /* Requested URI without vary bits - suitable for mortals. */ + const char *key; /* On-disk prefix; URI with Vary bits (if present) */ + apr_off_t file_size; /* File size of the cached data file */ + apr_off_t offset; /* Max size to set aside */ + apr_time_t timeout; /* Max time to set aside */ + unsigned int done :1; /* Is the attempt to cache complete? */ +} cache_socache_object_t; + +/* + * mod_cache_socache configuration + */ +#define DEFAULT_MAX_FILE_SIZE 100*1024 +#define DEFAULT_MAXTIME 86400 +#define DEFAULT_MINTIME 600 +#define DEFAULT_READSIZE 0 +#define DEFAULT_READTIME 0 + +typedef struct cache_socache_provider_conf +{ + const char *args; + ap_socache_provider_t *socache_provider; + ap_socache_instance_t *socache_instance; +} cache_socache_provider_conf; + +typedef struct cache_socache_conf +{ + cache_socache_provider_conf *provider; +} cache_socache_conf; + +typedef struct cache_socache_dir_conf +{ + apr_off_t max; /* maximum file size for cached files */ + apr_time_t maxtime; /* maximum expiry time */ + apr_time_t mintime; /* minimum expiry time */ + apr_off_t readsize; /* maximum data to attempt to cache in one go */ + apr_time_t readtime; /* maximum time taken to cache in one go */ + unsigned int max_set :1; + unsigned int maxtime_set :1; + unsigned int mintime_set :1; + unsigned int readsize_set :1; + unsigned int readtime_set :1; +} cache_socache_dir_conf; + +/* Shared object cache and mutex */ +static const char * const cache_socache_id = "cache-socache"; +static apr_global_mutex_t *socache_mutex = NULL; + +/* + * Local static functions + */ + +static apr_status_t read_array(request_rec *r, apr_array_header_t *arr, + unsigned char *buffer, apr_size_t buffer_len, apr_size_t *slider) +{ + apr_size_t val = *slider; + + while (*slider < buffer_len) { + if (buffer[*slider] == '\r') { + if (val == *slider) { + (*slider)++; + return APR_SUCCESS; + } + *((const char **) apr_array_push(arr)) = apr_pstrndup(r->pool, + (const char *) buffer + val, *slider - val); + (*slider)++; + if (buffer[*slider] == '\n') { + (*slider)++; + } + val = *slider; + } + else if (buffer[*slider] == '\0') { + (*slider)++; + return APR_SUCCESS; + } + else { + (*slider)++; + } + } + + return APR_EOF; +} + +static apr_status_t store_array(apr_array_header_t *arr, unsigned char *buffer, + apr_size_t buffer_len, apr_size_t *slider) +{ + int i, len; + const char **elts; + + elts = (const char **) arr->elts; + + for (i = 0; i < arr->nelts; i++) { + len = strlen(elts[i]); + if (len + 3 >= buffer_len - *slider) { + return APR_EOF; + } + len = apr_snprintf(buffer ? (char *) buffer + *slider : NULL, + buffer ? buffer_len - *slider : 0, "%s" CRLF, elts[i]); + *slider += len; + } + if (buffer) { + memcpy(buffer + *slider, CRLF, sizeof(CRLF) - 1); + } + *slider += sizeof(CRLF) - 1; + + return APR_SUCCESS; +} + +static apr_status_t read_table(cache_handle_t *handle, request_rec *r, + apr_table_t *table, unsigned char *buffer, apr_size_t buffer_len, + apr_size_t *slider) +{ + apr_size_t key = *slider, colon = 0, len = 0; + ; + + while (*slider < buffer_len) { + if (buffer[*slider] == ':') { + if (!colon) { + colon = *slider; + } + (*slider)++; + } + else if (buffer[*slider] == '\r') { + len = colon; + if (key == *slider) { + (*slider)++; + if (buffer[*slider] == '\n') { + (*slider)++; + } + return APR_SUCCESS; + } + if (!colon || buffer[colon++] != ':') { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02344) + "Premature end of cache headers."); + return APR_EGENERAL; + } + while (apr_isspace(buffer[colon])) { + colon++; + } + apr_table_addn(table, apr_pstrndup(r->pool, (const char *) buffer + + key, len - key), apr_pstrndup(r->pool, + (const char *) buffer + colon, *slider - colon)); + (*slider)++; + if (buffer[*slider] == '\n') { + (*slider)++; + } + key = *slider; + colon = 0; + } + else if (buffer[*slider] == '\0') { + (*slider)++; + return APR_SUCCESS; + } + else { + (*slider)++; + } + } + + return APR_EOF; +} + +static apr_status_t store_table(apr_table_t *table, unsigned char *buffer, + apr_size_t buffer_len, apr_size_t *slider) +{ + int i, len; + apr_table_entry_t *elts; + + elts = (apr_table_entry_t *) apr_table_elts(table)->elts; + for (i = 0; i < apr_table_elts(table)->nelts; ++i) { + if (elts[i].key != NULL) { + apr_size_t key_len = strlen(elts[i].key); + apr_size_t val_len = strlen(elts[i].val); + if (key_len + val_len + 5 >= buffer_len - *slider) { + return APR_EOF; + } + len = apr_snprintf(buffer ? (char *) buffer + *slider : NULL, + buffer ? buffer_len - *slider : 0, "%s: %s" CRLF, + elts[i].key, elts[i].val); + *slider += len; + } + } + if (3 >= buffer_len - *slider) { + return APR_EOF; + } + if (buffer) { + memcpy(buffer + *slider, CRLF, sizeof(CRLF) - 1); + } + *slider += sizeof(CRLF) - 1; + + return APR_SUCCESS; +} + +static const char* regen_key(apr_pool_t *p, apr_table_t *headers, + apr_array_header_t *varray, const char *oldkey) +{ + struct iovec *iov; + int i, k; + int nvec; + const char *header; + const char **elts; + + nvec = (varray->nelts * 2) + 1; + iov = apr_palloc(p, sizeof(struct iovec) * nvec); + elts = (const char **) varray->elts; + + /* TODO: + * - Handle multiple-value headers better. (sort them?) + * - Handle Case in-sensitive Values better. + * This isn't the end of the world, since it just lowers the cache + * hit rate, but it would be nice to fix. + * + * The majority are case insenstive if they are values (encoding etc). + * Most of rfc2616 is case insensitive on header contents. + * + * So the better solution may be to identify headers which should be + * treated case-sensitive? + * HTTP URI's (3.2.3) [host and scheme are insensitive] + * HTTP method (5.1.1) + * HTTP-date values (3.3.1) + * 3.7 Media Types [exerpt] + * The type, subtype, and parameter attribute names are case- + * insensitive. Parameter values might or might not be case-sensitive, + * depending on the semantics of the parameter name. + * 4.20 Except [exerpt] + * Comparison of expectation values is case-insensitive for unquoted + * tokens (including the 100-continue token), and is case-sensitive for + * quoted-string expectation-extensions. + */ + + for (i = 0, k = 0; i < varray->nelts; i++) { + header = apr_table_get(headers, elts[i]); + if (!header) { + header = ""; + } + iov[k].iov_base = (char*) elts[i]; + iov[k].iov_len = strlen(elts[i]); + k++; + iov[k].iov_base = (char*) header; + iov[k].iov_len = strlen(header); + k++; + } + iov[k].iov_base = (char*) oldkey; + iov[k].iov_len = strlen(oldkey); + k++; + + return apr_pstrcatv(p, iov, k, NULL); +} + +static int array_alphasort(const void *fn1, const void *fn2) +{ + return strcmp(*(char**) fn1, *(char**) fn2); +} + +static void tokens_to_array(apr_pool_t *p, const char *data, + apr_array_header_t *arr) +{ + char *token; + + while ((token = ap_get_list_item(p, &data)) != NULL) { + *((const char **) apr_array_push(arr)) = token; + } + + /* Sort it so that "Vary: A, B" and "Vary: B, A" are stored the same. */ + qsort((void *) arr->elts, arr->nelts, sizeof(char *), array_alphasort); +} + +/* + * Hook and mod_cache callback functions + */ +static int create_entity(cache_handle_t *h, request_rec *r, const char *key, + apr_off_t len, apr_bucket_brigade *bb) +{ + cache_socache_dir_conf *dconf = + ap_get_module_config(r->per_dir_config, &cache_socache_module); + cache_socache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_socache_module); + cache_object_t *obj; + cache_socache_object_t *sobj; + apr_size_t total; + + if (conf->provider == NULL) { + return DECLINED; + } + + /* we don't support caching of range requests (yet) */ + /* TODO: but we could */ + if (r->status == HTTP_PARTIAL_CONTENT) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02345) + "URL %s partial content response not cached", + key); + return DECLINED; + } + + /* + * We have a chicken and egg problem. We don't know until we + * attempt to store_headers just how big the response will be + * and whether it will fit in the cache limits set. But we + * need to make a decision now as to whether we plan to try. + * If we make the wrong decision, we could prevent another + * cache implementation, such as cache_disk, from getting the + * opportunity to cache, and that would be unfortunate. + * + * In a series of tests, from cheapest to most expensive, + * decide whether or not to ignore this attempt to cache, + * with a small margin just to be sure. + */ + if (len < 0) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02346) + "URL '%s' had no explicit size, ignoring", key); + return DECLINED; + } + if (len > dconf->max) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02347) + "URL '%s' body larger than limit, ignoring " + "(%" APR_OFF_T_FMT " > %" APR_OFF_T_FMT ")", + key, len, dconf->max); + return DECLINED; + } + + /* estimate the total cached size, given current headers */ + total = len + sizeof(cache_socache_info_t) + strlen(key); + if (APR_SUCCESS != store_table(r->headers_out, NULL, dconf->max, &total) + || APR_SUCCESS != store_table(r->headers_in, NULL, dconf->max, + &total)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02348) + "URL '%s' estimated headers size larger than limit, ignoring " + "(%" APR_SIZE_T_FMT " > %" APR_OFF_T_FMT ")", + key, total, dconf->max); + return DECLINED; + } + + if (total >= dconf->max) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02349) + "URL '%s' body and headers larger than limit, ignoring " + "(%" APR_OFF_T_FMT " > %" APR_OFF_T_FMT ")", + key, len, dconf->max); + return DECLINED; + } + + /* Allocate and initialize cache_object_t and cache_socache_object_t */ + h->cache_obj = obj = apr_pcalloc(r->pool, sizeof(*obj)); + obj->vobj = sobj = apr_pcalloc(r->pool, sizeof(*sobj)); + + obj->key = apr_pstrdup(r->pool, key); + sobj->key = obj->key; + sobj->name = obj->key; + + return OK; +} + +static int open_entity(cache_handle_t *h, request_rec *r, const char *key) +{ + cache_socache_dir_conf *dconf = + ap_get_module_config(r->per_dir_config, &cache_socache_module); + cache_socache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_socache_module); + apr_uint32_t format; + apr_size_t slider; + unsigned int buffer_len; + const char *nkey; + apr_status_t rc; + cache_object_t *obj; + cache_info *info; + cache_socache_object_t *sobj; + apr_size_t len; + + nkey = NULL; + h->cache_obj = NULL; + + if (!conf->provider || !conf->provider->socache_instance) { + return DECLINED; + } + + /* Create and init the cache object */ + obj = apr_pcalloc(r->pool, sizeof(cache_object_t)); + sobj = apr_pcalloc(r->pool, sizeof(cache_socache_object_t)); + + info = &(obj->info); + + /* Create a temporary pool for the buffer, and destroy it if something + * goes wrong so we don't have large buffers of unused memory hanging + * about for the lifetime of the response. + */ + apr_pool_create(&sobj->pool, r->pool); + + sobj->buffer = apr_palloc(sobj->pool, dconf->max + 1); + sobj->buffer_len = dconf->max + 1; + + /* attempt to retrieve the cached entry */ + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02350) + "could not acquire lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + buffer_len = sobj->buffer_len; + rc = conf->provider->socache_provider->retrieve( + conf->provider->socache_instance, r->server, (unsigned char *) key, + strlen(key), sobj->buffer, &buffer_len, r->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02351) + "could not release lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + if (rc != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02352) + "Key not found in cache: %s", key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + if (buffer_len >= sobj->buffer_len) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02353) + "Key found in cache but too big, ignoring: %s", key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + + /* read the format from the cache file */ + memcpy(&format, sobj->buffer, sizeof(format)); + slider = sizeof(format); + + if (format == CACHE_SOCACHE_VARY_FORMAT_VERSION) { + apr_array_header_t* varray; + apr_time_t expire; + + memcpy(&expire, sobj->buffer + slider, sizeof(expire)); + slider += sizeof(expire); + + varray = apr_array_make(r->pool, 5, sizeof(char*)); + rc = read_array(r, varray, sobj->buffer, buffer_len, &slider); + if (rc != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02354) + "Cannot parse vary entry for key: %s", key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + + nkey = regen_key(r->pool, r->headers_in, varray, key); + + /* attempt to retrieve the cached entry */ + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02355) + "could not acquire lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + buffer_len = sobj->buffer_len; + rc = conf->provider->socache_provider->retrieve( + conf->provider->socache_instance, r->server, + (unsigned char *) nkey, strlen(nkey), sobj->buffer, + &buffer_len, r->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02356) + "could not release lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + if (rc != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02357) + "Key not found in cache: %s", key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + if (buffer_len >= sobj->buffer_len) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02358) + "Key found in cache but too big, ignoring: %s", key); + goto fail; + } + + } + else if (format != CACHE_SOCACHE_DISK_FORMAT_VERSION) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02359) + "Key '%s' found in cache has version %d, expected %d, ignoring", + key, format, CACHE_SOCACHE_DISK_FORMAT_VERSION); + goto fail; + } + else { + nkey = key; + } + + obj->key = nkey; + sobj->key = nkey; + sobj->name = key; + + if (buffer_len >= sizeof(cache_socache_info_t)) { + memcpy(&sobj->socache_info, sobj->buffer, sizeof(cache_socache_info_t)); + } + else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02360) + "Cache entry for key '%s' too short, removing", nkey); + goto fail; + } + slider = sizeof(cache_socache_info_t); + + /* Store it away so we can get it later. */ + info->status = sobj->socache_info.status; + info->date = sobj->socache_info.date; + info->expire = sobj->socache_info.expire; + info->request_time = sobj->socache_info.request_time; + info->response_time = sobj->socache_info.response_time; + + memcpy(&info->control, &sobj->socache_info.control, sizeof(cache_control_t)); + + if (sobj->socache_info.name_len <= buffer_len - slider) { + if (strncmp((const char *) sobj->buffer + slider, sobj->name, + sobj->socache_info.name_len)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02361) + "Cache entry for key '%s' URL mismatch, ignoring", nkey); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + slider += sobj->socache_info.name_len; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02362) + "Cache entry for key '%s' too short, removing", nkey); + goto fail; + } + + /* Is this a cached HEAD request? */ + if (sobj->socache_info.header_only && !r->header_only) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02363) + "HEAD request cached, non-HEAD requested, ignoring: %s", + sobj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + + h->req_hdrs = apr_table_make(r->pool, 20); + h->resp_hdrs = apr_table_make(r->pool, 20); + + /* Call routine to read the header lines/status line */ + if (APR_SUCCESS != read_table(h, r, h->resp_hdrs, sobj->buffer, buffer_len, + &slider)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02364) + "Cache entry for key '%s' response headers unreadable, removing", nkey); + goto fail; + } + if (APR_SUCCESS != read_table(h, r, h->req_hdrs, sobj->buffer, buffer_len, + &slider)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02365) + "Cache entry for key '%s' request headers unreadable, removing", nkey); + goto fail; + } + + /* Retrieve the body if we have one */ + sobj->body = apr_brigade_create(r->pool, r->connection->bucket_alloc); + len = buffer_len - slider; + + /* + * Optimisation: if the body is small, we want to make a + * copy of the body and free the temporary pool, as we + * don't want large blocks of unused memory hanging around + * to the end of the response. In contrast, if the body is + * large, we would rather leave the body where it is in the + * temporary pool, and save ourselves the copy. + */ + if (len * 2 > dconf->max) { + apr_bucket *e; + + /* large - use the brigade as is, we're done */ + e = apr_bucket_immortal_create((const char *) sobj->buffer + slider, + len, r->connection->bucket_alloc); + + APR_BRIGADE_INSERT_TAIL(sobj->body, e); + } + else { + + /* small - make a copy of the data... */ + apr_brigade_write(sobj->body, NULL, NULL, (const char *) sobj->buffer + + slider, len); + + /* ...and get rid of the large memory buffer */ + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + } + + /* make the configuration stick */ + h->cache_obj = obj; + obj->vobj = sobj; + + return OK; + +fail: + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02366) + "could not acquire lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + conf->provider->socache_provider->remove( + conf->provider->socache_instance, r->server, + (unsigned char *) nkey, strlen(nkey), r->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02367) + "could not release lock, ignoring: %s", obj->key); + } + } + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; +} + +static int remove_entity(cache_handle_t *h) +{ + /* Null out the cache object pointer so next time we start from scratch */ + h->cache_obj = NULL; + return OK; +} + +static int remove_url(cache_handle_t *h, request_rec *r) +{ + cache_socache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_socache_module); + cache_socache_object_t *sobj; + + sobj = (cache_socache_object_t *) h->cache_obj->vobj; + if (!sobj) { + return DECLINED; + } + + /* Remove the key from the cache */ + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02368) + "could not acquire lock, ignoring: %s", sobj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + conf->provider->socache_provider->remove(conf->provider->socache_instance, + r->server, (unsigned char *) sobj->key, strlen(sobj->key), r->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02369) + "could not release lock, ignoring: %s", sobj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + + return OK; +} + +static apr_status_t recall_headers(cache_handle_t *h, request_rec *r) +{ + /* we recalled the headers during open_entity, so do nothing */ + return APR_SUCCESS; +} + +static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p, + apr_bucket_brigade *bb) +{ + cache_socache_object_t *sobj = (cache_socache_object_t*) h->cache_obj->vobj; + apr_bucket *e; + + e = APR_BRIGADE_FIRST(sobj->body); + + if (e != APR_BRIGADE_SENTINEL(sobj->body)) { + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(bb, e); + } + + return APR_SUCCESS; +} + +static apr_status_t store_headers(cache_handle_t *h, request_rec *r, + cache_info *info) +{ + cache_socache_dir_conf *dconf = + ap_get_module_config(r->per_dir_config, &cache_socache_module); + cache_socache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_socache_module); + apr_size_t slider; + apr_status_t rv; + cache_object_t *obj = h->cache_obj; + cache_socache_object_t *sobj = (cache_socache_object_t*) obj->vobj; + cache_socache_info_t *socache_info; + + memcpy(&h->cache_obj->info, info, sizeof(cache_info)); + + if (r->headers_out) { + sobj->headers_out = ap_cache_cacheable_headers_out(r); + } + + if (r->headers_in) { + sobj->headers_in = ap_cache_cacheable_headers_in(r); + } + + sobj->expire + = obj->info.expire > r->request_time + dconf->maxtime ? r->request_time + + dconf->maxtime + : obj->info.expire + dconf->mintime; + + apr_pool_create(&sobj->pool, r->pool); + + sobj->buffer = apr_palloc(sobj->pool, dconf->max); + sobj->buffer_len = dconf->max; + socache_info = (cache_socache_info_t *) sobj->buffer; + + if (sobj->headers_out) { + const char *vary; + + vary = apr_table_get(sobj->headers_out, "Vary"); + + if (vary) { + apr_array_header_t* varray; + apr_uint32_t format = CACHE_SOCACHE_VARY_FORMAT_VERSION; + + memcpy(sobj->buffer, &format, sizeof(format)); + slider = sizeof(format); + + memcpy(sobj->buffer + slider, &obj->info.expire, + sizeof(obj->info.expire)); + slider += sizeof(obj->info.expire); + + varray = apr_array_make(r->pool, 6, sizeof(char*)); + tokens_to_array(r->pool, vary, varray); + + if (APR_SUCCESS != (rv = store_array(varray, sobj->buffer, + sobj->buffer_len, &slider))) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02370) + "buffer too small for Vary array, caching aborted: %s", + obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; + } + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02371) + "could not acquire lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return status; + } + } + rv = conf->provider->socache_provider->store( + conf->provider->socache_instance, r->server, + (unsigned char *) obj->key, strlen(obj->key), sobj->expire, + (unsigned char *) sobj->buffer, (unsigned int) slider, + sobj->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02372) + "could not release lock, ignoring: %s", obj->key); + } + } + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02373) + "Vary not written to cache, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; + } + + obj->key = sobj->key = regen_key(r->pool, sobj->headers_in, varray, + sobj->name); + } + } + + socache_info->format = CACHE_SOCACHE_DISK_FORMAT_VERSION; + socache_info->date = obj->info.date; + socache_info->expire = obj->info.expire; + socache_info->entity_version = sobj->socache_info.entity_version++; + socache_info->request_time = obj->info.request_time; + socache_info->response_time = obj->info.response_time; + socache_info->status = obj->info.status; + socache_info->header_only = r->header_only; + + socache_info->name_len = strlen(sobj->name); + + memcpy(&socache_info->control, &obj->info.control, sizeof(cache_control_t)); + slider = sizeof(cache_socache_info_t); + + if (slider + socache_info->name_len >= sobj->buffer_len) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02374) + "cache buffer too small for name: %s", + sobj->name); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return APR_EGENERAL; + } + memcpy(sobj->buffer + slider, sobj->name, socache_info->name_len); + slider += socache_info->name_len; + + if (sobj->headers_out) { + if (APR_SUCCESS != store_table(sobj->headers_out, sobj->buffer, + sobj->buffer_len, &slider)) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02375) + "out-headers didn't fit in buffer: %s", sobj->name); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return APR_EGENERAL; + } + } + + /* Parse the vary header and dump those fields from the headers_in. */ + /* TODO: Make call to the same thing cache_select calls to crack Vary. */ + if (sobj->headers_in) { + if (APR_SUCCESS != store_table(sobj->headers_in, sobj->buffer, + sobj->buffer_len, &slider)) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(02376) + "in-headers didn't fit in buffer %s", + sobj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return APR_EGENERAL; + } + } + + sobj->body_offset = slider; + + return APR_SUCCESS; +} + +static apr_status_t store_body(cache_handle_t *h, request_rec *r, + apr_bucket_brigade *in, apr_bucket_brigade *out) +{ + apr_bucket *e; + apr_status_t rv = APR_SUCCESS; + cache_socache_object_t *sobj = + (cache_socache_object_t *) h->cache_obj->vobj; + cache_socache_dir_conf *dconf = + ap_get_module_config(r->per_dir_config, &cache_socache_module); + int seen_eos = 0; + + if (!sobj->offset) { + sobj->offset = dconf->readsize; + } + if (!sobj->timeout && dconf->readtime) { + sobj->timeout = apr_time_now() + dconf->readtime; + } + + if (!sobj->newbody) { + if (sobj->body) { + apr_brigade_cleanup(sobj->body); + } + else { + sobj->body = apr_brigade_create(r->pool, + r->connection->bucket_alloc); + } + sobj->newbody = 1; + } + if (sobj->offset) { + apr_brigade_partition(in, sobj->offset, &e); + } + + while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(in)) { + const char *str; + apr_size_t length; + + e = APR_BRIGADE_FIRST(in); + + /* are we done completely? if so, pass any trailing buckets right through */ + if (sobj->done || !sobj->pool) { + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + continue; + } + + /* have we seen eos yet? */ + if (APR_BUCKET_IS_EOS(e)) { + seen_eos = 1; + sobj->done = 1; + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + break; + } + + /* honour flush buckets, we'll get called again */ + if (APR_BUCKET_IS_FLUSH(e)) { + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + break; + } + + /* metadata buckets are preserved as is */ + if (APR_BUCKET_IS_METADATA(e)) { + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + continue; + } + + /* read the bucket, write to the cache */ + rv = apr_bucket_read(e, &str, &length, APR_BLOCK_READ); + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02377) + "Error when reading bucket for URL %s", + h->cache_obj->key); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; + } + + /* don't write empty buckets to the cache */ + if (!length) { + continue; + } + + sobj->file_size += length; + if (sobj->file_size >= sobj->buffer_len - sobj->body_offset) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02378) + "URL %s failed the buffer size check " + "(%" APR_OFF_T_FMT ">=%" APR_SIZE_T_FMT ")", + h->cache_obj->key, sobj->file_size, sobj->buffer_len - sobj->body_offset); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return APR_EGENERAL; + } + + rv = apr_bucket_copy(e, &e); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02379) + "Error when copying bucket for URL %s", + h->cache_obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; + } + APR_BRIGADE_INSERT_TAIL(sobj->body, e); + + /* have we reached the limit of how much we're prepared to write in one + * go? If so, leave, we'll get called again. This prevents us from trying + * to swallow too much data at once, or taking so long to write the data + * the client times out. + */ + sobj->offset -= length; + if (sobj->offset <= 0) { + sobj->offset = 0; + break; + } + if ((dconf->readtime && apr_time_now() > sobj->timeout)) { + sobj->timeout = 0; + break; + } + + } + + /* Was this the final bucket? If yes, perform sanity checks. + */ + if (seen_eos) { + const char *cl_header = apr_table_get(r->headers_out, "Content-Length"); + + if (r->connection->aborted || r->no_cache) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02380) + "Discarding body for URL %s " + "because connection has been aborted.", + h->cache_obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return APR_EGENERAL; + } + if (cl_header) { + apr_int64_t cl = apr_atoi64(cl_header); + if ((errno == 0) && (sobj->file_size != cl)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02381) + "URL %s didn't receive complete response, not caching", + h->cache_obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return APR_EGENERAL; + } + } + + /* All checks were fine, we're good to go when the commit comes */ + + } + + return APR_SUCCESS; +} + +static apr_status_t commit_entity(cache_handle_t *h, request_rec *r) +{ + cache_socache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_socache_module); + cache_object_t *obj = h->cache_obj; + cache_socache_object_t *sobj = (cache_socache_object_t *) obj->vobj; + apr_status_t rv; + apr_size_t len; + + /* flatten the body into the buffer */ + len = sobj->buffer_len - sobj->body_offset; + rv = apr_brigade_flatten(sobj->body, (char *) sobj->buffer + + sobj->body_offset, &len); + if (APR_SUCCESS != rv) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02382) + "could not flatten brigade, not caching: %s", + sobj->key); + goto fail; + } + if (len >= sobj->buffer_len - sobj->body_offset) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02383) + "body too big for the cache buffer, not caching: %s", + h->cache_obj->key); + goto fail; + } + + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02384) + "could not acquire lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; + } + } + rv = conf->provider->socache_provider->store( + conf->provider->socache_instance, r->server, + (unsigned char *) sobj->key, strlen(sobj->key), sobj->expire, + sobj->buffer, (unsigned int) sobj->body_offset + len, sobj->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02385) + "could not release lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(02386) + "could not write to cache, ignoring: %s", sobj->key); + goto fail; + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02387) + "commit_entity: Headers and body for URL %s cached for maximum of %d seconds.", + sobj->name, (apr_uint32_t)apr_time_sec(sobj->expire - r->request_time)); + + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + + return APR_SUCCESS; + +fail: + /* For safety, remove any existing entry on failure, just in case it could not + * be revalidated successfully. + */ + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02388) + "could not acquire lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; + } + } + conf->provider->socache_provider->remove(conf->provider->socache_instance, + r->server, (unsigned char *) sobj->key, strlen(sobj->key), r->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02389) + "could not release lock, ignoring: %s", obj->key); + } + } + + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; +} + +static apr_status_t invalidate_entity(cache_handle_t *h, request_rec *r) +{ + return APR_ENOTIMPL; +} + +static void *create_dir_config(apr_pool_t *p, char *dummy) +{ + cache_socache_dir_conf *dconf = + apr_pcalloc(p, sizeof(cache_socache_dir_conf)); + + dconf->max = DEFAULT_MAX_FILE_SIZE; + dconf->maxtime = apr_time_from_sec(DEFAULT_MAXTIME); + dconf->mintime = apr_time_from_sec(DEFAULT_MINTIME); + dconf->readsize = DEFAULT_READSIZE; + dconf->readtime = DEFAULT_READTIME; + + return dconf; +} + +static void *merge_dir_config(apr_pool_t *p, void *basev, void *addv) +{ + cache_socache_dir_conf + *new = + (cache_socache_dir_conf *) apr_pcalloc(p, sizeof(cache_socache_dir_conf)); + cache_socache_dir_conf *add = (cache_socache_dir_conf *) addv; + cache_socache_dir_conf *base = (cache_socache_dir_conf *) basev; + + new->max = (add->max_set == 0) ? base->max : add->max; + new->max_set = add->max_set || base->max_set; + new->maxtime = (add->maxtime_set == 0) ? base->maxtime : add->maxtime; + new->maxtime_set = add->maxtime_set || base->maxtime_set; + new->mintime = (add->mintime_set == 0) ? base->mintime : add->mintime; + new->mintime_set = add->mintime_set || base->mintime_set; + new->readsize = (add->readsize_set == 0) ? base->readsize : add->readsize; + new->readsize_set = add->readsize_set || base->readsize_set; + new->readtime = (add->readtime_set == 0) ? base->readtime : add->readtime; + new->readtime_set = add->readtime_set || base->readtime_set; + + return new; +} + +static void *create_config(apr_pool_t *p, server_rec *s) +{ + cache_socache_conf *conf = apr_pcalloc(p, sizeof(cache_socache_conf)); + + return conf; +} + +static void *merge_config(apr_pool_t *p, void *basev, void *overridesv) +{ + cache_socache_conf *ps = apr_pcalloc(p, sizeof(cache_socache_conf)); + cache_socache_conf *base = (cache_socache_conf *) basev; + cache_socache_conf *overrides = (cache_socache_conf *) overridesv; + + ps = overrides ? overrides : base; + + return ps; +} + +/* + * mod_cache_socache configuration directives handlers. + */ +static const char *set_cache_socache(cmd_parms *cmd, void *in_struct_ptr, + const char *arg) +{ + cache_socache_conf *conf = ap_get_module_config(cmd->server->module_config, + &cache_socache_module); + cache_socache_provider_conf *provider = conf->provider + = apr_pcalloc(cmd->pool, sizeof(cache_socache_provider_conf)); + + const char *err = NULL, *sep, *name; + + /* Argument is of form 'name:args' or just 'name'. */ + sep = ap_strchr_c(arg, ':'); + if (sep) { + name = apr_pstrmemdup(cmd->pool, arg, sep - arg); + sep++; + provider->args = sep; + } + else { + name = arg; + } + + provider->socache_provider = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP, + arg, AP_SOCACHE_PROVIDER_VERSION); + if (provider->socache_provider == NULL) { + err = apr_psprintf(cmd->pool, + "Unknown socache provider '%s'. Maybe you need " + "to load the appropriate socache module " + "(mod_socache_%s?)", name, name); + } + return err; +} + +static const char *set_cache_max(cmd_parms *parms, void *in_struct_ptr, + const char *arg) +{ + cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr; + + if (apr_strtoff(&dconf->max, arg, NULL, 10) != APR_SUCCESS || dconf->max + < 1024) { + return "CacheSocacheMaxSize argument must be a integer representing the max size of a cached entry (headers and body), at least 1024"; + } + return NULL; +} + +static const char *set_cache_maxtime(cmd_parms *parms, void *in_struct_ptr, + const char *arg) +{ + cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr; + apr_off_t seconds; + + if (apr_strtoff(&seconds, arg, NULL, 10) != APR_SUCCESS || seconds < 0) { + return "CacheSocacheMaxTime argument must be the maximum amount of time in seconds to cache an entry."; + } + dconf->maxtime = apr_time_from_sec(seconds); + dconf->maxtime_set = 1; + return NULL; +} + +static const char *set_cache_mintime(cmd_parms *parms, void *in_struct_ptr, + const char *arg) +{ + cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr; + apr_off_t seconds; + + if (apr_strtoff(&seconds, arg, NULL, 10) != APR_SUCCESS || seconds < 0) { + return "CacheSocacheMinTime argument must be the minimum amount of time in seconds to cache an entry."; + } + dconf->mintime = apr_time_from_sec(seconds); + dconf->mintime_set = 1; + return NULL; +} + +static const char *set_cache_readsize(cmd_parms *parms, void *in_struct_ptr, + const char *arg) +{ + cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr; + + if (apr_strtoff(&dconf->readsize, arg, NULL, 10) != APR_SUCCESS + || dconf->readsize < 0) { + return "CacheSocacheReadSize argument must be a non-negative integer representing the max amount of data to cache in go."; + } + dconf->readsize_set = 1; + return NULL; +} + +static const char *set_cache_readtime(cmd_parms *parms, void *in_struct_ptr, + const char *arg) +{ + cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr; + apr_off_t milliseconds; + + if (apr_strtoff(&milliseconds, arg, NULL, 10) != APR_SUCCESS + || milliseconds < 0) { + return "CacheSocacheReadTime argument must be a non-negative integer representing the max amount of time taken to cache in go."; + } + dconf->readtime = apr_time_from_msec(milliseconds); + dconf->readtime_set = 1; + return NULL; +} + +static apr_status_t remove_lock(void *data) +{ + if (socache_mutex) { + apr_global_mutex_destroy(socache_mutex); + socache_mutex = NULL; + } + return APR_SUCCESS; +} + +static apr_status_t destroy_cache(void *data) +{ + server_rec *s = data; + cache_socache_conf *conf = + ap_get_module_config(s->module_config, &cache_socache_module); + if (conf->provider && conf->provider->socache_instance) { + conf->provider->socache_provider->destroy( + conf->provider->socache_instance, s); + conf->provider->socache_instance = NULL; + } + return APR_SUCCESS; +} + +static int socache_precfg(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptmp) +{ + apr_status_t rv = ap_mutex_register(pconf, cache_socache_id, NULL, + APR_LOCK_DEFAULT, 0); + if (rv != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(02390) + "failed to register %s mutex", cache_socache_id); + return 500; /* An HTTP status would be a misnomer! */ + } + return OK; +} + +static int socache_post_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptmp, server_rec *base_server) +{ + server_rec *s; + apr_status_t rv; + const char *errmsg; + static struct ap_socache_hints socache_hints = + { 64, 32, 60000000 }; + + for (s = base_server; s; s = s->next) { + cache_socache_conf *conf = + ap_get_module_config(s->module_config, &cache_socache_module); + + if (!conf->provider) { + continue; + } + + if (!socache_mutex && conf->provider->socache_provider->flags + & AP_SOCACHE_FLAG_NOTMPSAFE) { + + rv = ap_global_mutex_create(&socache_mutex, NULL, cache_socache_id, + NULL, s, pconf, 0); + if (rv != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(02391) + "failed to create %s mutex", cache_socache_id); + return 500; /* An HTTP status would be a misnomer! */ + } + apr_pool_cleanup_register(pconf, NULL, remove_lock, + apr_pool_cleanup_null); + } + + errmsg = conf->provider->socache_provider->create( + &conf->provider->socache_instance, conf->provider->args, ptmp, + pconf); + if (errmsg) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, + APLOGNO(02392) "%s", errmsg); + return 500; /* An HTTP status would be a misnomer! */ + } + + rv = conf->provider->socache_provider->init( + conf->provider->socache_instance, cache_socache_id, + &socache_hints, s, pconf); + if (rv != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(02393) + "failed to initialise %s cache", cache_socache_id); + return 500; /* An HTTP status would be a misnomer! */ + } + apr_pool_cleanup_register(pconf, (void *) s, destroy_cache, + apr_pool_cleanup_null); + + } + + return OK; +} + +static void socache_child_init(apr_pool_t *p, server_rec *s) +{ + const char *lock; + apr_status_t rv; + if (!socache_mutex) { + return; /* don't waste the overhead of creating mutex & cache */ + } + lock = apr_global_mutex_lockfile(socache_mutex); + rv = apr_global_mutex_child_init(&socache_mutex, lock, p); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(02394) + "failed to initialise mutex in child_init"); + } +} + +static const command_rec cache_socache_cmds[] = +{ + AP_INIT_TAKE1("CacheSocache", set_cache_socache, NULL, RSRC_CONF, + "The shared object cache to store cache files"), + AP_INIT_TAKE1("CacheSocacheMaxTime", set_cache_maxtime, NULL, RSRC_CONF | ACCESS_CONF, + "The maximum cache expiry age to cache a document in seconds"), + AP_INIT_TAKE1("CacheSocacheMinTime", set_cache_mintime, NULL, RSRC_CONF | ACCESS_CONF, + "The minimum cache expiry age to cache a document in seconds"), + AP_INIT_TAKE1("CacheSocacheMaxSize", set_cache_max, NULL, RSRC_CONF | ACCESS_CONF, + "The maximum cache entry size (headers and body) to cache a document"), + AP_INIT_TAKE1("CacheSocacheReadSize", set_cache_readsize, NULL, RSRC_CONF | ACCESS_CONF, + "The maximum quantity of data to attempt to read and cache in one go"), + AP_INIT_TAKE1("CacheSocacheReadTime", set_cache_readtime, NULL, RSRC_CONF | ACCESS_CONF, + "The maximum time taken to attempt to read and cache in go"), + { NULL } +}; + +static const cache_provider cache_socache_provider = +{ + &remove_entity, &store_headers, &store_body, &recall_headers, &recall_body, + &create_entity, &open_entity, &remove_url, &commit_entity, + &invalidate_entity +}; + +static void cache_socache_register_hook(apr_pool_t *p) +{ + /* cache initializer */ + ap_register_provider(p, CACHE_PROVIDER_GROUP, "socache", "0", + &cache_socache_provider); + ap_hook_pre_config(socache_precfg, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_config(socache_post_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_child_init(socache_child_init, NULL, NULL, APR_HOOK_MIDDLE); +} + +AP_DECLARE_MODULE(cache_socache) = { STANDARD20_MODULE_STUFF, + create_dir_config, /* create per-directory config structure */ + merge_dir_config, /* merge per-directory config structures */ + create_config, /* create per-server config structure */ + merge_config, /* merge per-server config structures */ + cache_socache_cmds, /* command apr_table_t */ + cache_socache_register_hook /* register hooks */ +}; diff --git a/modules/cache/mod_cache_socache.dsp b/modules/cache/mod_cache_socache.dsp new file mode 100644 index 0000000000..e5d582e219 --- /dev/null +++ b/modules/cache/mod_cache_socache.dsp @@ -0,0 +1,115 @@ +# Microsoft Developer Studio Project File - Name="mod_cache_socache" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=mod_cache_socache - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "mod_cache_socache.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "mod_cache_socache.mak" CFG="mod_cache_socache - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_cache_socache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_cache_socache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "mod_cache_socache - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c +# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /Fd"Release\mod_cache_socache_src" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /fo"Release/mod_cache_socache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_cache_socache.so" /d LONG_NAME="cache_socache_module for Apache" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll +# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_cache_socache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_socache.so /opt:ref +# Begin Special Build Tool +TargetPath=.\Release\mod_cache_socache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ELSEIF "$(CFG)" == "mod_cache_socache - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c +# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /Fd"Debug\mod_cache_socache_src" /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /fo"Debug/mod_cache_socache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_cache_socache.so" /d LONG_NAME="cache_socache_module for Apache" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug +# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_cache_socache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_socache.so +# Begin Special Build Tool +TargetPath=.\Debug\mod_cache_socache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ENDIF + +# Begin Target + +# Name "mod_cache_socache - Win32 Release" +# Name "mod_cache_socache - Win32 Debug" +# Begin Source File + +SOURCE=.\mod_cache.h +# End Source File +# Begin Source File + +SOURCE=.\mod_cache_socache.c +# End Source File +# Begin Source File + +SOURCE=..\..\build\win32\httpd.rc +# End Source File +# End Target +# End Project