summaryrefslogtreecommitdiff
path: root/modules/cache/mod_mem_cache.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/cache/mod_mem_cache.c')
-rw-r--r--modules/cache/mod_mem_cache.c1095
1 files changed, 1095 insertions, 0 deletions
diff --git a/modules/cache/mod_mem_cache.c b/modules/cache/mod_mem_cache.c
new file mode 100644
index 00000000..b9c6915d
--- /dev/null
+++ b/modules/cache/mod_mem_cache.c
@@ -0,0 +1,1095 @@
+/* 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.
+ */
+
+/*
+ * Rules for managing obj->refcount:
+ * refcount should be incremented when an object is placed in the cache. Insertion
+ * of an object into the cache and the refcount increment should happen under
+ * protection of the sconf->lock.
+ *
+ * refcount should be decremented when the object is removed from the cache.
+ * Object should be removed from the cache and the refcount decremented while
+ * under protection of the sconf->lock.
+ *
+ * refcount should be incremented when an object is retrieved from the cache
+ * by a worker thread. The retrieval/find operation and refcount increment
+ * should occur under protection of the sconf->lock
+ *
+ * refcount can be atomically decremented w/o protection of the sconf->lock
+ * by worker threads.
+ *
+ * Any object whose refcount drops to 0 should be freed/cleaned up. A refcount
+ * of 0 means the object is not in the cache and no worker threads are accessing
+ * it.
+ */
+#define CORE_PRIVATE
+#include "mod_cache.h"
+#include "cache_pqueue.h"
+#include "cache_cache.h"
+#include "ap_provider.h"
+#include "ap_mpm.h"
+#include "apr_thread_mutex.h"
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if !APR_HAS_THREADS
+#error This module does not currently compile unless you have a thread-capable APR. Sorry!
+#endif
+
+module AP_MODULE_DECLARE_DATA mem_cache_module;
+
+typedef enum {
+ CACHE_TYPE_FILE = 1,
+ CACHE_TYPE_HEAP,
+ CACHE_TYPE_MMAP
+} cache_type_e;
+
+typedef struct {
+ char* hdr;
+ char* val;
+} cache_header_tbl_t;
+
+typedef struct mem_cache_object {
+ cache_type_e type;
+ apr_ssize_t num_header_out;
+ apr_ssize_t num_req_hdrs;
+ cache_header_tbl_t *header_out;
+ cache_header_tbl_t *req_hdrs; /* for Vary negotiation */
+ apr_size_t m_len;
+ void *m;
+ apr_os_file_t fd;
+ apr_int32_t flags; /* File open flags */
+ long priority; /**< the priority of this entry */
+ long total_refs; /**< total number of references this entry has had */
+
+ apr_uint32_t pos; /**< the position of this entry in the cache */
+
+} mem_cache_object_t;
+
+typedef struct {
+ apr_thread_mutex_t *lock;
+ cache_cache_t *cache_cache;
+
+ /* Fields set by config directives */
+ apr_size_t min_cache_object_size; /* in bytes */
+ apr_size_t max_cache_object_size; /* in bytes */
+ apr_size_t max_cache_size; /* in bytes */
+ apr_size_t max_object_cnt;
+ cache_pqueue_set_priority cache_remove_algorithm;
+
+ /* maximum amount of data to buffer on a streamed response where
+ * we haven't yet seen EOS */
+ apr_off_t max_streaming_buffer_size;
+} mem_cache_conf;
+static mem_cache_conf *sconf;
+
+#define DEFAULT_MAX_CACHE_SIZE 100*1024
+#define DEFAULT_MIN_CACHE_OBJECT_SIZE 0
+#define DEFAULT_MAX_CACHE_OBJECT_SIZE 10000
+#define DEFAULT_MAX_OBJECT_CNT 1009
+#define DEFAULT_MAX_STREAMING_BUFFER_SIZE 100000
+#define CACHEFILE_LEN 20
+
+/* Forward declarations */
+static int remove_entity(cache_handle_t *h);
+static apr_status_t store_headers(cache_handle_t *h, request_rec *r, cache_info *i);
+static apr_status_t store_body(cache_handle_t *h, request_rec *r, apr_bucket_brigade *b);
+static apr_status_t recall_headers(cache_handle_t *h, request_rec *r);
+static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb);
+
+static void cleanup_cache_object(cache_object_t *obj);
+
+static long memcache_get_priority(void*a)
+{
+ cache_object_t *obj = (cache_object_t *)a;
+ mem_cache_object_t *mobj = obj->vobj;
+
+ return mobj->priority;
+}
+
+static void memcache_inc_frequency(void*a)
+{
+ cache_object_t *obj = (cache_object_t *)a;
+ mem_cache_object_t *mobj = obj->vobj;
+
+ mobj->total_refs++;
+ mobj->priority = 0;
+}
+
+static void memcache_set_pos(void *a, apr_ssize_t pos)
+{
+ cache_object_t *obj = (cache_object_t *)a;
+ mem_cache_object_t *mobj = obj->vobj;
+
+ apr_atomic_set32(&mobj->pos, pos);
+}
+static apr_ssize_t memcache_get_pos(void *a)
+{
+ cache_object_t *obj = (cache_object_t *)a;
+ mem_cache_object_t *mobj = obj->vobj;
+
+ return apr_atomic_read32(&mobj->pos);
+}
+
+static apr_size_t memcache_cache_get_size(void*a)
+{
+ cache_object_t *obj = (cache_object_t *)a;
+ mem_cache_object_t *mobj = obj->vobj;
+ return mobj->m_len;
+}
+/** callback to get the key of a item */
+static const char* memcache_cache_get_key(void*a)
+{
+ cache_object_t *obj = (cache_object_t *)a;
+ return obj->key;
+}
+/**
+ * memcache_cache_free()
+ * memcache_cache_free is a callback that is only invoked by a thread
+ * running in cache_insert(). cache_insert() runs under protection
+ * of sconf->lock. By the time this function has been entered, the cache_object
+ * has been ejected from the cache. decrement the refcount and if the refcount drops
+ * to 0, cleanup the cache object.
+ */
+static void memcache_cache_free(void*a)
+{
+ cache_object_t *obj = (cache_object_t *)a;
+
+ /* Decrement the refcount to account for the object being ejected
+ * from the cache. If the refcount is 0, free the object.
+ */
+ if (!apr_atomic_dec32(&obj->refcount)) {
+ cleanup_cache_object(obj);
+ }
+}
+/*
+ * functions return a 'negative' score since priority queues
+ * dequeue the object with the highest value first
+ */
+static long memcache_lru_algorithm(long queue_clock, void *a)
+{
+ cache_object_t *obj = (cache_object_t *)a;
+ mem_cache_object_t *mobj = obj->vobj;
+ if (mobj->priority == 0)
+ mobj->priority = queue_clock - mobj->total_refs;
+
+ /*
+ * a 'proper' LRU function would just be
+ * mobj->priority = mobj->total_refs;
+ */
+ return mobj->priority;
+}
+
+static long memcache_gdsf_algorithm(long queue_clock, void *a)
+{
+ cache_object_t *obj = (cache_object_t *)a;
+ mem_cache_object_t *mobj = obj->vobj;
+
+ if (mobj->priority == 0)
+ mobj->priority = queue_clock -
+ (long)(mobj->total_refs*1000 / mobj->m_len);
+
+ return mobj->priority;
+}
+
+static void cleanup_cache_object(cache_object_t *obj)
+{
+ mem_cache_object_t *mobj = obj->vobj;
+
+ /* TODO:
+ * We desperately need a more efficient way of allocating objects. We're
+ * making way too many malloc calls to create a fully populated
+ * cache object...
+ */
+
+ /* Cleanup the cache_object_t */
+ if (obj->key) {
+ free((void*)obj->key);
+ }
+
+ free(obj);
+
+ /* Cleanup the mem_cache_object_t */
+ if (mobj) {
+ if (mobj->type == CACHE_TYPE_HEAP && mobj->m) {
+ free(mobj->m);
+ }
+ if (mobj->type == CACHE_TYPE_FILE && mobj->fd) {
+#ifdef WIN32
+ CloseHandle(mobj->fd);
+#else
+ close(mobj->fd);
+#endif
+ }
+ if (mobj->header_out) {
+ if (mobj->header_out[0].hdr)
+ free(mobj->header_out[0].hdr);
+ free(mobj->header_out);
+ }
+ if (mobj->req_hdrs) {
+ if (mobj->req_hdrs[0].hdr)
+ free(mobj->req_hdrs[0].hdr);
+ free(mobj->req_hdrs);
+ }
+ free(mobj);
+ }
+}
+static apr_status_t decrement_refcount(void *arg)
+{
+ cache_object_t *obj = (cache_object_t *) arg;
+
+ /* If obj->complete is not set, the cache update failed and the
+ * object needs to be removed from the cache then cleaned up.
+ * The garbage collector may have ejected the object from the
+ * cache already, so make sure it is really still in the cache
+ * before attempting to remove it.
+ */
+ if (!obj->complete) {
+ cache_object_t *tobj = NULL;
+ if (sconf->lock) {
+ apr_thread_mutex_lock(sconf->lock);
+ }
+ tobj = cache_find(sconf->cache_cache, obj->key);
+ if (tobj == obj) {
+ cache_remove(sconf->cache_cache, obj);
+ apr_atomic_dec32(&obj->refcount);
+ }
+ if (sconf->lock) {
+ apr_thread_mutex_unlock(sconf->lock);
+ }
+ }
+
+ /* If the refcount drops to 0, cleanup the cache object */
+ if (!apr_atomic_dec32(&obj->refcount)) {
+ cleanup_cache_object(obj);
+ }
+ return APR_SUCCESS;
+}
+static apr_status_t cleanup_cache_mem(void *sconfv)
+{
+ cache_object_t *obj;
+ mem_cache_conf *co = (mem_cache_conf*) sconfv;
+
+ if (!co) {
+ return APR_SUCCESS;
+ }
+ if (!co->cache_cache) {
+ return APR_SUCCESS;
+ }
+
+ if (sconf->lock) {
+ apr_thread_mutex_lock(sconf->lock);
+ }
+ obj = cache_pop(co->cache_cache);
+ while (obj) {
+ /* Iterate over the cache and clean up each unreferenced entry */
+ if (!apr_atomic_dec32(&obj->refcount)) {
+ cleanup_cache_object(obj);
+ }
+ obj = cache_pop(co->cache_cache);
+ }
+
+ /* Cache is empty, free the cache table */
+ cache_free(co->cache_cache);
+
+ if (sconf->lock) {
+ apr_thread_mutex_unlock(sconf->lock);
+ }
+ return APR_SUCCESS;
+}
+/*
+ * TODO: enable directives to be overridden in various containers
+ */
+static void *create_cache_config(apr_pool_t *p, server_rec *s)
+{
+ sconf = apr_pcalloc(p, sizeof(mem_cache_conf));
+
+ sconf->min_cache_object_size = DEFAULT_MIN_CACHE_OBJECT_SIZE;
+ sconf->max_cache_object_size = DEFAULT_MAX_CACHE_OBJECT_SIZE;
+ /* Number of objects in the cache */
+ sconf->max_object_cnt = DEFAULT_MAX_OBJECT_CNT;
+ /* Size of the cache in bytes */
+ sconf->max_cache_size = DEFAULT_MAX_CACHE_SIZE;
+ sconf->cache_cache = NULL;
+ sconf->cache_remove_algorithm = memcache_gdsf_algorithm;
+ sconf->max_streaming_buffer_size = DEFAULT_MAX_STREAMING_BUFFER_SIZE;
+
+ return sconf;
+}
+
+static int create_entity(cache_handle_t *h, cache_type_e type_e,
+ request_rec *r, const char *key, apr_off_t len)
+{
+ cache_object_t *obj, *tmp_obj;
+ mem_cache_object_t *mobj;
+ apr_size_t key_len;
+
+ if (len == -1) {
+ /* Caching a streaming response. Assume the response is
+ * less than or equal to max_streaming_buffer_size. We will
+ * correct all the cache size counters in store_body once
+ * we know exactly know how much we are caching.
+ */
+ len = sconf->max_streaming_buffer_size;
+ }
+
+ /* Note: cache_insert() will automatically garbage collect
+ * objects from the cache if the max_cache_size threshold is
+ * exceeded. This means mod_mem_cache does not need to implement
+ * max_cache_size checks.
+ */
+ if (len < sconf->min_cache_object_size ||
+ len > sconf->max_cache_object_size) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "mem_cache: URL %s failed the size check and will not be cached.",
+ key);
+ return DECLINED;
+ }
+
+ if (type_e == CACHE_TYPE_FILE) {
+ /* CACHE_TYPE_FILE is only valid for local content handled by the
+ * default handler. Need a better way to check if the file is
+ * local or not.
+ */
+ if (!r->filename) {
+ return DECLINED;
+ }
+ }
+
+ /* Allocate and initialize cache_object_t */
+ obj = calloc(1, sizeof(*obj));
+ if (!obj) {
+ return DECLINED;
+ }
+ key_len = strlen(key) + 1;
+ obj->key = malloc(key_len);
+ if (!obj->key) {
+ cleanup_cache_object(obj);
+ return DECLINED;
+ }
+ memcpy((void*)obj->key, key, key_len);
+
+ /* Allocate and init mem_cache_object_t */
+ mobj = calloc(1, sizeof(*mobj));
+ if (!mobj) {
+ cleanup_cache_object(obj);
+ return DECLINED;
+ }
+
+ /* Finish initing the cache object */
+ apr_atomic_set32(&obj->refcount, 1);
+ mobj->total_refs = 1;
+ obj->complete = 0;
+ obj->vobj = mobj;
+ /* Safe cast: We tested < sconf->max_cache_object_size above */
+ mobj->m_len = (apr_size_t)len;
+ mobj->type = type_e;
+
+ /* Place the cache_object_t into the hash table.
+ * Note: Perhaps we should wait to put the object in the
+ * hash table when the object is complete? I add the object here to
+ * avoid multiple threads attempting to cache the same content only
+ * to discover at the very end that only one of them will succeed.
+ * Furthermore, adding the cache object to the table at the end could
+ * open up a subtle but easy to exploit DoS hole: someone could request
+ * a very large file with multiple requests. Better to detect this here
+ * rather than after the cache object has been completely built and
+ * initialized...
+ * XXX Need a way to insert into the cache w/o such coarse grained locking
+ */
+ if (sconf->lock) {
+ apr_thread_mutex_lock(sconf->lock);
+ }
+ tmp_obj = (cache_object_t *) cache_find(sconf->cache_cache, key);
+
+ if (!tmp_obj) {
+ cache_insert(sconf->cache_cache, obj);
+ /* Add a refcount to account for the reference by the
+ * hashtable in the cache. Refcount should be 2 now, one
+ * for this thread, and one for the cache.
+ */
+ apr_atomic_inc32(&obj->refcount);
+ }
+ if (sconf->lock) {
+ apr_thread_mutex_unlock(sconf->lock);
+ }
+
+ if (tmp_obj) {
+ /* This thread collided with another thread loading the same object
+ * into the cache at the same time. Defer to the other thread which
+ * is further along.
+ */
+ cleanup_cache_object(obj);
+ return DECLINED;
+ }
+
+ apr_pool_cleanup_register(r->pool, obj, decrement_refcount,
+ apr_pool_cleanup_null);
+
+ /* Populate the cache handle */
+ h->cache_obj = obj;
+
+ return OK;
+}
+
+static int create_mem_entity(cache_handle_t *h, request_rec *r,
+ const char *key, apr_off_t len)
+{
+ return create_entity(h, CACHE_TYPE_HEAP, r, key, len);
+}
+
+static int create_fd_entity(cache_handle_t *h, request_rec *r,
+ const char *key, apr_off_t len)
+{
+ return create_entity(h, CACHE_TYPE_FILE, r, key, len);
+}
+
+static int open_entity(cache_handle_t *h, request_rec *r, const char *key)
+{
+ cache_object_t *obj;
+
+ /* Look up entity keyed to 'url' */
+ if (sconf->lock) {
+ apr_thread_mutex_lock(sconf->lock);
+ }
+ obj = (cache_object_t *) cache_find(sconf->cache_cache, key);
+ if (obj) {
+ if (obj->complete) {
+ request_rec *rmain=r, *rtmp;
+ apr_atomic_inc32(&obj->refcount);
+ /* cache is worried about overall counts, not 'open' ones */
+ cache_update(sconf->cache_cache, obj);
+
+ /* If this is a subrequest, register the cleanup against
+ * the main request. This will prevent the cache object
+ * from being cleaned up from under the request after the
+ * subrequest is destroyed.
+ */
+ rtmp = r;
+ while (rtmp) {
+ rmain = rtmp;
+ rtmp = rmain->main;
+ }
+ apr_pool_cleanup_register(rmain->pool, obj, decrement_refcount,
+ apr_pool_cleanup_null);
+ }
+ else {
+ obj = NULL;
+ }
+ }
+
+ if (sconf->lock) {
+ apr_thread_mutex_unlock(sconf->lock);
+ }
+
+ if (!obj) {
+ return DECLINED;
+ }
+
+ /* Initialize the cache_handle */
+ h->cache_obj = obj;
+ h->req_hdrs = NULL; /* Pick these up in recall_headers() */
+ return OK;
+}
+
+/* remove_entity()
+ * Notes:
+ * refcount should be at least 1 upon entry to this function to account
+ * for this thread's reference to the object. If the refcount is 1, then
+ * object has been removed from the cache by another thread and this thread
+ * is the last thread accessing the object.
+ */
+static int remove_entity(cache_handle_t *h)
+{
+ cache_object_t *obj = h->cache_obj;
+ cache_object_t *tobj = NULL;
+
+ if (sconf->lock) {
+ apr_thread_mutex_lock(sconf->lock);
+ }
+
+ /* If the entity is still in the cache, remove it and decrement the
+ * refcount. If the entity is not in the cache, do nothing. In both cases
+ * decrement_refcount called by the last thread referencing the object will
+ * trigger the cleanup.
+ */
+ tobj = cache_find(sconf->cache_cache, obj->key);
+ if (tobj == obj) {
+ cache_remove(sconf->cache_cache, obj);
+ apr_atomic_dec32(&obj->refcount);
+ }
+
+ if (sconf->lock) {
+ apr_thread_mutex_unlock(sconf->lock);
+ }
+
+ return OK;
+}
+static apr_status_t serialize_table(cache_header_tbl_t **obj,
+ apr_ssize_t *nelts,
+ apr_table_t *table)
+{
+ const apr_array_header_t *elts_arr = apr_table_elts(table);
+ apr_table_entry_t *elts = (apr_table_entry_t *) elts_arr->elts;
+ apr_ssize_t i;
+ apr_size_t len = 0;
+ apr_size_t idx = 0;
+ char *buf;
+
+ *nelts = elts_arr->nelts;
+ if (*nelts == 0 ) {
+ *obj=NULL;
+ return APR_SUCCESS;
+ }
+ *obj = malloc(sizeof(cache_header_tbl_t) * elts_arr->nelts);
+ if (NULL == *obj) {
+ return APR_ENOMEM;
+ }
+ for (i = 0; i < elts_arr->nelts; ++i) {
+ len += strlen(elts[i].key);
+ len += strlen(elts[i].val);
+ len += 2; /* Extra space for NULL string terminator for key and val */
+ }
+
+ /* Transfer the headers into a contiguous memory block */
+ buf = malloc(len);
+ if (!buf) {
+ *obj = NULL;
+ return APR_ENOMEM;
+ }
+
+ for (i = 0; i < *nelts; ++i) {
+ (*obj)[i].hdr = &buf[idx];
+ len = strlen(elts[i].key) + 1; /* Include NULL terminator */
+ memcpy(&buf[idx], elts[i].key, len);
+ idx+=len;
+
+ (*obj)[i].val = &buf[idx];
+ len = strlen(elts[i].val) + 1;
+ memcpy(&buf[idx], elts[i].val, len);
+ idx+=len;
+ }
+ return APR_SUCCESS;
+}
+static int unserialize_table( cache_header_tbl_t *ctbl,
+ int num_headers,
+ apr_table_t *t )
+{
+ int i;
+
+ for (i = 0; i < num_headers; ++i) {
+ apr_table_addn(t, ctbl[i].hdr, ctbl[i].val);
+ }
+
+ return APR_SUCCESS;
+}
+/* Define request processing hook handlers */
+/* remove_url()
+ * Notes:
+ */
+static int remove_url(cache_handle_t *h, apr_pool_t *p)
+{
+ cache_object_t *obj;
+ int cleanup = 0;
+
+ if (sconf->lock) {
+ apr_thread_mutex_lock(sconf->lock);
+ }
+
+ obj = h->cache_obj;
+ if (obj) {
+ cache_remove(sconf->cache_cache, obj);
+ /* For performance, cleanup cache object after releasing the lock */
+ cleanup = !apr_atomic_dec32(&obj->refcount);
+ }
+ if (sconf->lock) {
+ apr_thread_mutex_unlock(sconf->lock);
+ }
+
+ if (cleanup) {
+ cleanup_cache_object(obj);
+ }
+
+ return OK;
+}
+
+static apr_status_t recall_headers(cache_handle_t *h, request_rec *r)
+{
+ int rc;
+ mem_cache_object_t *mobj = (mem_cache_object_t*) h->cache_obj->vobj;
+
+ h->req_hdrs = apr_table_make(r->pool, mobj->num_req_hdrs);
+ h->resp_hdrs = apr_table_make(r->pool, mobj->num_header_out);
+
+ rc = unserialize_table(mobj->req_hdrs, mobj->num_req_hdrs, h->req_hdrs);
+ rc = unserialize_table(mobj->header_out, mobj->num_header_out,
+ h->resp_hdrs);
+
+ return rc;
+}
+
+static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb)
+{
+ apr_bucket *b;
+ mem_cache_object_t *mobj = (mem_cache_object_t*) h->cache_obj->vobj;
+
+ if (mobj->type == CACHE_TYPE_FILE) {
+ /* CACHE_TYPE_FILE */
+ apr_file_t *file;
+ apr_os_file_put(&file, &mobj->fd, mobj->flags, p);
+ b = apr_bucket_file_create(file, 0, mobj->m_len, p, bb->bucket_alloc);
+ }
+ else {
+ /* CACHE_TYPE_HEAP */
+ b = apr_bucket_immortal_create(mobj->m, mobj->m_len, bb->bucket_alloc);
+ }
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ b = apr_bucket_eos_create(bb->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ return APR_SUCCESS;
+}
+
+
+static apr_status_t store_headers(cache_handle_t *h, request_rec *r, cache_info *info)
+{
+ cache_object_t *obj = h->cache_obj;
+ mem_cache_object_t *mobj = (mem_cache_object_t*) obj->vobj;
+ int rc;
+ apr_table_t *headers_out;
+
+ /*
+ * The cache needs to keep track of the following information:
+ * - Date, LastMod, Version, ReqTime, RespTime, ContentLength
+ * - The original request headers (for Vary)
+ * - The original response headers (for returning with a cached response)
+ * - The body of the message
+ */
+ rc = serialize_table(&mobj->req_hdrs,
+ &mobj->num_req_hdrs,
+ r->headers_in);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
+
+ /* Precompute how much storage we need to hold the headers */
+ headers_out = ap_cache_cacheable_hdrs_out(r->pool, r->headers_out,
+ r->server);
+
+ /* If not set in headers_out, set Content-Type */
+ 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));
+ }
+
+ headers_out = apr_table_overlay(r->pool, headers_out, r->err_headers_out);
+
+ rc = serialize_table(&mobj->header_out, &mobj->num_header_out,
+ headers_out);
+ if (rc != APR_SUCCESS) {
+ return rc;
+ }
+
+ /* Init the info struct */
+ obj->info.status = info->status;
+ if (info->date) {
+ obj->info.date = info->date;
+ }
+ if (info->response_time) {
+ obj->info.response_time = info->response_time;
+ }
+ if (info->request_time) {
+ obj->info.request_time = info->request_time;
+ }
+ if (info->expire) {
+ obj->info.expire = info->expire;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t store_body(cache_handle_t *h, request_rec *r, apr_bucket_brigade *b)
+{
+ apr_status_t rv;
+ cache_object_t *obj = h->cache_obj;
+ cache_object_t *tobj = NULL;
+ mem_cache_object_t *mobj = (mem_cache_object_t*) obj->vobj;
+ apr_read_type_e eblock = APR_BLOCK_READ;
+ apr_bucket *e;
+ char *cur;
+ int eos = 0;
+
+ if (mobj->type == CACHE_TYPE_FILE) {
+ apr_file_t *file = NULL;
+ int fd = 0;
+ int other = 0;
+
+ /* We can cache an open file descriptor if:
+ * - the brigade contains one and only one file_bucket &&
+ * - the brigade is complete &&
+ * - the file_bucket is the last data bucket in the brigade
+ */
+ for (e = APR_BRIGADE_FIRST(b);
+ e != APR_BRIGADE_SENTINEL(b);
+ e = APR_BUCKET_NEXT(e))
+ {
+ if (APR_BUCKET_IS_EOS(e)) {
+ eos = 1;
+ }
+ else if (APR_BUCKET_IS_FILE(e)) {
+ apr_bucket_file *a = e->data;
+ fd++;
+ file = a->fd;
+ }
+ else {
+ other++;
+ }
+ }
+ if (fd == 1 && !other && eos) {
+ apr_file_t *tmpfile;
+ const char *name;
+ /* Open a new XTHREAD handle to the file */
+ apr_file_name_get(&name, file);
+ mobj->flags = ((APR_SENDFILE_ENABLED & apr_file_flags_get(file))
+ | APR_READ | APR_BINARY | APR_XTHREAD | APR_FILE_NOCLEANUP);
+ rv = apr_file_open(&tmpfile, name, mobj->flags,
+ APR_OS_DEFAULT, r->pool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ apr_file_inherit_unset(tmpfile);
+ apr_os_file_get(&(mobj->fd), tmpfile);
+
+ /* Open for business */
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
+ "mem_cache: Cached file: %s with key: %s", name, obj->key);
+ obj->complete = 1;
+ return APR_SUCCESS;
+ }
+
+ /* Content not suitable for fd caching. Cache in-memory instead. */
+ mobj->type = CACHE_TYPE_HEAP;
+ }
+
+ /*
+ * FD cacheing is not enabled or the content was not
+ * suitable for fd caching.
+ */
+ if (mobj->m == NULL) {
+ mobj->m = malloc(mobj->m_len);
+ if (mobj->m == NULL) {
+ return APR_ENOMEM;
+ }
+ obj->count = 0;
+ }
+ cur = (char*) mobj->m + obj->count;
+
+ /* Iterate accross the brigade and populate the cache storage */
+ for (e = APR_BRIGADE_FIRST(b);
+ e != APR_BRIGADE_SENTINEL(b);
+ e = APR_BUCKET_NEXT(e))
+ {
+ const char *s;
+ apr_size_t len;
+
+ if (APR_BUCKET_IS_EOS(e)) {
+ if (mobj->m_len > obj->count) {
+ /* Caching a streamed response. Reallocate a buffer of the
+ * correct size and copy the streamed response into that
+ * buffer */
+ char *buf = malloc(obj->count);
+ if (!buf) {
+ return APR_ENOMEM;
+ }
+ memcpy(buf, mobj->m, obj->count);
+ free(mobj->m);
+ mobj->m = buf;
+
+ /* Now comes the crufty part... there is no way to tell the
+ * cache that the size of the object has changed. We need
+ * to remove the object, update the size and re-add the
+ * object, all under protection of the lock.
+ */
+ if (sconf->lock) {
+ apr_thread_mutex_lock(sconf->lock);
+ }
+ /* Has the object been ejected from the cache?
+ */
+ tobj = (cache_object_t *) cache_find(sconf->cache_cache, obj->key);
+ if (tobj == obj) {
+ /* Object is still in the cache, remove it, update the len field then
+ * replace it under protection of sconf->lock.
+ */
+ cache_remove(sconf->cache_cache, obj);
+ /* For illustration, cache no longer has reference to the object
+ * so decrement the refcount
+ * apr_atomic_dec32(&obj->refcount);
+ */
+ mobj->m_len = obj->count;
+
+ cache_insert(sconf->cache_cache, obj);
+ /* For illustration, cache now has reference to the object, so
+ * increment the refcount
+ * apr_atomic_inc32(&obj->refcount);
+ */
+ }
+ else if (tobj) {
+ /* Different object with the same key found in the cache. Doing nothing
+ * here will cause the object refcount to drop to 0 in decrement_refcount
+ * and the object will be cleaned up.
+ */
+
+ } else {
+ /* Object has been ejected from the cache, add it back to the cache */
+ mobj->m_len = obj->count;
+ cache_insert(sconf->cache_cache, obj);
+ apr_atomic_inc32(&obj->refcount);
+ }
+
+ if (sconf->lock) {
+ apr_thread_mutex_unlock(sconf->lock);
+ }
+ }
+ /* Open for business */
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
+ "mem_cache: Cached url: %s", obj->key);
+ obj->complete = 1;
+ break;
+ }
+ rv = apr_bucket_read(e, &s, &len, eblock);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ if (len) {
+ /* Check for buffer overflow */
+ if ((obj->count + len) > mobj->m_len) {
+ return APR_ENOMEM;
+ }
+ else {
+ memcpy(cur, s, len);
+ cur+=len;
+ obj->count+=len;
+ }
+ }
+ /* This should not fail, but if it does, we are in BIG trouble
+ * cause we just stomped all over the heap.
+ */
+ AP_DEBUG_ASSERT(obj->count <= mobj->m_len);
+ }
+ return APR_SUCCESS;
+}
+/**
+ * Configuration and start-up
+ */
+static int mem_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ int threaded_mpm;
+
+ /* Sanity check the cache configuration */
+ if (sconf->min_cache_object_size >= sconf->max_cache_object_size) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s,
+ "MCacheMaxObjectSize must be greater than MCacheMinObjectSize");
+ return DONE;
+ }
+ if (sconf->max_cache_object_size >= sconf->max_cache_size) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s,
+ "MCacheSize must be greater than MCacheMaxObjectSize");
+ return DONE;
+ }
+ if (sconf->max_streaming_buffer_size > sconf->max_cache_object_size) {
+ /* Issue a notice only if something other than the default config
+ * is being used */
+ if (sconf->max_streaming_buffer_size != DEFAULT_MAX_STREAMING_BUFFER_SIZE &&
+ sconf->max_cache_object_size != DEFAULT_MAX_CACHE_OBJECT_SIZE) {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s,
+ "MCacheMaxStreamingBuffer must be less than or equal to MCacheMaxObjectSize. "
+ "Resetting MCacheMaxStreamingBuffer to MCacheMaxObjectSize.");
+ }
+ sconf->max_streaming_buffer_size = sconf->max_cache_object_size;
+ }
+ if (sconf->max_streaming_buffer_size < sconf->min_cache_object_size) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
+ "MCacheMaxStreamingBuffer must be greater than or equal to MCacheMinObjectSize. "
+ "Resetting MCacheMaxStreamingBuffer to MCacheMinObjectSize.");
+ sconf->max_streaming_buffer_size = sconf->min_cache_object_size;
+ }
+ ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded_mpm);
+ if (threaded_mpm) {
+ apr_thread_mutex_create(&sconf->lock, APR_THREAD_MUTEX_DEFAULT, p);
+ }
+
+ sconf->cache_cache = cache_init(sconf->max_object_cnt,
+ sconf->max_cache_size,
+ memcache_get_priority,
+ sconf->cache_remove_algorithm,
+ memcache_get_pos,
+ memcache_set_pos,
+ memcache_inc_frequency,
+ memcache_cache_get_size,
+ memcache_cache_get_key,
+ memcache_cache_free);
+ apr_pool_cleanup_register(p, sconf, cleanup_cache_mem, apr_pool_cleanup_null);
+
+ if (sconf->cache_cache)
+ return OK;
+
+ return -1;
+
+}
+
+static const char
+*set_max_cache_size(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ apr_size_t val;
+
+ if (sscanf(arg, "%" APR_SIZE_T_FMT, &val) != 1) {
+ return "MCacheSize argument must be an integer representing the max cache size in KBytes.";
+ }
+ sconf->max_cache_size = val*1024;
+ return NULL;
+}
+static const char
+*set_min_cache_object_size(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ apr_size_t val;
+
+ if (sscanf(arg, "%" APR_SIZE_T_FMT, &val) != 1) {
+ return "MCacheMinObjectSize value must be an integer (bytes)";
+ }
+ sconf->min_cache_object_size = val;
+ return NULL;
+}
+static const char
+*set_max_cache_object_size(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ apr_size_t val;
+
+ if (sscanf(arg, "%" APR_SIZE_T_FMT, &val) != 1) {
+ return "MCacheMaxObjectSize value must be an integer (bytes)";
+ }
+ sconf->max_cache_object_size = val;
+ return NULL;
+}
+static const char
+*set_max_object_count(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+ apr_size_t val;
+
+ if (sscanf(arg, "%" APR_SIZE_T_FMT, &val) != 1) {
+ return "MCacheMaxObjectCount value must be an integer";
+ }
+ sconf->max_object_cnt = val;
+ return NULL;
+}
+
+static const char
+*set_cache_removal_algorithm(cmd_parms *parms, void *name, const char *arg)
+{
+ if (strcasecmp("LRU", arg)) {
+ sconf->cache_remove_algorithm = memcache_lru_algorithm;
+ }
+ else {
+ if (strcasecmp("GDSF", arg)) {
+ sconf->cache_remove_algorithm = memcache_gdsf_algorithm;
+ }
+ else {
+ return "currently implemented algorithms are LRU and GDSF";
+ }
+ }
+ return NULL;
+}
+
+static const char *set_max_streaming_buffer(cmd_parms *parms, void *dummy,
+ const char *arg)
+{
+ char *err;
+ if (apr_strtoff(&sconf->max_streaming_buffer_size, arg, &err, 10) || *err) {
+ return "MCacheMaxStreamingBuffer value must be a number";
+ }
+
+ return NULL;
+}
+
+static const command_rec cache_cmds[] =
+{
+ AP_INIT_TAKE1("MCacheSize", set_max_cache_size, NULL, RSRC_CONF,
+ "The maximum amount of memory used by the cache in KBytes"),
+ AP_INIT_TAKE1("MCacheMaxObjectCount", set_max_object_count, NULL, RSRC_CONF,
+ "The maximum number of objects allowed to be placed in the cache"),
+ AP_INIT_TAKE1("MCacheMinObjectSize", set_min_cache_object_size, NULL, RSRC_CONF,
+ "The minimum size (in bytes) of an object to be placed in the cache"),
+ AP_INIT_TAKE1("MCacheMaxObjectSize", set_max_cache_object_size, NULL, RSRC_CONF,
+ "The maximum size (in bytes) of an object to be placed in the cache"),
+ AP_INIT_TAKE1("MCacheRemovalAlgorithm", set_cache_removal_algorithm, NULL, RSRC_CONF,
+ "The algorithm used to remove entries from the cache (default: GDSF)"),
+ AP_INIT_TAKE1("MCacheMaxStreamingBuffer", set_max_streaming_buffer, NULL, RSRC_CONF,
+ "Maximum number of bytes of content to buffer for a streamed response"),
+ {NULL}
+};
+
+static const cache_provider cache_mem_provider =
+{
+ &remove_entity,
+ &store_headers,
+ &store_body,
+ &recall_headers,
+ &recall_body,
+ &create_mem_entity,
+ &open_entity,
+ &remove_url,
+};
+
+static const cache_provider cache_fd_provider =
+{
+ &remove_entity,
+ &store_headers,
+ &store_body,
+ &recall_headers,
+ &recall_body,
+ &create_fd_entity,
+ &open_entity,
+ &remove_url,
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_post_config(mem_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ /* cache initializer */
+ /* cache_hook_init(cache_mem_init, NULL, NULL, APR_HOOK_MIDDLE); */
+ /*
+ cache_hook_create_entity(create_entity, NULL, NULL, APR_HOOK_MIDDLE);
+ cache_hook_open_entity(open_entity, NULL, NULL, APR_HOOK_MIDDLE);
+ cache_hook_remove_url(remove_url, NULL, NULL, APR_HOOK_MIDDLE);
+ */
+ ap_register_provider(p, CACHE_PROVIDER_GROUP, "mem", "0",
+ &cache_mem_provider);
+ ap_register_provider(p, CACHE_PROVIDER_GROUP, "fd", "0",
+ &cache_fd_provider);
+}
+
+module AP_MODULE_DECLARE_DATA mem_cache_module =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ create_cache_config, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+ cache_cmds, /* command apr_table_t */
+ register_hooks
+};