| /* 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 "http_protocol.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 "mod_status.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 */ |
| apr_off_t body_length; /* length of the cached entity 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 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++) { |
| apr_size_t e_len = strlen(elts[i]); |
| if (e_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); |
| sobj->buffer_len = dconf->max; |
| |
| /* 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, 0, 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) { |
| sobj->body_length = 0; |
| 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->body_length += length; |
| if (sobj->body_length >= 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->body_length, |
| sobj->buffer_len - sobj->body_offset); |
| apr_pool_destroy(sobj->pool); |
| sobj->pool = NULL; |
| return APR_EGENERAL; |
| } |
| memcpy(sobj->buffer + sobj->body_offset + sobj->body_length - length, |
| str, length); |
| |
| /* 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_off_t cl; |
| char *cl_endp; |
| if (apr_strtoff(&cl, cl_header, &cl_endp, 10) != APR_SUCCESS |
| || *cl_endp != '\0' || cl != sobj->body_length) { |
| 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; |
| |
| 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 status; |
| } |
| } |
| rv = conf->provider->socache_provider->store( |
| conf->provider->socache_instance, r->server, |
| (unsigned char *) sobj->key, strlen(sobj->key), sobj->expire, |
| sobj->buffer, sobj->body_offset + sobj->body_length, 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 status; |
| } |
| } |
| 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; |
| cache_socache_conf *base = (cache_socache_conf *) basev; |
| cache_socache_conf *overrides = (cache_socache_conf *) overridesv; |
| |
| /* socache server config only has one field */ |
| 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 || dconf->max > APR_UINT32_MAX) { |
| return "CacheSocacheMaxSize argument must be a integer representing " |
| "the max size of a cached entry (headers and body), at least 1024 " |
| "and at most " APR_STRINGIFY(APR_UINT32_MAX); |
| } |
| 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_status_hook(request_rec *r, int flags) |
| { |
| apr_status_t status = APR_SUCCESS; |
| cache_socache_conf *conf = ap_get_module_config(r->server->module_config, |
| &cache_socache_module); |
| if (!conf->provider || !conf->provider->socache_provider || |
| !conf->provider->socache_instance) { |
| return DECLINED; |
| } |
| |
| if (!(flags & AP_STATUS_SHORT)) { |
| ap_rputs("<hr>\n" |
| "<table cellspacing=0 cellpadding=0>\n" |
| "<tr><td bgcolor=\"#000000\">\n" |
| "<b><font color=\"#ffffff\" face=\"Arial,Helvetica\">" |
| "mod_cache_socache Status:</font></b>\n" |
| "</td></tr>\n" |
| "<tr><td bgcolor=\"#ffffff\">\n", r); |
| } |
| else { |
| ap_rputs("ModCacheSocacheStatus\n", r); |
| } |
| |
| if (socache_mutex) { |
| status = apr_global_mutex_lock(socache_mutex); |
| if (status != APR_SUCCESS) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02816) |
| "could not acquire lock for cache status"); |
| } |
| } |
| |
| if (status != APR_SUCCESS) { |
| if (!(flags & AP_STATUS_SHORT)) { |
| ap_rputs("No cache status data available\n", r); |
| } |
| else { |
| ap_rputs("NotAvailable\n", r); |
| } |
| } else { |
| conf->provider->socache_provider->status(conf->provider->socache_instance, |
| r, flags); |
| } |
| |
| if (socache_mutex && status == APR_SUCCESS) { |
| status = apr_global_mutex_unlock(socache_mutex); |
| if (status != APR_SUCCESS) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02817) |
| "could not release lock for cache status"); |
| } |
| } |
| |
| if (!(flags & AP_STATUS_SHORT)) { |
| ap_rputs("</td></tr>\n</table>\n", r); |
| } |
| return OK; |
| } |
| |
| static void socache_status_register(apr_pool_t *p) |
| { |
| APR_OPTIONAL_HOOK(ap, status_hook, socache_status_hook, NULL, NULL, APR_HOOK_MIDDLE); |
| } |
| |
| 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! */ |
| } |
| |
| /* Register to handle mod_status status page generation */ |
| socache_status_register(pconf); |
| |
| 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, 2048, 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 */ |
| }; |