httpd-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Graham Leggett <minf...@sharp.fm>
Subject [Patch] mod_cache_socache (to replace mod_mem_cache)
Date Wed, 22 Feb 2012 00:33:25 GMT
Hi all,

During development of mod_cache, mod_disk_cache was renamed to mod_cache_disk to make it possible to have a sensible name for this module, mod_cache_socache, which replaces mod_mem_cache from httpd v2.2.

This module provides a simple backend for mod_cache backed with the socache interface, which allows you to cache to memcache, shared memory, or any other mod_socache_* implementation. The docs explain it further. This module is useful where there is no suitable cache disk available to support mod_cache_disk.

(I didn't want to post this until v2.4 was baked, so as not to hold up that release).

Index: docs/manual/mod/mod_cache_socache.xml
===================================================================
--- docs/manual/mod/mod_cache_socache.xml	(revision 0)
+++ docs/manual/mod/mod_cache_socache.xml	(revision 0)
@@ -0,0 +1,247 @@
+<?xml version="1.0"?>
+<!DOCTYPE modulesynopsis SYSTEM "../style/modulesynopsis.dtd">
+<?xml-stylesheet type="text/xsl" href="../style/manual.en.xsl"?>
+<!-- $LastChangedRevision: 1174747 $ -->
+
+<!--
+ 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.
+-->
+
+<modulesynopsis metafile="mod_cache_socache.xml.meta">
+
+<name>mod_cache_socache</name>
+<description>Shared object cache (socache) based storage module for the
+HTTP caching filter.</description>
+<status>Extension</status>
+<sourcefile>mod_cache_socache.c</sourcefile>
+<identifier>cache_socache_module</identifier>
+
+<summary>
+    <p><module>mod_cache_socache</module> implements a shared object cache
+    (socache) based storage manager for <module>mod_cache</module>.</p>
+
+    <p>The headers and bodies of cached responses are combined, and stored
+    underneath a single key in the shared object cache. A
+    <a href="../socache.html">number of implementations</a> of shared object
+    caches are available to choose from.</p>
+
+    <p>Multiple content negotiated responses can be stored concurrently,
+    however the caching of partial content is not yet supported by this
+    module.</p>
+
+    <example>
+      # Turn on caching<br />
+      CacheSocache shmcb<br />
+      CacheSocacheMaxSize 102400<br />
+      &lt;Location /foo&gt;<br />
+        <indent>
+        CacheEnable socache<br />
+        </indent>
+      &lt;/Location&gt;<br />
+      <br />
+      # Fall back to the disk cache<br />
+      CacheSocache shmcb<br />
+      CacheSocacheMaxSize 102400<br />
+      &lt;Location /foo&gt;<br />
+        <indent>
+        CacheEnable socache<br />
+        CacheEnable disk<br />
+        </indent>
+      &lt;/Location&gt;<br />
+    </example>
+
+    <note><title>Note:</title>
+      <p><module>mod_cache_socache</module> requires the services of
+      <module>mod_cache</module>, which must be loaded before
+      mod_cache_socache.</p>
+    </note>
+</summary>
+<seealso><module>mod_cache</module></seealso>
+<seealso><module>mod_cache_disk</module></seealso>
+<seealso><a href="../caching.html">Caching Guide</a></seealso>
+
+<directivesynopsis>
+<name>CacheSocache</name>
+<description>The directory root under which cache files are
+stored</description>
+<syntax>CacheSocache <var>type[:args]</var></syntax>
+<contextlist><context>server config</context><context>virtual host</context>
+</contextlist>
+<compatibility>Available in Apache 2.5 and later</compatibility>
+
+<usage>
+    <p>The <directive>CacheSocache</directive> directive defines the name of
+    the shared object cache implementation to use, followed by optional
+    arguments for that implementation. A <a href="../socache.html">number of
+    implementations</a> of shared object caches are available to choose
+    from.</p>
+
+    <example>
+      CacheSocache shmcb
+    </example>
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>CacheSocacheMaxTime</name>
+<description>The maximum time (in seconds) for a document to be placed in the
+cache</description>
+<syntax>CacheSocacheMaxTime <var>seconds</var></syntax>
+<default>CacheSocacheMaxTime 86400</default>
+<contextlist><context>server config</context>
+  <context>virtual host</context>
+  <context>directory</context>
+  <context>.htaccess</context>
+</contextlist>
+<compatibility>Available in Apache 2.5 and later</compatibility>
+
+<usage>
+    <p>The <directive>CacheSocacheMaxTime</directive> directive sets the
+    maximum freshness lifetime, in seconds, for a document to be stored in
+    the cache. This value overrides the freshness lifetime defined for the
+    document by the HTTP protocol.</p>
+
+    <example>
+      CacheSocacheMaxTime 86400
+    </example>
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>CacheSocacheMinTime</name>
+<description>The maximum time (in seconds) for a document to be placed in the
+cache</description>
+<syntax>CacheSocacheMinTime <var>seconds</var></syntax>
+<default>CacheSocacheMinTime 600</default>
+<contextlist><context>server config</context>
+  <context>virtual host</context>
+  <context>directory</context>
+  <context>.htaccess</context>
+</contextlist>
+<compatibility>Available in Apache 2.5 and later</compatibility>
+
+<usage>
+    <p>The <directive>CacheSocacheMinTime</directive> directive sets the
+    amount of seconds beyond the freshness lifetime of the response that the
+    response should be cached for in the shared object cache. If a response is
+    only stored for its freshness lifetime, there will be no opportunity to
+    revalidate the response to make it fresh again.</p>
+
+    <example>
+      CacheSocacheMinTime 600
+    </example>
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>CacheSocacheMaxSize</name>
+<description>The maximum size (in bytes) of an entry to be placed in the
+cache</description>
+<syntax>CacheSocacheMaxSize <var>bytes</var></syntax>
+<default>CacheSocacheMaxSize 102400</default>
+<contextlist><context>server config</context>
+  <context>virtual host</context>
+  <context>directory</context>
+  <context>.htaccess</context>
+</contextlist>
+<compatibility>Available in Apache 2.5 and later</compatibility>
+
+<usage>
+    <p>The <directive>CacheSocacheMaxSize</directive> directive sets the
+    maximum size, in bytes, for the combined headers and body of a document
+    to be considered for storage in the cache. The larger the headers that
+    are stored alongside the body, the smaller the body may be.</p>
+
+    <p>The <module>mod_cache_socache</module> module will only attempt to
+    cache responses that have an explicit content length, or that are small
+    enough to be written in one pass. This is done to allow the
+    <module>mod_cache_disk</module> module to have an opportunity to cache
+    responses larger than those cacheable within
+    <module>mod_cache_socache</module>.</p>
+
+    <example>
+      CacheSocacheMaxSize 102400
+    </example>
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>CacheSocacheReadSize</name>
+<description>The minimum size (in bytes) of the document to read and be cached
+  before sending the data downstream</description>
+<syntax>CacheSocacheReadSize <var>bytes</var></syntax>
+<default>CacheSocacheReadSize 0</default>
+<contextlist><context>server config</context>
+    <context>virtual host</context>
+    <context>directory</context>
+    <context>.htaccess</context>
+</contextlist>
+<compatibility>Available in Apache 2.5 and later</compatibility>
+
+<usage>
+    <p>The <directive>CacheSocacheReadSize</directive> directive sets the
+    minimum amount of data, in bytes, to be read from the backend before the
+    data is sent to the client. The default of zero causes all data read of
+    any size to be passed downstream to the client immediately as it arrives.
+    Setting this to a higher value causes the disk cache to buffer at least
+    this amount before sending the result to the client. This can improve
+    performance when caching content from a slow reverse proxy.</p>
+
+    <p>This directive only takes effect when the data is being saved to the
+    cache, as opposed to data being served from the cache.</p>
+
+    <example>
+      CacheReadSize 102400
+    </example>
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>CacheSocacheReadTime</name>
+<description>The minimum time (in milliseconds) that should elapse while reading
+  before data is sent downstream</description>
+<syntax>CacheSocacheReadTime <var>milliseconds</var></syntax>
+<default>CacheSocacheReadTime 0</default>
+<contextlist><context>server config</context>
+  <context>virtual host</context>
+  <context>directory</context>
+  <context>.htaccess</context>
+</contextlist>
+<compatibility>Available in Apache 2.5 and later</compatibility>
+
+<usage>
+    <p>The <directive>CacheSocacheReadTime</directive> directive sets the minimum amount
+    of elapsed time that should pass before making an attempt to send data
+    downstream to the client. During the time period, data will be buffered
+    before sending the result to the client. This can improve performance when
+    caching content from a reverse proxy.</p>
+
+    <p>The default of zero disables this option.</p>
+
+    <p>This directive only takes effect when the data is being saved to the
+    cache, as opposed to data being served from the cache. It is recommended
+    that this option be used alongside the
+    <directive module="mod_cache_socache">CacheSocacheReadSize</directive> directive
+    to ensure that the server does not buffer excessively should data arrive faster
+    than expected.</p>
+
+    <example>
+      CacheSocacheReadTime 1000
+    </example>
+</usage>
+</directivesynopsis>
+
+</modulesynopsis>
Index: modules/cache/mod_cache_socache.c
===================================================================
--- modules/cache/mod_cache_socache.c	(revision 0)
+++ modules/cache/mod_cache_socache.c	(revision 0)
@@ -0,0 +1,1494 @@
+/* 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(XXXXX)
+                        "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) */
+    // FIXME: but we could
+    if (r->status == HTTP_PARTIAL_CONTENT) {
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(XXXXX)
+                "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(XXXXX)
+                "URL '%s' had no explicit size, ignoring", key);
+        return DECLINED;
+    }
+    if (len > dconf->max) {
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(XXXXX)
+                "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(XXXXX)
+                "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(XXXXX)
+                "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;
+
+    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(XXXXX)
+                    "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(XXXXX)
+                    "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(XXXXX)
+                "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(XXXXX)
+                "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(XXXXX)
+                    "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(XXXXX)
+                        "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(XXXXX)
+                        "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(XXXXX)
+                    "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(XXXXX)
+                    "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(XXXXX)
+                "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(XXXXX)
+                "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(XXXXX)
+                    "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(XXXXX)
+                "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(XXXXX)
+                "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(XXXXX)
+                "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(XXXXX)
+                "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(XXXXX)
+                    "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(XXXXX)
+                    "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(XXXXX)
+                    "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(XXXXX)
+                    "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 != store_array(varray, sobj->buffer,
+                    sobj->buffer_len, &slider)) {
+                ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(XXXXX)
+                        "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(XXXXX)
+                            "could not acquire lock, ignoring: %s", obj->key);
+                    apr_pool_destroy(sobj->pool);
+                    sobj->pool = NULL;
+                    return DECLINED;
+                }
+            }
+            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) {
+                rv = apr_global_mutex_unlock(socache_mutex);
+                if (rv != APR_SUCCESS) {
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(XXXXX)
+                            "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_DEBUG, rv, r, APLOGNO(XXXXX)
+                        "Vary not written to cache, ignoring: %s", obj->key);
+                apr_pool_destroy(sobj->pool);
+                sobj->pool = NULL;
+                return DECLINED;
+            }
+
+            obj->key = sobj->key = regen_key(r->pool, sobj->headers_in, varray,
+                    sobj->name);
+        }
+    }
+
+    socache_info->format = CACHE_SOCACHE_DISK_FORMAT_VERSION;
+    socache_info->date = obj->info.date;
+    socache_info->expire = obj->info.expire;
+    socache_info->entity_version = sobj->socache_info.entity_version++;
+    socache_info->request_time = obj->info.request_time;
+    socache_info->response_time = obj->info.response_time;
+    socache_info->status = obj->info.status;
+    socache_info->header_only = r->header_only;
+
+    socache_info->name_len = strlen(sobj->name);
+
+    memcpy(&socache_info->control, &obj->info.control, sizeof(cache_control_t));
+    slider = sizeof(cache_socache_info_t);
+
+    if (slider + socache_info->name_len >= sobj->buffer_len) {
+        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(XXXXX)
+                "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(XXXXX)
+                    "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. */
+    /* FIXME: 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(XXXXX)
+                    "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(XXXXX)
+                    "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(XXXXX)
+                    "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(XXXXX)
+                    "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(XXXXX)
+                    "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(XXXXX)
+                        "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(XXXXX)
+                "could not flatten brigade, not caching: %s",
+                sobj->key);
+        goto fail;
+    }
+    apr_brigade_cleanup(sobj->body);
+    if (len >= sobj->buffer_len - sobj->body_offset) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(XXXXX)
+                "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(XXXXX)
+                    "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(XXXXX)
+                    "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(XXXXX)
+                "could not write to cache, ignoring: %s", sobj->key);
+        goto fail;
+    }
+
+    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(XXXXX)
+            "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(XXXXX)
+                    "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(XXXXX)
+                    "could not release lock, ignoring: %s", obj->key);
+        }
+    }
+
+    apr_pool_destroy(sobj->pool);
+    sobj->pool = NULL;
+    return rv;
+}
+
+static apr_status_t invalidate_entity(cache_handle_t *h, request_rec *r)
+{
+    return APR_ENOTIMPL;
+}
+
+static void *create_dir_config(apr_pool_t *p, char *dummy)
+{
+    cache_socache_dir_conf *dconf =
+            apr_pcalloc(p, sizeof(cache_socache_dir_conf));
+
+    dconf->max = DEFAULT_MAX_FILE_SIZE;
+    dconf->maxtime = apr_time_from_sec(DEFAULT_MAXTIME);
+    dconf->mintime = apr_time_from_sec(DEFAULT_MINTIME);
+    dconf->readsize = DEFAULT_READSIZE;
+    dconf->readtime = DEFAULT_READTIME;
+
+    return dconf;
+}
+
+static void *merge_dir_config(apr_pool_t *p, void *basev, void *addv)
+{
+    cache_socache_dir_conf
+            *new =
+                    (cache_socache_dir_conf *) apr_pcalloc(p, sizeof(cache_socache_dir_conf));
+    cache_socache_dir_conf *add = (cache_socache_dir_conf *) addv;
+    cache_socache_dir_conf *base = (cache_socache_dir_conf *) basev;
+
+    new->max = (add->max_set == 0) ? base->max : add->max;
+    new->max_set = add->max_set || base->max_set;
+    new->maxtime = (add->maxtime_set == 0) ? base->maxtime : add->maxtime;
+    new->maxtime_set = add->maxtime_set || base->maxtime_set;
+    new->mintime = (add->mintime_set == 0) ? base->mintime : add->mintime;
+    new->mintime_set = add->mintime_set || base->mintime_set;
+    new->readsize = (add->readsize_set == 0) ? base->readsize : add->readsize;
+    new->readsize_set = add->readsize_set || base->readsize_set;
+    new->readtime = (add->readtime_set == 0) ? base->readtime : add->readtime;
+    new->readtime_set = add->readtime_set || base->readtime_set;
+
+    return new;
+}
+
+static void *create_config(apr_pool_t *p, server_rec *s)
+{
+    cache_socache_conf *conf = apr_pcalloc(p, sizeof(cache_socache_conf));
+
+    return conf;
+}
+
+static void *merge_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+    cache_socache_conf *ps = apr_pcalloc(p, sizeof(cache_socache_conf));
+    cache_socache_conf *base = (cache_socache_conf *) basev;
+    cache_socache_conf *overrides = (cache_socache_conf *) overridesv;
+
+    ps = overrides ? overrides : base;
+
+    return ps;
+}
+
+/*
+ * mod_cache_socache configuration directives handlers.
+ */
+static const char *set_cache_socache(cmd_parms *cmd, void *in_struct_ptr,
+        const char *arg)
+{
+    cache_socache_conf *conf = ap_get_module_config(cmd->server->module_config,
+            &cache_socache_module);
+    cache_socache_provider_conf *provider = conf->provider
+            = apr_pcalloc(cmd->pool, sizeof(cache_socache_provider_conf));
+
+    const char *err = NULL, *sep, *name;
+
+    /* Argument is of form 'name:args' or just 'name'. */
+    sep = ap_strchr_c(arg, ':');
+    if (sep) {
+        name = apr_pstrmemdup(cmd->pool, arg, sep - arg);
+        sep++;
+        provider->args = sep;
+    }
+    else {
+        name = arg;
+    }
+
+    provider->socache_provider = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP,
+            arg, AP_SOCACHE_PROVIDER_VERSION);
+    if (provider->socache_provider == NULL) {
+        err = apr_psprintf(cmd->pool,
+                "Unknown socache provider '%s'. Maybe you need "
+                    "to load the appropriate socache module "
+                    "(mod_socache_%s?)", name, name);
+    }
+    return err;
+}
+
+static const char *set_cache_max(cmd_parms *parms, void *in_struct_ptr,
+        const char *arg)
+{
+    cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr;
+
+    if (apr_strtoff(&dconf->max, arg, NULL, 10) != APR_SUCCESS || dconf->max
+            < 1024) {
+        return "CacheSocacheMaxSize argument must be a integer representing the max size of a cached entry (headers and body), at least 1024";
+    }
+    return NULL;
+}
+
+static const char *set_cache_maxtime(cmd_parms *parms, void *in_struct_ptr,
+        const char *arg)
+{
+    cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr;
+    apr_off_t seconds;
+
+    if (apr_strtoff(&seconds, arg, NULL, 10) != APR_SUCCESS || seconds < 0) {
+        return "CacheSocacheMaxTime argument must be the maximum amount of time in seconds to cache an entry.";
+    }
+    dconf->maxtime = apr_time_from_sec(seconds);
+    dconf->maxtime_set = 1;
+    return NULL;
+}
+
+static const char *set_cache_mintime(cmd_parms *parms, void *in_struct_ptr,
+        const char *arg)
+{
+    cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr;
+    apr_off_t seconds;
+
+    if (apr_strtoff(&seconds, arg, NULL, 10) != APR_SUCCESS || seconds < 0) {
+        return "CacheSocacheMinTime argument must be the minimum amount of time in seconds to cache an entry.";
+    }
+    dconf->mintime = apr_time_from_sec(seconds);
+    dconf->mintime_set = 1;
+    return NULL;
+}
+
+static const char *set_cache_readsize(cmd_parms *parms, void *in_struct_ptr,
+        const char *arg)
+{
+    cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr;
+
+    if (apr_strtoff(&dconf->readsize, arg, NULL, 10) != APR_SUCCESS
+            || dconf->readsize < 0) {
+        return "CacheSocacheReadSize argument must be a non-negative integer representing the max amount of data to cache in go.";
+    }
+    dconf->readsize_set = 1;
+    return NULL;
+}
+
+static const char *set_cache_readtime(cmd_parms *parms, void *in_struct_ptr,
+        const char *arg)
+{
+    cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr;
+    apr_off_t milliseconds;
+
+    if (apr_strtoff(&milliseconds, arg, NULL, 10) != APR_SUCCESS
+            || milliseconds < 0) {
+        return "CacheSocacheReadTime argument must be a non-negative integer representing the max amount of time taken to cache in go.";
+    }
+    dconf->readtime = apr_time_from_msec(milliseconds);
+    dconf->readtime_set = 1;
+    return NULL;
+}
+
+static apr_status_t remove_lock(void *data)
+{
+    if (socache_mutex) {
+        apr_global_mutex_destroy(socache_mutex);
+        socache_mutex = NULL;
+    }
+    return APR_SUCCESS;
+}
+
+static apr_status_t destroy_cache(void *data)
+{
+    server_rec *s = data;
+    cache_socache_conf *conf =
+            ap_get_module_config(s->module_config, &cache_socache_module);
+    if (conf->provider && conf->provider->socache_instance) {
+        conf->provider->socache_provider->destroy(
+                conf->provider->socache_instance, s);
+        conf->provider->socache_instance = NULL;
+    }
+    return APR_SUCCESS;
+}
+
+static int socache_precfg(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptmp)
+{
+    apr_status_t rv = ap_mutex_register(pconf, cache_socache_id, NULL,
+            APR_LOCK_DEFAULT, 0);
+    if (rv != APR_SUCCESS) {
+        ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(XXXXX)
+        "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(XXXXX)
+                "failed to create %s mutex", cache_socache_id);
+                return 500; /* An HTTP status would be a misnomer! */
+            }
+            apr_pool_cleanup_register(pconf, NULL, remove_lock,
+                    apr_pool_cleanup_null);
+        }
+
+        errmsg = conf->provider->socache_provider->create(
+                &conf->provider->socache_instance, conf->provider->args, ptmp,
+                pconf);
+        if (errmsg) {
+            ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog,
+                    APLOGNO(XXXXX) "%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(XXXXX)
+            "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(XXXXX)
+                "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 */
+};
Index: modules/cache/config.m4
===================================================================
--- modules/cache/config.m4	(revision 1292090)
+++ modules/cache/config.m4	(working copy)
@@ -13,17 +13,20 @@
 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
Index: modules/cache/cache_socache_common.h
===================================================================
--- modules/cache/cache_socache_common.h	(revision 0)
+++ modules/cache/cache_socache_common.h	(revision 0)
@@ -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 */
+/** @} */

Regards,
Graham
--


Mime
View raw message