summaryrefslogtreecommitdiffstats
path: root/modules/cache/mod_socache_memcache.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/cache/mod_socache_memcache.c')
-rw-r--r--modules/cache/mod_socache_memcache.c418
1 files changed, 418 insertions, 0 deletions
diff --git a/modules/cache/mod_socache_memcache.c b/modules/cache/mod_socache_memcache.c
new file mode 100644
index 0000000..f122ba4
--- /dev/null
+++ b/modules/cache/mod_socache_memcache.c
@@ -0,0 +1,418 @@
+/* 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 "httpd.h"
+#include "http_config.h"
+#include "http_protocol.h"
+
+#include "apr.h"
+
+#include "ap_socache.h"
+#include "ap_mpm.h"
+#include "http_log.h"
+#include "apr_memcache.h"
+#include "apr_strings.h"
+#include "mod_status.h"
+
+/* The underlying apr_memcache system is thread safe.. */
+#define MC_KEY_LEN 254
+
+#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_TTL
+#define MC_DEFAULT_SERVER_TTL apr_time_from_sec(15)
+#endif
+
+module AP_MODULE_DECLARE_DATA socache_memcache_module;
+
+typedef struct {
+ apr_uint32_t ttl;
+} socache_mc_svr_cfg;
+
+struct ap_socache_instance_t {
+ const char *servers;
+ apr_memcache_t *mc;
+ const char *tag;
+ apr_size_t taglen; /* strlen(tag) + 1 */
+};
+
+static const char *socache_mc_create(ap_socache_instance_t **context,
+ const char *arg,
+ apr_pool_t *tmp, apr_pool_t *p)
+{
+ ap_socache_instance_t *ctx;
+
+ *context = ctx = apr_palloc(p, sizeof *ctx);
+
+ if (!arg || !*arg) {
+ return "List of server names required to create memcache socache.";
+ }
+
+ ctx->servers = apr_pstrdup(p, arg);
+
+ return NULL;
+}
+
+static apr_status_t socache_mc_init(ap_socache_instance_t *ctx,
+ const char *namespace,
+ const struct ap_socache_hints *hints,
+ server_rec *s, apr_pool_t *p)
+{
+ apr_status_t rv;
+ int thread_limit = 0;
+ apr_uint16_t nservers = 0;
+ char *cache_config;
+ char *split;
+ char *tok;
+
+ socache_mc_svr_cfg *sconf = ap_get_module_config(s->module_config,
+ &socache_memcache_module);
+
+ ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit);
+
+ /* Find all the servers in the first run to get a total count */
+ cache_config = apr_pstrdup(p, ctx->servers);
+ split = apr_strtok(cache_config, ",", &tok);
+ while (split) {
+ nservers++;
+ split = apr_strtok(NULL,",", &tok);
+ }
+
+ rv = apr_memcache_create(p, nservers, 0, &ctx->mc);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00785)
+ "Failed to create Memcache Object of '%d' size.",
+ nservers);
+ return rv;
+ }
+
+ /* Now add each server to the memcache */
+ cache_config = apr_pstrdup(p, ctx->servers);
+ split = apr_strtok(cache_config, ",", &tok);
+ while (split) {
+ apr_memcache_server_t *st;
+ char *host_str;
+ char *scope_id;
+ apr_port_t port;
+
+ rv = apr_parse_addr_port(&host_str, &scope_id, &port, split, p);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00786)
+ "Failed to Parse memcache Server: '%s'", split);
+ return rv;
+ }
+
+ if (host_str == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00787)
+ "Failed to Parse Server, "
+ "no hostname specified: '%s'", split);
+ return APR_EINVAL;
+ }
+
+ if (port == 0) {
+ port = MC_DEFAULT_SERVER_PORT;
+ }
+
+ rv = apr_memcache_server_create(p,
+ host_str, port,
+ MC_DEFAULT_SERVER_MIN,
+ MC_DEFAULT_SERVER_SMAX,
+ thread_limit,
+ sconf->ttl,
+ &st);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00788)
+ "Failed to Create memcache Server: %s:%d",
+ host_str, port);
+ return rv;
+ }
+
+ rv = apr_memcache_add_server(ctx->mc, st);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00789)
+ "Failed to Add memcache Server: %s:%d",
+ host_str, port);
+ return rv;
+ }
+
+ split = apr_strtok(NULL,",", &tok);
+ }
+
+ ctx->tag = apr_pstrcat(p, namespace, ":", NULL);
+ ctx->taglen = strlen(ctx->tag) + 1;
+
+ /* socache API constraint: */
+ AP_DEBUG_ASSERT(ctx->taglen <= 16);
+
+ return APR_SUCCESS;
+}
+
+static void socache_mc_destroy(ap_socache_instance_t *context, server_rec *s)
+{
+ /* noop. */
+}
+
+/* Converts (binary) id into a key prefixed by the predetermined
+ * namespace tag; writes output to key buffer. Returns non-zero if
+ * the id won't fit in the key buffer. */
+static int socache_mc_id2key(ap_socache_instance_t *ctx,
+ const unsigned char *id, unsigned int idlen,
+ char *key, apr_size_t keylen)
+{
+ char *cp;
+
+ if (idlen * 2 + ctx->taglen >= keylen)
+ return 1;
+
+ cp = apr_cpystrn(key, ctx->tag, ctx->taglen);
+ ap_bin2hex(id, idlen, cp);
+
+ return 0;
+}
+
+static apr_status_t socache_mc_store(ap_socache_instance_t *ctx, server_rec *s,
+ const unsigned char *id, unsigned int idlen,
+ apr_time_t expiry,
+ unsigned char *ucaData, unsigned int nData,
+ apr_pool_t *p)
+{
+ char buf[MC_KEY_LEN];
+ apr_status_t rv;
+
+ if (socache_mc_id2key(ctx, id, idlen, buf, sizeof buf)) {
+ return APR_EINVAL;
+ }
+
+ /* memcache needs time in seconds till expiry; fail if this is not
+ * positive *before* casting to unsigned (apr_uint32_t). */
+ expiry -= apr_time_now();
+ if (apr_time_sec(expiry) <= 0) {
+ return APR_EINVAL;
+ }
+ rv = apr_memcache_set(ctx->mc, buf, (char*)ucaData, nData,
+ apr_time_sec(expiry), 0);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00790)
+ "scache_mc: error setting key '%s' "
+ "with %d bytes of data", buf, nData);
+ return rv;
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t socache_mc_retrieve(ap_socache_instance_t *ctx, server_rec *s,
+ const unsigned char *id, unsigned int idlen,
+ unsigned char *dest, unsigned int *destlen,
+ apr_pool_t *p)
+{
+ apr_size_t data_len;
+ char buf[MC_KEY_LEN], *data;
+ apr_status_t rv;
+
+ if (socache_mc_id2key(ctx, id, idlen, buf, sizeof buf)) {
+ return APR_EINVAL;
+ }
+
+ /* ### this could do with a subpool, but _getp looks like it will
+ * eat memory like it's going out of fashion anyway. */
+
+ rv = apr_memcache_getp(ctx->mc, p, buf, &data, &data_len, NULL);
+ if (rv) {
+ if (rv != APR_NOTFOUND) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00791)
+ "scache_mc: 'retrieve' FAIL");
+ }
+ return rv;
+ }
+ else if (data_len > *destlen) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00792)
+ "scache_mc: 'retrieve' OVERFLOW");
+ return APR_ENOMEM;
+ }
+
+ memcpy(dest, data, data_len);
+ *destlen = data_len;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t socache_mc_remove(ap_socache_instance_t *ctx, server_rec *s,
+ const unsigned char *id,
+ unsigned int idlen, apr_pool_t *p)
+{
+ char buf[MC_KEY_LEN];
+ apr_status_t rv;
+
+ if (socache_mc_id2key(ctx, id, idlen, buf, sizeof buf)) {
+ return APR_EINVAL;
+ }
+
+ rv = apr_memcache_delete(ctx->mc, buf, 0);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(00793)
+ "scache_mc: error deleting key '%s' ",
+ buf);
+ }
+
+ return rv;
+}
+
+static void socache_mc_status(ap_socache_instance_t *ctx, request_rec *r, int flags)
+{
+ apr_memcache_t *rc = ctx->mc;
+ int i;
+
+ for (i = 0; i < rc->ntotal; i++) {
+ apr_memcache_server_t *ms;
+ apr_memcache_stats_t *stats;
+ apr_status_t rv;
+ char *br = (!(flags & AP_STATUS_SHORT) ? "<br />" : "");
+
+ ms = rc->live_servers[i];
+
+ ap_rprintf(r, "Memcached server: %s:%d [%s]%s\n", ms->host, (int)ms->port,
+ (ms->status == APR_MC_SERVER_LIVE) ? "Up" : "Down",
+ br);
+ rv = apr_memcache_stats(ms, r->pool, &stats);
+ if (rv != APR_SUCCESS)
+ continue;
+ if (!(flags & AP_STATUS_SHORT)) {
+ ap_rprintf(r, "<b>Version:</b> <i>%s</i> [%u bits], PID: <i>%u</i>, Uptime: <i>%u hrs</i> <br />\n",
+ stats->version , stats->pointer_size, stats->pid, stats->uptime/3600);
+ ap_rprintf(r, "<b>Clients::</b> Structures: <i>%u</i>, Total: <i>%u</i>, Current: <i>%u</i> <br />\n",
+ stats->connection_structures, stats->total_connections, stats->curr_connections);
+ ap_rprintf(r, "<b>Storage::</b> Total Items: <i>%u</i>, Current Items: <i>%u</i>, Bytes: <i>%" APR_UINT64_T_FMT "</i> <br />\n",
+ stats->total_items, stats->curr_items, stats->bytes);
+ ap_rprintf(r, "<b>CPU::</b> System: <i>%u</i>, User: <i>%u</i> <br />\n",
+ (unsigned)stats->rusage_system, (unsigned)stats->rusage_user );
+ ap_rprintf(r, "<b>Cache::</b> Gets: <i>%u</i>, Sets: <i>%u</i>, Hits: <i>%u</i>, Misses: <i>%u</i> <br />\n",
+ stats->cmd_get, stats->cmd_set, stats->get_hits, stats->get_misses);
+ ap_rprintf(r, "<b>Net::</b> Input bytes: <i>%" APR_UINT64_T_FMT "</i>, Output bytes: <i>%" APR_UINT64_T_FMT "</i> <br />\n",
+ stats->bytes_read, stats->bytes_written);
+ ap_rprintf(r, "<b>Misc::</b> Evictions: <i>%" APR_UINT64_T_FMT "</i>, MaxMem: <i>%u</i>, Threads: <i>%u</i> <br />\n",
+ stats->evictions, stats->limit_maxbytes, stats->threads);
+ ap_rputs("<hr><br />\n", r);
+ }
+ else {
+ ap_rprintf(r, "Version: %s [%u bits], PID: %u, Uptime: %u hrs %s\n",
+ stats->version , stats->pointer_size, stats->pid, stats->uptime/3600, br);
+ ap_rprintf(r, "Clients:: Structures: %d, Total: %d, Current: %u %s\n",
+ stats->connection_structures, stats->total_connections, stats->curr_connections, br);
+ ap_rprintf(r, "Storage:: Total Items: %u, Current Items: %u, Bytes: %" APR_UINT64_T_FMT " %s\n",
+ stats->total_items, stats->curr_items, stats->bytes, br);
+ ap_rprintf(r, "CPU:: System: %u, User: %u %s\n",
+ (unsigned)stats->rusage_system, (unsigned)stats->rusage_user , br);
+ ap_rprintf(r, "Cache:: Gets: %u, Sets: %u, Hits: %u, Misses: %u %s\n",
+ stats->cmd_get, stats->cmd_set, stats->get_hits, stats->get_misses, br);
+ ap_rprintf(r, "Net:: Input bytes: %" APR_UINT64_T_FMT ", Output bytes: %" APR_UINT64_T_FMT " %s\n",
+ stats->bytes_read, stats->bytes_written, br);
+ ap_rprintf(r, "Misc:: Evictions: %" APR_UINT64_T_FMT ", MaxMem: %u, Threads: %u %s\n",
+ stats->evictions, stats->limit_maxbytes, stats->threads, br);
+ }
+ }
+
+}
+
+static apr_status_t socache_mc_iterate(ap_socache_instance_t *instance,
+ server_rec *s, void *userctx,
+ ap_socache_iterator_t *iterator,
+ apr_pool_t *pool)
+{
+ return APR_ENOTIMPL;
+}
+
+static const ap_socache_provider_t socache_mc = {
+ "memcache",
+ 0,
+ socache_mc_create,
+ socache_mc_init,
+ socache_mc_destroy,
+ socache_mc_store,
+ socache_mc_retrieve,
+ socache_mc_remove,
+ socache_mc_status,
+ socache_mc_iterate
+};
+
+static void *create_server_config(apr_pool_t *p, server_rec *s)
+{
+ socache_mc_svr_cfg *sconf = apr_pcalloc(p, sizeof(socache_mc_svr_cfg));
+
+ sconf->ttl = MC_DEFAULT_SERVER_TTL;
+
+ return sconf;
+}
+
+static const char *socache_mc_set_ttl(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ apr_interval_time_t ttl;
+ socache_mc_svr_cfg *sconf = ap_get_module_config(cmd->server->module_config,
+ &socache_memcache_module);
+
+ if (ap_timeout_parameter_parse(arg, &ttl, "s") != APR_SUCCESS) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ " has wrong format", NULL);
+ }
+
+ if ((ttl < apr_time_from_sec(0)) || (ttl > apr_time_from_sec(3600))) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ " can only be 0 or up to one hour.", NULL);
+ }
+
+ /* apr_memcache_server_create needs a ttl in usec. */
+ sconf->ttl = ttl;
+
+ return NULL;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "memcache",
+ AP_SOCACHE_PROVIDER_VERSION,
+ &socache_mc);
+}
+
+static const command_rec socache_memcache_cmds[] = {
+ AP_INIT_TAKE1("MemcacheConnTTL", socache_mc_set_ttl, NULL, RSRC_CONF,
+ "TTL used for the connection with the memcache server(s)"),
+ { NULL }
+};
+
+AP_DECLARE_MODULE(socache_memcache) = {
+ STANDARD20_MODULE_STUFF,
+ NULL, /* create per-dir config structures */
+ NULL, /* merge per-dir config structures */
+ create_server_config, /* create per-server config structures */
+ NULL, /* merge per-server config structures */
+ socache_memcache_cmds, /* table of config file commands */
+ register_hooks /* register hooks */
+};