1/* Licensed to the Apache Software Foundation (ASF) under one or more 2* contributor license agreements. See the NOTICE file distributed with 3* this work for additional information regarding copyright ownership. 4* The ASF licenses this file to You under the Apache License, Version 2.0 5* (the "License"); you may not use this file except in compliance with 6* the License. You may obtain a copy of the License at 7* 8* http://www.apache.org/licenses/LICENSE-2.0 9* 10* Unless required by applicable law or agreed to in writing, software 11* distributed under the License is distributed on an "AS IS" BASIS, 12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13* See the License for the specific language governing permissions and 14* limitations under the License. 15*/ 16 17 18#include "httpd.h" 19#include "http_config.h" 20 21#include "apr.h" 22#include "apu_version.h" 23 24/* apr_memcache support requires >= 1.3 */ 25#if APU_MAJOR_VERSION > 1 || \ 26 (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION > 2) 27#define HAVE_APU_MEMCACHE 1 28#endif 29 30#ifdef HAVE_APU_MEMCACHE 31 32#include "ap_socache.h" 33#include "ap_mpm.h" 34#include "http_log.h" 35#include "apr_memcache.h" 36 37/* The underlying apr_memcache system is thread safe.. */ 38#define MC_KEY_LEN 254 39 40#ifndef MC_DEFAULT_SERVER_PORT 41#define MC_DEFAULT_SERVER_PORT 11211 42#endif 43 44 45#ifndef MC_DEFAULT_SERVER_MIN 46#define MC_DEFAULT_SERVER_MIN 0 47#endif 48 49#ifndef MC_DEFAULT_SERVER_SMAX 50#define MC_DEFAULT_SERVER_SMAX 1 51#endif 52 53#ifndef MC_DEFAULT_SERVER_TTL 54#define MC_DEFAULT_SERVER_TTL 600 55#endif 56 57struct ap_socache_instance_t { 58 const char *servers; 59 apr_memcache_t *mc; 60 const char *tag; 61 apr_size_t taglen; /* strlen(tag) + 1 */ 62}; 63 64static const char *socache_mc_create(ap_socache_instance_t **context, 65 const char *arg, 66 apr_pool_t *tmp, apr_pool_t *p) 67{ 68 ap_socache_instance_t *ctx; 69 70 *context = ctx = apr_palloc(p, sizeof *ctx); 71 72 if (!arg || !*arg) { 73 return "List of server names required to create memcache socache."; 74 } 75 76 ctx->servers = apr_pstrdup(p, arg); 77 78 return NULL; 79} 80 81static apr_status_t socache_mc_init(ap_socache_instance_t *ctx, 82 const char *namespace, 83 const struct ap_socache_hints *hints, 84 server_rec *s, apr_pool_t *p) 85{ 86 apr_status_t rv; 87 int thread_limit = 0; 88 apr_uint16_t nservers = 0; 89 char *cache_config; 90 char *split; 91 char *tok; 92 93 ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit); 94 95 /* Find all the servers in the first run to get a total count */ 96 cache_config = apr_pstrdup(p, ctx->servers); 97 split = apr_strtok(cache_config, ",", &tok); 98 while (split) { 99 nservers++; 100 split = apr_strtok(NULL,",", &tok); 101 } 102 103 rv = apr_memcache_create(p, nservers, 0, &ctx->mc); 104 if (rv != APR_SUCCESS) { 105 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00785) 106 "Failed to create Memcache Object of '%d' size.", 107 nservers); 108 return rv; 109 } 110 111 /* Now add each server to the memcache */ 112 cache_config = apr_pstrdup(p, ctx->servers); 113 split = apr_strtok(cache_config, ",", &tok); 114 while (split) { 115 apr_memcache_server_t *st; 116 char *host_str; 117 char *scope_id; 118 apr_port_t port; 119 120 rv = apr_parse_addr_port(&host_str, &scope_id, &port, split, p); 121 if (rv != APR_SUCCESS) { 122 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00786) 123 "Failed to Parse memcache Server: '%s'", split); 124 return rv; 125 } 126 127 if (host_str == NULL) { 128 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00787) 129 "Failed to Parse Server, " 130 "no hostname specified: '%s'", split); 131 return APR_EINVAL; 132 } 133 134 if (port == 0) { 135 port = MC_DEFAULT_SERVER_PORT; 136 } 137 138 rv = apr_memcache_server_create(p, 139 host_str, port, 140 MC_DEFAULT_SERVER_MIN, 141 MC_DEFAULT_SERVER_SMAX, 142 thread_limit, 143 MC_DEFAULT_SERVER_TTL, 144 &st); 145 if (rv != APR_SUCCESS) { 146 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00788) 147 "Failed to Create memcache Server: %s:%d", 148 host_str, port); 149 return rv; 150 } 151 152 rv = apr_memcache_add_server(ctx->mc, st); 153 if (rv != APR_SUCCESS) { 154 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00789) 155 "Failed to Add memcache Server: %s:%d", 156 host_str, port); 157 return rv; 158 } 159 160 split = apr_strtok(NULL,",", &tok); 161 } 162 163 ctx->tag = apr_pstrcat(p, namespace, ":", NULL); 164 ctx->taglen = strlen(ctx->tag) + 1; 165 166 /* socache API constraint: */ 167 AP_DEBUG_ASSERT(ctx->taglen <= 16); 168 169 return APR_SUCCESS; 170} 171 172static void socache_mc_destroy(ap_socache_instance_t *context, server_rec *s) 173{ 174 /* noop. */ 175} 176 177/* Converts (binary) id into a key prefixed by the predetermined 178 * namespace tag; writes output to key buffer. Returns non-zero if 179 * the id won't fit in the key buffer. */ 180static int socache_mc_id2key(ap_socache_instance_t *ctx, 181 const unsigned char *id, unsigned int idlen, 182 char *key, apr_size_t keylen) 183{ 184 char *cp; 185 186 if (idlen * 2 + ctx->taglen >= keylen) 187 return 1; 188 189 cp = apr_cpystrn(key, ctx->tag, ctx->taglen); 190 ap_bin2hex(id, idlen, cp); 191 192 return 0; 193} 194 195static apr_status_t socache_mc_store(ap_socache_instance_t *ctx, server_rec *s, 196 const unsigned char *id, unsigned int idlen, 197 apr_time_t expiry, 198 unsigned char *ucaData, unsigned int nData, 199 apr_pool_t *p) 200{ 201 char buf[MC_KEY_LEN]; 202 apr_status_t rv; 203 204 if (socache_mc_id2key(ctx, id, idlen, buf, sizeof buf)) { 205 return APR_EINVAL; 206 } 207 208 /* In APR-util - unclear what 'timeout' is, as it was not implemented */ 209 rv = apr_memcache_set(ctx->mc, buf, (char*)ucaData, nData, 0, 0); 210 211 if (rv != APR_SUCCESS) { 212 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00790) 213 "scache_mc: error setting key '%s' " 214 "with %d bytes of data", buf, nData); 215 return rv; 216 } 217 218 return APR_SUCCESS; 219} 220 221static apr_status_t socache_mc_retrieve(ap_socache_instance_t *ctx, server_rec *s, 222 const unsigned char *id, unsigned int idlen, 223 unsigned char *dest, unsigned int *destlen, 224 apr_pool_t *p) 225{ 226 apr_size_t data_len; 227 char buf[MC_KEY_LEN], *data; 228 apr_status_t rv; 229 230 if (socache_mc_id2key(ctx, id, idlen, buf, sizeof buf)) { 231 return APR_EINVAL; 232 } 233 234 /* ### this could do with a subpool, but _getp looks like it will 235 * eat memory like it's going out of fashion anyway. */ 236 237 rv = apr_memcache_getp(ctx->mc, p, buf, &data, &data_len, NULL); 238 if (rv) { 239 if (rv != APR_NOTFOUND) { 240 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00791) 241 "scache_mc: 'retrieve' FAIL"); 242 } 243 return rv; 244 } 245 else if (data_len > *destlen) { 246 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00792) 247 "scache_mc: 'retrieve' OVERFLOW"); 248 return APR_ENOMEM; 249 } 250 251 memcpy(dest, data, data_len); 252 *destlen = data_len; 253 254 return APR_SUCCESS; 255} 256 257static apr_status_t socache_mc_remove(ap_socache_instance_t *ctx, server_rec *s, 258 const unsigned char *id, 259 unsigned int idlen, apr_pool_t *p) 260{ 261 char buf[MC_KEY_LEN]; 262 apr_status_t rv; 263 264 if (socache_mc_id2key(ctx, id, idlen, buf, sizeof buf)) { 265 return APR_EINVAL; 266 } 267 268 rv = apr_memcache_delete(ctx->mc, buf, 0); 269 270 if (rv != APR_SUCCESS) { 271 ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(00793) 272 "scache_mc: error deleting key '%s' ", 273 buf); 274 } 275 276 return rv; 277} 278 279static void socache_mc_status(ap_socache_instance_t *ctx, request_rec *r, int flags) 280{ 281 /* TODO: Make a mod_status handler. meh. */ 282} 283 284static apr_status_t socache_mc_iterate(ap_socache_instance_t *instance, 285 server_rec *s, void *userctx, 286 ap_socache_iterator_t *iterator, 287 apr_pool_t *pool) 288{ 289 return APR_ENOTIMPL; 290} 291 292static const ap_socache_provider_t socache_mc = { 293 "memcache", 294 0, 295 socache_mc_create, 296 socache_mc_init, 297 socache_mc_destroy, 298 socache_mc_store, 299 socache_mc_retrieve, 300 socache_mc_remove, 301 socache_mc_status, 302 socache_mc_iterate 303}; 304 305#endif /* HAVE_APU_MEMCACHE */ 306 307static void register_hooks(apr_pool_t *p) 308{ 309#ifdef HAVE_APU_MEMCACHE 310 ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "memcache", 311 AP_SOCACHE_PROVIDER_VERSION, 312 &socache_mc); 313#endif 314} 315 316AP_DECLARE_MODULE(socache_memcache) = { 317 STANDARD20_MODULE_STUFF, 318 NULL, NULL, NULL, NULL, NULL, 319 register_hooks 320}; 321