summaryrefslogtreecommitdiff
path: root/modules/cache
diff options
context:
space:
mode:
authorStefan Fritsch <sf@sfritsch.de>2013-07-20 22:21:25 +0200
committerStefan Fritsch <sf@sfritsch.de>2013-07-20 22:21:25 +0200
commit4a336a5b117419c33c29eadd6409c69df78cd586 (patch)
treec9787e4bd0f1be8f471e1883262a695a6c4e954f /modules/cache
parent717c182588f1eb0b7ef189a709f858b44e348489 (diff)
downloadapache2-upstream/2.4.6.tar.gz
Imported Upstream version 2.4.6upstream/2.4.6
Diffstat (limited to 'modules/cache')
-rw-r--r--modules/cache/NWGNUcach_socache262
-rw-r--r--modules/cache/NWGNUmakefile1
-rw-r--r--modules/cache/cache_common.h1
-rw-r--r--modules/cache/cache_socache_common.h57
-rw-r--r--modules/cache/cache_storage.c394
-rw-r--r--modules/cache/cache_storage.h23
-rw-r--r--modules/cache/cache_util.c365
-rw-r--r--modules/cache/cache_util.h31
-rw-r--r--modules/cache/config.m43
-rw-r--r--modules/cache/mod_cache.c393
-rw-r--r--modules/cache/mod_cache_disk.c170
-rw-r--r--modules/cache/mod_cache_socache.c1501
-rw-r--r--modules/cache/mod_cache_socache.dsp115
-rw-r--r--modules/cache/mod_socache_memcache.c8
14 files changed, 2908 insertions, 416 deletions
diff --git a/modules/cache/NWGNUcach_socache b/modules/cache/NWGNUcach_socache
new file mode 100644
index 00000000..f7ed0e43
--- /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/NWGNUmakefile b/modules/cache/NWGNUmakefile
index bc0c58fe..e544df62 100644
--- a/modules/cache/NWGNUmakefile
+++ b/modules/cache/NWGNUmakefile
@@ -154,6 +154,7 @@ XDCDATA =
TARGET_nlm = \
$(OBJDIR)/mod_cach.nlm \
$(OBJDIR)/cach_dsk.nlm \
+ $(OBJDIR)/cach_socache.nlm \
$(OBJDIR)/socachdbm.nlm \
$(OBJDIR)/socachmem.nlm \
$(OBJDIR)/socachshmcb.nlm \
diff --git a/modules/cache/cache_common.h b/modules/cache/cache_common.h
index cedce076..9d56d28b 100644
--- a/modules/cache/cache_common.h
+++ b/modules/cache/cache_common.h
@@ -45,6 +45,7 @@ typedef struct cache_control {
unsigned int must_revalidate:1;
unsigned int proxy_revalidate:1;
unsigned int s_maxage:1;
+ unsigned int invalidated:1; /* has this entity been invalidated? */
apr_int64_t max_age_value; /* if positive, then set */
apr_int64_t max_stale_value; /* if positive, then set */
apr_int64_t min_fresh_value; /* if positive, then set */
diff --git a/modules/cache/cache_socache_common.h b/modules/cache/cache_socache_common.h
new file mode 100644
index 00000000..3ee3d0da
--- /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/cache_storage.c b/modules/cache/cache_storage.c
index 9021ec1d..af60a39b 100644
--- a/modules/cache/cache_storage.c
+++ b/modules/cache/cache_storage.c
@@ -113,26 +113,69 @@ int cache_create_entity(cache_request_rec *cache, request_rec *r,
return DECLINED;
}
-static int set_cookie_doo_doo(void *v, const char *key, const char *val)
+static int filter_header_do(void *v, const char *key, const char *val)
+{
+ if ((*key == 'W' || *key == 'w') && !strcasecmp(key, "Warning")
+ && *val == '1') {
+ /* any stored Warning headers with warn-code 1xx (see section
+ * 14.46) MUST be deleted from the cache entry and the forwarded
+ * response.
+ */
+ }
+ else {
+ apr_table_addn(v, key, val);
+ }
+ return 1;
+}
+static int remove_header_do(void *v, const char *key, const char *val)
+{
+ if ((*key == 'W' || *key == 'w') && !strcasecmp(key, "Warning")) {
+ /* any stored Warning headers with warn-code 2xx MUST be retained
+ * in the cache entry and the forwarded response.
+ */
+ }
+ else {
+ apr_table_unset(v, key);
+ }
+ return 1;
+}
+static int add_header_do(void *v, const char *key, const char *val)
{
apr_table_addn(v, key, val);
return 1;
}
/**
- * Take headers from the cache, and overlap them over the existing response
- * headers.
+ * Take two sets of headers, sandwich them together, and apply the result to
+ * r->headers_out.
+ *
+ * To complicate this, a header may be duplicated in either table. Should a
+ * header exist in the top table, all matching headers will be removed from
+ * the bottom table before the headers are combined. The Warning headers are
+ * handled specially. Warnings are added rather than being replaced, while
+ * in the case of revalidation 1xx Warnings are stripped.
+ *
+ * The Content-Type and Last-Modified headers are then re-parsed and inserted
+ * into the request.
*/
-void cache_accept_headers(cache_handle_t *h, request_rec *r,
- int preserve_orig)
+void cache_accept_headers(cache_handle_t *h, request_rec *r, apr_table_t *top,
+ apr_table_t *bottom, int revalidation)
{
- apr_table_t *cookie_table, *hdr_copy;
const char *v;
- v = apr_table_get(h->resp_hdrs, "Content-Type");
+ if (revalidation) {
+ r->headers_out = apr_table_make(r->pool, 10);
+ apr_table_do(filter_header_do, r->headers_out, bottom, NULL);
+ }
+ else if (r->headers_out != bottom) {
+ r->headers_out = apr_table_copy(r->pool, bottom);
+ }
+ apr_table_do(remove_header_do, r->headers_out, top, NULL);
+ apr_table_do(add_header_do, r->headers_out, top, NULL);
+
+ v = apr_table_get(r->headers_out, "Content-Type");
if (v) {
ap_set_content_type(r, v);
- apr_table_unset(h->resp_hdrs, "Content-Type");
/*
* Also unset possible Content-Type headers in r->headers_out and
* r->err_headers_out as they may be different to what we have received
@@ -149,39 +192,12 @@ void cache_accept_headers(cache_handle_t *h, request_rec *r,
/* If the cache gave us a Last-Modified header, we can't just
* pass it on blindly because of restrictions on future values.
*/
- v = apr_table_get(h->resp_hdrs, "Last-Modified");
+ v = apr_table_get(r->headers_out, "Last-Modified");
if (v) {
ap_update_mtime(r, apr_date_parse_http(v));
ap_set_last_modified(r);
- apr_table_unset(h->resp_hdrs, "Last-Modified");
}
- /* The HTTP specification says that it is legal to merge duplicate
- * headers into one. Some browsers that support Cookies don't like
- * merged headers and prefer that each Set-Cookie header is sent
- * separately. Lets humour those browsers by not merging.
- * Oh what a pain it is.
- */
- cookie_table = apr_table_make(r->pool, 2);
- apr_table_do(set_cookie_doo_doo, cookie_table, r->err_headers_out,
- "Set-Cookie", NULL);
- apr_table_do(set_cookie_doo_doo, cookie_table, h->resp_hdrs,
- "Set-Cookie", NULL);
- apr_table_unset(r->err_headers_out, "Set-Cookie");
- apr_table_unset(h->resp_hdrs, "Set-Cookie");
-
- if (preserve_orig) {
- hdr_copy = apr_table_copy(r->pool, h->resp_hdrs);
- apr_table_overlap(hdr_copy, r->headers_out, APR_OVERLAP_TABLES_SET);
- r->headers_out = hdr_copy;
- }
- else {
- apr_table_overlap(r->headers_out, h->resp_hdrs, APR_OVERLAP_TABLES_SET);
- }
- if (!apr_is_empty_table(cookie_table)) {
- r->err_headers_out = apr_table_overlay(r->pool, r->err_headers_out,
- cookie_table);
- }
}
/*
@@ -209,6 +225,13 @@ int cache_select(cache_request_rec *cache, request_rec *r)
return DECLINED;
}
+ /* if no-cache, we can't serve from the cache, but we may store to the
+ * cache.
+ */
+ if (!ap_cache_check_no_cache(cache, r)) {
+ return DECLINED;
+ }
+
if (!cache->key) {
rv = cache_generate_key(r, r->pool, &cache->key);
if (rv != APR_SUCCESS) {
@@ -216,10 +239,6 @@ int cache_select(cache_request_rec *cache, request_rec *r)
}
}
- if (!ap_cache_check_allowed(cache, r)) {
- return DECLINED;
- }
-
/* go through the cache types till we get a match */
h = apr_palloc(r->pool, sizeof(cache_handle_t));
@@ -229,7 +248,8 @@ int cache_select(cache_request_rec *cache, request_rec *r)
switch ((rv = list->provider->open_entity(h, r, cache->key))) {
case OK: {
char *vary = NULL;
- int fresh, mismatch = 0;
+ int mismatch = 0;
+ char *last = NULL;
if (list->provider->recall_headers(h, r) != APR_SUCCESS) {
/* try again with next cache type */
@@ -255,25 +275,19 @@ int cache_select(cache_request_rec *cache, request_rec *r)
*
* RFC2616 13.6 and 14.44 describe the Vary mechanism.
*/
- vary = apr_pstrdup(r->pool, apr_table_get(h->resp_hdrs, "Vary"));
- while (vary && *vary) {
- char *name = vary;
+ vary = cache_strqtok(
+ apr_pstrdup(r->pool,
+ cache_table_getm(r->pool, h->resp_hdrs, "Vary")),
+ CACHE_SEPARATOR, &last);
+ while (vary) {
const char *h1, *h2;
- /* isolate header name */
- while (*vary && !apr_isspace(*vary) && (*vary != ','))
- ++vary;
- while (*vary && (apr_isspace(*vary) || (*vary == ','))) {
- *vary = '\0';
- ++vary;
- }
-
/*
* is this header in the request and the header in the cached
* request identical? If not, we give up and do a straight get
*/
- h1 = apr_table_get(r->headers_in, name);
- h2 = apr_table_get(h->req_hdrs, name);
+ h1 = cache_table_getm(r->pool, r->headers_in, vary);
+ h2 = cache_table_getm(r->pool, h->req_hdrs, vary);
if (h1 == h2) {
/* both headers NULL, so a match - do nothing */
}
@@ -283,9 +297,11 @@ int cache_select(cache_request_rec *cache, request_rec *r)
else {
/* headers do not match, so Vary failed */
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
- r, APLOGNO(00694) "cache_select_url(): Vary header mismatch.");
+ r, APLOGNO(00694) "cache_select(): Vary header mismatch.");
mismatch = 1;
+ break;
}
+ vary = cache_strqtok(NULL, CACHE_SEPARATOR, &last);
}
/* no vary match, try next provider */
@@ -298,9 +314,27 @@ int cache_select(cache_request_rec *cache, request_rec *r)
cache->provider = list->provider;
cache->provider_name = list->provider_name;
+ /*
+ * RFC2616 13.3.4 Rules for When to Use Entity Tags and Last-Modified
+ * Dates: An HTTP/1.1 caching proxy, upon receiving a conditional request
+ * that includes both a Last-Modified date and one or more entity tags as
+ * cache validators, MUST NOT return a locally cached response to the
+ * client unless that cached response is consistent with all of the
+ * conditional header fields in the request.
+ */
+ if (ap_condition_if_match(r, h->resp_hdrs) == AP_CONDITION_NOMATCH
+ || ap_condition_if_unmodified_since(r, h->resp_hdrs)
+ == AP_CONDITION_NOMATCH
+ || ap_condition_if_none_match(r, h->resp_hdrs)
+ == AP_CONDITION_NOMATCH
+ || ap_condition_if_modified_since(r, h->resp_hdrs)
+ == AP_CONDITION_NOMATCH
+ || ap_condition_if_range(r, h->resp_hdrs) == AP_CONDITION_NOMATCH) {
+ mismatch = 1;
+ }
+
/* Is our cached response fresh enough? */
- fresh = cache_check_freshness(h, cache, r);
- if (!fresh) {
+ if (mismatch || !cache_check_freshness(h, cache, r)) {
const char *etag, *lastmod;
/* Cache-Control: only-if-cached and revalidation required, try
@@ -317,42 +351,45 @@ int cache_select(cache_request_rec *cache, request_rec *r)
r->headers_in);
cache->stale_handle = h;
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00695)
- "Cached response for %s isn't fresh. Adding/replacing "
- "conditional request headers.", r->uri);
+ /* if no existing conditionals, use conditionals of our own */
+ if (!mismatch) {
- /* We can only revalidate with our own conditionals: remove the
- * conditions from the original request.
- */
- apr_table_unset(r->headers_in, "If-Match");
- apr_table_unset(r->headers_in, "If-Modified-Since");
- apr_table_unset(r->headers_in, "If-None-Match");
- apr_table_unset(r->headers_in, "If-Range");
- apr_table_unset(r->headers_in, "If-Unmodified-Since");
-
- etag = apr_table_get(h->resp_hdrs, "ETag");
- lastmod = apr_table_get(h->resp_hdrs, "Last-Modified");
-
- if (etag || lastmod) {
- /* If we have a cached etag and/or Last-Modified add in
- * our own conditionals.
- */
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00695) "Cached response for %s isn't fresh. Adding "
+ "conditional request headers.", r->uri);
- if (etag) {
- apr_table_set(r->headers_in, "If-None-Match", etag);
- }
+ /* Remove existing conditionals that might conflict with ours */
+ apr_table_unset(r->headers_in, "If-Match");
+ apr_table_unset(r->headers_in, "If-Modified-Since");
+ apr_table_unset(r->headers_in, "If-None-Match");
+ apr_table_unset(r->headers_in, "If-Range");
+ apr_table_unset(r->headers_in, "If-Unmodified-Since");
- if (lastmod) {
- apr_table_set(r->headers_in, "If-Modified-Since",
- lastmod);
- }
+ etag = apr_table_get(h->resp_hdrs, "ETag");
+ lastmod = apr_table_get(h->resp_hdrs, "Last-Modified");
- /*
- * Do not do Range requests with our own conditionals: If
- * we get 304 the Range does not matter and otherwise the
- * entity changed and we want to have the complete entity
- */
- apr_table_unset(r->headers_in, "Range");
+ if (etag || lastmod) {
+ /* If we have a cached etag and/or Last-Modified add in
+ * our own conditionals.
+ */
+
+ if (etag) {
+ apr_table_set(r->headers_in, "If-None-Match", etag);
+ }
+
+ if (lastmod) {
+ apr_table_set(r->headers_in, "If-Modified-Since",
+ lastmod);
+ }
+
+ /*
+ * Do not do Range requests with our own conditionals: If
+ * we get 304 the Range does not matter and otherwise the
+ * entity changed and we want to have the complete entity
+ */
+ apr_table_unset(r->headers_in, "Range");
+
+ }
}
@@ -361,7 +398,7 @@ int cache_select(cache_request_rec *cache, request_rec *r)
}
/* Okay, this response looks okay. Merge in our stuff and go. */
- cache_accept_headers(h, r, 0);
+ cache_accept_headers(h, r, h->resp_hdrs, r->headers_out, 0);
cache->handle = h;
return OK;
@@ -389,14 +426,15 @@ int cache_select(cache_request_rec *cache, request_rec *r)
return DECLINED;
}
-apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
- const char **key)
+static apr_status_t cache_canonicalise_key(request_rec *r, apr_pool_t* p,
+ const char *uri, apr_uri_t *parsed_uri, const char **key)
{
cache_server_conf *conf;
char *port_str, *hn, *lcs;
const char *hostname, *scheme;
int i;
- char *path, *querystring;
+ const char *path;
+ char *querystring;
if (*key) {
/*
@@ -410,7 +448,7 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
* option below.
*/
conf = (cache_server_conf *) ap_get_module_config(r->server->module_config,
- &cache_module);
+ &cache_module);
/*
* Use the canonical name to improve cache hit rate, but only if this is
@@ -436,15 +474,15 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
}
else {
/* Use _default_ as the hostname if none present, as in mod_vhost */
- hostname = ap_get_server_name(r);
+ hostname = ap_get_server_name(r);
if (!hostname) {
hostname = "_default_";
}
}
}
- else if(r->parsed_uri.hostname) {
+ else if (parsed_uri->hostname) {
/* Copy the parsed uri hostname */
- hn = apr_pstrdup(p, r->parsed_uri.hostname);
+ hn = apr_pstrdup(p, parsed_uri->hostname);
ap_str_tolower(hn);
/* const work-around */
hostname = hn;
@@ -463,9 +501,9 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
* "no proxy request" and "reverse proxy request" are handled in the same
* manner (see above why this is needed).
*/
- if (r->proxyreq && r->parsed_uri.scheme) {
+ if (r->proxyreq && parsed_uri->scheme) {
/* Copy the scheme and lower-case it */
- lcs = apr_pstrdup(p, r->parsed_uri.scheme);
+ lcs = apr_pstrdup(p, parsed_uri->scheme);
ap_str_tolower(lcs);
/* const work-around */
scheme = lcs;
@@ -488,11 +526,11 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
* server.
*/
if (r->proxyreq && (r->proxyreq != PROXYREQ_REVERSE)) {
- if (r->parsed_uri.port_str) {
- port_str = apr_pcalloc(p, strlen(r->parsed_uri.port_str) + 2);
+ if (parsed_uri->port_str) {
+ port_str = apr_pcalloc(p, strlen(parsed_uri->port_str) + 2);
port_str[0] = ':';
- for (i = 0; r->parsed_uri.port_str[i]; i++) {
- port_str[i + 1] = apr_tolower(r->parsed_uri.port_str[i]);
+ for (i = 0; parsed_uri->port_str[i]; i++) {
+ port_str[i + 1] = apr_tolower(parsed_uri->port_str[i]);
}
}
else if (apr_uri_port_of_scheme(scheme)) {
@@ -524,26 +562,26 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
* Check if we need to ignore session identifiers in the URL and do so
* if needed.
*/
- path = r->uri;
- querystring = r->parsed_uri.query;
+ path = uri;
+ querystring = parsed_uri->query;
if (conf->ignore_session_id->nelts) {
int i;
char **identifier;
- identifier = (char **)conf->ignore_session_id->elts;
+ identifier = (char **) conf->ignore_session_id->elts;
for (i = 0; i < conf->ignore_session_id->nelts; i++, identifier++) {
int len;
- char *param;
+ const char *param;
len = strlen(*identifier);
/*
* Check that we have a parameter separator in the last segment
* of the path and that the parameter matches our identifier
*/
- if ((param = strrchr(path, ';'))
- && !strncmp(param + 1, *identifier, len)
- && (*(param + len + 1) == '=')
- && !strchr(param + len + 2, '/')) {
+ if ((param = ap_strrchr_c(path, ';'))
+ && !strncmp(param + 1, *identifier, len)
+ && (*(param + len + 1) == '=')
+ && !ap_strchr_c(param + len + 2, '/')) {
path = apr_pstrndup(p, path, param - path);
continue;
}
@@ -556,7 +594,7 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
* querystring and followed by a '='
*/
if (!strncmp(querystring, *identifier, len)
- && (*(querystring + len) == '=')) {
+ && (*(querystring + len) == '=')) {
param = querystring;
}
else {
@@ -574,18 +612,19 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
}
}
if (param) {
- char *amp;
+ const char *amp;
if (querystring != param) {
querystring = apr_pstrndup(p, querystring,
- param - querystring);
+ param - querystring);
}
else {
querystring = "";
}
- if ((amp = strchr(param + len + 1, '&'))) {
- querystring = apr_pstrcat(p, querystring, amp + 1, NULL);
+ if ((amp = ap_strchr_c(param + len + 1, '&'))) {
+ querystring = apr_pstrcat(p, querystring, amp + 1,
+ NULL);
}
else {
/*
@@ -605,12 +644,12 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
/* Key format is a URI, optionally without the query-string */
if (conf->ignorequerystring) {
- *key = apr_pstrcat(p, scheme, "://", hostname, port_str,
- path, "?", NULL);
+ *key = apr_pstrcat(p, scheme, "://", hostname, port_str, path, "?",
+ NULL);
}
else {
- *key = apr_pstrcat(p, scheme, "://", hostname, port_str,
- path, "?", querystring, NULL);
+ *key = apr_pstrcat(p, scheme, "://", hostname, port_str, path, "?",
+ querystring, NULL);
}
/*
@@ -621,9 +660,118 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
* resource in the cache under a key where it is never found by the quick
* handler during following requests.
*/
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00698)
- "cache: Key for entity %s?%s is %s", r->uri,
- r->parsed_uri.query, *key);
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00698) "cache: Key for entity %s?%s is %s", uri, parsed_uri->query, *key);
return APR_SUCCESS;
}
+
+apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
+ const char **key)
+{
+ return cache_canonicalise_key(r, p, r->uri, &r->parsed_uri, key);
+}
+
+/*
+ * Invalidate a specific URL entity in all caches
+ *
+ * All cached entities for this URL are removed, usually in
+ * response to a POST/PUT or DELETE.
+ *
+ * This function returns OK if at least one entity was found and
+ * removed, and DECLINED if no cached entities were removed.
+ */
+int cache_invalidate(cache_request_rec *cache, request_rec *r)
+{
+ cache_provider_list *list;
+ apr_status_t rv, status = DECLINED;
+ cache_handle_t *h;
+ apr_uri_t location_uri;
+ apr_uri_t content_location_uri;
+
+ const char *location, *location_key = NULL;
+ const char *content_location, *content_location_key = NULL;
+
+ if (!cache) {
+ /* This should never happen */
+ ap_log_rerror(
+ APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(00697) "cache: No cache request information available for key"
+ " generation");
+ return DECLINED;
+ }
+
+ if (!cache->key) {
+ rv = cache_generate_key(r, r->pool, &cache->key);
+ if (rv != APR_SUCCESS) {
+ return DECLINED;
+ }
+ }
+
+ location = apr_table_get(r->headers_out, "Location");
+ if (location) {
+ if (APR_SUCCESS != apr_uri_parse(r->pool, location, &location_uri)
+ || APR_SUCCESS
+ != cache_canonicalise_key(r, r->pool, location,
+ &location_uri, &location_key)
+ || strcmp(r->parsed_uri.hostname, location_uri.hostname)) {
+ location_key = NULL;
+ }
+ }
+
+ content_location = apr_table_get(r->headers_out, "Content-Location");
+ if (content_location) {
+ if (APR_SUCCESS
+ != apr_uri_parse(r->pool, content_location,
+ &content_location_uri)
+ || APR_SUCCESS
+ != cache_canonicalise_key(r, r->pool, content_location,
+ &content_location_uri, &content_location_key)
+ || strcmp(r->parsed_uri.hostname,
+ content_location_uri.hostname)) {
+ content_location_key = NULL;
+ }
+ }
+
+ /* go through the cache types */
+ h = apr_palloc(r->pool, sizeof(cache_handle_t));
+
+ list = cache->providers;
+
+ while (list) {
+
+ /* invalidate the request uri */
+ rv = list->provider->open_entity(h, r, cache->key);
+ if (OK == rv) {
+ rv = list->provider->invalidate_entity(h, r);
+ status = OK;
+ }
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02468) "cache: Attempted to invalidate cached entity with key: %s", cache->key);
+
+ /* invalidate the Location */
+ if (location_key) {
+ rv = list->provider->open_entity(h, r, location_key);
+ if (OK == rv) {
+ rv = list->provider->invalidate_entity(h, r);
+ status = OK;
+ }
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02469) "cache: Attempted to invalidate cached entity with key: %s", location_key);
+ }
+
+ /* invalidate the Content-Location */
+ if (content_location_key) {
+ rv = list->provider->open_entity(h, r, content_location_key);
+ if (OK == rv) {
+ rv = list->provider->invalidate_entity(h, r);
+ status = OK;
+ }
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02470) "cache: Attempted to invalidate cached entity with key: %s", content_location_key);
+ }
+
+ list = list->next;
+ }
+
+ return status;
+}
diff --git a/modules/cache/cache_storage.h b/modules/cache/cache_storage.h
index 2b67970e..83f2946f 100644
--- a/modules/cache/cache_storage.h
+++ b/modules/cache/cache_storage.h
@@ -40,6 +40,20 @@ int cache_remove_url(cache_request_rec *cache, request_rec *r);
int cache_create_entity(cache_request_rec *cache, request_rec *r,
apr_off_t size, apr_bucket_brigade *in);
int cache_select(cache_request_rec *cache, request_rec *r);
+
+/**
+ * invalidate a specific URL entity in all caches
+ *
+ * All cached entities for this URL are removed, usually in
+ * response to a POST/PUT or DELETE.
+ *
+ * This function returns OK if at least one entity was found and
+ * removed, and DECLINED if no cached entities were removed.
+ * @param cache cache_request_rec
+ * @param r request_rec
+ */
+int cache_invalidate(cache_request_rec *cache, request_rec *r);
+
apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
const char **key);
@@ -47,11 +61,12 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
* Merge in cached headers into the response
* @param h cache_handle_t
* @param r request_rec
- * @param preserve_orig If 1, the values in r->headers_out are preserved.
- * Otherwise, they are overwritten by the cached value.
+ * @param top headers to be applied
+ * @param bottom headers to be overwritten
+ * @param revalidation true if revalidation is taking place
*/
-void cache_accept_headers(cache_handle_t *h, request_rec *r,
- int preserve_orig);
+void cache_accept_headers(cache_handle_t *h, request_rec *r, apr_table_t *top,
+ apr_table_t *bottom, int revalidation);
#ifdef __cplusplus
}
diff --git a/modules/cache/cache_util.c b/modules/cache/cache_util.c
index 1e5098d5..7b7fb45c 100644
--- a/modules/cache/cache_util.c
+++ b/modules/cache/cache_util.c
@@ -27,8 +27,6 @@ extern APR_OPTIONAL_FN_TYPE(ap_cache_generate_key) *cache_generate_key;
extern module AP_MODULE_DECLARE_DATA cache_module;
-#define CACHE_SEPARATOR ", "
-
/* Determine if "url" matches the hostname, scheme and port and path
* in "filter". All but the path comparisons are case-insensitive.
*/
@@ -412,9 +410,9 @@ apr_status_t cache_remove_lock(cache_server_conf *conf,
return apr_file_remove(lockname, r->pool);
}
-CACHE_DECLARE(int) ap_cache_check_allowed(cache_request_rec *cache, request_rec *r) {
- const char *cc_req;
- const char *pragma;
+int ap_cache_check_no_cache(cache_request_rec *cache, request_rec *r)
+{
+
cache_server_conf *conf =
(cache_server_conf *)ap_get_module_config(r->server->module_config,
&cache_module);
@@ -429,16 +427,15 @@ CACHE_DECLARE(int) ap_cache_check_allowed(cache_request_rec *cache, request_rec
* - RFC2616 14.9.4 End to end reload, Cache-Control: no-cache, or Pragma:
* no-cache. The server MUST NOT use a cached copy when responding to such
* a request.
- *
- * - RFC2616 14.9.2 What May be Stored by Caches. If Cache-Control:
- * no-store arrives, do not serve from the cache.
*/
/* This value comes from the client's initial request. */
- cc_req = apr_table_get(r->headers_in, "Cache-Control");
- pragma = apr_table_get(r->headers_in, "Pragma");
-
- ap_cache_control(r, &cache->control_in, cc_req, pragma, r->headers_in);
+ if (!cache->control_in.parsed) {
+ const char *cc_req = cache_table_getm(r->pool, r->headers_in,
+ "Cache-Control");
+ const char *pragma = cache_table_getm(r->pool, r->headers_in, "Pragma");
+ ap_cache_control(r, &cache->control_in, cc_req, pragma, r->headers_in);
+ }
if (cache->control_in.no_cache) {
@@ -453,6 +450,32 @@ CACHE_DECLARE(int) ap_cache_check_allowed(cache_request_rec *cache, request_rec
}
}
+ return 1;
+}
+
+int ap_cache_check_no_store(cache_request_rec *cache, request_rec *r)
+{
+
+ cache_server_conf *conf =
+ (cache_server_conf *)ap_get_module_config(r->server->module_config,
+ &cache_module);
+
+ /*
+ * At this point, we may have data cached, but the request may have
+ * specified that cached data may not be used in a response.
+ *
+ * - RFC2616 14.9.2 What May be Stored by Caches. If Cache-Control:
+ * no-store arrives, do not serve from or store to the cache.
+ */
+
+ /* This value comes from the client's initial request. */
+ if (!cache->control_in.parsed) {
+ const char *cc_req = cache_table_getm(r->pool, r->headers_in,
+ "Cache-Control");
+ const char *pragma = cache_table_getm(r->pool, r->headers_in, "Pragma");
+ ap_cache_control(r, &cache->control_in, cc_req, pragma, r->headers_in);
+ }
+
if (cache->control_in.no_store) {
if (!conf->ignorecachecontrol) {
@@ -470,7 +493,6 @@ CACHE_DECLARE(int) ap_cache_check_allowed(cache_request_rec *cache, request_rec
return 1;
}
-
int cache_check_freshness(cache_handle_t *h, cache_request_rec *cache,
request_rec *r)
{
@@ -543,12 +565,12 @@ int cache_check_freshness(cache_handle_t *h, cache_request_rec *cache,
/* These come from the cached entity. */
if (h->cache_obj->info.control.no_cache
- || h->cache_obj->info.control.no_cache_header
- || h->cache_obj->info.control.private_header) {
+ || h->cache_obj->info.control.invalidated) {
/*
* The cached entity contained Cache-Control: no-cache, or a
* no-cache with a header present, or a private with a header
- * present, so treat as stale causing revalidation.
+ * present, or the cached entity has been invalidated in the
+ * past, so treat as stale causing revalidation.
*/
return 0;
}
@@ -858,100 +880,11 @@ CACHE_DECLARE(char *)ap_cache_generate_name(apr_pool_t *p, int dirlevels,
return apr_pstrdup(p, hashfile);
}
-/*
- * Create a new table consisting of those elements from an
- * headers table that are allowed to be stored in a cache.
- */
-CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers(apr_pool_t *pool,
- apr_table_t *t,
- server_rec *s)
-{
- cache_server_conf *conf;
- char **header;
- int i;
- apr_table_t *headers_out;
-
- /* Short circuit the common case that there are not
- * (yet) any headers populated.
- */
- if (t == NULL) {
- return apr_table_make(pool, 10);
- };
-
- /* Make a copy of the headers, and remove from
- * the copy any hop-by-hop headers, as defined in Section
- * 13.5.1 of RFC 2616
- */
- headers_out = apr_table_copy(pool, t);
-
- apr_table_unset(headers_out, "Connection");
- apr_table_unset(headers_out, "Keep-Alive");
- apr_table_unset(headers_out, "Proxy-Authenticate");
- apr_table_unset(headers_out, "Proxy-Authorization");
- apr_table_unset(headers_out, "TE");
- apr_table_unset(headers_out, "Trailers");
- apr_table_unset(headers_out, "Transfer-Encoding");
- apr_table_unset(headers_out, "Upgrade");
-
- conf = (cache_server_conf *)ap_get_module_config(s->module_config,
- &cache_module);
-
- /* Remove the user defined headers set with CacheIgnoreHeaders.
- * This may break RFC 2616 compliance on behalf of the administrator.
- */
- header = (char **)conf->ignore_headers->elts;
- for (i = 0; i < conf->ignore_headers->nelts; i++) {
- apr_table_unset(headers_out, header[i]);
- }
- return headers_out;
-}
-
-/*
- * Create a new table consisting of those elements from an input
- * headers table that are allowed to be stored in a cache.
- */
-CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers_in(request_rec *r)
-{
- return ap_cache_cacheable_headers(r->pool, r->headers_in, r->server);
-}
-
-/*
- * Create a new table consisting of those elements from an output
- * headers table that are allowed to be stored in a cache;
- * ensure there is a content type and capture any errors.
- */
-CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers_out(request_rec *r)
-{
- apr_table_t *headers_out;
-
- headers_out = apr_table_overlay(r->pool, r->headers_out,
- r->err_headers_out);
-
- apr_table_clear(r->err_headers_out);
-
- headers_out = ap_cache_cacheable_headers(r->pool, headers_out,
- r->server);
-
- if (!apr_table_get(headers_out, "Content-Type")
- && r->content_type) {
- apr_table_setn(headers_out, "Content-Type",
- ap_make_content_type(r, r->content_type));
- }
-
- if (!apr_table_get(headers_out, "Content-Encoding")
- && r->content_encoding) {
- apr_table_setn(headers_out, "Content-Encoding",
- r->content_encoding);
- }
-
- return headers_out;
-}
-
/**
* String tokenizer that ignores separator characters within quoted strings
* and escaped characters, as per RFC2616 section 2.2.
*/
-static char *cache_strqtok(char *str, const char *sep, char **last)
+char *cache_strqtok(char *str, const char *sep, char **last)
{
char *token;
int quoted = 0;
@@ -960,6 +893,10 @@ static char *cache_strqtok(char *str, const char *sep, char **last)
str = *last; /* start where we left off */
}
+ if (!str) { /* no more tokens */
+ return NULL;
+ }
+
/* skip characters in sep (will terminate at '\0') */
while (*str && ap_strchr_c(sep, *str)) {
++str;
@@ -979,7 +916,7 @@ static char *cache_strqtok(char *str, const char *sep, char **last)
*last = token;
while (**last) {
if (!quoted) {
- if (**last == '\"') {
+ if (**last == '\"' && !ap_strchr_c(sep, '\"')) {
quoted = 1;
++*last;
}
@@ -1069,9 +1006,7 @@ int ap_cache_control(request_rec *r, cache_control_t *cc,
/* ...then try slowest cases */
else if (!strncasecmp(token, "no-cache", 8)) {
if (token[8] == '=') {
- if (apr_table_get(headers, token + 9)) {
- cc->no_cache_header = 1;
- }
+ cc->no_cache_header = 1;
}
else if (!token[8]) {
cc->no_cache = 1;
@@ -1146,9 +1081,7 @@ int ap_cache_control(request_rec *r, cache_control_t *cc,
}
else if (!strncasecmp(token, "private", 7)) {
if (token[7] == '=') {
- if (apr_table_get(headers, token + 8)) {
- cc->private_header = 1;
- }
+ cc->private_header = 1;
}
else if (!token[7]) {
cc->private = 1;
@@ -1179,3 +1112,209 @@ int ap_cache_control(request_rec *r, cache_control_t *cc,
return (cc_header != NULL || pragma_header != NULL);
}
+
+/**
+ * Parse the Cache-Control, identifying and removing headers that
+ * exist as tokens after the no-cache and private tokens.
+ */
+static int cache_control_remove(request_rec *r, const char *cc_header,
+ apr_table_t *headers)
+{
+ char *last, *slast;
+ int found = 0;
+
+ if (cc_header) {
+ char *header = apr_pstrdup(r->pool, cc_header);
+ char *token = cache_strqtok(header, CACHE_SEPARATOR, &last);
+ while (token) {
+ switch (token[0]) {
+ case 'n':
+ case 'N': {
+ if (!strncmp(token, "no-cache", 8)
+ || !strncasecmp(token, "no-cache", 8)) {
+ if (token[8] == '=') {
+ const char *header = cache_strqtok(token + 9,
+ CACHE_SEPARATOR "\"", &slast);
+ while (header) {
+ apr_table_unset(headers, header);
+ header = cache_strqtok(NULL, CACHE_SEPARATOR "\"",
+ &slast);
+ }
+ found = 1;
+ }
+ break;
+ }
+ break;
+ }
+ case 'p':
+ case 'P': {
+ if (!strncmp(token, "private", 7)
+ || !strncasecmp(token, "private", 7)) {
+ if (token[7] == '=') {
+ const char *header = cache_strqtok(token + 8,
+ CACHE_SEPARATOR "\"", &slast);
+ while (header) {
+ apr_table_unset(headers, header);
+ header = cache_strqtok(NULL, CACHE_SEPARATOR "\"",
+ &slast);
+ }
+ found = 1;
+ }
+ }
+ break;
+ }
+ }
+ token = cache_strqtok(NULL, CACHE_SEPARATOR, &last);
+ }
+ }
+
+ return found;
+}
+
+/*
+ * Create a new table consisting of those elements from an
+ * headers table that are allowed to be stored in a cache.
+ */
+CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers(apr_pool_t *pool,
+ apr_table_t *t,
+ server_rec *s)
+{
+ cache_server_conf *conf;
+ char **header;
+ int i;
+ apr_table_t *headers_out;
+
+ /* Short circuit the common case that there are not
+ * (yet) any headers populated.
+ */
+ if (t == NULL) {
+ return apr_table_make(pool, 10);
+ };
+
+ /* Make a copy of the headers, and remove from
+ * the copy any hop-by-hop headers, as defined in Section
+ * 13.5.1 of RFC 2616
+ */
+ headers_out = apr_table_copy(pool, t);
+
+ apr_table_unset(headers_out, "Connection");
+ apr_table_unset(headers_out, "Keep-Alive");
+ apr_table_unset(headers_out, "Proxy-Authenticate");
+ apr_table_unset(headers_out, "Proxy-Authorization");
+ apr_table_unset(headers_out, "TE");
+ apr_table_unset(headers_out, "Trailers");
+ apr_table_unset(headers_out, "Transfer-Encoding");
+ apr_table_unset(headers_out, "Upgrade");
+
+ conf = (cache_server_conf *)ap_get_module_config(s->module_config,
+ &cache_module);
+
+ /* Remove the user defined headers set with CacheIgnoreHeaders.
+ * This may break RFC 2616 compliance on behalf of the administrator.
+ */
+ header = (char **)conf->ignore_headers->elts;
+ for (i = 0; i < conf->ignore_headers->nelts; i++) {
+ apr_table_unset(headers_out, header[i]);
+ }
+ return headers_out;
+}
+
+/*
+ * Create a new table consisting of those elements from an input
+ * headers table that are allowed to be stored in a cache.
+ */
+CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers_in(request_rec *r)
+{
+ return ap_cache_cacheable_headers(r->pool, r->headers_in, r->server);
+}
+
+/*
+ * Create a new table consisting of those elements from an output
+ * headers table that are allowed to be stored in a cache;
+ * ensure there is a content type and capture any errors.
+ */
+CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers_out(request_rec *r)
+{
+ apr_table_t *headers_out;
+
+ headers_out = apr_table_overlay(r->pool, r->headers_out,
+ r->err_headers_out);
+
+ apr_table_clear(r->err_headers_out);
+
+ headers_out = ap_cache_cacheable_headers(r->pool, headers_out,
+ r->server);
+
+ cache_control_remove(r,
+ cache_table_getm(r->pool, headers_out, "Cache-Control"),
+ headers_out);
+
+ if (!apr_table_get(headers_out, "Content-Type")
+ && r->content_type) {
+ apr_table_setn(headers_out, "Content-Type",
+ ap_make_content_type(r, r->content_type));
+ }
+
+ if (!apr_table_get(headers_out, "Content-Encoding")
+ && r->content_encoding) {
+ apr_table_setn(headers_out, "Content-Encoding",
+ r->content_encoding);
+ }
+
+ return headers_out;
+}
+
+typedef struct
+{
+ apr_pool_t *p;
+ const char *first;
+ apr_array_header_t *merged;
+} cache_table_getm_t;
+
+static int cache_table_getm_do(void *v, const char *key, const char *val)
+{
+ cache_table_getm_t *state = (cache_table_getm_t *) v;
+
+ if (!state->first) {
+ /**
+ * The most common case is a single header, and this is covered by
+ * a fast path that doesn't allocate any memory. On the second and
+ * subsequent header, an array is created and the array concatenated
+ * together to form the final value.
+ */
+ state->first = val;
+ }
+ else {
+ const char **elt;
+ if (!state->merged) {
+ state->merged = apr_array_make(state->p, 10, sizeof(const char *));
+ elt = apr_array_push(state->merged);
+ *elt = state->first;
+ }
+ elt = apr_array_push(state->merged);
+ *elt = val;
+ }
+ return 1;
+}
+
+const char *cache_table_getm(apr_pool_t *p, const apr_table_t *t,
+ const char *key)
+{
+ cache_table_getm_t state;
+
+ state.p = p;
+ state.first = NULL;
+ state.merged = NULL;
+
+ apr_table_do(cache_table_getm_do, &state, t, key, NULL);
+
+ if (!state.first) {
+ return NULL;
+ }
+ else if (!state.merged) {
+ return state.first;
+ }
+ else {
+ return apr_array_pstrcat(p, state.merged, ',');
+ }
+}
diff --git a/modules/cache/cache_util.h b/modules/cache/cache_util.h
index eec38f3a..3a54fadd 100644
--- a/modules/cache/cache_util.h
+++ b/modules/cache/cache_util.h
@@ -99,6 +99,7 @@ extern "C" {
#define CACHE_LOCKNAME_KEY "mod_cache-lockname"
#define CACHE_LOCKFILE_KEY "mod_cache-lockfile"
#define CACHE_CTX_KEY "mod_cache-ctx"
+#define CACHE_SEPARATOR ", "
/**
* cache_util.c
@@ -238,7 +239,16 @@ typedef struct {
* @param r request_rec
* @return 0 ==> cache object may not be served, 1 ==> cache object may be served
*/
-CACHE_DECLARE(int) ap_cache_check_allowed(cache_request_rec *cache, request_rec *r);
+int ap_cache_check_no_cache(cache_request_rec *cache, request_rec *r);
+
+/**
+ * Check the whether the request allows a cached object to be stored as per RFC2616
+ * section 14.9.2 (What May be Stored by Caches)
+ * @param cache cache_request_rec
+ * @param r request_rec
+ * @return 0 ==> cache object may not be served, 1 ==> cache object may be served
+ */
+int ap_cache_check_no_store(cache_request_rec *cache, request_rec *r);
/**
* Check the freshness of the cache object per RFC2616 section 13.2 (Expiration Model)
@@ -292,6 +302,25 @@ apr_status_t cache_remove_lock(cache_server_conf *conf,
cache_provider_list *cache_get_providers(request_rec *r,
cache_server_conf *conf, apr_uri_t uri);
+/**
+ * Get a value from a table, where the table may contain multiple
+ * values for a given key.
+ *
+ * When the table contains a single value, that value is returned
+ * unchanged.
+ *
+ * When the table contains two or more values for a key, all values
+ * for the key are returned, separated by commas.
+ */
+const char *cache_table_getm(apr_pool_t *p, const apr_table_t *t,
+ const char *key);
+
+/**
+ * String tokenizer that ignores separator characters within quoted strings
+ * and escaped characters, as per RFC2616 section 2.2.
+ */
+char *cache_strqtok(char *str, const char *sep, char **last);
+
#ifdef __cplusplus
}
#endif
diff --git a/modules/cache/config.m4 b/modules/cache/config.m4
index 5647e89b..b9799b76 100644
--- a/modules/cache/config.m4
+++ b/modules/cache/config.m4
@@ -13,17 +13,20 @@ cache_storage.lo dnl
cache_util.lo dnl
"
cache_disk_objs="mod_cache_disk.lo"
+cache_socache_objs="mod_cache_socache.lo"
case "$host" in
*os2*)
# OS/2 DLLs must resolve all symbols at build time
# and we need some from main cache module
cache_disk_objs="$cache_disk_objs mod_cache.la"
+ cache_socache_objs="$cache_socache_objs mod_cache.la"
;;
esac
APACHE_MODULE(cache, dynamic file caching. At least one storage management module (e.g. mod_cache_disk) is also necessary., $cache_objs, , most)
APACHE_MODULE(cache_disk, disk caching module, $cache_disk_objs, , most, , cache)
+APACHE_MODULE(cache_socache, shared object caching module, $cache_socache_objs, , most)
dnl
dnl APACHE_CHECK_DISTCACHE
diff --git a/modules/cache/mod_cache.c b/modules/cache/mod_cache.c
index 4f2d3e04..06bdf460 100644
--- a/modules/cache/mod_cache.c
+++ b/modules/cache/mod_cache.c
@@ -34,6 +34,7 @@ static ap_filter_rec_t *cache_save_subreq_filter_handle;
static ap_filter_rec_t *cache_out_filter_handle;
static ap_filter_rec_t *cache_out_subreq_filter_handle;
static ap_filter_rec_t *cache_remove_url_filter_handle;
+static ap_filter_rec_t *cache_invalidate_filter_handle;
/*
* CACHE handler
@@ -75,11 +76,6 @@ static int cache_quick_handler(request_rec *r, int lookup)
ap_filter_rec_t *cache_out_handle;
cache_server_conf *conf;
- /* Delay initialization until we know we are handling a GET */
- if (r->method_number != M_GET) {
- return DECLINED;
- }
-
conf = (cache_server_conf *) ap_get_module_config(r->server->module_config,
&cache_module);
@@ -106,6 +102,9 @@ static int cache_quick_handler(request_rec *r, int lookup)
/*
* Are we allowed to serve cached info at all?
*/
+ if (!ap_cache_check_no_store(cache, r)) {
+ return DECLINED;
+ }
/* find certain cache controlling headers */
auth = apr_table_get(r->headers_in, "Authorization");
@@ -117,6 +116,40 @@ static int cache_quick_handler(request_rec *r, int lookup)
return DECLINED;
}
+ /* Are we PUT/POST/DELETE? If so, prepare to invalidate the cached entities.
+ */
+ switch (r->method_number) {
+ case M_PUT:
+ case M_POST:
+ case M_DELETE:
+ {
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02461)
+ "PUT/POST/DELETE: Adding CACHE_INVALIDATE filter for %s",
+ r->uri);
+
+ /* Add cache_invalidate filter to this request to force a
+ * cache entry to be invalidated if the response is
+ * ultimately successful (2xx).
+ */
+ ap_add_output_filter_handle(
+ cache_invalidate_filter_handle, cache, r,
+ r->connection);
+
+ return DECLINED;
+ }
+ case M_GET: {
+ break;
+ }
+ default : {
+
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02462) "cache: Method '%s' not cacheable by mod_cache, ignoring: %s", r->method, r->uri);
+
+ return DECLINED;
+ }
+ }
+
/*
* Try to serve this request from the cache.
*
@@ -176,9 +209,10 @@ static int cache_quick_handler(request_rec *r, int lookup)
* is available later during running the filter may be
* different due to an internal redirect.
*/
- cache->remove_url_filter =
- ap_add_output_filter_handle(cache_remove_url_filter_handle,
- cache, r, r->connection);
+ cache->remove_url_filter = ap_add_output_filter_handle(
+ cache_remove_url_filter_handle, cache, r,
+ r->connection);
+
}
else {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv,
@@ -347,11 +381,6 @@ static int cache_handler(request_rec *r)
ap_filter_rec_t *cache_save_handle;
cache_server_conf *conf;
- /* Delay initialization until we know we are handling a GET */
- if (r->method_number != M_GET) {
- return DECLINED;
- }
-
conf = (cache_server_conf *) ap_get_module_config(r->server->module_config,
&cache_module);
@@ -376,6 +405,47 @@ static int cache_handler(request_rec *r)
cache->providers = providers;
/*
+ * Are we allowed to serve cached info at all?
+ */
+ if (!ap_cache_check_no_store(cache, r)) {
+ return DECLINED;
+ }
+
+ /* Are we PUT/POST/DELETE? If so, prepare to invalidate the cached entities.
+ */
+ switch (r->method_number) {
+ case M_PUT:
+ case M_POST:
+ case M_DELETE:
+ {
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02463)
+ "PUT/POST/DELETE: Adding CACHE_INVALIDATE filter for %s",
+ r->uri);
+
+ /* Add cache_invalidate filter to this request to force a
+ * cache entry to be invalidated if the response is
+ * ultimately successful (2xx).
+ */
+ ap_add_output_filter_handle(
+ cache_invalidate_filter_handle, cache, r,
+ r->connection);
+
+ return DECLINED;
+ }
+ case M_GET: {
+ break;
+ }
+ default : {
+
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02464) "cache: Method '%s' not cacheable by mod_cache, ignoring: %s", r->method, r->uri);
+
+ return DECLINED;
+ }
+ }
+
+ /*
* Try to serve this request from the cache.
*
* If no existing cache file (DECLINED)
@@ -455,9 +525,10 @@ static int cache_handler(request_rec *r)
* is available later during running the filter may be
* different due to an internal redirect.
*/
- cache->remove_url_filter =
- ap_add_output_filter_handle(cache_remove_url_filter_handle,
- cache, r, r->connection);
+ cache->remove_url_filter
+ = ap_add_output_filter_handle(
+ cache_remove_url_filter_handle, cache, r,
+ r->connection);
}
else {
@@ -665,7 +736,7 @@ static int cache_save_store(ap_filter_t *f, apr_bucket_brigade *in,
*/
ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, f->r, APLOGNO(00766)
"cache: Cache provider's store_body returned an "
- "empty brigade, but didn't consume all of the"
+ "empty brigade, but didn't consume all of the "
"input brigade, standing down to prevent a spin");
ap_remove_output_filter(f);
@@ -682,6 +753,22 @@ static int cache_save_store(ap_filter_t *f, apr_bucket_brigade *in,
return rv;
}
+/**
+ * Sanity check for 304 Not Modified responses, as per RFC2616 Section 10.3.5.
+ */
+static const char *cache_header_cmp(apr_pool_t *pool, apr_table_t *left,
+ apr_table_t *right, const char *key)
+{
+ const char *h1, *h2;
+
+ if ((h1 = cache_table_getm(pool, left, key))
+ && (h2 = cache_table_getm(pool, right, key)) && (strcmp(h1, h2))) {
+ return apr_pstrcat(pool, "contradiction: 304 Not Modified, but ", key,
+ " modified", NULL);
+ }
+ return NULL;
+}
+
/*
* CACHE_SAVE filter
* ---------------
@@ -715,7 +802,7 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
apr_time_t exp, date, lastmod, now;
apr_off_t size = -1;
cache_info *info = NULL;
- char *reason;
+ const char *reason;
apr_pool_t *p;
apr_bucket *e;
apr_table_t *headers;
@@ -857,12 +944,12 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
if (etag == NULL) {
etag = apr_table_get(r->headers_out, "Etag");
}
- cc_out = apr_table_get(r->err_headers_out, "Cache-Control");
- pragma = apr_table_get(r->err_headers_out, "Pragma");
+ cc_out = cache_table_getm(r->pool, r->err_headers_out, "Cache-Control");
+ pragma = cache_table_getm(r->pool, r->err_headers_out, "Pragma");
headers = r->err_headers_out;
if (!cc_out && !pragma) {
- cc_out = apr_table_get(r->headers_out, "Cache-Control");
- pragma = apr_table_get(r->headers_out, "Pragma");
+ cc_out = cache_table_getm(r->pool, r->headers_out, "Cache-Control");
+ pragma = cache_table_getm(r->pool, r->headers_out, "Pragma");
headers = r->headers_out;
}
@@ -871,8 +958,10 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
*/
if (r->status == HTTP_NOT_MODIFIED && cache->stale_handle && !cc_out
&& !pragma) {
- cc_out = apr_table_get(cache->stale_handle->resp_hdrs, "Cache-Control");
- pragma = apr_table_get(cache->stale_handle->resp_hdrs, "Pragma");
+ cc_out = cache_table_getm(r->pool, cache->stale_handle->resp_hdrs,
+ "Cache-Control");
+ pragma = cache_table_getm(r->pool, cache->stale_handle->resp_hdrs,
+ "Pragma");
}
/* Parse the cache control header */
@@ -1000,78 +1089,117 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
/* or we've been asked not to cache it above */
reason = "r->no_cache present";
}
+ else if (cache->stale_handle
+ && APR_DATE_BAD
+ != (date = apr_date_parse_http(
+ apr_table_get(r->headers_out, "Date")))
+ && date < cache->stale_handle->cache_obj->info.date) {
- /* Hold the phone. Some servers might allow us to cache a 2xx, but
- * then make their 304 responses non cacheable. This leaves us in a
- * sticky position. If the 304 is in answer to our own conditional
- * request, we cannot send this 304 back to the client because the
- * client isn't expecting it. Instead, our only option is to respect
- * the answer to the question we asked (has it changed, answer was
- * no) and return the cached item to the client, and then respect
- * the uncacheable nature of this 304 by allowing the remove_url
- * filter to kick in and remove the cached entity.
- */
- if (reason && r->status == HTTP_NOT_MODIFIED &&
- cache->stale_handle) {
- apr_bucket_brigade *bb;
- apr_bucket *bkt;
- int status;
-
- cache->handle = cache->stale_handle;
- info = &cache->handle->cache_obj->info;
-
- /* Load in the saved status and clear the status line. */
- r->status = info->status;
- r->status_line = NULL;
-
- bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ /**
+ * 13.12 Cache Replacement:
+ *
+ * Note: a new response that has an older Date header value than
+ * existing cached responses is not cacheable.
+ */
+ reason = "updated entity is older than cached entity";
- r->headers_in = cache->stale_headers;
- status = ap_meets_conditions(r);
- if (status != OK) {
- r->status = status;
+ /* while this response is not cacheable, the previous response still is */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00770)
+ "cache: Removing CACHE_REMOVE_URL filter.");
+ ap_remove_output_filter(cache->remove_url_filter);
+ }
+ else if (r->status == HTTP_NOT_MODIFIED && cache->stale_handle) {
+ apr_table_t *left = cache->stale_handle->resp_hdrs;
+ apr_table_t *right = r->headers_out;
- bkt = apr_bucket_flush_create(bb->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, bkt);
+ /* and lastly, contradiction checks for revalidated responses
+ * as per RFC2616 Section 10.3.5
+ */
+ if (((reason = cache_header_cmp(r->pool, left, right, "Allow")))
+ || ((reason = cache_header_cmp(r->pool, left, right,
+ "Content-Encoding")))
+ || ((reason = cache_header_cmp(r->pool, left, right,
+ "Content-Language")))
+ || ((reason = cache_header_cmp(r->pool, left, right,
+ "Content-Length")))
+ || ((reason = cache_header_cmp(r->pool, left, right,
+ "Content-Location")))
+ || ((reason = cache_header_cmp(r->pool, left, right,
+ "Content-MD5")))
+ || ((reason = cache_header_cmp(r->pool, left, right,
+ "Content-Range")))
+ || ((reason = cache_header_cmp(r->pool, left, right,
+ "Content-Type")))
+ || ((reason = cache_header_cmp(r->pool, left, right, "Expires")))
+ || ((reason = cache_header_cmp(r->pool, left, right, "ETag")))
+ || ((reason = cache_header_cmp(r->pool, left, right,
+ "Last-Modified")))) {
+ /* contradiction: 304 Not Modified, but entity header modified */
}
- else {
- /* RFC 2616 10.3.5 states that entity headers are not supposed
- * to be in the 304 response. Therefore, we need to combine the
- * response headers with the cached headers *before* we update
- * the cached headers.
- *
- * However, before doing that, we need to first merge in
- * err_headers_out and we also need to strip any hop-by-hop
- * headers that might have snuck in.
- */
- r->headers_out = ap_cache_cacheable_headers_out(r);
-
- /* Merge in our cached headers. However, keep any updated values. */
- cache_accept_headers(cache->handle, r, 1);
+ }
- cache->provider->recall_body(cache->handle, r->pool, bb);
+ /**
+ * Enforce RFC2616 Section 10.3.5, just in case. We caught any
+ * inconsistencies above.
+ *
+ * If the conditional GET used a strong cache validator (see section
+ * 13.3.3), the response SHOULD NOT include other entity-headers.
+ * Otherwise (i.e., the conditional GET used a weak validator), the
+ * response MUST NOT include other entity-headers; this prevents
+ * inconsistencies between cached entity-bodies and updated headers.
+ */
+ if (r->status == HTTP_NOT_MODIFIED) {
+ apr_table_unset(r->headers_out, "Allow");
+ apr_table_unset(r->headers_out, "Content-Encoding");
+ apr_table_unset(r->headers_out, "Content-Language");
+ apr_table_unset(r->headers_out, "Content-Length");
+ apr_table_unset(r->headers_out, "Content-MD5");
+ apr_table_unset(r->headers_out, "Content-Range");
+ apr_table_unset(r->headers_out, "Content-Type");
+ apr_table_unset(r->headers_out, "Last-Modified");
+ }
- bkt = apr_bucket_eos_create(bb->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, bkt);
- }
+ /* Hold the phone. Some servers might allow us to cache a 2xx, but
+ * then make their 304 responses non cacheable. RFC2616 says this:
+ *
+ * If a 304 response indicates an entity not currently cached, then
+ * the cache MUST disregard the response and repeat the request
+ * without the conditional.
+ *
+ * A 304 response with contradictory headers is technically a
+ * different entity, to be safe, we remove the entity from the cache.
+ */
+ if (reason && r->status == HTTP_NOT_MODIFIED && cache->stale_handle) {
- cache->block_response = 1;
+ ap_log_rerror(
+ APLOG_MARK, APLOG_INFO, 0, r, APLOGNO() "cache: %s responded with an uncacheable 304, retrying the request. Reason: %s", r->unparsed_uri, reason);
- /* we've got a cache conditional hit! tell anyone who cares */
- cache_run_cache_status(
- cache->handle,
- r,
- r->headers_out,
- AP_CACHE_REVALIDATE,
- apr_psprintf(
- r->pool,
- "conditional cache hit: 304 was uncacheable though (%s); entity removed",
+ /* we've got a cache conditional miss! tell anyone who cares */
+ cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS,
+ apr_psprintf(r->pool,
+ "conditional cache miss: 304 was uncacheable, entity removed: %s",
reason));
+ /* remove the cached entity immediately, we might cache it again */
+ ap_remove_output_filter(cache->remove_url_filter);
+ cache_remove_url(cache, r);
+
/* let someone else attempt to cache */
cache_remove_lock(conf, cache, r, NULL);
- return ap_pass_brigade(f->next, bb);
+ /* remove this filter from the chain */
+ ap_remove_output_filter(f);
+
+ /* retry without the conditionals */
+ apr_table_unset(r->headers_in, "If-Match");
+ apr_table_unset(r->headers_in, "If-Modified-Since");
+ apr_table_unset(r->headers_in, "If-None-Match");
+ apr_table_unset(r->headers_in, "If-Range");
+ apr_table_unset(r->headers_in, "If-Unmodified-Since");
+
+ ap_internal_redirect(r->uri, r);
+
+ return APR_SUCCESS;
}
if (reason) {
@@ -1186,7 +1314,7 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
if (rv != OK) {
/* we've got a cache miss! tell anyone who cares */
cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS,
- "cache miss: create_entity failed");
+ "cache miss: cache unwilling to store response");
/* Caching layer declined the opportunity to cache the response */
ap_remove_output_filter(f);
@@ -1303,9 +1431,6 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
/* We found a stale entry which wasn't really stale. */
if (cache->stale_handle) {
- /* Load in the saved status and clear the status line. */
- r->status = info->status;
- r->status_line = NULL;
/* RFC 2616 10.3.5 states that entity headers are not supposed
* to be in the 304 response. Therefore, we need to combine the
@@ -1319,7 +1444,9 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
r->headers_out = ap_cache_cacheable_headers_out(r);
/* Merge in our cached headers. However, keep any updated values. */
- cache_accept_headers(cache->handle, r, 1);
+ /* take output, overlay on top of cached */
+ cache_accept_headers(cache->handle, r, r->headers_out,
+ cache->handle->resp_hdrs, 1);
}
/* Write away header information to cache. It is possible that we are
@@ -1342,6 +1469,10 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
apr_bucket *bkt;
int status;
+ /* Load in the saved status and clear the status line. */
+ r->status = info->status;
+ r->status_line = NULL;
+
/* We're just saving response headers, so we are done. Commit
* the response at this point, unless there was a previous error.
*/
@@ -1483,6 +1614,70 @@ static apr_status_t cache_remove_url_filter(ap_filter_t *f,
}
/*
+ * CACHE_INVALIDATE filter
+ * -----------------------
+ *
+ * This filter gets added in the quick handler should a PUT, POST or DELETE
+ * method be detected. If the response is successful, we must invalidate any
+ * cached entity as per RFC2616 section 13.10.
+ *
+ * CACHE_INVALIDATE has to be a protocol filter to ensure that is run even if
+ * the response is a canned error message, which removes the content filters
+ * from the chain.
+ *
+ * CACHE_INVALIDATE expects cache request rec within its context because the
+ * request this filter runs on can be different from the one whose cache entry
+ * should be removed, due to internal redirects.
+ */
+static apr_status_t cache_invalidate_filter(ap_filter_t *f,
+ apr_bucket_brigade *in)
+{
+ request_rec *r = f->r;
+ cache_request_rec *cache;
+
+ /* Setup cache_request_rec */
+ cache = (cache_request_rec *) f->ctx;
+
+ if (!cache) {
+ /* user likely configured CACHE_INVALIDATE manually; they should really
+ * use mod_cache configuration to do that. So:
+ * 1. Remove ourselves
+ * 2. Do nothing and bail out
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02465)
+ "cache: CACHE_INVALIDATE enabled unexpectedly: %s", r->uri);
+ }
+ else {
+
+ if (r->status > 299) {
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02466)
+ "cache: response status to '%s' method is %d (>299), not invalidating cached entity: %s", r->method, r->status, r->uri);
+
+ }
+ else {
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02467)
+ "cache: Invalidating all cached entities in response to '%s' request for %s",
+ r->method, r->uri);
+
+ cache_invalidate(cache, r);
+
+ /* we've got a cache invalidate! tell everyone who cares */
+ cache_run_cache_status(cache->handle, r, r->headers_out,
+ AP_CACHE_INVALIDATE, apr_psprintf(r->pool,
+ "cache invalidated by %s", r->method));
+
+ }
+
+ }
+
+ /* remove ourselves */
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, in);
+}
+
+/*
* CACHE filter
* ------------
*
@@ -1579,11 +1774,11 @@ static int cache_status(cache_handle_t *h, request_rec *r,
x_cache = conf->x_cache;
}
if (x_cache) {
- apr_table_setn(headers, "X-Cache",
- apr_psprintf(r->pool, "%s from %s",
- status == AP_CACHE_HIT ? "HIT" : status
- == AP_CACHE_REVALIDATE ? "REVALIDATE" : "MISS",
- r->server->server_hostname));
+ apr_table_setn(headers, "X-Cache", apr_psprintf(r->pool, "%s from %s",
+ status == AP_CACHE_HIT ? "HIT"
+ : status == AP_CACHE_REVALIDATE ? "REVALIDATE" : status
+ == AP_CACHE_INVALIDATE ? "INVALIDATE" : "MISS",
+ r->server->server_hostname));
}
if (dconf && dconf->x_cache_detail_set) {
@@ -1640,7 +1835,8 @@ static void cache_insert_error_filter(request_rec *r)
if (cache->stale_handle && cache->save_filter
&& !cache->stale_handle->cache_obj->info.control.must_revalidate
- && !cache->stale_handle->cache_obj->info.control.proxy_revalidate) {
+ && !cache->stale_handle->cache_obj->info.control.proxy_revalidate
+ && !cache->stale_handle->cache_obj->info.control.s_maxage) {
const char *warn_head;
cache_server_conf
*conf =
@@ -1773,7 +1969,7 @@ static void *merge_dir_config(apr_pool_t *p, void *basev, void *addv) {
static void * create_cache_config(apr_pool_t *p, server_rec *s)
{
- const char *tmppath;
+ const char *tmppath = NULL;
cache_server_conf *ps = apr_pcalloc(p, sizeof(cache_server_conf));
/* array of URL prefixes for which caching is enabled */
@@ -2068,7 +2264,7 @@ static const char *add_cache_disable(cmd_parms *parms, void *dummy,
&cache_module);
if (parms->path) {
- if (!strcmp(url, "on")) {
+ if (!strcasecmp(url, "on")) {
dconf->disable = 1;
dconf->disable_set = 1;
return NULL;
@@ -2454,6 +2650,11 @@ static void register_hooks(apr_pool_t *p)
cache_remove_url_filter,
NULL,
AP_FTYPE_PROTOCOL);
+ cache_invalidate_filter_handle =
+ ap_register_output_filter("CACHE_INVALIDATE",
+ cache_invalidate_filter,
+ NULL,
+ AP_FTYPE_PROTOCOL);
ap_hook_post_config(cache_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
}
diff --git a/modules/cache/mod_cache_disk.c b/modules/cache/mod_cache_disk.c
index 8427e8fd..2b50aef9 100644
--- a/modules/cache/mod_cache_disk.c
+++ b/modules/cache/mod_cache_disk.c
@@ -80,7 +80,7 @@ static char *header_file(apr_pool_t *p, disk_cache_conf *conf,
}
if (dobj->prefix) {
- return apr_pstrcat(p, dobj->prefix, CACHE_VDIR_SUFFIX, "/",
+ return apr_pstrcat(p, dobj->prefix, CACHE_VDIR_SUFFIX "/",
dobj->hashfile, CACHE_HEADER_SUFFIX, NULL);
}
else {
@@ -98,7 +98,7 @@ static char *data_file(apr_pool_t *p, disk_cache_conf *conf,
}
if (dobj->prefix) {
- return apr_pstrcat(p, dobj->prefix, CACHE_VDIR_SUFFIX, "/",
+ return apr_pstrcat(p, dobj->prefix, CACHE_VDIR_SUFFIX "/",
dobj->hashfile, CACHE_DATA_SUFFIX, NULL);
}
else {
@@ -385,6 +385,7 @@ static int create_entity(cache_handle_t *h, request_rec *r, const char *key, apr
dobj->root_len = conf->cache_root_len;
apr_pool_create(&pool, r->pool);
+ apr_pool_tag(pool, "mod_cache (create_entity)");
file_cache_create(conf, &dobj->hdrs, pool);
file_cache_create(conf, &dobj->vary, pool);
@@ -511,6 +512,7 @@ static int open_entity(cache_handle_t *h, request_rec *r, const char *key)
dobj->name = key;
apr_pool_create(&pool, r->pool);
+ apr_pool_tag(pool, "mod_cache (open_entity)");
file_cache_create(conf, &dobj->hdrs, pool);
file_cache_create(conf, &dobj->vary, pool);
@@ -841,7 +843,7 @@ static apr_status_t read_table(cache_handle_t *handle, request_rec *r,
}
*l++ = '\0';
- while (*l && apr_isspace(*l)) {
+ while (apr_isspace(*l)) {
++l;
}
@@ -940,6 +942,10 @@ static apr_status_t store_headers(cache_handle_t *h, request_rec *r, cache_info
dobj->headers_in = ap_cache_cacheable_headers_in(r);
}
+ if (r->header_only && r->status != HTTP_NOT_MODIFIED) {
+ dobj->disk_info.header_only = 1;
+ }
+
return APR_SUCCESS;
}
@@ -1188,49 +1194,51 @@ static apr_status_t store_body(cache_handle_t *h, request_rec *r,
continue;
}
- /* Attempt to create the data file at the last possible moment, if
- * the body is empty, we don't write a file at all, and save an inode.
- */
- if (!dobj->data.tempfd) {
- apr_finfo_t finfo;
- rv = apr_file_mktemp(&dobj->data.tempfd, dobj->data.tempfile,
- APR_CREATE | APR_WRITE | APR_BINARY |
- APR_BUFFERED | APR_EXCL, dobj->data.pool);
+ if (!dobj->disk_info.header_only) {
+
+ /* Attempt to create the data file at the last possible moment, if
+ * the body is empty, we don't write a file at all, and save an inode.
+ */
+ if (!dobj->data.tempfd) {
+ apr_finfo_t finfo;
+ rv = apr_file_mktemp(&dobj->data.tempfd, dobj->data.tempfile,
+ APR_CREATE | APR_WRITE | APR_BINARY | APR_BUFFERED
+ | APR_EXCL, dobj->data.pool);
+ if (rv != APR_SUCCESS) {
+ apr_pool_destroy(dobj->data.pool);
+ return rv;
+ }
+ dobj->file_size = 0;
+ rv = apr_file_info_get(&finfo, APR_FINFO_IDENT,
+ dobj->data.tempfd);
+ if (rv != APR_SUCCESS) {
+ apr_pool_destroy(dobj->data.pool);
+ return rv;
+ }
+ dobj->disk_info.device = finfo.device;
+ dobj->disk_info.inode = finfo.inode;
+ dobj->disk_info.has_body = 1;
+ }
+
+ /* write to the cache, leave if we fail */
+ rv = apr_file_write_full(dobj->data.tempfd, str, length, &written);
if (rv != APR_SUCCESS) {
+ ap_log_rerror(
+ APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00731) "Error when writing cache file for URL %s", h->cache_obj->key);
+ /* Remove the intermediate cache file and return non-APR_SUCCESS */
apr_pool_destroy(dobj->data.pool);
return rv;
}
- dobj->file_size = 0;
- rv = apr_file_info_get(&finfo, APR_FINFO_IDENT,
- dobj->data.tempfd);
- if (rv != APR_SUCCESS) {
+ dobj->file_size += written;
+ if (dobj->file_size > dconf->maxfs) {
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00732) "URL %s failed the size check "
+ "(%" APR_OFF_T_FMT ">%" APR_OFF_T_FMT ")", h->cache_obj->key, dobj->file_size, dconf->maxfs);
+ /* Remove the intermediate cache file and return non-APR_SUCCESS */
apr_pool_destroy(dobj->data.pool);
- return rv;
+ return APR_EGENERAL;
}
- dobj->disk_info.device = finfo.device;
- dobj->disk_info.inode = finfo.inode;
- dobj->disk_info.has_body = 1;
- }
- /* write to the cache, leave if we fail */
- rv = apr_file_write_full(dobj->data.tempfd, str, length, &written);
- if (rv != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00731)
- "Error when writing cache file for URL %s",
- h->cache_obj->key);
- /* Remove the intermediate cache file and return non-APR_SUCCESS */
- apr_pool_destroy(dobj->data.pool);
- return rv;
- }
- dobj->file_size += written;
- if (dobj->file_size > dconf->maxfs) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00732)
- "URL %s failed the size check "
- "(%" APR_OFF_T_FMT ">%" APR_OFF_T_FMT ")",
- h->cache_obj->key, dobj->file_size, dconf->maxfs);
- /* Remove the intermediate cache file and return non-APR_SUCCESS */
- apr_pool_destroy(dobj->data.pool);
- return APR_EGENERAL;
}
/* have we reached the limit of how much we're prepared to write in one
@@ -1256,43 +1264,44 @@ static apr_status_t store_body(cache_handle_t *h, request_rec *r,
if (seen_eos) {
const char *cl_header = apr_table_get(r->headers_out, "Content-Length");
- if (dobj->data.tempfd) {
- rv = apr_file_close(dobj->data.tempfd);
- if (rv != APR_SUCCESS) {
- /* Buffered write failed, abandon attempt to write */
- apr_pool_destroy(dobj->data.pool);
- return rv;
+ if (!dobj->disk_info.header_only) {
+
+ if (dobj->data.tempfd) {
+ rv = apr_file_close(dobj->data.tempfd);
+ if (rv != APR_SUCCESS) {
+ /* Buffered write failed, abandon attempt to write */
+ apr_pool_destroy(dobj->data.pool);
+ return rv;
+ }
}
- }
- if (r->connection->aborted || r->no_cache) {
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00733)
- "Discarding body for URL %s "
- "because connection has been aborted.",
- h->cache_obj->key);
- /* Remove the intermediate cache file and return non-APR_SUCCESS */
- apr_pool_destroy(dobj->data.pool);
- return APR_EGENERAL;
- }
- if (dobj->file_size < dconf->minfs) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00734)
- "URL %s failed the size check "
- "(%" APR_OFF_T_FMT "<%" APR_OFF_T_FMT ")",
- h->cache_obj->key, dobj->file_size, dconf->minfs);
- /* Remove the intermediate cache file and return non-APR_SUCCESS */
- apr_pool_destroy(dobj->data.pool);
- return APR_EGENERAL;
- }
- if (cl_header) {
- apr_int64_t cl = apr_atoi64(cl_header);
- if ((errno == 0) && (dobj->file_size != cl)) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00735)
- "URL %s didn't receive complete response, not caching",
- h->cache_obj->key);
+ if (r->connection->aborted || r->no_cache) {
+ ap_log_rerror(
+ APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00733) "Discarding body for URL %s "
+ "because connection has been aborted.", h->cache_obj->key);
/* Remove the intermediate cache file and return non-APR_SUCCESS */
apr_pool_destroy(dobj->data.pool);
return APR_EGENERAL;
}
+ if (dobj->file_size < dconf->minfs) {
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00734) "URL %s failed the size check "
+ "(%" APR_OFF_T_FMT "<%" APR_OFF_T_FMT ")", h->cache_obj->key, dobj->file_size, dconf->minfs);
+ /* Remove the intermediate cache file and return non-APR_SUCCESS */
+ apr_pool_destroy(dobj->data.pool);
+ return APR_EGENERAL;
+ }
+ if (cl_header) {
+ apr_int64_t cl = apr_atoi64(cl_header);
+ if ((errno == 0) && (dobj->file_size != cl)) {
+ ap_log_rerror(
+ APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00735) "URL %s didn't receive complete response, not caching", h->cache_obj->key);
+ /* Remove the intermediate cache file and return non-APR_SUCCESS */
+ apr_pool_destroy(dobj->data.pool);
+ return APR_EGENERAL;
+ }
+ }
+
}
/* All checks were fine, we're good to go when the commit comes */
@@ -1319,7 +1328,12 @@ static apr_status_t commit_entity(cache_handle_t *h, request_rec *r)
rv = file_cache_el_final(conf, &dobj->vary, r);
}
if (APR_SUCCESS == rv) {
- rv = file_cache_el_final(conf, &dobj->data, r);
+ if (!dobj->disk_info.header_only) {
+ rv = file_cache_el_final(conf, &dobj->data, r);
+ }
+ else if (dobj->data.file){
+ rv = apr_file_remove(dobj->data.file, dobj->data.pool);
+ }
}
/* remove the cached items completely on any failure */
@@ -1342,7 +1356,17 @@ static apr_status_t commit_entity(cache_handle_t *h, request_rec *r)
static apr_status_t invalidate_entity(cache_handle_t *h, request_rec *r)
{
- return APR_ENOTIMPL;
+ apr_status_t rv;
+
+ rv = recall_headers(h, r);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ /* mark the entity as invalidated */
+ h->cache_obj->info.control.invalidated = 1;
+
+ return commit_entity(h, r);
}
static void *create_dir_config(apr_pool_t *p, char *dummy)
@@ -1447,6 +1471,7 @@ static const char
{
return "CacheMinFileSize argument must be a non-negative integer representing the min size of a file to cache in bytes.";
}
+ dconf->minfs_set = 1;
return NULL;
}
@@ -1460,6 +1485,7 @@ static const char
{
return "CacheMaxFileSize argument must be a non-negative integer representing the max size of a file to cache in bytes.";
}
+ dconf->maxfs_set = 1;
return NULL;
}
diff --git a/modules/cache/mod_cache_socache.c b/modules/cache/mod_cache_socache.c
new file mode 100644
index 00000000..913de2ee
--- /dev/null
+++ b/modules/cache/mod_cache_socache.c
@@ -0,0 +1,1501 @@
+/* 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;
+
+ if (r->header_only && r->status != HTTP_NOT_MODIFIED) {
+ socache_info->header_only = 1;
+ }
+ else {
+ socache_info->header_only = sobj->socache_info.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)
+{
+ /* mark the entity as invalidated */
+ h->cache_obj->info.control.invalidated = 1;
+
+ return commit_entity(h, r);
+}
+
+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,
+ name, 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";
+ }
+ dconf->max_set = 1;
+ 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, 0, 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 00000000..e5d582e2
--- /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
diff --git a/modules/cache/mod_socache_memcache.c b/modules/cache/mod_socache_memcache.c
index ccb1bde7..beeeec2c 100644
--- a/modules/cache/mod_socache_memcache.c
+++ b/modules/cache/mod_socache_memcache.c
@@ -182,19 +182,13 @@ static int socache_mc_id2key(ap_socache_instance_t *ctx,
char *key, apr_size_t keylen)
{
char *cp;
- unsigned int n;
if (idlen * 2 + ctx->taglen >= keylen)
return 1;
cp = apr_cpystrn(key, ctx->tag, ctx->taglen);
+ ap_bin2hex(id, idlen, cp);
- for (n = 0; n < idlen; n++) {
- apr_snprintf(cp, 3, "%02X", (unsigned) id[n]);
- cp += 2;
- }
-
- *cp = '\0';
return 0;
}