/* * 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 "mod_cache.h" #include "apr_strings.h" #include "apr_memcache.h" #include "mod_cache.h" #include "ap_provider.h" #include "ap_mpm.h" #define ME "Memcached" #if APR_HAVE_UNISTD_H #include #endif static int timeout = 0; static apr_uint16_t flags = 0; #ifndef MEMCACHED_MIN_SIZE #define MEMCACHED_MIN_SIZE (0 * 1024) #endif #ifndef MEMCACHED_MAX_SIZE #define MEMCACHED_MAX_SIZE (0 * 1024) #endif #ifndef MC_DEFAULT_SERVER_PORT #define MC_DEFAULT_SERVER_PORT (11211) #endif #ifndef MC_DEFAULT_SERVER_MIN #define MC_DEFAULT_SERVER_MIN (0) #endif #ifndef MC_DEFAULT_SERVER_SMAX #define MC_DEFAULT_SERVER_SMAX (1) #endif #ifndef MC_DEFAULT_SERVER_MAX #define MC_DEFAULT_SERVER_MAX (0) #endif #ifndef MC_DEFAULT_SERVER_TTL #define MC_DEFAULT_SERVER_TTL (600) #endif module AP_MODULE_DECLARE_DATA memcached_cache_module; typedef struct { apr_array_header_t *servers; int minSize, maxSize, min, max, smax, ttl; apr_memcache_t *memctx; } _cfg; typedef struct { int status; apr_time_t date, expire, request_time, response_time; } _info; typedef struct _cache_object { apr_size_t body_size, header_size, header_len, body_len; _cfg *sconf; server_rec *server; /* XXX we're only doing this to let a ap_log * below NOTICE; which otherwise get * surpresed at s==null */ char *body, *header; char bname[22 + 2 + 1]; /* base 64 md5 with prefixed * 'B_' for body and a * trailing \0 */ char hname[22 + 2 + 1]; } _cache_object_t; static void * _create_config(apr_pool_t * p, server_rec * s) { _cfg *sconf = apr_pcalloc(p, sizeof(_cfg)); sconf->servers = apr_array_make(p, 10, sizeof(const char *)); sconf->minSize = MEMCACHED_MIN_SIZE; sconf->maxSize = MEMCACHED_MAX_SIZE; sconf->ttl = MC_DEFAULT_SERVER_TTL; sconf->min = MC_DEFAULT_SERVER_MIN; sconf->smax = MC_DEFAULT_SERVER_SMAX; sconf->max = MC_DEFAULT_SERVER_MAX; sconf->memctx = NULL; /* initialized on post init - so we also have * a quick 'opt out' detection when not * configured. */ return sconf; }; static const char * _add_array_slot(cmd_parms * cmd, void *struct_ptr, const char *string) { _cfg *sconf = ap_get_module_config(cmd->server->module_config, &memcached_cache_module); int offset = (int) (long) cmd->info; apr_array_header_t ** arrayp; arrayp = (apr_array_header_t **)((char *)sconf + offset); *(const char **) (apr_array_push(*arrayp)) = string; return NULL; }; static const char * _set_int_slot(cmd_parms * cmd, void *struct_ptr, const char *arg) { _cfg *sconf = ap_get_module_config(cmd->server->module_config, &memcached_cache_module); int offset = (int) (long) cmd->info; char *error_str = NULL; char *endptr; *(int *) ((char *) sconf + offset) = strtol(arg, &endptr, 10); if ((*arg == '\0') || (*endptr != '\0')) { error_str = apr_psprintf(cmd->pool, "Invalid value for directive %s, expected integer", cmd->directive->directive); } return error_str; }; #ifndef _ #define _(x) #x #endif static const command_rec _cmds[] = { AP_INIT_ITERATE("MemcachedServerList", _add_array_slot, (void *) APR_OFFSETOF(_cfg, servers), RSRC_CONF, "Space separate list of memcached servers with optional colon separated port numbers."), AP_INIT_TAKE1("MemcachedMinObjectSize", _set_int_slot, (void *) APR_OFFSETOF(_cfg, minSize), RSRC_CONF, "The minimum size (in bytes) of an object to be placed in the cache, " "0 implies no lower limit (default " _(MEMCACHED_MIN_SIZE) ")."), AP_INIT_TAKE1("MemcachedMaxObjectSize", _set_int_slot, (void *) APR_OFFSETOF(_cfg, maxSize), RSRC_CONF, "The maximum size (in bytes) of an object to be placed in the cache, " "0 implies no upper limit (default " _(MEMCACHED_MAX_SIZE) ")."), AP_INIT_TAKE1("MemcachedMinServers", _set_int_slot, (void *) APR_OFFSETOF(_cfg, min), RSRC_CONF, "Minimal number of memcached to keep open at all times" "0 implies no upper limit (default " _(MC_DEFAULT_SERVER_MIN) ")."), AP_INIT_TAKE1("MemcachedSoftMaxServers", _set_int_slot, (void *) APR_OFFSETOF(_cfg, smax), RSRC_CONF, "Max number of connections which should be kept open, " "0 implies no upper limit (default " _(MC_DEFAULT_SERVER_SMAX) ")."), AP_INIT_TAKE1("MemcachedMaxServers", _set_int_slot, (void *) APR_OFFSETOF(_cfg, max), RSRC_CONF, "Max number of connections which can be kept open; equal or smaller than the systems thread limit, " "0 implies no upper limit (default " _(MC_DEFAULT_SERVER_MAX) ")."), AP_INIT_TAKE1("MemcachedSoftExceededTTL", _set_int_slot, (void *) APR_OFFSETOF(_cfg, ttl), RSRC_CONF, "Maximum amount of time (in seconds) that a connection may be kept open when exceeding the soft limit, " "0 implies no upper limit (default " _(MC_DEFAULT_SERVER_TTL) ")."), {NULL} }; static int _post_config(apr_pool_t * p, apr_pool_t * plog, apr_pool_t * ptemp, server_rec * s) { _cfg *sconf = ap_get_module_config(s->module_config, &memcached_cache_module); apr_array_header_t *arr = sconf->servers; int i, nservers; apr_memcache_server_t *st; apr_status_t rv; int thread_limit = 0; nservers = arr->nelts; ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit); if (sconf->max) thread_limit = (sconf->max) > thread_limit ? thread_limit : sconf->max; if (thread_limit < sconf->max) { ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, ME ": MemcachedMaxServers value (%d) capped to the MPM imposed thread limit of %d.", sconf->max, thread_limit); sconf->max = thread_limit; }; if (sconf->smax > sconf->max) { sconf->smax = sconf->max; ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, ME ": MemcachedSoftMaxServers value capped to MemcachedMaxServers (%d)", sconf->smax); } if (sconf->min > sconf->smax) { sconf->min = sconf->smax; ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, ME ": MemcachedMinServers value capped to MemcachedSoftMaxServers (%d)", sconf->min); } if (nservers == 0) { ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, ME ": no memcached servers specified (MemcachedServerList missing?)"); return -1; } rv = apr_memcache_create(p, nservers, 0, &(sconf->memctx)); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, ME ": Failed to create Memcached for '%d' servers.", nservers); return -1; } for (i = 0; i < arr->nelts; i++) { int port = MC_DEFAULT_SERVER_PORT; char *q, *server = apr_pstrdup(p, ((const char **) arr->elts)[i]); if ((q = rindex(server, ':'))) { *q++ = '\0'; port = atoi(q); }; rv = apr_memcache_server_create(p, server, port, sconf->min, sconf->smax, thread_limit, sconf->ttl, &st); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, ME ": Failed to create memcached: %s:%d", server, port); return -1; } rv = apr_memcache_add_server(sconf->memctx, st); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, ME ": Failed to Add Server: %s:%d", server, port); return -1; } ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "Memcache server <%s:%d> configured and " "added to the pool as # %d", server, port, i); } ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, ME ": %d Memcached server%s in the pool " "(connections min %d < soft max %d (for up to ttl %d sec) < hard max %d)", nservers, (nservers == 1) ? "" : "s", sconf->min, sconf->smax, sconf->ttl, thread_limit); return OK; } static cache_object_t * _make_cache_object(apr_pool_t * pool, _cfg * sconf, server_rec * s, const char *key) { unsigned char digest[APR_MD5_DIGESTSIZE]; unsigned char tmp[22 + 1]; /* md5 with trailing \0 */ static const char enc_table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_@"; int i, k, x; /* Allocate and initialize cache_object_t and my cache_object_t */ cache_object_t *obj = apr_pcalloc(pool, sizeof(*obj)); _cache_object_t *_my_obj; obj->key = apr_pstrdup(pool, key); obj->vobj = _my_obj = apr_pcalloc(pool, sizeof(*_my_obj)); _my_obj->sconf = sconf; _my_obj->server = s; /* * Key is guaranteed to fit a unsigned char * digest[APR_MD5_DIGESTSIZE] - with one extra \0 */ apr_md5(digest, key, strlen(key)); /* XXX move into APR_Util */ /* * encode 128 bits as 22 characters, using a modified uuencoding the * encoding is 3 bytes -> 4 characters* i.e. 128 bits is 5 x 3 bytes * + 1 byte -> 5 * 4 characters + 2 characters */ for (i = 0, k = 0; i < 15; i += 3) { x = (digest[i] << 16) | (digest[i + 1] << 8) | digest[i + 2]; tmp[k++] = enc_table[x >> 18]; tmp[k++] = enc_table[(x >> 12) & 0x3f]; tmp[k++] = enc_table[(x >> 6) & 0x3f]; tmp[k++] = enc_table[x & 0x3f]; } /* one byte left */ x = digest[15]; tmp[k++] = enc_table[x >> 2]; /* use up 6 bits */ tmp[k++] = enc_table[(x << 4) & 0x3f]; tmp[k] = '\0'; /* pre-create two keys - one for the header and one for the body. */ _my_obj->hname[0] = 'H'; _my_obj->hname[1] = '_'; strcpy(_my_obj->hname + 2, (char *) tmp); _my_obj->bname[0] = 'B'; _my_obj->bname[1] = '_'; strcpy(_my_obj->bname + 2, (char *) tmp); return obj; } static int _create_entity(cache_handle_t * h, request_rec * r, const char *key, apr_off_t len) { _cfg *sconf = ap_get_module_config(r->server->module_config, &memcached_cache_module); _cache_object_t *_my_obj; if (sconf->memctx == NULL) { ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server, ME ": CacheEnable memcached '%s' active " "but no memcached servers specified (Missing MemcachedServerList?)", r->uri); return DECLINED;/* we're not configured */ } /* Early cut-off if we already have a len set (len=-1 when not) */ if ((len > sconf->maxSize) && (sconf->maxSize)) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, ME ": URL %s failed the size check (too large, %" APR_OFF_T_FMT " > %d)", key, len, sconf->maxSize); return DECLINED; }; if (len >= 0 && len < sconf->minSize) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, ME ": URL %s failed the size check (too small) " "(%" APR_OFF_T_FMT " < %d)", key, len, sconf->minSize); return DECLINED; } h->cache_obj = _make_cache_object(r->pool, sconf, r->server, key); _my_obj = (_cache_object_t *) h->cache_obj->vobj; _my_obj->header_size = 1024 * 16; _my_obj->body_size = (len > 1024 * 16) ? len + 1024 : (1024 * 16); _my_obj->header = apr_palloc(r->pool, _my_obj->header_size); _my_obj->body = apr_palloc(r->pool, _my_obj->body_size); _my_obj->body_len = _my_obj->header_len = 0; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, ME ": created a cache object for %s, %s/%s", key, _my_obj->hname, _my_obj->bname); return OK; } static int _open_entity(cache_handle_t * h, request_rec * r, const char *key) { _cfg *sconf = ap_get_module_config(r->server->module_config, &memcached_cache_module); _cache_object_t *_my_obj; if (sconf->memctx == NULL) { ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server, ME ": CacheEnable memcached '%s' active " "but no memcached servers specified (Missing MemcachedServerList?)", r->uri); return DECLINED;/* we're not configured */ } h->cache_obj = _make_cache_object(r->pool, sconf, r->server, key); _my_obj = (_cache_object_t *) h->cache_obj->vobj; _my_obj->header_size = 0; _my_obj->body_size = 0; _my_obj->header = NULL; _my_obj->body = NULL; _my_obj->body_len = _my_obj->header_len = 0; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, ME ": opened a cache object for %s, %s/%s", key, _my_obj->hname, _my_obj->bname); return OK; } static void * _prealloc(apr_pool_t * p, void *pold, int oldlen, int len) { apr_pool_t *pnew = apr_palloc(p, len); memcpy(pnew, pold, oldlen); return pnew; } static apr_size_t _serialize_table(apr_pool_t * pool, void **outp, apr_size_t * size, apr_table_t * table, int len) { apr_table_entry_t *elts; char *p; int i, j, l; elts = (apr_table_entry_t *) apr_table_elts(table)->elts; l = len + 2 * 80 * apr_table_elts(table)->nelts; if ((*outp == NULL) || (*size == 0) || (l / 4 > *size)) { p = apr_palloc(pool, l); if (*outp && len) memcpy(p, *outp, len); *size = l; *outp = p; } else { l = *size; p = *outp; } for (j = len, i = 0; i < apr_table_elts(table)->nelts; ++i) { if (elts[i].key != NULL) { int n = strlen(elts[i].key); int m = strlen(elts[i].val); if (j + n + m + 2 > l) { int nl = (l > 1024 * 32) ? l + 8 * 1024 : l * 2; p = _prealloc(pool, (void *) p, l, nl); l = nl; *outp = p; *size = l; } /* note -- strcpy -- so we also create the \0 */ strcpy(p + j, elts[i].key); j += n + 1; strcpy(p + j, elts[i].val); j += m + 1; }; }; /* Mark end of (this) array) with an empty key */ p[j++] = '\0'; return j; /* return length */ } static apr_size_t _deserialize_table(apr_pool_t * pool, apr_table_t ** tablep, char *in, apr_size_t at, apr_size_t len) { int j = at; if (*tablep == NULL) *tablep = apr_table_make(pool, 20); while ((j < len) && in[j]) { char *key = in + j; j += strlen(key) + 1; char *val = in + j; j += strlen(val) + 1; apr_table_add(*tablep, key, val); } return j + 1; } static apr_status_t _store_headers(cache_handle_t * h, request_rec * r, cache_info * info) { cache_object_t *obj = h->cache_obj; _cache_object_t *_my_obj = (_cache_object_t *) obj->vobj; apr_status_t rv; _info glob; if (r->headers_out) { /* Table with the cachable headers */ apr_table_t *headers_out = ap_cache_cacheable_hdrs_out( r->pool, r->headers_out, r->server); /* If not set in headers_out, set Content-Type */ if (!apr_table_get(headers_out, "Content-Type") && r->content_type) { apr_table_setn(headers_out, "Content-Type", ap_make_content_type(r, r->content_type)); } headers_out = apr_table_overlay(r->pool, headers_out, r->err_headers_out); /* Serialize the headers into something we can store */ _my_obj->header_len = _serialize_table(r->pool, (void **) &(_my_obj->header), &(_my_obj->header_size), headers_out, 0); } if (r->headers_in) { apr_table_t *headers_in; headers_in = ap_cache_cacheable_hdrs_out(r->pool, r->headers_in, r->server); _my_obj->header_len = _serialize_table(r->pool, (void **) &(_my_obj->header), &(_my_obj->header_size), headers_in, _my_obj->header_len); } /* and concatenate a binary glob with the key info data */ glob.status = htonl(info->status); glob.date = htonl(info->date); glob.expire = htonl(info->expire); glob.request_time = htonl(info->request_time); glob.response_time = htonl(info->response_time); if (_my_obj->header_size < _my_obj->header_len + sizeof(glob)) { int len = _my_obj->header_len + sizeof(glob); _my_obj->header = _prealloc(r->pool, _my_obj->header, _my_obj->header_len, len); _my_obj->header_size = len; }; memcpy(_my_obj->header + _my_obj->header_len, (void *) &glob, sizeof(glob)); _my_obj->header_len += sizeof(glob); rv = apr_memcache_set(_my_obj->sconf->memctx, _my_obj->hname, _my_obj->header, _my_obj->header_len, timeout, flags); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, ME ": Failed to store headers for %s", h->cache_obj->key); return rv; } ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, ME ": Stored %" APR_SIZE_T_FMT " bytes of Header for URL %s, cached as %s.", _my_obj->header_len, h->cache_obj->key, _my_obj->hname); return APR_SUCCESS; } static apr_status_t _store_body(cache_handle_t * h, request_rec * r, apr_bucket_brigade * bb) { apr_bucket *e; apr_status_t rv; _cache_object_t *_my_obj = (_cache_object_t *) h->cache_obj->vobj; _cfg *sconf = ap_get_module_config(r->server->module_config, &memcached_cache_module); if (sconf->memctx == NULL) { return APR_EGENERAL; /* we're not configured */ } /* XXX we used to use apr_brigade_pflatten() here -- but then we get an indication of something * beeing tooo large very late in the game. Is that really true - or can we generally rely on * lenght before we call apr_brigade_pflatten() -- which would simply below a lOT. */ /* * apr_brigade_to_iovec()-- would be ideal if the memcache would * accept such directly. as then we 'd be able to skip virtually all * of below - but for the size issues. */ for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) { const char *str; apr_size_t length; rv = apr_bucket_read(e, &str, &length, APR_BLOCK_READ); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, ME ":Error when reading bucket for URL %s", h->cache_obj->key); return rv; } if ((sconf->maxSize) && (_my_obj->body_len + length > sconf->maxSize)) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, ME ":Not caching URL %s, too large", h->cache_obj->key); return APR_EGENERAL; }; if (_my_obj->body_len + length > _my_obj->body_size) { int ns; if (_my_obj->body_len > 1024 * 1024) ns = _my_obj->body_len + 1024 * 1024; else ns = _my_obj->body_len * 2; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, ME ":Increasing tempory memory buffer for %s to %d kB", h->cache_obj->key, ns >> 10); _my_obj->body = _prealloc(r->pool, (void *) _my_obj->body, _my_obj->body_len, ns); _my_obj->body_len = ns; }; memcpy(_my_obj->body + _my_obj->body_len, str, length); _my_obj->body_len += length; } /* * Was this the final bucket? If yes, try to store.. */ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) { if (r->connection->aborted || r->no_cache) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server, ME ": Discarding body for URL %s " "because connection has been aborted.", h->cache_obj->key); /* * Remove the intermediate cache file and return * non-APR_SUCCESS */ return APR_EGENERAL; } if (_my_obj->body_len < sconf->minSize) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, ME ": URL %s failed the size check - really too small " "(%" APR_SIZE_T_FMT "<%d)", h->cache_obj->key, _my_obj->body_len, sconf->minSize); return APR_EGENERAL; } if ((sconf->maxSize) && (_my_obj->body_len > sconf->maxSize)) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, ME ": URL %s failed the size check - really too large " "(%" APR_SIZE_T_FMT "<%d)", h->cache_obj->key, _my_obj->body_len, sconf->maxSize); return APR_EGENERAL; } ///actual storing.. rv = apr_memcache_set(sconf->memctx, _my_obj->bname, _my_obj->body, _my_obj->body_len, timeout, flags); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, ME ": failed to store %" APR_SIZE_T_FMT " bytes in memcachd URI %s Key %s", _my_obj->body_len, h->cache_obj->key, _my_obj->bname); return rv; } ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, ME ": Stored %" APR_SIZE_T_FMT " bytes of Body for URL %s, cached as %s.", _my_obj->body_len, h->cache_obj->key, _my_obj->bname); } return APR_SUCCESS; } static int _remove_entity(cache_handle_t * h) { /* * Null out the cache object pointer so next time we start from * scratch */ _cache_object_t *_my_obj = (_cache_object_t *) h->cache_obj->vobj; h->cache_obj = NULL; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, _my_obj->server, ME ": removed a cache object"); return OK; } static int _remove_url(cache_handle_t * h, apr_pool_t * p) { apr_status_t rv; _cache_object_t *_my_obj = (_cache_object_t *) h->cache_obj->vobj; if (!_my_obj || !_my_obj->sconf || !_my_obj->sconf->memctx) return DECLINED; rv = apr_memcache_delete(_my_obj->sconf->memctx, _my_obj->hname, _my_obj->sconf->blankout); rv = apr_memcache_delete(_my_obj->sconf->memctx, _my_obj->bname, _my_obj->sconf->blankout); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, _my_obj->server, ME ": removed a cache url"); return OK; } /* * @@@: XXX: FIXME: currently the headers are passed thru un-merged. Is that * okay, or should they be collapsed where possible? */ static apr_status_t _recall_headers(cache_handle_t * h, request_rec * r) { _cache_object_t *_my_obj = (_cache_object_t *) h->cache_obj->vobj; apr_size_t len, at; char *buff; _info glob; apr_status_t rv; /* This case should not happen... */ if (!_my_obj || !_my_obj->sconf || !_my_obj->sconf->memctx) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, ME ": in _recall_headers() - but no private object (bug)"); return APR_NOTFOUND; }; h->req_hdrs = NULL; h->resp_hdrs = NULL; rv = apr_memcache_getp(_my_obj->sconf->memctx, r->pool, _my_obj->hname, &buff, &len, &flags); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, ME ": header cache miss for %s", _my_obj->hname); return APR_NOTFOUND; } at = _deserialize_table(r->pool, &(h->resp_hdrs), buff, 0, len); at = _deserialize_table(r->pool, &(h->req_hdrs), buff, at, len); /* XXX we propably want to rewrite this as ascii and/or combine with open/create */ if (len - at != sizeof(_info)) { ap_log_error(APLOG_MARK, APLOG_CRIT, 0, r->server, "glob lost"); return APR_NOTFOUND; }; memcpy((void *) &glob, buff + at, sizeof(glob)); h->cache_obj->info.status = ntohl(glob.status); h->cache_obj->info.date = ntohl(glob.date); h->cache_obj->info.expire = ntohl(glob.expire); h->cache_obj->info.request_time = ntohl(glob.request_time); h->cache_obj->info.response_time = ntohl(glob.response_time); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, ME ": Recalled %" APR_SIZE_T_FMT " bytes of headers " "for URL %s from key %s", len, h->cache_obj->key, _my_obj->hname); return APR_SUCCESS; } static apr_status_t _recall_body(cache_handle_t * h, apr_pool_t * p, apr_bucket_brigade * bb) { char *buff; _cache_object_t *_my_obj = (_cache_object_t *) h->cache_obj->vobj; apr_size_t len; apr_bucket *e; apr_status_t rv; /* This case should not happen... */ if (!_my_obj || !_my_obj->sconf || !_my_obj->sconf->memctx) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, _my_obj->server, ME ": in _recall_body() - but no private object (bug)"); return APR_NOTFOUND; }; rv = apr_memcache_getp(_my_obj->sconf->memctx, p, _my_obj->bname, &buff, &len, &flags); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, _my_obj->server, ME ": body cache miss for %s", _my_obj->bname); return APR_NOTFOUND; }; e = apr_bucket_immortal_create(buff, len, bb->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, e); e = apr_bucket_eos_create(bb->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, e); /* XXX server object */ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, _my_obj->server, ME ": Recalled %" APR_SIZE_T_FMT " bytes of body for URL %s from key %s", len, h->cache_obj->key, _my_obj->bname); return APR_SUCCESS; } static const cache_provider _provider = { &_remove_entity, &_store_headers, &_store_body, &_recall_headers, &_recall_body, &_create_entity, &_open_entity, &_remove_url, }; static void _hooks(apr_pool_t * p) { ap_hook_post_config(_post_config, NULL, NULL, APR_HOOK_MIDDLE); ap_register_provider(p, CACHE_PROVIDER_GROUP, "memcached", "0", &_provider); } module AP_MODULE_DECLARE_DATA memcached_cache_module = { STANDARD20_MODULE_STUFF, NULL, /* create per-directory config structure */ NULL, /* merge per-directory config structures */ _create_config, /* create per-server config structure */ NULL, /* merge per-server config structures */ _cmds, /* command apr_table_t */ _hooks };