diff options
author | Stefan Fritsch <sf@sfritsch.de> | 2013-07-20 22:21:25 +0200 |
---|---|---|
committer | Stefan Fritsch <sf@sfritsch.de> | 2013-07-20 22:21:25 +0200 |
commit | 4a336a5b117419c33c29eadd6409c69df78cd586 (patch) | |
tree | c9787e4bd0f1be8f471e1883262a695a6c4e954f /modules/cache | |
parent | 717c182588f1eb0b7ef189a709f858b44e348489 (diff) | |
download | apache2-upstream/2.4.6.tar.gz |
Imported Upstream version 2.4.6upstream/2.4.6
Diffstat (limited to 'modules/cache')
-rw-r--r-- | modules/cache/NWGNUcach_socache | 262 | ||||
-rw-r--r-- | modules/cache/NWGNUmakefile | 1 | ||||
-rw-r--r-- | modules/cache/cache_common.h | 1 | ||||
-rw-r--r-- | modules/cache/cache_socache_common.h | 57 | ||||
-rw-r--r-- | modules/cache/cache_storage.c | 394 | ||||
-rw-r--r-- | modules/cache/cache_storage.h | 23 | ||||
-rw-r--r-- | modules/cache/cache_util.c | 365 | ||||
-rw-r--r-- | modules/cache/cache_util.h | 31 | ||||
-rw-r--r-- | modules/cache/config.m4 | 3 | ||||
-rw-r--r-- | modules/cache/mod_cache.c | 393 | ||||
-rw-r--r-- | modules/cache/mod_cache_disk.c | 170 | ||||
-rw-r--r-- | modules/cache/mod_cache_socache.c | 1501 | ||||
-rw-r--r-- | modules/cache/mod_cache_socache.dsp | 115 | ||||
-rw-r--r-- | modules/cache/mod_socache_memcache.c | 8 |
14 files changed, 2908 insertions, 416 deletions
diff --git a/modules/cache/NWGNUcach_socache b/modules/cache/NWGNUcach_socache new file mode 100644 index 00000000..f7ed0e43 --- /dev/null +++ b/modules/cache/NWGNUcach_socache @@ -0,0 +1,262 @@ +# +# Declare the sub-directories to be built here +# + +SUBDIRS = \ + $(EOLIST) + +# +# Get the 'head' of the build environment. This includes default targets and +# paths to tools +# + +include $(AP_WORK)/build/NWGNUhead.inc + +# +# build this level's files +# +# Make sure all needed macro's are defined +# + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(APR)/include \ + $(APRUTIL)/include \ + $(SRC)/include \ + $(SERVER)/mpm/netware \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = cach_socache + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache $(VERSION_STR) Cache Socache Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = cach_socache + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)/build/NWGNUenvironment.inc +# +NLM_VERSION = + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 65536 + + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = + +# +# If this is specified it will be used by the link '-check' directive +# +NLM_CHECK_SYM = + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# If this is specified it will be linked in with the XDCData option in the def +# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled +# by setting APACHE_UNIPROC in the environment +# +XDCDATA = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/$(NLM_NAME).nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_cache_socache.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(PRELUDE) \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + Apache2 \ + Libc \ + mod_cach \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @libc.imp \ + @aprlib.imp \ + @httpd.imp \ + @mod_cache.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + cache_socache_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)/build/NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(APBUILD)/NWGNUtail.inc + + diff --git a/modules/cache/NWGNUmakefile b/modules/cache/NWGNUmakefile index bc0c58fe..e544df62 100644 --- a/modules/cache/NWGNUmakefile +++ b/modules/cache/NWGNUmakefile @@ -154,6 +154,7 @@ XDCDATA = TARGET_nlm = \ $(OBJDIR)/mod_cach.nlm \ $(OBJDIR)/cach_dsk.nlm \ + $(OBJDIR)/cach_socache.nlm \ $(OBJDIR)/socachdbm.nlm \ $(OBJDIR)/socachmem.nlm \ $(OBJDIR)/socachshmcb.nlm \ diff --git a/modules/cache/cache_common.h b/modules/cache/cache_common.h index cedce076..9d56d28b 100644 --- a/modules/cache/cache_common.h +++ b/modules/cache/cache_common.h @@ -45,6 +45,7 @@ typedef struct cache_control { unsigned int must_revalidate:1; unsigned int proxy_revalidate:1; unsigned int s_maxage:1; + unsigned int invalidated:1; /* has this entity been invalidated? */ apr_int64_t max_age_value; /* if positive, then set */ apr_int64_t max_stale_value; /* if positive, then set */ apr_int64_t min_fresh_value; /* if positive, then set */ diff --git a/modules/cache/cache_socache_common.h b/modules/cache/cache_socache_common.h new file mode 100644 index 00000000..3ee3d0da --- /dev/null +++ b/modules/cache/cache_socache_common.h @@ -0,0 +1,57 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file cache_socache_common.h + * @brief Common Shared Object Cache vars/structs + * + * @defgroup Cache_cache Cache Functions + * @ingroup MOD_SOCACHE_CACHE + * @{ + */ + +#ifndef CACHE_SOCACHE_COMMON_H +#define CACHE_SOCACHE_COMMON_H + +#include "apr_time.h" + +#include "cache_common.h" + +#define CACHE_SOCACHE_VARY_FORMAT_VERSION 1 +#define CACHE_SOCACHE_DISK_FORMAT_VERSION 2 + +typedef struct { + /* Indicates the format of the header struct stored on-disk. */ + apr_uint32_t format; + /* The HTTP status code returned for this response. */ + int status; + /* The size of the entity name that follows. */ + apr_size_t name_len; + /* The number of times we've cached this entity. */ + apr_size_t entity_version; + /* Miscellaneous time values. */ + apr_time_t date; + apr_time_t expire; + apr_time_t request_time; + apr_time_t response_time; + /* Does this cached request have a body? */ + unsigned int header_only:1; + /* The parsed cache control header */ + cache_control_t control; +} cache_socache_info_t; + +#endif /* CACHE_SOCACHE_COMMON_H */ +/** @} */ diff --git a/modules/cache/cache_storage.c b/modules/cache/cache_storage.c index 9021ec1d..af60a39b 100644 --- a/modules/cache/cache_storage.c +++ b/modules/cache/cache_storage.c @@ -113,26 +113,69 @@ int cache_create_entity(cache_request_rec *cache, request_rec *r, return DECLINED; } -static int set_cookie_doo_doo(void *v, const char *key, const char *val) +static int filter_header_do(void *v, const char *key, const char *val) +{ + if ((*key == 'W' || *key == 'w') && !strcasecmp(key, "Warning") + && *val == '1') { + /* any stored Warning headers with warn-code 1xx (see section + * 14.46) MUST be deleted from the cache entry and the forwarded + * response. + */ + } + else { + apr_table_addn(v, key, val); + } + return 1; +} +static int remove_header_do(void *v, const char *key, const char *val) +{ + if ((*key == 'W' || *key == 'w') && !strcasecmp(key, "Warning")) { + /* any stored Warning headers with warn-code 2xx MUST be retained + * in the cache entry and the forwarded response. + */ + } + else { + apr_table_unset(v, key); + } + return 1; +} +static int add_header_do(void *v, const char *key, const char *val) { apr_table_addn(v, key, val); return 1; } /** - * Take headers from the cache, and overlap them over the existing response - * headers. + * Take two sets of headers, sandwich them together, and apply the result to + * r->headers_out. + * + * To complicate this, a header may be duplicated in either table. Should a + * header exist in the top table, all matching headers will be removed from + * the bottom table before the headers are combined. The Warning headers are + * handled specially. Warnings are added rather than being replaced, while + * in the case of revalidation 1xx Warnings are stripped. + * + * The Content-Type and Last-Modified headers are then re-parsed and inserted + * into the request. */ -void cache_accept_headers(cache_handle_t *h, request_rec *r, - int preserve_orig) +void cache_accept_headers(cache_handle_t *h, request_rec *r, apr_table_t *top, + apr_table_t *bottom, int revalidation) { - apr_table_t *cookie_table, *hdr_copy; const char *v; - v = apr_table_get(h->resp_hdrs, "Content-Type"); + if (revalidation) { + r->headers_out = apr_table_make(r->pool, 10); + apr_table_do(filter_header_do, r->headers_out, bottom, NULL); + } + else if (r->headers_out != bottom) { + r->headers_out = apr_table_copy(r->pool, bottom); + } + apr_table_do(remove_header_do, r->headers_out, top, NULL); + apr_table_do(add_header_do, r->headers_out, top, NULL); + + v = apr_table_get(r->headers_out, "Content-Type"); if (v) { ap_set_content_type(r, v); - apr_table_unset(h->resp_hdrs, "Content-Type"); /* * Also unset possible Content-Type headers in r->headers_out and * r->err_headers_out as they may be different to what we have received @@ -149,39 +192,12 @@ void cache_accept_headers(cache_handle_t *h, request_rec *r, /* If the cache gave us a Last-Modified header, we can't just * pass it on blindly because of restrictions on future values. */ - v = apr_table_get(h->resp_hdrs, "Last-Modified"); + v = apr_table_get(r->headers_out, "Last-Modified"); if (v) { ap_update_mtime(r, apr_date_parse_http(v)); ap_set_last_modified(r); - apr_table_unset(h->resp_hdrs, "Last-Modified"); } - /* The HTTP specification says that it is legal to merge duplicate - * headers into one. Some browsers that support Cookies don't like - * merged headers and prefer that each Set-Cookie header is sent - * separately. Lets humour those browsers by not merging. - * Oh what a pain it is. - */ - cookie_table = apr_table_make(r->pool, 2); - apr_table_do(set_cookie_doo_doo, cookie_table, r->err_headers_out, - "Set-Cookie", NULL); - apr_table_do(set_cookie_doo_doo, cookie_table, h->resp_hdrs, - "Set-Cookie", NULL); - apr_table_unset(r->err_headers_out, "Set-Cookie"); - apr_table_unset(h->resp_hdrs, "Set-Cookie"); - - if (preserve_orig) { - hdr_copy = apr_table_copy(r->pool, h->resp_hdrs); - apr_table_overlap(hdr_copy, r->headers_out, APR_OVERLAP_TABLES_SET); - r->headers_out = hdr_copy; - } - else { - apr_table_overlap(r->headers_out, h->resp_hdrs, APR_OVERLAP_TABLES_SET); - } - if (!apr_is_empty_table(cookie_table)) { - r->err_headers_out = apr_table_overlay(r->pool, r->err_headers_out, - cookie_table); - } } /* @@ -209,6 +225,13 @@ int cache_select(cache_request_rec *cache, request_rec *r) return DECLINED; } + /* if no-cache, we can't serve from the cache, but we may store to the + * cache. + */ + if (!ap_cache_check_no_cache(cache, r)) { + return DECLINED; + } + if (!cache->key) { rv = cache_generate_key(r, r->pool, &cache->key); if (rv != APR_SUCCESS) { @@ -216,10 +239,6 @@ int cache_select(cache_request_rec *cache, request_rec *r) } } - if (!ap_cache_check_allowed(cache, r)) { - return DECLINED; - } - /* go through the cache types till we get a match */ h = apr_palloc(r->pool, sizeof(cache_handle_t)); @@ -229,7 +248,8 @@ int cache_select(cache_request_rec *cache, request_rec *r) switch ((rv = list->provider->open_entity(h, r, cache->key))) { case OK: { char *vary = NULL; - int fresh, mismatch = 0; + int mismatch = 0; + char *last = NULL; if (list->provider->recall_headers(h, r) != APR_SUCCESS) { /* try again with next cache type */ @@ -255,25 +275,19 @@ int cache_select(cache_request_rec *cache, request_rec *r) * * RFC2616 13.6 and 14.44 describe the Vary mechanism. */ - vary = apr_pstrdup(r->pool, apr_table_get(h->resp_hdrs, "Vary")); - while (vary && *vary) { - char *name = vary; + vary = cache_strqtok( + apr_pstrdup(r->pool, + cache_table_getm(r->pool, h->resp_hdrs, "Vary")), + CACHE_SEPARATOR, &last); + while (vary) { const char *h1, *h2; - /* isolate header name */ - while (*vary && !apr_isspace(*vary) && (*vary != ',')) - ++vary; - while (*vary && (apr_isspace(*vary) || (*vary == ','))) { - *vary = '\0'; - ++vary; - } - /* * is this header in the request and the header in the cached * request identical? If not, we give up and do a straight get */ - h1 = apr_table_get(r->headers_in, name); - h2 = apr_table_get(h->req_hdrs, name); + h1 = cache_table_getm(r->pool, r->headers_in, vary); + h2 = cache_table_getm(r->pool, h->req_hdrs, vary); if (h1 == h2) { /* both headers NULL, so a match - do nothing */ } @@ -283,9 +297,11 @@ int cache_select(cache_request_rec *cache, request_rec *r) else { /* headers do not match, so Vary failed */ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, - r, APLOGNO(00694) "cache_select_url(): Vary header mismatch."); + r, APLOGNO(00694) "cache_select(): Vary header mismatch."); mismatch = 1; + break; } + vary = cache_strqtok(NULL, CACHE_SEPARATOR, &last); } /* no vary match, try next provider */ @@ -298,9 +314,27 @@ int cache_select(cache_request_rec *cache, request_rec *r) cache->provider = list->provider; cache->provider_name = list->provider_name; + /* + * RFC2616 13.3.4 Rules for When to Use Entity Tags and Last-Modified + * Dates: An HTTP/1.1 caching proxy, upon receiving a conditional request + * that includes both a Last-Modified date and one or more entity tags as + * cache validators, MUST NOT return a locally cached response to the + * client unless that cached response is consistent with all of the + * conditional header fields in the request. + */ + if (ap_condition_if_match(r, h->resp_hdrs) == AP_CONDITION_NOMATCH + || ap_condition_if_unmodified_since(r, h->resp_hdrs) + == AP_CONDITION_NOMATCH + || ap_condition_if_none_match(r, h->resp_hdrs) + == AP_CONDITION_NOMATCH + || ap_condition_if_modified_since(r, h->resp_hdrs) + == AP_CONDITION_NOMATCH + || ap_condition_if_range(r, h->resp_hdrs) == AP_CONDITION_NOMATCH) { + mismatch = 1; + } + /* Is our cached response fresh enough? */ - fresh = cache_check_freshness(h, cache, r); - if (!fresh) { + if (mismatch || !cache_check_freshness(h, cache, r)) { const char *etag, *lastmod; /* Cache-Control: only-if-cached and revalidation required, try @@ -317,42 +351,45 @@ int cache_select(cache_request_rec *cache, request_rec *r) r->headers_in); cache->stale_handle = h; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00695) - "Cached response for %s isn't fresh. Adding/replacing " - "conditional request headers.", r->uri); + /* if no existing conditionals, use conditionals of our own */ + if (!mismatch) { - /* We can only revalidate with our own conditionals: remove the - * conditions from the original request. - */ - apr_table_unset(r->headers_in, "If-Match"); - apr_table_unset(r->headers_in, "If-Modified-Since"); - apr_table_unset(r->headers_in, "If-None-Match"); - apr_table_unset(r->headers_in, "If-Range"); - apr_table_unset(r->headers_in, "If-Unmodified-Since"); - - etag = apr_table_get(h->resp_hdrs, "ETag"); - lastmod = apr_table_get(h->resp_hdrs, "Last-Modified"); - - if (etag || lastmod) { - /* If we have a cached etag and/or Last-Modified add in - * our own conditionals. - */ + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00695) "Cached response for %s isn't fresh. Adding " + "conditional request headers.", r->uri); - if (etag) { - apr_table_set(r->headers_in, "If-None-Match", etag); - } + /* Remove existing conditionals that might conflict with ours */ + apr_table_unset(r->headers_in, "If-Match"); + apr_table_unset(r->headers_in, "If-Modified-Since"); + apr_table_unset(r->headers_in, "If-None-Match"); + apr_table_unset(r->headers_in, "If-Range"); + apr_table_unset(r->headers_in, "If-Unmodified-Since"); - if (lastmod) { - apr_table_set(r->headers_in, "If-Modified-Since", - lastmod); - } + etag = apr_table_get(h->resp_hdrs, "ETag"); + lastmod = apr_table_get(h->resp_hdrs, "Last-Modified"); - /* - * Do not do Range requests with our own conditionals: If - * we get 304 the Range does not matter and otherwise the - * entity changed and we want to have the complete entity - */ - apr_table_unset(r->headers_in, "Range"); + if (etag || lastmod) { + /* If we have a cached etag and/or Last-Modified add in + * our own conditionals. + */ + + if (etag) { + apr_table_set(r->headers_in, "If-None-Match", etag); + } + + if (lastmod) { + apr_table_set(r->headers_in, "If-Modified-Since", + lastmod); + } + + /* + * Do not do Range requests with our own conditionals: If + * we get 304 the Range does not matter and otherwise the + * entity changed and we want to have the complete entity + */ + apr_table_unset(r->headers_in, "Range"); + + } } @@ -361,7 +398,7 @@ int cache_select(cache_request_rec *cache, request_rec *r) } /* Okay, this response looks okay. Merge in our stuff and go. */ - cache_accept_headers(h, r, 0); + cache_accept_headers(h, r, h->resp_hdrs, r->headers_out, 0); cache->handle = h; return OK; @@ -389,14 +426,15 @@ int cache_select(cache_request_rec *cache, request_rec *r) return DECLINED; } -apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, - const char **key) +static apr_status_t cache_canonicalise_key(request_rec *r, apr_pool_t* p, + const char *uri, apr_uri_t *parsed_uri, const char **key) { cache_server_conf *conf; char *port_str, *hn, *lcs; const char *hostname, *scheme; int i; - char *path, *querystring; + const char *path; + char *querystring; if (*key) { /* @@ -410,7 +448,7 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, * option below. */ conf = (cache_server_conf *) ap_get_module_config(r->server->module_config, - &cache_module); + &cache_module); /* * Use the canonical name to improve cache hit rate, but only if this is @@ -436,15 +474,15 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, } else { /* Use _default_ as the hostname if none present, as in mod_vhost */ - hostname = ap_get_server_name(r); + hostname = ap_get_server_name(r); if (!hostname) { hostname = "_default_"; } } } - else if(r->parsed_uri.hostname) { + else if (parsed_uri->hostname) { /* Copy the parsed uri hostname */ - hn = apr_pstrdup(p, r->parsed_uri.hostname); + hn = apr_pstrdup(p, parsed_uri->hostname); ap_str_tolower(hn); /* const work-around */ hostname = hn; @@ -463,9 +501,9 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, * "no proxy request" and "reverse proxy request" are handled in the same * manner (see above why this is needed). */ - if (r->proxyreq && r->parsed_uri.scheme) { + if (r->proxyreq && parsed_uri->scheme) { /* Copy the scheme and lower-case it */ - lcs = apr_pstrdup(p, r->parsed_uri.scheme); + lcs = apr_pstrdup(p, parsed_uri->scheme); ap_str_tolower(lcs); /* const work-around */ scheme = lcs; @@ -488,11 +526,11 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, * server. */ if (r->proxyreq && (r->proxyreq != PROXYREQ_REVERSE)) { - if (r->parsed_uri.port_str) { - port_str = apr_pcalloc(p, strlen(r->parsed_uri.port_str) + 2); + if (parsed_uri->port_str) { + port_str = apr_pcalloc(p, strlen(parsed_uri->port_str) + 2); port_str[0] = ':'; - for (i = 0; r->parsed_uri.port_str[i]; i++) { - port_str[i + 1] = apr_tolower(r->parsed_uri.port_str[i]); + for (i = 0; parsed_uri->port_str[i]; i++) { + port_str[i + 1] = apr_tolower(parsed_uri->port_str[i]); } } else if (apr_uri_port_of_scheme(scheme)) { @@ -524,26 +562,26 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, * Check if we need to ignore session identifiers in the URL and do so * if needed. */ - path = r->uri; - querystring = r->parsed_uri.query; + path = uri; + querystring = parsed_uri->query; if (conf->ignore_session_id->nelts) { int i; char **identifier; - identifier = (char **)conf->ignore_session_id->elts; + identifier = (char **) conf->ignore_session_id->elts; for (i = 0; i < conf->ignore_session_id->nelts; i++, identifier++) { int len; - char *param; + const char *param; len = strlen(*identifier); /* * Check that we have a parameter separator in the last segment * of the path and that the parameter matches our identifier */ - if ((param = strrchr(path, ';')) - && !strncmp(param + 1, *identifier, len) - && (*(param + len + 1) == '=') - && !strchr(param + len + 2, '/')) { + if ((param = ap_strrchr_c(path, ';')) + && !strncmp(param + 1, *identifier, len) + && (*(param + len + 1) == '=') + && !ap_strchr_c(param + len + 2, '/')) { path = apr_pstrndup(p, path, param - path); continue; } @@ -556,7 +594,7 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, * querystring and followed by a '=' */ if (!strncmp(querystring, *identifier, len) - && (*(querystring + len) == '=')) { + && (*(querystring + len) == '=')) { param = querystring; } else { @@ -574,18 +612,19 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, } } if (param) { - char *amp; + const char *amp; if (querystring != param) { querystring = apr_pstrndup(p, querystring, - param - querystring); + param - querystring); } else { querystring = ""; } - if ((amp = strchr(param + len + 1, '&'))) { - querystring = apr_pstrcat(p, querystring, amp + 1, NULL); + if ((amp = ap_strchr_c(param + len + 1, '&'))) { + querystring = apr_pstrcat(p, querystring, amp + 1, + NULL); } else { /* @@ -605,12 +644,12 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, /* Key format is a URI, optionally without the query-string */ if (conf->ignorequerystring) { - *key = apr_pstrcat(p, scheme, "://", hostname, port_str, - path, "?", NULL); + *key = apr_pstrcat(p, scheme, "://", hostname, port_str, path, "?", + NULL); } else { - *key = apr_pstrcat(p, scheme, "://", hostname, port_str, - path, "?", querystring, NULL); + *key = apr_pstrcat(p, scheme, "://", hostname, port_str, path, "?", + querystring, NULL); } /* @@ -621,9 +660,118 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, * resource in the cache under a key where it is never found by the quick * handler during following requests. */ - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00698) - "cache: Key for entity %s?%s is %s", r->uri, - r->parsed_uri.query, *key); + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00698) "cache: Key for entity %s?%s is %s", uri, parsed_uri->query, *key); return APR_SUCCESS; } + +apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, + const char **key) +{ + return cache_canonicalise_key(r, p, r->uri, &r->parsed_uri, key); +} + +/* + * Invalidate a specific URL entity in all caches + * + * All cached entities for this URL are removed, usually in + * response to a POST/PUT or DELETE. + * + * This function returns OK if at least one entity was found and + * removed, and DECLINED if no cached entities were removed. + */ +int cache_invalidate(cache_request_rec *cache, request_rec *r) +{ + cache_provider_list *list; + apr_status_t rv, status = DECLINED; + cache_handle_t *h; + apr_uri_t location_uri; + apr_uri_t content_location_uri; + + const char *location, *location_key = NULL; + const char *content_location, *content_location_key = NULL; + + if (!cache) { + /* This should never happen */ + ap_log_rerror( + APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(00697) "cache: No cache request information available for key" + " generation"); + return DECLINED; + } + + if (!cache->key) { + rv = cache_generate_key(r, r->pool, &cache->key); + if (rv != APR_SUCCESS) { + return DECLINED; + } + } + + location = apr_table_get(r->headers_out, "Location"); + if (location) { + if (APR_SUCCESS != apr_uri_parse(r->pool, location, &location_uri) + || APR_SUCCESS + != cache_canonicalise_key(r, r->pool, location, + &location_uri, &location_key) + || strcmp(r->parsed_uri.hostname, location_uri.hostname)) { + location_key = NULL; + } + } + + content_location = apr_table_get(r->headers_out, "Content-Location"); + if (content_location) { + if (APR_SUCCESS + != apr_uri_parse(r->pool, content_location, + &content_location_uri) + || APR_SUCCESS + != cache_canonicalise_key(r, r->pool, content_location, + &content_location_uri, &content_location_key) + || strcmp(r->parsed_uri.hostname, + content_location_uri.hostname)) { + content_location_key = NULL; + } + } + + /* go through the cache types */ + h = apr_palloc(r->pool, sizeof(cache_handle_t)); + + list = cache->providers; + + while (list) { + + /* invalidate the request uri */ + rv = list->provider->open_entity(h, r, cache->key); + if (OK == rv) { + rv = list->provider->invalidate_entity(h, r); + status = OK; + } + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02468) "cache: Attempted to invalidate cached entity with key: %s", cache->key); + + /* invalidate the Location */ + if (location_key) { + rv = list->provider->open_entity(h, r, location_key); + if (OK == rv) { + rv = list->provider->invalidate_entity(h, r); + status = OK; + } + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02469) "cache: Attempted to invalidate cached entity with key: %s", location_key); + } + + /* invalidate the Content-Location */ + if (content_location_key) { + rv = list->provider->open_entity(h, r, content_location_key); + if (OK == rv) { + rv = list->provider->invalidate_entity(h, r); + status = OK; + } + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02470) "cache: Attempted to invalidate cached entity with key: %s", content_location_key); + } + + list = list->next; + } + + return status; +} diff --git a/modules/cache/cache_storage.h b/modules/cache/cache_storage.h index 2b67970e..83f2946f 100644 --- a/modules/cache/cache_storage.h +++ b/modules/cache/cache_storage.h @@ -40,6 +40,20 @@ int cache_remove_url(cache_request_rec *cache, request_rec *r); int cache_create_entity(cache_request_rec *cache, request_rec *r, apr_off_t size, apr_bucket_brigade *in); int cache_select(cache_request_rec *cache, request_rec *r); + +/** + * invalidate a specific URL entity in all caches + * + * All cached entities for this URL are removed, usually in + * response to a POST/PUT or DELETE. + * + * This function returns OK if at least one entity was found and + * removed, and DECLINED if no cached entities were removed. + * @param cache cache_request_rec + * @param r request_rec + */ +int cache_invalidate(cache_request_rec *cache, request_rec *r); + apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, const char **key); @@ -47,11 +61,12 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, * Merge in cached headers into the response * @param h cache_handle_t * @param r request_rec - * @param preserve_orig If 1, the values in r->headers_out are preserved. - * Otherwise, they are overwritten by the cached value. + * @param top headers to be applied + * @param bottom headers to be overwritten + * @param revalidation true if revalidation is taking place */ -void cache_accept_headers(cache_handle_t *h, request_rec *r, - int preserve_orig); +void cache_accept_headers(cache_handle_t *h, request_rec *r, apr_table_t *top, + apr_table_t *bottom, int revalidation); #ifdef __cplusplus } diff --git a/modules/cache/cache_util.c b/modules/cache/cache_util.c index 1e5098d5..7b7fb45c 100644 --- a/modules/cache/cache_util.c +++ b/modules/cache/cache_util.c @@ -27,8 +27,6 @@ extern APR_OPTIONAL_FN_TYPE(ap_cache_generate_key) *cache_generate_key; extern module AP_MODULE_DECLARE_DATA cache_module; -#define CACHE_SEPARATOR ", " - /* Determine if "url" matches the hostname, scheme and port and path * in "filter". All but the path comparisons are case-insensitive. */ @@ -412,9 +410,9 @@ apr_status_t cache_remove_lock(cache_server_conf *conf, return apr_file_remove(lockname, r->pool); } -CACHE_DECLARE(int) ap_cache_check_allowed(cache_request_rec *cache, request_rec *r) { - const char *cc_req; - const char *pragma; +int ap_cache_check_no_cache(cache_request_rec *cache, request_rec *r) +{ + cache_server_conf *conf = (cache_server_conf *)ap_get_module_config(r->server->module_config, &cache_module); @@ -429,16 +427,15 @@ CACHE_DECLARE(int) ap_cache_check_allowed(cache_request_rec *cache, request_rec * - RFC2616 14.9.4 End to end reload, Cache-Control: no-cache, or Pragma: * no-cache. The server MUST NOT use a cached copy when responding to such * a request. - * - * - RFC2616 14.9.2 What May be Stored by Caches. If Cache-Control: - * no-store arrives, do not serve from the cache. */ /* This value comes from the client's initial request. */ - cc_req = apr_table_get(r->headers_in, "Cache-Control"); - pragma = apr_table_get(r->headers_in, "Pragma"); - - ap_cache_control(r, &cache->control_in, cc_req, pragma, r->headers_in); + if (!cache->control_in.parsed) { + const char *cc_req = cache_table_getm(r->pool, r->headers_in, + "Cache-Control"); + const char *pragma = cache_table_getm(r->pool, r->headers_in, "Pragma"); + ap_cache_control(r, &cache->control_in, cc_req, pragma, r->headers_in); + } if (cache->control_in.no_cache) { @@ -453,6 +450,32 @@ CACHE_DECLARE(int) ap_cache_check_allowed(cache_request_rec *cache, request_rec } } + return 1; +} + +int ap_cache_check_no_store(cache_request_rec *cache, request_rec *r) +{ + + cache_server_conf *conf = + (cache_server_conf *)ap_get_module_config(r->server->module_config, + &cache_module); + + /* + * At this point, we may have data cached, but the request may have + * specified that cached data may not be used in a response. + * + * - RFC2616 14.9.2 What May be Stored by Caches. If Cache-Control: + * no-store arrives, do not serve from or store to the cache. + */ + + /* This value comes from the client's initial request. */ + if (!cache->control_in.parsed) { + const char *cc_req = cache_table_getm(r->pool, r->headers_in, + "Cache-Control"); + const char *pragma = cache_table_getm(r->pool, r->headers_in, "Pragma"); + ap_cache_control(r, &cache->control_in, cc_req, pragma, r->headers_in); + } + if (cache->control_in.no_store) { if (!conf->ignorecachecontrol) { @@ -470,7 +493,6 @@ CACHE_DECLARE(int) ap_cache_check_allowed(cache_request_rec *cache, request_rec return 1; } - int cache_check_freshness(cache_handle_t *h, cache_request_rec *cache, request_rec *r) { @@ -543,12 +565,12 @@ int cache_check_freshness(cache_handle_t *h, cache_request_rec *cache, /* These come from the cached entity. */ if (h->cache_obj->info.control.no_cache - || h->cache_obj->info.control.no_cache_header - || h->cache_obj->info.control.private_header) { + || h->cache_obj->info.control.invalidated) { /* * The cached entity contained Cache-Control: no-cache, or a * no-cache with a header present, or a private with a header - * present, so treat as stale causing revalidation. + * present, or the cached entity has been invalidated in the + * past, so treat as stale causing revalidation. */ return 0; } @@ -858,100 +880,11 @@ CACHE_DECLARE(char *)ap_cache_generate_name(apr_pool_t *p, int dirlevels, return apr_pstrdup(p, hashfile); } -/* - * Create a new table consisting of those elements from an - * headers table that are allowed to be stored in a cache. - */ -CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers(apr_pool_t *pool, - apr_table_t *t, - server_rec *s) -{ - cache_server_conf *conf; - char **header; - int i; - apr_table_t *headers_out; - - /* Short circuit the common case that there are not - * (yet) any headers populated. - */ - if (t == NULL) { - return apr_table_make(pool, 10); - }; - - /* Make a copy of the headers, and remove from - * the copy any hop-by-hop headers, as defined in Section - * 13.5.1 of RFC 2616 - */ - headers_out = apr_table_copy(pool, t); - - apr_table_unset(headers_out, "Connection"); - apr_table_unset(headers_out, "Keep-Alive"); - apr_table_unset(headers_out, "Proxy-Authenticate"); - apr_table_unset(headers_out, "Proxy-Authorization"); - apr_table_unset(headers_out, "TE"); - apr_table_unset(headers_out, "Trailers"); - apr_table_unset(headers_out, "Transfer-Encoding"); - apr_table_unset(headers_out, "Upgrade"); - - conf = (cache_server_conf *)ap_get_module_config(s->module_config, - &cache_module); - - /* Remove the user defined headers set with CacheIgnoreHeaders. - * This may break RFC 2616 compliance on behalf of the administrator. - */ - header = (char **)conf->ignore_headers->elts; - for (i = 0; i < conf->ignore_headers->nelts; i++) { - apr_table_unset(headers_out, header[i]); - } - return headers_out; -} - -/* - * Create a new table consisting of those elements from an input - * headers table that are allowed to be stored in a cache. - */ -CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers_in(request_rec *r) -{ - return ap_cache_cacheable_headers(r->pool, r->headers_in, r->server); -} - -/* - * Create a new table consisting of those elements from an output - * headers table that are allowed to be stored in a cache; - * ensure there is a content type and capture any errors. - */ -CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers_out(request_rec *r) -{ - apr_table_t *headers_out; - - headers_out = apr_table_overlay(r->pool, r->headers_out, - r->err_headers_out); - - apr_table_clear(r->err_headers_out); - - headers_out = ap_cache_cacheable_headers(r->pool, headers_out, - r->server); - - if (!apr_table_get(headers_out, "Content-Type") - && r->content_type) { - apr_table_setn(headers_out, "Content-Type", - ap_make_content_type(r, r->content_type)); - } - - if (!apr_table_get(headers_out, "Content-Encoding") - && r->content_encoding) { - apr_table_setn(headers_out, "Content-Encoding", - r->content_encoding); - } - - return headers_out; -} - /** * String tokenizer that ignores separator characters within quoted strings * and escaped characters, as per RFC2616 section 2.2. */ -static char *cache_strqtok(char *str, const char *sep, char **last) +char *cache_strqtok(char *str, const char *sep, char **last) { char *token; int quoted = 0; @@ -960,6 +893,10 @@ static char *cache_strqtok(char *str, const char *sep, char **last) str = *last; /* start where we left off */ } + if (!str) { /* no more tokens */ + return NULL; + } + /* skip characters in sep (will terminate at '\0') */ while (*str && ap_strchr_c(sep, *str)) { ++str; @@ -979,7 +916,7 @@ static char *cache_strqtok(char *str, const char *sep, char **last) *last = token; while (**last) { if (!quoted) { - if (**last == '\"') { + if (**last == '\"' && !ap_strchr_c(sep, '\"')) { quoted = 1; ++*last; } @@ -1069,9 +1006,7 @@ int ap_cache_control(request_rec *r, cache_control_t *cc, /* ...then try slowest cases */ else if (!strncasecmp(token, "no-cache", 8)) { if (token[8] == '=') { - if (apr_table_get(headers, token + 9)) { - cc->no_cache_header = 1; - } + cc->no_cache_header = 1; } else if (!token[8]) { cc->no_cache = 1; @@ -1146,9 +1081,7 @@ int ap_cache_control(request_rec *r, cache_control_t *cc, } else if (!strncasecmp(token, "private", 7)) { if (token[7] == '=') { - if (apr_table_get(headers, token + 8)) { - cc->private_header = 1; - } + cc->private_header = 1; } else if (!token[7]) { cc->private = 1; @@ -1179,3 +1112,209 @@ int ap_cache_control(request_rec *r, cache_control_t *cc, return (cc_header != NULL || pragma_header != NULL); } + +/** + * Parse the Cache-Control, identifying and removing headers that + * exist as tokens after the no-cache and private tokens. + */ +static int cache_control_remove(request_rec *r, const char *cc_header, + apr_table_t *headers) +{ + char *last, *slast; + int found = 0; + + if (cc_header) { + char *header = apr_pstrdup(r->pool, cc_header); + char *token = cache_strqtok(header, CACHE_SEPARATOR, &last); + while (token) { + switch (token[0]) { + case 'n': + case 'N': { + if (!strncmp(token, "no-cache", 8) + || !strncasecmp(token, "no-cache", 8)) { + if (token[8] == '=') { + const char *header = cache_strqtok(token + 9, + CACHE_SEPARATOR "\"", &slast); + while (header) { + apr_table_unset(headers, header); + header = cache_strqtok(NULL, CACHE_SEPARATOR "\"", + &slast); + } + found = 1; + } + break; + } + break; + } + case 'p': + case 'P': { + if (!strncmp(token, "private", 7) + || !strncasecmp(token, "private", 7)) { + if (token[7] == '=') { + const char *header = cache_strqtok(token + 8, + CACHE_SEPARATOR "\"", &slast); + while (header) { + apr_table_unset(headers, header); + header = cache_strqtok(NULL, CACHE_SEPARATOR "\"", + &slast); + } + found = 1; + } + } + break; + } + } + token = cache_strqtok(NULL, CACHE_SEPARATOR, &last); + } + } + + return found; +} + +/* + * Create a new table consisting of those elements from an + * headers table that are allowed to be stored in a cache. + */ +CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers(apr_pool_t *pool, + apr_table_t *t, + server_rec *s) +{ + cache_server_conf *conf; + char **header; + int i; + apr_table_t *headers_out; + + /* Short circuit the common case that there are not + * (yet) any headers populated. + */ + if (t == NULL) { + return apr_table_make(pool, 10); + }; + + /* Make a copy of the headers, and remove from + * the copy any hop-by-hop headers, as defined in Section + * 13.5.1 of RFC 2616 + */ + headers_out = apr_table_copy(pool, t); + + apr_table_unset(headers_out, "Connection"); + apr_table_unset(headers_out, "Keep-Alive"); + apr_table_unset(headers_out, "Proxy-Authenticate"); + apr_table_unset(headers_out, "Proxy-Authorization"); + apr_table_unset(headers_out, "TE"); + apr_table_unset(headers_out, "Trailers"); + apr_table_unset(headers_out, "Transfer-Encoding"); + apr_table_unset(headers_out, "Upgrade"); + + conf = (cache_server_conf *)ap_get_module_config(s->module_config, + &cache_module); + + /* Remove the user defined headers set with CacheIgnoreHeaders. + * This may break RFC 2616 compliance on behalf of the administrator. + */ + header = (char **)conf->ignore_headers->elts; + for (i = 0; i < conf->ignore_headers->nelts; i++) { + apr_table_unset(headers_out, header[i]); + } + return headers_out; +} + +/* + * Create a new table consisting of those elements from an input + * headers table that are allowed to be stored in a cache. + */ +CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers_in(request_rec *r) +{ + return ap_cache_cacheable_headers(r->pool, r->headers_in, r->server); +} + +/* + * Create a new table consisting of those elements from an output + * headers table that are allowed to be stored in a cache; + * ensure there is a content type and capture any errors. + */ +CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers_out(request_rec *r) +{ + apr_table_t *headers_out; + + headers_out = apr_table_overlay(r->pool, r->headers_out, + r->err_headers_out); + + apr_table_clear(r->err_headers_out); + + headers_out = ap_cache_cacheable_headers(r->pool, headers_out, + r->server); + + cache_control_remove(r, + cache_table_getm(r->pool, headers_out, "Cache-Control"), + headers_out); + + if (!apr_table_get(headers_out, "Content-Type") + && r->content_type) { + apr_table_setn(headers_out, "Content-Type", + ap_make_content_type(r, r->content_type)); + } + + if (!apr_table_get(headers_out, "Content-Encoding") + && r->content_encoding) { + apr_table_setn(headers_out, "Content-Encoding", + r->content_encoding); + } + + return headers_out; +} + +typedef struct +{ + apr_pool_t *p; + const char *first; + apr_array_header_t *merged; +} cache_table_getm_t; + +static int cache_table_getm_do(void *v, const char *key, const char *val) +{ + cache_table_getm_t *state = (cache_table_getm_t *) v; + + if (!state->first) { + /** + * The most common case is a single header, and this is covered by + * a fast path that doesn't allocate any memory. On the second and + * subsequent header, an array is created and the array concatenated + * together to form the final value. + */ + state->first = val; + } + else { + const char **elt; + if (!state->merged) { + state->merged = apr_array_make(state->p, 10, sizeof(const char *)); + elt = apr_array_push(state->merged); + *elt = state->first; + } + elt = apr_array_push(state->merged); + *elt = val; + } + return 1; +} + +const char *cache_table_getm(apr_pool_t *p, const apr_table_t *t, + const char *key) +{ + cache_table_getm_t state; + + state.p = p; + state.first = NULL; + state.merged = NULL; + + apr_table_do(cache_table_getm_do, &state, t, key, NULL); + + if (!state.first) { + return NULL; + } + else if (!state.merged) { + return state.first; + } + else { + return apr_array_pstrcat(p, state.merged, ','); + } +} diff --git a/modules/cache/cache_util.h b/modules/cache/cache_util.h index eec38f3a..3a54fadd 100644 --- a/modules/cache/cache_util.h +++ b/modules/cache/cache_util.h @@ -99,6 +99,7 @@ extern "C" { #define CACHE_LOCKNAME_KEY "mod_cache-lockname" #define CACHE_LOCKFILE_KEY "mod_cache-lockfile" #define CACHE_CTX_KEY "mod_cache-ctx" +#define CACHE_SEPARATOR ", " /** * cache_util.c @@ -238,7 +239,16 @@ typedef struct { * @param r request_rec * @return 0 ==> cache object may not be served, 1 ==> cache object may be served */ -CACHE_DECLARE(int) ap_cache_check_allowed(cache_request_rec *cache, request_rec *r); +int ap_cache_check_no_cache(cache_request_rec *cache, request_rec *r); + +/** + * Check the whether the request allows a cached object to be stored as per RFC2616 + * section 14.9.2 (What May be Stored by Caches) + * @param cache cache_request_rec + * @param r request_rec + * @return 0 ==> cache object may not be served, 1 ==> cache object may be served + */ +int ap_cache_check_no_store(cache_request_rec *cache, request_rec *r); /** * Check the freshness of the cache object per RFC2616 section 13.2 (Expiration Model) @@ -292,6 +302,25 @@ apr_status_t cache_remove_lock(cache_server_conf *conf, cache_provider_list *cache_get_providers(request_rec *r, cache_server_conf *conf, apr_uri_t uri); +/** + * Get a value from a table, where the table may contain multiple + * values for a given key. + * + * When the table contains a single value, that value is returned + * unchanged. + * + * When the table contains two or more values for a key, all values + * for the key are returned, separated by commas. + */ +const char *cache_table_getm(apr_pool_t *p, const apr_table_t *t, + const char *key); + +/** + * String tokenizer that ignores separator characters within quoted strings + * and escaped characters, as per RFC2616 section 2.2. + */ +char *cache_strqtok(char *str, const char *sep, char **last); + #ifdef __cplusplus } #endif diff --git a/modules/cache/config.m4 b/modules/cache/config.m4 index 5647e89b..b9799b76 100644 --- a/modules/cache/config.m4 +++ b/modules/cache/config.m4 @@ -13,17 +13,20 @@ cache_storage.lo dnl cache_util.lo dnl " cache_disk_objs="mod_cache_disk.lo" +cache_socache_objs="mod_cache_socache.lo" case "$host" in *os2*) # OS/2 DLLs must resolve all symbols at build time # and we need some from main cache module cache_disk_objs="$cache_disk_objs mod_cache.la" + cache_socache_objs="$cache_socache_objs mod_cache.la" ;; esac APACHE_MODULE(cache, dynamic file caching. At least one storage management module (e.g. mod_cache_disk) is also necessary., $cache_objs, , most) APACHE_MODULE(cache_disk, disk caching module, $cache_disk_objs, , most, , cache) +APACHE_MODULE(cache_socache, shared object caching module, $cache_socache_objs, , most) dnl dnl APACHE_CHECK_DISTCACHE diff --git a/modules/cache/mod_cache.c b/modules/cache/mod_cache.c index 4f2d3e04..06bdf460 100644 --- a/modules/cache/mod_cache.c +++ b/modules/cache/mod_cache.c @@ -34,6 +34,7 @@ static ap_filter_rec_t *cache_save_subreq_filter_handle; static ap_filter_rec_t *cache_out_filter_handle; static ap_filter_rec_t *cache_out_subreq_filter_handle; static ap_filter_rec_t *cache_remove_url_filter_handle; +static ap_filter_rec_t *cache_invalidate_filter_handle; /* * CACHE handler @@ -75,11 +76,6 @@ static int cache_quick_handler(request_rec *r, int lookup) ap_filter_rec_t *cache_out_handle; cache_server_conf *conf; - /* Delay initialization until we know we are handling a GET */ - if (r->method_number != M_GET) { - return DECLINED; - } - conf = (cache_server_conf *) ap_get_module_config(r->server->module_config, &cache_module); @@ -106,6 +102,9 @@ static int cache_quick_handler(request_rec *r, int lookup) /* * Are we allowed to serve cached info at all? */ + if (!ap_cache_check_no_store(cache, r)) { + return DECLINED; + } /* find certain cache controlling headers */ auth = apr_table_get(r->headers_in, "Authorization"); @@ -117,6 +116,40 @@ static int cache_quick_handler(request_rec *r, int lookup) return DECLINED; } + /* Are we PUT/POST/DELETE? If so, prepare to invalidate the cached entities. + */ + switch (r->method_number) { + case M_PUT: + case M_POST: + case M_DELETE: + { + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02461) + "PUT/POST/DELETE: Adding CACHE_INVALIDATE filter for %s", + r->uri); + + /* Add cache_invalidate filter to this request to force a + * cache entry to be invalidated if the response is + * ultimately successful (2xx). + */ + ap_add_output_filter_handle( + cache_invalidate_filter_handle, cache, r, + r->connection); + + return DECLINED; + } + case M_GET: { + break; + } + default : { + + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02462) "cache: Method '%s' not cacheable by mod_cache, ignoring: %s", r->method, r->uri); + + return DECLINED; + } + } + /* * Try to serve this request from the cache. * @@ -176,9 +209,10 @@ static int cache_quick_handler(request_rec *r, int lookup) * is available later during running the filter may be * different due to an internal redirect. */ - cache->remove_url_filter = - ap_add_output_filter_handle(cache_remove_url_filter_handle, - cache, r, r->connection); + cache->remove_url_filter = ap_add_output_filter_handle( + cache_remove_url_filter_handle, cache, r, + r->connection); + } else { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, @@ -347,11 +381,6 @@ static int cache_handler(request_rec *r) ap_filter_rec_t *cache_save_handle; cache_server_conf *conf; - /* Delay initialization until we know we are handling a GET */ - if (r->method_number != M_GET) { - return DECLINED; - } - conf = (cache_server_conf *) ap_get_module_config(r->server->module_config, &cache_module); @@ -376,6 +405,47 @@ static int cache_handler(request_rec *r) cache->providers = providers; /* + * Are we allowed to serve cached info at all? + */ + if (!ap_cache_check_no_store(cache, r)) { + return DECLINED; + } + + /* Are we PUT/POST/DELETE? If so, prepare to invalidate the cached entities. + */ + switch (r->method_number) { + case M_PUT: + case M_POST: + case M_DELETE: + { + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02463) + "PUT/POST/DELETE: Adding CACHE_INVALIDATE filter for %s", + r->uri); + + /* Add cache_invalidate filter to this request to force a + * cache entry to be invalidated if the response is + * ultimately successful (2xx). + */ + ap_add_output_filter_handle( + cache_invalidate_filter_handle, cache, r, + r->connection); + + return DECLINED; + } + case M_GET: { + break; + } + default : { + + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02464) "cache: Method '%s' not cacheable by mod_cache, ignoring: %s", r->method, r->uri); + + return DECLINED; + } + } + + /* * Try to serve this request from the cache. * * If no existing cache file (DECLINED) @@ -455,9 +525,10 @@ static int cache_handler(request_rec *r) * is available later during running the filter may be * different due to an internal redirect. */ - cache->remove_url_filter = - ap_add_output_filter_handle(cache_remove_url_filter_handle, - cache, r, r->connection); + cache->remove_url_filter + = ap_add_output_filter_handle( + cache_remove_url_filter_handle, cache, r, + r->connection); } else { @@ -665,7 +736,7 @@ static int cache_save_store(ap_filter_t *f, apr_bucket_brigade *in, */ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, f->r, APLOGNO(00766) "cache: Cache provider's store_body returned an " - "empty brigade, but didn't consume all of the" + "empty brigade, but didn't consume all of the " "input brigade, standing down to prevent a spin"); ap_remove_output_filter(f); @@ -682,6 +753,22 @@ static int cache_save_store(ap_filter_t *f, apr_bucket_brigade *in, return rv; } +/** + * Sanity check for 304 Not Modified responses, as per RFC2616 Section 10.3.5. + */ +static const char *cache_header_cmp(apr_pool_t *pool, apr_table_t *left, + apr_table_t *right, const char *key) +{ + const char *h1, *h2; + + if ((h1 = cache_table_getm(pool, left, key)) + && (h2 = cache_table_getm(pool, right, key)) && (strcmp(h1, h2))) { + return apr_pstrcat(pool, "contradiction: 304 Not Modified, but ", key, + " modified", NULL); + } + return NULL; +} + /* * CACHE_SAVE filter * --------------- @@ -715,7 +802,7 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) apr_time_t exp, date, lastmod, now; apr_off_t size = -1; cache_info *info = NULL; - char *reason; + const char *reason; apr_pool_t *p; apr_bucket *e; apr_table_t *headers; @@ -857,12 +944,12 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) if (etag == NULL) { etag = apr_table_get(r->headers_out, "Etag"); } - cc_out = apr_table_get(r->err_headers_out, "Cache-Control"); - pragma = apr_table_get(r->err_headers_out, "Pragma"); + cc_out = cache_table_getm(r->pool, r->err_headers_out, "Cache-Control"); + pragma = cache_table_getm(r->pool, r->err_headers_out, "Pragma"); headers = r->err_headers_out; if (!cc_out && !pragma) { - cc_out = apr_table_get(r->headers_out, "Cache-Control"); - pragma = apr_table_get(r->headers_out, "Pragma"); + cc_out = cache_table_getm(r->pool, r->headers_out, "Cache-Control"); + pragma = cache_table_getm(r->pool, r->headers_out, "Pragma"); headers = r->headers_out; } @@ -871,8 +958,10 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) */ if (r->status == HTTP_NOT_MODIFIED && cache->stale_handle && !cc_out && !pragma) { - cc_out = apr_table_get(cache->stale_handle->resp_hdrs, "Cache-Control"); - pragma = apr_table_get(cache->stale_handle->resp_hdrs, "Pragma"); + cc_out = cache_table_getm(r->pool, cache->stale_handle->resp_hdrs, + "Cache-Control"); + pragma = cache_table_getm(r->pool, cache->stale_handle->resp_hdrs, + "Pragma"); } /* Parse the cache control header */ @@ -1000,78 +1089,117 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) /* or we've been asked not to cache it above */ reason = "r->no_cache present"; } + else if (cache->stale_handle + && APR_DATE_BAD + != (date = apr_date_parse_http( + apr_table_get(r->headers_out, "Date"))) + && date < cache->stale_handle->cache_obj->info.date) { - /* Hold the phone. Some servers might allow us to cache a 2xx, but - * then make their 304 responses non cacheable. This leaves us in a - * sticky position. If the 304 is in answer to our own conditional - * request, we cannot send this 304 back to the client because the - * client isn't expecting it. Instead, our only option is to respect - * the answer to the question we asked (has it changed, answer was - * no) and return the cached item to the client, and then respect - * the uncacheable nature of this 304 by allowing the remove_url - * filter to kick in and remove the cached entity. - */ - if (reason && r->status == HTTP_NOT_MODIFIED && - cache->stale_handle) { - apr_bucket_brigade *bb; - apr_bucket *bkt; - int status; - - cache->handle = cache->stale_handle; - info = &cache->handle->cache_obj->info; - - /* Load in the saved status and clear the status line. */ - r->status = info->status; - r->status_line = NULL; - - bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + /** + * 13.12 Cache Replacement: + * + * Note: a new response that has an older Date header value than + * existing cached responses is not cacheable. + */ + reason = "updated entity is older than cached entity"; - r->headers_in = cache->stale_headers; - status = ap_meets_conditions(r); - if (status != OK) { - r->status = status; + /* while this response is not cacheable, the previous response still is */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00770) + "cache: Removing CACHE_REMOVE_URL filter."); + ap_remove_output_filter(cache->remove_url_filter); + } + else if (r->status == HTTP_NOT_MODIFIED && cache->stale_handle) { + apr_table_t *left = cache->stale_handle->resp_hdrs; + apr_table_t *right = r->headers_out; - bkt = apr_bucket_flush_create(bb->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, bkt); + /* and lastly, contradiction checks for revalidated responses + * as per RFC2616 Section 10.3.5 + */ + if (((reason = cache_header_cmp(r->pool, left, right, "Allow"))) + || ((reason = cache_header_cmp(r->pool, left, right, + "Content-Encoding"))) + || ((reason = cache_header_cmp(r->pool, left, right, + "Content-Language"))) + || ((reason = cache_header_cmp(r->pool, left, right, + "Content-Length"))) + || ((reason = cache_header_cmp(r->pool, left, right, + "Content-Location"))) + || ((reason = cache_header_cmp(r->pool, left, right, + "Content-MD5"))) + || ((reason = cache_header_cmp(r->pool, left, right, + "Content-Range"))) + || ((reason = cache_header_cmp(r->pool, left, right, + "Content-Type"))) + || ((reason = cache_header_cmp(r->pool, left, right, "Expires"))) + || ((reason = cache_header_cmp(r->pool, left, right, "ETag"))) + || ((reason = cache_header_cmp(r->pool, left, right, + "Last-Modified")))) { + /* contradiction: 304 Not Modified, but entity header modified */ } - else { - /* RFC 2616 10.3.5 states that entity headers are not supposed - * to be in the 304 response. Therefore, we need to combine the - * response headers with the cached headers *before* we update - * the cached headers. - * - * However, before doing that, we need to first merge in - * err_headers_out and we also need to strip any hop-by-hop - * headers that might have snuck in. - */ - r->headers_out = ap_cache_cacheable_headers_out(r); - - /* Merge in our cached headers. However, keep any updated values. */ - cache_accept_headers(cache->handle, r, 1); + } - cache->provider->recall_body(cache->handle, r->pool, bb); + /** + * Enforce RFC2616 Section 10.3.5, just in case. We caught any + * inconsistencies above. + * + * If the conditional GET used a strong cache validator (see section + * 13.3.3), the response SHOULD NOT include other entity-headers. + * Otherwise (i.e., the conditional GET used a weak validator), the + * response MUST NOT include other entity-headers; this prevents + * inconsistencies between cached entity-bodies and updated headers. + */ + if (r->status == HTTP_NOT_MODIFIED) { + apr_table_unset(r->headers_out, "Allow"); + apr_table_unset(r->headers_out, "Content-Encoding"); + apr_table_unset(r->headers_out, "Content-Language"); + apr_table_unset(r->headers_out, "Content-Length"); + apr_table_unset(r->headers_out, "Content-MD5"); + apr_table_unset(r->headers_out, "Content-Range"); + apr_table_unset(r->headers_out, "Content-Type"); + apr_table_unset(r->headers_out, "Last-Modified"); + } - bkt = apr_bucket_eos_create(bb->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, bkt); - } + /* Hold the phone. Some servers might allow us to cache a 2xx, but + * then make their 304 responses non cacheable. RFC2616 says this: + * + * If a 304 response indicates an entity not currently cached, then + * the cache MUST disregard the response and repeat the request + * without the conditional. + * + * A 304 response with contradictory headers is technically a + * different entity, to be safe, we remove the entity from the cache. + */ + if (reason && r->status == HTTP_NOT_MODIFIED && cache->stale_handle) { - cache->block_response = 1; + ap_log_rerror( + APLOG_MARK, APLOG_INFO, 0, r, APLOGNO() "cache: %s responded with an uncacheable 304, retrying the request. Reason: %s", r->unparsed_uri, reason); - /* we've got a cache conditional hit! tell anyone who cares */ - cache_run_cache_status( - cache->handle, - r, - r->headers_out, - AP_CACHE_REVALIDATE, - apr_psprintf( - r->pool, - "conditional cache hit: 304 was uncacheable though (%s); entity removed", + /* we've got a cache conditional miss! tell anyone who cares */ + cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS, + apr_psprintf(r->pool, + "conditional cache miss: 304 was uncacheable, entity removed: %s", reason)); + /* remove the cached entity immediately, we might cache it again */ + ap_remove_output_filter(cache->remove_url_filter); + cache_remove_url(cache, r); + /* let someone else attempt to cache */ cache_remove_lock(conf, cache, r, NULL); - return ap_pass_brigade(f->next, bb); + /* remove this filter from the chain */ + ap_remove_output_filter(f); + + /* retry without the conditionals */ + apr_table_unset(r->headers_in, "If-Match"); + apr_table_unset(r->headers_in, "If-Modified-Since"); + apr_table_unset(r->headers_in, "If-None-Match"); + apr_table_unset(r->headers_in, "If-Range"); + apr_table_unset(r->headers_in, "If-Unmodified-Since"); + + ap_internal_redirect(r->uri, r); + + return APR_SUCCESS; } if (reason) { @@ -1186,7 +1314,7 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) if (rv != OK) { /* we've got a cache miss! tell anyone who cares */ cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS, - "cache miss: create_entity failed"); + "cache miss: cache unwilling to store response"); /* Caching layer declined the opportunity to cache the response */ ap_remove_output_filter(f); @@ -1303,9 +1431,6 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) /* We found a stale entry which wasn't really stale. */ if (cache->stale_handle) { - /* Load in the saved status and clear the status line. */ - r->status = info->status; - r->status_line = NULL; /* RFC 2616 10.3.5 states that entity headers are not supposed * to be in the 304 response. Therefore, we need to combine the @@ -1319,7 +1444,9 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) r->headers_out = ap_cache_cacheable_headers_out(r); /* Merge in our cached headers. However, keep any updated values. */ - cache_accept_headers(cache->handle, r, 1); + /* take output, overlay on top of cached */ + cache_accept_headers(cache->handle, r, r->headers_out, + cache->handle->resp_hdrs, 1); } /* Write away header information to cache. It is possible that we are @@ -1342,6 +1469,10 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) apr_bucket *bkt; int status; + /* Load in the saved status and clear the status line. */ + r->status = info->status; + r->status_line = NULL; + /* We're just saving response headers, so we are done. Commit * the response at this point, unless there was a previous error. */ @@ -1483,6 +1614,70 @@ static apr_status_t cache_remove_url_filter(ap_filter_t *f, } /* + * CACHE_INVALIDATE filter + * ----------------------- + * + * This filter gets added in the quick handler should a PUT, POST or DELETE + * method be detected. If the response is successful, we must invalidate any + * cached entity as per RFC2616 section 13.10. + * + * CACHE_INVALIDATE has to be a protocol filter to ensure that is run even if + * the response is a canned error message, which removes the content filters + * from the chain. + * + * CACHE_INVALIDATE expects cache request rec within its context because the + * request this filter runs on can be different from the one whose cache entry + * should be removed, due to internal redirects. + */ +static apr_status_t cache_invalidate_filter(ap_filter_t *f, + apr_bucket_brigade *in) +{ + request_rec *r = f->r; + cache_request_rec *cache; + + /* Setup cache_request_rec */ + cache = (cache_request_rec *) f->ctx; + + if (!cache) { + /* user likely configured CACHE_INVALIDATE manually; they should really + * use mod_cache configuration to do that. So: + * 1. Remove ourselves + * 2. Do nothing and bail out + */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02465) + "cache: CACHE_INVALIDATE enabled unexpectedly: %s", r->uri); + } + else { + + if (r->status > 299) { + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02466) + "cache: response status to '%s' method is %d (>299), not invalidating cached entity: %s", r->method, r->status, r->uri); + + } + else { + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02467) + "cache: Invalidating all cached entities in response to '%s' request for %s", + r->method, r->uri); + + cache_invalidate(cache, r); + + /* we've got a cache invalidate! tell everyone who cares */ + cache_run_cache_status(cache->handle, r, r->headers_out, + AP_CACHE_INVALIDATE, apr_psprintf(r->pool, + "cache invalidated by %s", r->method)); + + } + + } + + /* remove ourselves */ + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, in); +} + +/* * CACHE filter * ------------ * @@ -1579,11 +1774,11 @@ static int cache_status(cache_handle_t *h, request_rec *r, x_cache = conf->x_cache; } if (x_cache) { - apr_table_setn(headers, "X-Cache", - apr_psprintf(r->pool, "%s from %s", - status == AP_CACHE_HIT ? "HIT" : status - == AP_CACHE_REVALIDATE ? "REVALIDATE" : "MISS", - r->server->server_hostname)); + apr_table_setn(headers, "X-Cache", apr_psprintf(r->pool, "%s from %s", + status == AP_CACHE_HIT ? "HIT" + : status == AP_CACHE_REVALIDATE ? "REVALIDATE" : status + == AP_CACHE_INVALIDATE ? "INVALIDATE" : "MISS", + r->server->server_hostname)); } if (dconf && dconf->x_cache_detail_set) { @@ -1640,7 +1835,8 @@ static void cache_insert_error_filter(request_rec *r) if (cache->stale_handle && cache->save_filter && !cache->stale_handle->cache_obj->info.control.must_revalidate - && !cache->stale_handle->cache_obj->info.control.proxy_revalidate) { + && !cache->stale_handle->cache_obj->info.control.proxy_revalidate + && !cache->stale_handle->cache_obj->info.control.s_maxage) { const char *warn_head; cache_server_conf *conf = @@ -1773,7 +1969,7 @@ static void *merge_dir_config(apr_pool_t *p, void *basev, void *addv) { static void * create_cache_config(apr_pool_t *p, server_rec *s) { - const char *tmppath; + const char *tmppath = NULL; cache_server_conf *ps = apr_pcalloc(p, sizeof(cache_server_conf)); /* array of URL prefixes for which caching is enabled */ @@ -2068,7 +2264,7 @@ static const char *add_cache_disable(cmd_parms *parms, void *dummy, &cache_module); if (parms->path) { - if (!strcmp(url, "on")) { + if (!strcasecmp(url, "on")) { dconf->disable = 1; dconf->disable_set = 1; return NULL; @@ -2454,6 +2650,11 @@ static void register_hooks(apr_pool_t *p) cache_remove_url_filter, NULL, AP_FTYPE_PROTOCOL); + cache_invalidate_filter_handle = + ap_register_output_filter("CACHE_INVALIDATE", + cache_invalidate_filter, + NULL, + AP_FTYPE_PROTOCOL); ap_hook_post_config(cache_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST); } diff --git a/modules/cache/mod_cache_disk.c b/modules/cache/mod_cache_disk.c index 8427e8fd..2b50aef9 100644 --- a/modules/cache/mod_cache_disk.c +++ b/modules/cache/mod_cache_disk.c @@ -80,7 +80,7 @@ static char *header_file(apr_pool_t *p, disk_cache_conf *conf, } if (dobj->prefix) { - return apr_pstrcat(p, dobj->prefix, CACHE_VDIR_SUFFIX, "/", + return apr_pstrcat(p, dobj->prefix, CACHE_VDIR_SUFFIX "/", dobj->hashfile, CACHE_HEADER_SUFFIX, NULL); } else { @@ -98,7 +98,7 @@ static char *data_file(apr_pool_t *p, disk_cache_conf *conf, } if (dobj->prefix) { - return apr_pstrcat(p, dobj->prefix, CACHE_VDIR_SUFFIX, "/", + return apr_pstrcat(p, dobj->prefix, CACHE_VDIR_SUFFIX "/", dobj->hashfile, CACHE_DATA_SUFFIX, NULL); } else { @@ -385,6 +385,7 @@ static int create_entity(cache_handle_t *h, request_rec *r, const char *key, apr dobj->root_len = conf->cache_root_len; apr_pool_create(&pool, r->pool); + apr_pool_tag(pool, "mod_cache (create_entity)"); file_cache_create(conf, &dobj->hdrs, pool); file_cache_create(conf, &dobj->vary, pool); @@ -511,6 +512,7 @@ static int open_entity(cache_handle_t *h, request_rec *r, const char *key) dobj->name = key; apr_pool_create(&pool, r->pool); + apr_pool_tag(pool, "mod_cache (open_entity)"); file_cache_create(conf, &dobj->hdrs, pool); file_cache_create(conf, &dobj->vary, pool); @@ -841,7 +843,7 @@ static apr_status_t read_table(cache_handle_t *handle, request_rec *r, } *l++ = '\0'; - while (*l && apr_isspace(*l)) { + while (apr_isspace(*l)) { ++l; } @@ -940,6 +942,10 @@ static apr_status_t store_headers(cache_handle_t *h, request_rec *r, cache_info dobj->headers_in = ap_cache_cacheable_headers_in(r); } + if (r->header_only && r->status != HTTP_NOT_MODIFIED) { + dobj->disk_info.header_only = 1; + } + return APR_SUCCESS; } @@ -1188,49 +1194,51 @@ static apr_status_t store_body(cache_handle_t *h, request_rec *r, continue; } - /* Attempt to create the data file at the last possible moment, if - * the body is empty, we don't write a file at all, and save an inode. - */ - if (!dobj->data.tempfd) { - apr_finfo_t finfo; - rv = apr_file_mktemp(&dobj->data.tempfd, dobj->data.tempfile, - APR_CREATE | APR_WRITE | APR_BINARY | - APR_BUFFERED | APR_EXCL, dobj->data.pool); + if (!dobj->disk_info.header_only) { + + /* Attempt to create the data file at the last possible moment, if + * the body is empty, we don't write a file at all, and save an inode. + */ + if (!dobj->data.tempfd) { + apr_finfo_t finfo; + rv = apr_file_mktemp(&dobj->data.tempfd, dobj->data.tempfile, + APR_CREATE | APR_WRITE | APR_BINARY | APR_BUFFERED + | APR_EXCL, dobj->data.pool); + if (rv != APR_SUCCESS) { + apr_pool_destroy(dobj->data.pool); + return rv; + } + dobj->file_size = 0; + rv = apr_file_info_get(&finfo, APR_FINFO_IDENT, + dobj->data.tempfd); + if (rv != APR_SUCCESS) { + apr_pool_destroy(dobj->data.pool); + return rv; + } + dobj->disk_info.device = finfo.device; + dobj->disk_info.inode = finfo.inode; + dobj->disk_info.has_body = 1; + } + + /* write to the cache, leave if we fail */ + rv = apr_file_write_full(dobj->data.tempfd, str, length, &written); if (rv != APR_SUCCESS) { + ap_log_rerror( + APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00731) "Error when writing cache file for URL %s", h->cache_obj->key); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ apr_pool_destroy(dobj->data.pool); return rv; } - dobj->file_size = 0; - rv = apr_file_info_get(&finfo, APR_FINFO_IDENT, - dobj->data.tempfd); - if (rv != APR_SUCCESS) { + dobj->file_size += written; + if (dobj->file_size > dconf->maxfs) { + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00732) "URL %s failed the size check " + "(%" APR_OFF_T_FMT ">%" APR_OFF_T_FMT ")", h->cache_obj->key, dobj->file_size, dconf->maxfs); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ apr_pool_destroy(dobj->data.pool); - return rv; + return APR_EGENERAL; } - dobj->disk_info.device = finfo.device; - dobj->disk_info.inode = finfo.inode; - dobj->disk_info.has_body = 1; - } - /* write to the cache, leave if we fail */ - rv = apr_file_write_full(dobj->data.tempfd, str, length, &written); - if (rv != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00731) - "Error when writing cache file for URL %s", - h->cache_obj->key); - /* Remove the intermediate cache file and return non-APR_SUCCESS */ - apr_pool_destroy(dobj->data.pool); - return rv; - } - dobj->file_size += written; - if (dobj->file_size > dconf->maxfs) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00732) - "URL %s failed the size check " - "(%" APR_OFF_T_FMT ">%" APR_OFF_T_FMT ")", - h->cache_obj->key, dobj->file_size, dconf->maxfs); - /* Remove the intermediate cache file and return non-APR_SUCCESS */ - apr_pool_destroy(dobj->data.pool); - return APR_EGENERAL; } /* have we reached the limit of how much we're prepared to write in one @@ -1256,43 +1264,44 @@ static apr_status_t store_body(cache_handle_t *h, request_rec *r, if (seen_eos) { const char *cl_header = apr_table_get(r->headers_out, "Content-Length"); - if (dobj->data.tempfd) { - rv = apr_file_close(dobj->data.tempfd); - if (rv != APR_SUCCESS) { - /* Buffered write failed, abandon attempt to write */ - apr_pool_destroy(dobj->data.pool); - return rv; + if (!dobj->disk_info.header_only) { + + if (dobj->data.tempfd) { + rv = apr_file_close(dobj->data.tempfd); + if (rv != APR_SUCCESS) { + /* Buffered write failed, abandon attempt to write */ + apr_pool_destroy(dobj->data.pool); + return rv; + } } - } - if (r->connection->aborted || r->no_cache) { - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00733) - "Discarding body for URL %s " - "because connection has been aborted.", - h->cache_obj->key); - /* Remove the intermediate cache file and return non-APR_SUCCESS */ - apr_pool_destroy(dobj->data.pool); - return APR_EGENERAL; - } - if (dobj->file_size < dconf->minfs) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00734) - "URL %s failed the size check " - "(%" APR_OFF_T_FMT "<%" APR_OFF_T_FMT ")", - h->cache_obj->key, dobj->file_size, dconf->minfs); - /* Remove the intermediate cache file and return non-APR_SUCCESS */ - apr_pool_destroy(dobj->data.pool); - return APR_EGENERAL; - } - if (cl_header) { - apr_int64_t cl = apr_atoi64(cl_header); - if ((errno == 0) && (dobj->file_size != cl)) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00735) - "URL %s didn't receive complete response, not caching", - h->cache_obj->key); + if (r->connection->aborted || r->no_cache) { + ap_log_rerror( + APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00733) "Discarding body for URL %s " + "because connection has been aborted.", h->cache_obj->key); /* Remove the intermediate cache file and return non-APR_SUCCESS */ apr_pool_destroy(dobj->data.pool); return APR_EGENERAL; } + if (dobj->file_size < dconf->minfs) { + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00734) "URL %s failed the size check " + "(%" APR_OFF_T_FMT "<%" APR_OFF_T_FMT ")", h->cache_obj->key, dobj->file_size, dconf->minfs); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ + apr_pool_destroy(dobj->data.pool); + return APR_EGENERAL; + } + if (cl_header) { + apr_int64_t cl = apr_atoi64(cl_header); + if ((errno == 0) && (dobj->file_size != cl)) { + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00735) "URL %s didn't receive complete response, not caching", h->cache_obj->key); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ + apr_pool_destroy(dobj->data.pool); + return APR_EGENERAL; + } + } + } /* All checks were fine, we're good to go when the commit comes */ @@ -1319,7 +1328,12 @@ static apr_status_t commit_entity(cache_handle_t *h, request_rec *r) rv = file_cache_el_final(conf, &dobj->vary, r); } if (APR_SUCCESS == rv) { - rv = file_cache_el_final(conf, &dobj->data, r); + if (!dobj->disk_info.header_only) { + rv = file_cache_el_final(conf, &dobj->data, r); + } + else if (dobj->data.file){ + rv = apr_file_remove(dobj->data.file, dobj->data.pool); + } } /* remove the cached items completely on any failure */ @@ -1342,7 +1356,17 @@ static apr_status_t commit_entity(cache_handle_t *h, request_rec *r) static apr_status_t invalidate_entity(cache_handle_t *h, request_rec *r) { - return APR_ENOTIMPL; + apr_status_t rv; + + rv = recall_headers(h, r); + if (rv != APR_SUCCESS) { + return rv; + } + + /* mark the entity as invalidated */ + h->cache_obj->info.control.invalidated = 1; + + return commit_entity(h, r); } static void *create_dir_config(apr_pool_t *p, char *dummy) @@ -1447,6 +1471,7 @@ static const char { return "CacheMinFileSize argument must be a non-negative integer representing the min size of a file to cache in bytes."; } + dconf->minfs_set = 1; return NULL; } @@ -1460,6 +1485,7 @@ static const char { return "CacheMaxFileSize argument must be a non-negative integer representing the max size of a file to cache in bytes."; } + dconf->maxfs_set = 1; return NULL; } diff --git a/modules/cache/mod_cache_socache.c b/modules/cache/mod_cache_socache.c new file mode 100644 index 00000000..913de2ee --- /dev/null +++ b/modules/cache/mod_cache_socache.c @@ -0,0 +1,1501 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "apr_lib.h" +#include "apr_file_io.h" +#include "apr_strings.h" +#include "apr_buckets.h" +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" +#include "http_core.h" +#include "ap_provider.h" +#include "ap_socache.h" +#include "util_filter.h" +#include "util_script.h" +#include "util_charset.h" +#include "util_mutex.h" + +#include "mod_cache.h" + +#include "cache_socache_common.h" + +/* + * mod_cache_socache: Shared Object Cache Based HTTP 1.1 Cache. + * + * Flow to Find the entry: + * Incoming client requests URI /foo/bar/baz + * Fetch URI key (may contain Format #1 or Format #2) + * If format #1 (Contains a list of Vary Headers): + * Use each header name (from .header) with our request values (headers_in) to + * regenerate key using HeaderName+HeaderValue+.../foo/bar/baz + * re-read in key (must be format #2) + * + * Format #1: + * apr_uint32_t format; + * apr_time_t expire; + * apr_array_t vary_headers (delimited by CRLF) + * + * Format #2: + * cache_socache_info_t (first sizeof(apr_uint32_t) bytes is the format) + * entity name (sobj->name) [length is in cache_socache_info_t->name_len] + * r->headers_out (delimited by CRLF) + * CRLF + * r->headers_in (delimited by CRLF) + * CRLF + */ + +module AP_MODULE_DECLARE_DATA cache_socache_module; + +/* + * cache_socache_object_t + * Pointed to by cache_object_t::vobj + */ +typedef struct cache_socache_object_t +{ + apr_pool_t *pool; /* pool */ + unsigned char *buffer; /* the cache buffer */ + apr_size_t buffer_len; /* size of the buffer */ + apr_bucket_brigade *body; /* brigade containing the body, if any */ + apr_table_t *headers_in; /* Input headers to save */ + apr_table_t *headers_out; /* Output headers to save */ + cache_socache_info_t socache_info; /* Header information. */ + apr_size_t body_offset; /* offset to the start of the body */ + unsigned int newbody :1; /* whether a new body is present */ + apr_time_t expire; /* when to expire the entry */ + + const char *name; /* Requested URI without vary bits - suitable for mortals. */ + const char *key; /* On-disk prefix; URI with Vary bits (if present) */ + apr_off_t file_size; /* File size of the cached data file */ + apr_off_t offset; /* Max size to set aside */ + apr_time_t timeout; /* Max time to set aside */ + unsigned int done :1; /* Is the attempt to cache complete? */ +} cache_socache_object_t; + +/* + * mod_cache_socache configuration + */ +#define DEFAULT_MAX_FILE_SIZE 100*1024 +#define DEFAULT_MAXTIME 86400 +#define DEFAULT_MINTIME 600 +#define DEFAULT_READSIZE 0 +#define DEFAULT_READTIME 0 + +typedef struct cache_socache_provider_conf +{ + const char *args; + ap_socache_provider_t *socache_provider; + ap_socache_instance_t *socache_instance; +} cache_socache_provider_conf; + +typedef struct cache_socache_conf +{ + cache_socache_provider_conf *provider; +} cache_socache_conf; + +typedef struct cache_socache_dir_conf +{ + apr_off_t max; /* maximum file size for cached files */ + apr_time_t maxtime; /* maximum expiry time */ + apr_time_t mintime; /* minimum expiry time */ + apr_off_t readsize; /* maximum data to attempt to cache in one go */ + apr_time_t readtime; /* maximum time taken to cache in one go */ + unsigned int max_set :1; + unsigned int maxtime_set :1; + unsigned int mintime_set :1; + unsigned int readsize_set :1; + unsigned int readtime_set :1; +} cache_socache_dir_conf; + +/* Shared object cache and mutex */ +static const char * const cache_socache_id = "cache-socache"; +static apr_global_mutex_t *socache_mutex = NULL; + +/* + * Local static functions + */ + +static apr_status_t read_array(request_rec *r, apr_array_header_t *arr, + unsigned char *buffer, apr_size_t buffer_len, apr_size_t *slider) +{ + apr_size_t val = *slider; + + while (*slider < buffer_len) { + if (buffer[*slider] == '\r') { + if (val == *slider) { + (*slider)++; + return APR_SUCCESS; + } + *((const char **) apr_array_push(arr)) = apr_pstrndup(r->pool, + (const char *) buffer + val, *slider - val); + (*slider)++; + if (buffer[*slider] == '\n') { + (*slider)++; + } + val = *slider; + } + else if (buffer[*slider] == '\0') { + (*slider)++; + return APR_SUCCESS; + } + else { + (*slider)++; + } + } + + return APR_EOF; +} + +static apr_status_t store_array(apr_array_header_t *arr, unsigned char *buffer, + apr_size_t buffer_len, apr_size_t *slider) +{ + int i, len; + const char **elts; + + elts = (const char **) arr->elts; + + for (i = 0; i < arr->nelts; i++) { + len = strlen(elts[i]); + if (len + 3 >= buffer_len - *slider) { + return APR_EOF; + } + len = apr_snprintf(buffer ? (char *) buffer + *slider : NULL, + buffer ? buffer_len - *slider : 0, "%s" CRLF, elts[i]); + *slider += len; + } + if (buffer) { + memcpy(buffer + *slider, CRLF, sizeof(CRLF) - 1); + } + *slider += sizeof(CRLF) - 1; + + return APR_SUCCESS; +} + +static apr_status_t read_table(cache_handle_t *handle, request_rec *r, + apr_table_t *table, unsigned char *buffer, apr_size_t buffer_len, + apr_size_t *slider) +{ + apr_size_t key = *slider, colon = 0, len = 0; + ; + + while (*slider < buffer_len) { + if (buffer[*slider] == ':') { + if (!colon) { + colon = *slider; + } + (*slider)++; + } + else if (buffer[*slider] == '\r') { + len = colon; + if (key == *slider) { + (*slider)++; + if (buffer[*slider] == '\n') { + (*slider)++; + } + return APR_SUCCESS; + } + if (!colon || buffer[colon++] != ':') { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02344) + "Premature end of cache headers."); + return APR_EGENERAL; + } + while (apr_isspace(buffer[colon])) { + colon++; + } + apr_table_addn(table, apr_pstrndup(r->pool, (const char *) buffer + + key, len - key), apr_pstrndup(r->pool, + (const char *) buffer + colon, *slider - colon)); + (*slider)++; + if (buffer[*slider] == '\n') { + (*slider)++; + } + key = *slider; + colon = 0; + } + else if (buffer[*slider] == '\0') { + (*slider)++; + return APR_SUCCESS; + } + else { + (*slider)++; + } + } + + return APR_EOF; +} + +static apr_status_t store_table(apr_table_t *table, unsigned char *buffer, + apr_size_t buffer_len, apr_size_t *slider) +{ + int i, len; + apr_table_entry_t *elts; + + elts = (apr_table_entry_t *) apr_table_elts(table)->elts; + for (i = 0; i < apr_table_elts(table)->nelts; ++i) { + if (elts[i].key != NULL) { + apr_size_t key_len = strlen(elts[i].key); + apr_size_t val_len = strlen(elts[i].val); + if (key_len + val_len + 5 >= buffer_len - *slider) { + return APR_EOF; + } + len = apr_snprintf(buffer ? (char *) buffer + *slider : NULL, + buffer ? buffer_len - *slider : 0, "%s: %s" CRLF, + elts[i].key, elts[i].val); + *slider += len; + } + } + if (3 >= buffer_len - *slider) { + return APR_EOF; + } + if (buffer) { + memcpy(buffer + *slider, CRLF, sizeof(CRLF) - 1); + } + *slider += sizeof(CRLF) - 1; + + return APR_SUCCESS; +} + +static const char* regen_key(apr_pool_t *p, apr_table_t *headers, + apr_array_header_t *varray, const char *oldkey) +{ + struct iovec *iov; + int i, k; + int nvec; + const char *header; + const char **elts; + + nvec = (varray->nelts * 2) + 1; + iov = apr_palloc(p, sizeof(struct iovec) * nvec); + elts = (const char **) varray->elts; + + /* TODO: + * - Handle multiple-value headers better. (sort them?) + * - Handle Case in-sensitive Values better. + * This isn't the end of the world, since it just lowers the cache + * hit rate, but it would be nice to fix. + * + * The majority are case insenstive if they are values (encoding etc). + * Most of rfc2616 is case insensitive on header contents. + * + * So the better solution may be to identify headers which should be + * treated case-sensitive? + * HTTP URI's (3.2.3) [host and scheme are insensitive] + * HTTP method (5.1.1) + * HTTP-date values (3.3.1) + * 3.7 Media Types [exerpt] + * The type, subtype, and parameter attribute names are case- + * insensitive. Parameter values might or might not be case-sensitive, + * depending on the semantics of the parameter name. + * 4.20 Except [exerpt] + * Comparison of expectation values is case-insensitive for unquoted + * tokens (including the 100-continue token), and is case-sensitive for + * quoted-string expectation-extensions. + */ + + for (i = 0, k = 0; i < varray->nelts; i++) { + header = apr_table_get(headers, elts[i]); + if (!header) { + header = ""; + } + iov[k].iov_base = (char*) elts[i]; + iov[k].iov_len = strlen(elts[i]); + k++; + iov[k].iov_base = (char*) header; + iov[k].iov_len = strlen(header); + k++; + } + iov[k].iov_base = (char*) oldkey; + iov[k].iov_len = strlen(oldkey); + k++; + + return apr_pstrcatv(p, iov, k, NULL); +} + +static int array_alphasort(const void *fn1, const void *fn2) +{ + return strcmp(*(char**) fn1, *(char**) fn2); +} + +static void tokens_to_array(apr_pool_t *p, const char *data, + apr_array_header_t *arr) +{ + char *token; + + while ((token = ap_get_list_item(p, &data)) != NULL) { + *((const char **) apr_array_push(arr)) = token; + } + + /* Sort it so that "Vary: A, B" and "Vary: B, A" are stored the same. */ + qsort((void *) arr->elts, arr->nelts, sizeof(char *), array_alphasort); +} + +/* + * Hook and mod_cache callback functions + */ +static int create_entity(cache_handle_t *h, request_rec *r, const char *key, + apr_off_t len, apr_bucket_brigade *bb) +{ + cache_socache_dir_conf *dconf = + ap_get_module_config(r->per_dir_config, &cache_socache_module); + cache_socache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_socache_module); + cache_object_t *obj; + cache_socache_object_t *sobj; + apr_size_t total; + + if (conf->provider == NULL) { + return DECLINED; + } + + /* we don't support caching of range requests (yet) */ + /* TODO: but we could */ + if (r->status == HTTP_PARTIAL_CONTENT) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02345) + "URL %s partial content response not cached", + key); + return DECLINED; + } + + /* + * We have a chicken and egg problem. We don't know until we + * attempt to store_headers just how big the response will be + * and whether it will fit in the cache limits set. But we + * need to make a decision now as to whether we plan to try. + * If we make the wrong decision, we could prevent another + * cache implementation, such as cache_disk, from getting the + * opportunity to cache, and that would be unfortunate. + * + * In a series of tests, from cheapest to most expensive, + * decide whether or not to ignore this attempt to cache, + * with a small margin just to be sure. + */ + if (len < 0) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02346) + "URL '%s' had no explicit size, ignoring", key); + return DECLINED; + } + if (len > dconf->max) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02347) + "URL '%s' body larger than limit, ignoring " + "(%" APR_OFF_T_FMT " > %" APR_OFF_T_FMT ")", + key, len, dconf->max); + return DECLINED; + } + + /* estimate the total cached size, given current headers */ + total = len + sizeof(cache_socache_info_t) + strlen(key); + if (APR_SUCCESS != store_table(r->headers_out, NULL, dconf->max, &total) + || APR_SUCCESS != store_table(r->headers_in, NULL, dconf->max, + &total)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02348) + "URL '%s' estimated headers size larger than limit, ignoring " + "(%" APR_SIZE_T_FMT " > %" APR_OFF_T_FMT ")", + key, total, dconf->max); + return DECLINED; + } + + if (total >= dconf->max) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02349) + "URL '%s' body and headers larger than limit, ignoring " + "(%" APR_OFF_T_FMT " > %" APR_OFF_T_FMT ")", + key, len, dconf->max); + return DECLINED; + } + + /* Allocate and initialize cache_object_t and cache_socache_object_t */ + h->cache_obj = obj = apr_pcalloc(r->pool, sizeof(*obj)); + obj->vobj = sobj = apr_pcalloc(r->pool, sizeof(*sobj)); + + obj->key = apr_pstrdup(r->pool, key); + sobj->key = obj->key; + sobj->name = obj->key; + + return OK; +} + +static int open_entity(cache_handle_t *h, request_rec *r, const char *key) +{ + cache_socache_dir_conf *dconf = + ap_get_module_config(r->per_dir_config, &cache_socache_module); + cache_socache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_socache_module); + apr_uint32_t format; + apr_size_t slider; + unsigned int buffer_len; + const char *nkey; + apr_status_t rc; + cache_object_t *obj; + cache_info *info; + cache_socache_object_t *sobj; + apr_size_t len; + + nkey = NULL; + h->cache_obj = NULL; + + if (!conf->provider || !conf->provider->socache_instance) { + return DECLINED; + } + + /* Create and init the cache object */ + obj = apr_pcalloc(r->pool, sizeof(cache_object_t)); + sobj = apr_pcalloc(r->pool, sizeof(cache_socache_object_t)); + + info = &(obj->info); + + /* Create a temporary pool for the buffer, and destroy it if something + * goes wrong so we don't have large buffers of unused memory hanging + * about for the lifetime of the response. + */ + apr_pool_create(&sobj->pool, r->pool); + + sobj->buffer = apr_palloc(sobj->pool, dconf->max + 1); + sobj->buffer_len = dconf->max + 1; + + /* attempt to retrieve the cached entry */ + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02350) + "could not acquire lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + buffer_len = sobj->buffer_len; + rc = conf->provider->socache_provider->retrieve( + conf->provider->socache_instance, r->server, (unsigned char *) key, + strlen(key), sobj->buffer, &buffer_len, r->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02351) + "could not release lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + if (rc != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02352) + "Key not found in cache: %s", key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + if (buffer_len >= sobj->buffer_len) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02353) + "Key found in cache but too big, ignoring: %s", key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + + /* read the format from the cache file */ + memcpy(&format, sobj->buffer, sizeof(format)); + slider = sizeof(format); + + if (format == CACHE_SOCACHE_VARY_FORMAT_VERSION) { + apr_array_header_t* varray; + apr_time_t expire; + + memcpy(&expire, sobj->buffer + slider, sizeof(expire)); + slider += sizeof(expire); + + varray = apr_array_make(r->pool, 5, sizeof(char*)); + rc = read_array(r, varray, sobj->buffer, buffer_len, &slider); + if (rc != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02354) + "Cannot parse vary entry for key: %s", key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + + nkey = regen_key(r->pool, r->headers_in, varray, key); + + /* attempt to retrieve the cached entry */ + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02355) + "could not acquire lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + buffer_len = sobj->buffer_len; + rc = conf->provider->socache_provider->retrieve( + conf->provider->socache_instance, r->server, + (unsigned char *) nkey, strlen(nkey), sobj->buffer, + &buffer_len, r->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02356) + "could not release lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + if (rc != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02357) + "Key not found in cache: %s", key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + if (buffer_len >= sobj->buffer_len) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02358) + "Key found in cache but too big, ignoring: %s", key); + goto fail; + } + + } + else if (format != CACHE_SOCACHE_DISK_FORMAT_VERSION) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02359) + "Key '%s' found in cache has version %d, expected %d, ignoring", + key, format, CACHE_SOCACHE_DISK_FORMAT_VERSION); + goto fail; + } + else { + nkey = key; + } + + obj->key = nkey; + sobj->key = nkey; + sobj->name = key; + + if (buffer_len >= sizeof(cache_socache_info_t)) { + memcpy(&sobj->socache_info, sobj->buffer, sizeof(cache_socache_info_t)); + } + else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02360) + "Cache entry for key '%s' too short, removing", nkey); + goto fail; + } + slider = sizeof(cache_socache_info_t); + + /* Store it away so we can get it later. */ + info->status = sobj->socache_info.status; + info->date = sobj->socache_info.date; + info->expire = sobj->socache_info.expire; + info->request_time = sobj->socache_info.request_time; + info->response_time = sobj->socache_info.response_time; + + memcpy(&info->control, &sobj->socache_info.control, sizeof(cache_control_t)); + + if (sobj->socache_info.name_len <= buffer_len - slider) { + if (strncmp((const char *) sobj->buffer + slider, sobj->name, + sobj->socache_info.name_len)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02361) + "Cache entry for key '%s' URL mismatch, ignoring", nkey); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + slider += sobj->socache_info.name_len; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02362) + "Cache entry for key '%s' too short, removing", nkey); + goto fail; + } + + /* Is this a cached HEAD request? */ + if (sobj->socache_info.header_only && !r->header_only) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02363) + "HEAD request cached, non-HEAD requested, ignoring: %s", + sobj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + + h->req_hdrs = apr_table_make(r->pool, 20); + h->resp_hdrs = apr_table_make(r->pool, 20); + + /* Call routine to read the header lines/status line */ + if (APR_SUCCESS != read_table(h, r, h->resp_hdrs, sobj->buffer, buffer_len, + &slider)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02364) + "Cache entry for key '%s' response headers unreadable, removing", nkey); + goto fail; + } + if (APR_SUCCESS != read_table(h, r, h->req_hdrs, sobj->buffer, buffer_len, + &slider)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02365) + "Cache entry for key '%s' request headers unreadable, removing", nkey); + goto fail; + } + + /* Retrieve the body if we have one */ + sobj->body = apr_brigade_create(r->pool, r->connection->bucket_alloc); + len = buffer_len - slider; + + /* + * Optimisation: if the body is small, we want to make a + * copy of the body and free the temporary pool, as we + * don't want large blocks of unused memory hanging around + * to the end of the response. In contrast, if the body is + * large, we would rather leave the body where it is in the + * temporary pool, and save ourselves the copy. + */ + if (len * 2 > dconf->max) { + apr_bucket *e; + + /* large - use the brigade as is, we're done */ + e = apr_bucket_immortal_create((const char *) sobj->buffer + slider, + len, r->connection->bucket_alloc); + + APR_BRIGADE_INSERT_TAIL(sobj->body, e); + } + else { + + /* small - make a copy of the data... */ + apr_brigade_write(sobj->body, NULL, NULL, (const char *) sobj->buffer + + slider, len); + + /* ...and get rid of the large memory buffer */ + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + } + + /* make the configuration stick */ + h->cache_obj = obj; + obj->vobj = sobj; + + return OK; + +fail: + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02366) + "could not acquire lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + conf->provider->socache_provider->remove( + conf->provider->socache_instance, r->server, + (unsigned char *) nkey, strlen(nkey), r->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02367) + "could not release lock, ignoring: %s", obj->key); + } + } + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; +} + +static int remove_entity(cache_handle_t *h) +{ + /* Null out the cache object pointer so next time we start from scratch */ + h->cache_obj = NULL; + return OK; +} + +static int remove_url(cache_handle_t *h, request_rec *r) +{ + cache_socache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_socache_module); + cache_socache_object_t *sobj; + + sobj = (cache_socache_object_t *) h->cache_obj->vobj; + if (!sobj) { + return DECLINED; + } + + /* Remove the key from the cache */ + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02368) + "could not acquire lock, ignoring: %s", sobj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + conf->provider->socache_provider->remove(conf->provider->socache_instance, + r->server, (unsigned char *) sobj->key, strlen(sobj->key), r->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02369) + "could not release lock, ignoring: %s", sobj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + + return OK; +} + +static apr_status_t recall_headers(cache_handle_t *h, request_rec *r) +{ + /* we recalled the headers during open_entity, so do nothing */ + return APR_SUCCESS; +} + +static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p, + apr_bucket_brigade *bb) +{ + cache_socache_object_t *sobj = (cache_socache_object_t*) h->cache_obj->vobj; + apr_bucket *e; + + e = APR_BRIGADE_FIRST(sobj->body); + + if (e != APR_BRIGADE_SENTINEL(sobj->body)) { + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(bb, e); + } + + return APR_SUCCESS; +} + +static apr_status_t store_headers(cache_handle_t *h, request_rec *r, + cache_info *info) +{ + cache_socache_dir_conf *dconf = + ap_get_module_config(r->per_dir_config, &cache_socache_module); + cache_socache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_socache_module); + apr_size_t slider; + apr_status_t rv; + cache_object_t *obj = h->cache_obj; + cache_socache_object_t *sobj = (cache_socache_object_t*) obj->vobj; + cache_socache_info_t *socache_info; + + memcpy(&h->cache_obj->info, info, sizeof(cache_info)); + + if (r->headers_out) { + sobj->headers_out = ap_cache_cacheable_headers_out(r); + } + + if (r->headers_in) { + sobj->headers_in = ap_cache_cacheable_headers_in(r); + } + + sobj->expire + = obj->info.expire > r->request_time + dconf->maxtime ? r->request_time + + dconf->maxtime + : obj->info.expire + dconf->mintime; + + apr_pool_create(&sobj->pool, r->pool); + + sobj->buffer = apr_palloc(sobj->pool, dconf->max); + sobj->buffer_len = dconf->max; + socache_info = (cache_socache_info_t *) sobj->buffer; + + if (sobj->headers_out) { + const char *vary; + + vary = apr_table_get(sobj->headers_out, "Vary"); + + if (vary) { + apr_array_header_t* varray; + apr_uint32_t format = CACHE_SOCACHE_VARY_FORMAT_VERSION; + + memcpy(sobj->buffer, &format, sizeof(format)); + slider = sizeof(format); + + memcpy(sobj->buffer + slider, &obj->info.expire, + sizeof(obj->info.expire)); + slider += sizeof(obj->info.expire); + + varray = apr_array_make(r->pool, 6, sizeof(char*)); + tokens_to_array(r->pool, vary, varray); + + if (APR_SUCCESS != (rv = store_array(varray, sobj->buffer, + sobj->buffer_len, &slider))) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02370) + "buffer too small for Vary array, caching aborted: %s", + obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; + } + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02371) + "could not acquire lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return status; + } + } + rv = conf->provider->socache_provider->store( + conf->provider->socache_instance, r->server, + (unsigned char *) obj->key, strlen(obj->key), sobj->expire, + (unsigned char *) sobj->buffer, (unsigned int) slider, + sobj->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02372) + "could not release lock, ignoring: %s", obj->key); + } + } + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02373) + "Vary not written to cache, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; + } + + obj->key = sobj->key = regen_key(r->pool, sobj->headers_in, varray, + sobj->name); + } + } + + socache_info->format = CACHE_SOCACHE_DISK_FORMAT_VERSION; + socache_info->date = obj->info.date; + socache_info->expire = obj->info.expire; + socache_info->entity_version = sobj->socache_info.entity_version++; + socache_info->request_time = obj->info.request_time; + socache_info->response_time = obj->info.response_time; + socache_info->status = obj->info.status; + + if (r->header_only && r->status != HTTP_NOT_MODIFIED) { + socache_info->header_only = 1; + } + else { + socache_info->header_only = sobj->socache_info.header_only; + } + + socache_info->name_len = strlen(sobj->name); + + memcpy(&socache_info->control, &obj->info.control, sizeof(cache_control_t)); + slider = sizeof(cache_socache_info_t); + + if (slider + socache_info->name_len >= sobj->buffer_len) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02374) + "cache buffer too small for name: %s", + sobj->name); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return APR_EGENERAL; + } + memcpy(sobj->buffer + slider, sobj->name, socache_info->name_len); + slider += socache_info->name_len; + + if (sobj->headers_out) { + if (APR_SUCCESS != store_table(sobj->headers_out, sobj->buffer, + sobj->buffer_len, &slider)) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02375) + "out-headers didn't fit in buffer: %s", sobj->name); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return APR_EGENERAL; + } + } + + /* Parse the vary header and dump those fields from the headers_in. */ + /* TODO: Make call to the same thing cache_select calls to crack Vary. */ + if (sobj->headers_in) { + if (APR_SUCCESS != store_table(sobj->headers_in, sobj->buffer, + sobj->buffer_len, &slider)) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(02376) + "in-headers didn't fit in buffer %s", + sobj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return APR_EGENERAL; + } + } + + sobj->body_offset = slider; + + return APR_SUCCESS; +} + +static apr_status_t store_body(cache_handle_t *h, request_rec *r, + apr_bucket_brigade *in, apr_bucket_brigade *out) +{ + apr_bucket *e; + apr_status_t rv = APR_SUCCESS; + cache_socache_object_t *sobj = + (cache_socache_object_t *) h->cache_obj->vobj; + cache_socache_dir_conf *dconf = + ap_get_module_config(r->per_dir_config, &cache_socache_module); + int seen_eos = 0; + + if (!sobj->offset) { + sobj->offset = dconf->readsize; + } + if (!sobj->timeout && dconf->readtime) { + sobj->timeout = apr_time_now() + dconf->readtime; + } + + if (!sobj->newbody) { + if (sobj->body) { + apr_brigade_cleanup(sobj->body); + } + else { + sobj->body = apr_brigade_create(r->pool, + r->connection->bucket_alloc); + } + sobj->newbody = 1; + } + if (sobj->offset) { + apr_brigade_partition(in, sobj->offset, &e); + } + + while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(in)) { + const char *str; + apr_size_t length; + + e = APR_BRIGADE_FIRST(in); + + /* are we done completely? if so, pass any trailing buckets right through */ + if (sobj->done || !sobj->pool) { + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + continue; + } + + /* have we seen eos yet? */ + if (APR_BUCKET_IS_EOS(e)) { + seen_eos = 1; + sobj->done = 1; + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + break; + } + + /* honour flush buckets, we'll get called again */ + if (APR_BUCKET_IS_FLUSH(e)) { + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + break; + } + + /* metadata buckets are preserved as is */ + if (APR_BUCKET_IS_METADATA(e)) { + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + continue; + } + + /* read the bucket, write to the cache */ + rv = apr_bucket_read(e, &str, &length, APR_BLOCK_READ); + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02377) + "Error when reading bucket for URL %s", + h->cache_obj->key); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; + } + + /* don't write empty buckets to the cache */ + if (!length) { + continue; + } + + sobj->file_size += length; + if (sobj->file_size >= sobj->buffer_len - sobj->body_offset) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02378) + "URL %s failed the buffer size check " + "(%" APR_OFF_T_FMT ">=%" APR_SIZE_T_FMT ")", + h->cache_obj->key, sobj->file_size, sobj->buffer_len - sobj->body_offset); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return APR_EGENERAL; + } + + rv = apr_bucket_copy(e, &e); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02379) + "Error when copying bucket for URL %s", + h->cache_obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; + } + APR_BRIGADE_INSERT_TAIL(sobj->body, e); + + /* have we reached the limit of how much we're prepared to write in one + * go? If so, leave, we'll get called again. This prevents us from trying + * to swallow too much data at once, or taking so long to write the data + * the client times out. + */ + sobj->offset -= length; + if (sobj->offset <= 0) { + sobj->offset = 0; + break; + } + if ((dconf->readtime && apr_time_now() > sobj->timeout)) { + sobj->timeout = 0; + break; + } + + } + + /* Was this the final bucket? If yes, perform sanity checks. + */ + if (seen_eos) { + const char *cl_header = apr_table_get(r->headers_out, "Content-Length"); + + if (r->connection->aborted || r->no_cache) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02380) + "Discarding body for URL %s " + "because connection has been aborted.", + h->cache_obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return APR_EGENERAL; + } + if (cl_header) { + apr_int64_t cl = apr_atoi64(cl_header); + if ((errno == 0) && (sobj->file_size != cl)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02381) + "URL %s didn't receive complete response, not caching", + h->cache_obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return APR_EGENERAL; + } + } + + /* All checks were fine, we're good to go when the commit comes */ + + } + + return APR_SUCCESS; +} + +static apr_status_t commit_entity(cache_handle_t *h, request_rec *r) +{ + cache_socache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_socache_module); + cache_object_t *obj = h->cache_obj; + cache_socache_object_t *sobj = (cache_socache_object_t *) obj->vobj; + apr_status_t rv; + apr_size_t len; + + /* flatten the body into the buffer */ + len = sobj->buffer_len - sobj->body_offset; + rv = apr_brigade_flatten(sobj->body, (char *) sobj->buffer + + sobj->body_offset, &len); + if (APR_SUCCESS != rv) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02382) + "could not flatten brigade, not caching: %s", + sobj->key); + goto fail; + } + if (len >= sobj->buffer_len - sobj->body_offset) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02383) + "body too big for the cache buffer, not caching: %s", + h->cache_obj->key); + goto fail; + } + + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02384) + "could not acquire lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; + } + } + rv = conf->provider->socache_provider->store( + conf->provider->socache_instance, r->server, + (unsigned char *) sobj->key, strlen(sobj->key), sobj->expire, + sobj->buffer, (unsigned int) sobj->body_offset + len, sobj->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02385) + "could not release lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(02386) + "could not write to cache, ignoring: %s", sobj->key); + goto fail; + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02387) + "commit_entity: Headers and body for URL %s cached for maximum of %d seconds.", + sobj->name, (apr_uint32_t)apr_time_sec(sobj->expire - r->request_time)); + + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + + return APR_SUCCESS; + +fail: + /* For safety, remove any existing entry on failure, just in case it could not + * be revalidated successfully. + */ + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02388) + "could not acquire lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; + } + } + conf->provider->socache_provider->remove(conf->provider->socache_instance, + r->server, (unsigned char *) sobj->key, strlen(sobj->key), r->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02389) + "could not release lock, ignoring: %s", obj->key); + } + } + + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; +} + +static apr_status_t invalidate_entity(cache_handle_t *h, request_rec *r) +{ + /* mark the entity as invalidated */ + h->cache_obj->info.control.invalidated = 1; + + return commit_entity(h, r); +} + +static void *create_dir_config(apr_pool_t *p, char *dummy) +{ + cache_socache_dir_conf *dconf = + apr_pcalloc(p, sizeof(cache_socache_dir_conf)); + + dconf->max = DEFAULT_MAX_FILE_SIZE; + dconf->maxtime = apr_time_from_sec(DEFAULT_MAXTIME); + dconf->mintime = apr_time_from_sec(DEFAULT_MINTIME); + dconf->readsize = DEFAULT_READSIZE; + dconf->readtime = DEFAULT_READTIME; + + return dconf; +} + +static void *merge_dir_config(apr_pool_t *p, void *basev, void *addv) +{ + cache_socache_dir_conf + *new = + (cache_socache_dir_conf *) apr_pcalloc(p, sizeof(cache_socache_dir_conf)); + cache_socache_dir_conf *add = (cache_socache_dir_conf *) addv; + cache_socache_dir_conf *base = (cache_socache_dir_conf *) basev; + + new->max = (add->max_set == 0) ? base->max : add->max; + new->max_set = add->max_set || base->max_set; + new->maxtime = (add->maxtime_set == 0) ? base->maxtime : add->maxtime; + new->maxtime_set = add->maxtime_set || base->maxtime_set; + new->mintime = (add->mintime_set == 0) ? base->mintime : add->mintime; + new->mintime_set = add->mintime_set || base->mintime_set; + new->readsize = (add->readsize_set == 0) ? base->readsize : add->readsize; + new->readsize_set = add->readsize_set || base->readsize_set; + new->readtime = (add->readtime_set == 0) ? base->readtime : add->readtime; + new->readtime_set = add->readtime_set || base->readtime_set; + + return new; +} + +static void *create_config(apr_pool_t *p, server_rec *s) +{ + cache_socache_conf *conf = apr_pcalloc(p, sizeof(cache_socache_conf)); + + return conf; +} + +static void *merge_config(apr_pool_t *p, void *basev, void *overridesv) +{ + cache_socache_conf *ps = apr_pcalloc(p, sizeof(cache_socache_conf)); + cache_socache_conf *base = (cache_socache_conf *) basev; + cache_socache_conf *overrides = (cache_socache_conf *) overridesv; + + ps = overrides ? overrides : base; + + return ps; +} + +/* + * mod_cache_socache configuration directives handlers. + */ +static const char *set_cache_socache(cmd_parms *cmd, void *in_struct_ptr, + const char *arg) +{ + cache_socache_conf *conf = ap_get_module_config(cmd->server->module_config, + &cache_socache_module); + cache_socache_provider_conf *provider = conf->provider + = apr_pcalloc(cmd->pool, sizeof(cache_socache_provider_conf)); + + const char *err = NULL, *sep, *name; + + /* Argument is of form 'name:args' or just 'name'. */ + sep = ap_strchr_c(arg, ':'); + if (sep) { + name = apr_pstrmemdup(cmd->pool, arg, sep - arg); + sep++; + provider->args = sep; + } + else { + name = arg; + } + + provider->socache_provider = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP, + name, AP_SOCACHE_PROVIDER_VERSION); + if (provider->socache_provider == NULL) { + err = apr_psprintf(cmd->pool, + "Unknown socache provider '%s'. Maybe you need " + "to load the appropriate socache module " + "(mod_socache_%s?)", name, name); + } + return err; +} + +static const char *set_cache_max(cmd_parms *parms, void *in_struct_ptr, + const char *arg) +{ + cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr; + + if (apr_strtoff(&dconf->max, arg, NULL, 10) != APR_SUCCESS || dconf->max + < 1024) { + return "CacheSocacheMaxSize argument must be a integer representing the max size of a cached entry (headers and body), at least 1024"; + } + dconf->max_set = 1; + return NULL; +} + +static const char *set_cache_maxtime(cmd_parms *parms, void *in_struct_ptr, + const char *arg) +{ + cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr; + apr_off_t seconds; + + if (apr_strtoff(&seconds, arg, NULL, 10) != APR_SUCCESS || seconds < 0) { + return "CacheSocacheMaxTime argument must be the maximum amount of time in seconds to cache an entry."; + } + dconf->maxtime = apr_time_from_sec(seconds); + dconf->maxtime_set = 1; + return NULL; +} + +static const char *set_cache_mintime(cmd_parms *parms, void *in_struct_ptr, + const char *arg) +{ + cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr; + apr_off_t seconds; + + if (apr_strtoff(&seconds, arg, NULL, 10) != APR_SUCCESS || seconds < 0) { + return "CacheSocacheMinTime argument must be the minimum amount of time in seconds to cache an entry."; + } + dconf->mintime = apr_time_from_sec(seconds); + dconf->mintime_set = 1; + return NULL; +} + +static const char *set_cache_readsize(cmd_parms *parms, void *in_struct_ptr, + const char *arg) +{ + cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr; + + if (apr_strtoff(&dconf->readsize, arg, NULL, 10) != APR_SUCCESS + || dconf->readsize < 0) { + return "CacheSocacheReadSize argument must be a non-negative integer representing the max amount of data to cache in go."; + } + dconf->readsize_set = 1; + return NULL; +} + +static const char *set_cache_readtime(cmd_parms *parms, void *in_struct_ptr, + const char *arg) +{ + cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr; + apr_off_t milliseconds; + + if (apr_strtoff(&milliseconds, arg, NULL, 10) != APR_SUCCESS + || milliseconds < 0) { + return "CacheSocacheReadTime argument must be a non-negative integer representing the max amount of time taken to cache in go."; + } + dconf->readtime = apr_time_from_msec(milliseconds); + dconf->readtime_set = 1; + return NULL; +} + +static apr_status_t remove_lock(void *data) +{ + if (socache_mutex) { + apr_global_mutex_destroy(socache_mutex); + socache_mutex = NULL; + } + return APR_SUCCESS; +} + +static apr_status_t destroy_cache(void *data) +{ + server_rec *s = data; + cache_socache_conf *conf = + ap_get_module_config(s->module_config, &cache_socache_module); + if (conf->provider && conf->provider->socache_instance) { + conf->provider->socache_provider->destroy( + conf->provider->socache_instance, s); + conf->provider->socache_instance = NULL; + } + return APR_SUCCESS; +} + +static int socache_precfg(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptmp) +{ + apr_status_t rv = ap_mutex_register(pconf, cache_socache_id, NULL, + APR_LOCK_DEFAULT, 0); + if (rv != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(02390) + "failed to register %s mutex", cache_socache_id); + return 500; /* An HTTP status would be a misnomer! */ + } + return OK; +} + +static int socache_post_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptmp, server_rec *base_server) +{ + server_rec *s; + apr_status_t rv; + const char *errmsg; + static struct ap_socache_hints socache_hints = + { 64, 32, 60000000 }; + + for (s = base_server; s; s = s->next) { + cache_socache_conf *conf = + ap_get_module_config(s->module_config, &cache_socache_module); + + if (!conf->provider) { + continue; + } + + if (!socache_mutex && conf->provider->socache_provider->flags + & AP_SOCACHE_FLAG_NOTMPSAFE) { + + rv = ap_global_mutex_create(&socache_mutex, NULL, cache_socache_id, + NULL, s, pconf, 0); + if (rv != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(02391) + "failed to create %s mutex", cache_socache_id); + return 500; /* An HTTP status would be a misnomer! */ + } + apr_pool_cleanup_register(pconf, NULL, remove_lock, + apr_pool_cleanup_null); + } + + errmsg = conf->provider->socache_provider->create( + &conf->provider->socache_instance, conf->provider->args, ptmp, + pconf); + if (errmsg) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, 0, plog, + APLOGNO(02392) "%s", errmsg); + return 500; /* An HTTP status would be a misnomer! */ + } + + rv = conf->provider->socache_provider->init( + conf->provider->socache_instance, cache_socache_id, + &socache_hints, s, pconf); + if (rv != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(02393) + "failed to initialise %s cache", cache_socache_id); + return 500; /* An HTTP status would be a misnomer! */ + } + apr_pool_cleanup_register(pconf, (void *) s, destroy_cache, + apr_pool_cleanup_null); + + } + + return OK; +} + +static void socache_child_init(apr_pool_t *p, server_rec *s) +{ + const char *lock; + apr_status_t rv; + if (!socache_mutex) { + return; /* don't waste the overhead of creating mutex & cache */ + } + lock = apr_global_mutex_lockfile(socache_mutex); + rv = apr_global_mutex_child_init(&socache_mutex, lock, p); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(02394) + "failed to initialise mutex in child_init"); + } +} + +static const command_rec cache_socache_cmds[] = +{ + AP_INIT_TAKE1("CacheSocache", set_cache_socache, NULL, RSRC_CONF, + "The shared object cache to store cache files"), + AP_INIT_TAKE1("CacheSocacheMaxTime", set_cache_maxtime, NULL, RSRC_CONF | ACCESS_CONF, + "The maximum cache expiry age to cache a document in seconds"), + AP_INIT_TAKE1("CacheSocacheMinTime", set_cache_mintime, NULL, RSRC_CONF | ACCESS_CONF, + "The minimum cache expiry age to cache a document in seconds"), + AP_INIT_TAKE1("CacheSocacheMaxSize", set_cache_max, NULL, RSRC_CONF | ACCESS_CONF, + "The maximum cache entry size (headers and body) to cache a document"), + AP_INIT_TAKE1("CacheSocacheReadSize", set_cache_readsize, NULL, RSRC_CONF | ACCESS_CONF, + "The maximum quantity of data to attempt to read and cache in one go"), + AP_INIT_TAKE1("CacheSocacheReadTime", set_cache_readtime, NULL, RSRC_CONF | ACCESS_CONF, + "The maximum time taken to attempt to read and cache in go"), + { NULL } +}; + +static const cache_provider cache_socache_provider = +{ + &remove_entity, &store_headers, &store_body, &recall_headers, &recall_body, + &create_entity, &open_entity, &remove_url, &commit_entity, + &invalidate_entity +}; + +static void cache_socache_register_hook(apr_pool_t *p) +{ + /* cache initializer */ + ap_register_provider(p, CACHE_PROVIDER_GROUP, "socache", "0", + &cache_socache_provider); + ap_hook_pre_config(socache_precfg, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_config(socache_post_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_child_init(socache_child_init, NULL, NULL, APR_HOOK_MIDDLE); +} + +AP_DECLARE_MODULE(cache_socache) = { STANDARD20_MODULE_STUFF, + create_dir_config, /* create per-directory config structure */ + merge_dir_config, /* merge per-directory config structures */ + create_config, /* create per-server config structure */ + merge_config, /* merge per-server config structures */ + cache_socache_cmds, /* command apr_table_t */ + cache_socache_register_hook /* register hooks */ +}; diff --git a/modules/cache/mod_cache_socache.dsp b/modules/cache/mod_cache_socache.dsp new file mode 100644 index 00000000..e5d582e2 --- /dev/null +++ b/modules/cache/mod_cache_socache.dsp @@ -0,0 +1,115 @@ +# Microsoft Developer Studio Project File - Name="mod_cache_socache" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=mod_cache_socache - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "mod_cache_socache.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "mod_cache_socache.mak" CFG="mod_cache_socache - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_cache_socache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_cache_socache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "mod_cache_socache - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c +# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /Fd"Release\mod_cache_socache_src" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /fo"Release/mod_cache_socache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_cache_socache.so" /d LONG_NAME="cache_socache_module for Apache" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll +# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_cache_socache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_socache.so /opt:ref +# Begin Special Build Tool +TargetPath=.\Release\mod_cache_socache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ELSEIF "$(CFG)" == "mod_cache_socache - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c +# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /Fd"Debug\mod_cache_socache_src" /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /fo"Debug/mod_cache_socache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_cache_socache.so" /d LONG_NAME="cache_socache_module for Apache" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug +# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_cache_socache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_socache.so +# Begin Special Build Tool +TargetPath=.\Debug\mod_cache_socache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ENDIF + +# Begin Target + +# Name "mod_cache_socache - Win32 Release" +# Name "mod_cache_socache - Win32 Debug" +# Begin Source File + +SOURCE=.\mod_cache.h +# End Source File +# Begin Source File + +SOURCE=.\mod_cache_socache.c +# End Source File +# Begin Source File + +SOURCE=..\..\build\win32\httpd.rc +# End Source File +# End Target +# End Project diff --git a/modules/cache/mod_socache_memcache.c b/modules/cache/mod_socache_memcache.c index ccb1bde7..beeeec2c 100644 --- a/modules/cache/mod_socache_memcache.c +++ b/modules/cache/mod_socache_memcache.c @@ -182,19 +182,13 @@ static int socache_mc_id2key(ap_socache_instance_t *ctx, char *key, apr_size_t keylen) { char *cp; - unsigned int n; if (idlen * 2 + ctx->taglen >= keylen) return 1; cp = apr_cpystrn(key, ctx->tag, ctx->taglen); + ap_bin2hex(id, idlen, cp); - for (n = 0; n < idlen; n++) { - apr_snprintf(cp, 3, "%02X", (unsigned) id[n]); - cp += 2; - } - - *cp = '\0'; return 0; } |