]> granicus.if.org Git - apache/blob - modules/cache/mod_socache_memcache.c
With the current implementation, it is likely to connect/close a socket with the...
[apache] / modules / cache / mod_socache_memcache.c
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 (15*1000*1000)        /* 15 seconds */
55 #endif
56
57 module AP_MODULE_DECLARE_DATA socache_memcache_module;
58
59 typedef struct {
60     unsigned int ttl;
61 } socache_mc_svr_cfg;
62
63 struct ap_socache_instance_t {
64     const char *servers;
65     apr_memcache_t *mc;
66     const char *tag;
67     apr_size_t taglen; /* strlen(tag) + 1 */
68 };
69
70 static const char *socache_mc_create(ap_socache_instance_t **context,
71                                      const char *arg,
72                                      apr_pool_t *tmp, apr_pool_t *p)
73 {
74     ap_socache_instance_t *ctx;
75
76     *context = ctx = apr_palloc(p, sizeof *ctx);
77
78     if (!arg || !*arg) {
79         return "List of server names required to create memcache socache.";
80     }
81
82     ctx->servers = apr_pstrdup(p, arg);
83
84     return NULL;
85 }
86
87 static apr_status_t socache_mc_init(ap_socache_instance_t *ctx,
88                                     const char *namespace,
89                                     const struct ap_socache_hints *hints,
90                                     server_rec *s, apr_pool_t *p)
91 {
92     apr_status_t rv;
93     int thread_limit = 0;
94     apr_uint16_t nservers = 0;
95     char *cache_config;
96     char *split;
97     char *tok;
98
99     socache_mc_svr_cfg *sconf = ap_get_module_config(s->module_config,
100                                                      &socache_memcache_module);
101
102     ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit);
103
104     /* Find all the servers in the first run to get a total count */
105     cache_config = apr_pstrdup(p, ctx->servers);
106     split = apr_strtok(cache_config, ",", &tok);
107     while (split) {
108         nservers++;
109         split = apr_strtok(NULL,",", &tok);
110     }
111
112     rv = apr_memcache_create(p, nservers, 0, &ctx->mc);
113     if (rv != APR_SUCCESS) {
114         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00785)
115                      "Failed to create Memcache Object of '%d' size.",
116                      nservers);
117         return rv;
118     }
119
120     /* Now add each server to the memcache */
121     cache_config = apr_pstrdup(p, ctx->servers);
122     split = apr_strtok(cache_config, ",", &tok);
123     while (split) {
124         apr_memcache_server_t *st;
125         char *host_str;
126         char *scope_id;
127         apr_port_t port;
128
129         rv = apr_parse_addr_port(&host_str, &scope_id, &port, split, p);
130         if (rv != APR_SUCCESS) {
131             ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00786)
132                          "Failed to Parse memcache Server: '%s'", split);
133             return rv;
134         }
135
136         if (host_str == NULL) {
137             ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00787)
138                          "Failed to Parse Server, "
139                          "no hostname specified: '%s'", split);
140             return APR_EINVAL;
141         }
142
143         if (port == 0) {
144             port = MC_DEFAULT_SERVER_PORT;
145         }
146
147         rv = apr_memcache_server_create(p,
148                                         host_str, port,
149                                         MC_DEFAULT_SERVER_MIN,
150                                         MC_DEFAULT_SERVER_SMAX,
151                                         thread_limit,
152                                         sconf->ttl,
153                                         &st);
154         if (rv != APR_SUCCESS) {
155             ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00788)
156                          "Failed to Create memcache Server: %s:%d",
157                          host_str, port);
158             return rv;
159         }
160
161         rv = apr_memcache_add_server(ctx->mc, st);
162         if (rv != APR_SUCCESS) {
163             ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00789)
164                          "Failed to Add memcache Server: %s:%d",
165                          host_str, port);
166             return rv;
167         }
168
169         split = apr_strtok(NULL,",", &tok);
170     }
171
172     ctx->tag = apr_pstrcat(p, namespace, ":", NULL);
173     ctx->taglen = strlen(ctx->tag) + 1;
174
175     /* socache API constraint: */
176     AP_DEBUG_ASSERT(ctx->taglen <= 16);
177
178     return APR_SUCCESS;
179 }
180
181 static void socache_mc_destroy(ap_socache_instance_t *context, server_rec *s)
182 {
183     /* noop. */
184 }
185
186 /* Converts (binary) id into a key prefixed by the predetermined
187  * namespace tag; writes output to key buffer.  Returns non-zero if
188  * the id won't fit in the key buffer. */
189 static int socache_mc_id2key(ap_socache_instance_t *ctx,
190                              const unsigned char *id, unsigned int idlen,
191                              char *key, apr_size_t keylen)
192 {
193     char *cp;
194
195     if (idlen * 2 + ctx->taglen >= keylen)
196         return 1;
197
198     cp = apr_cpystrn(key, ctx->tag, ctx->taglen);
199     ap_bin2hex(id, idlen, cp);
200
201     return 0;
202 }
203
204 static apr_status_t socache_mc_store(ap_socache_instance_t *ctx, server_rec *s,
205                                      const unsigned char *id, unsigned int idlen,
206                                      apr_time_t expiry,
207                                      unsigned char *ucaData, unsigned int nData,
208                                      apr_pool_t *p)
209 {
210     char buf[MC_KEY_LEN];
211     apr_status_t rv;
212
213     if (socache_mc_id2key(ctx, id, idlen, buf, sizeof buf)) {
214         return APR_EINVAL;
215     }
216
217     /* memcache needs time in seconds till expiry; fail if this is not
218      * positive *before* casting to unsigned (apr_uint32_t). */
219     expiry -= apr_time_now();
220     if (apr_time_sec(expiry) <= 0) {
221         return APR_EINVAL;
222     }
223     rv = apr_memcache_set(ctx->mc, buf, (char*)ucaData, nData,
224                           apr_time_sec(expiry), 0);
225
226     if (rv != APR_SUCCESS) {
227         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00790)
228                      "scache_mc: error setting key '%s' "
229                      "with %d bytes of data", buf, nData);
230         return rv;
231     }
232
233     return APR_SUCCESS;
234 }
235
236 static apr_status_t socache_mc_retrieve(ap_socache_instance_t *ctx, server_rec *s,
237                                         const unsigned char *id, unsigned int idlen,
238                                         unsigned char *dest, unsigned int *destlen,
239                                         apr_pool_t *p)
240 {
241     apr_size_t data_len;
242     char buf[MC_KEY_LEN], *data;
243     apr_status_t rv;
244
245     if (socache_mc_id2key(ctx, id, idlen, buf, sizeof buf)) {
246         return APR_EINVAL;
247     }
248
249     /* ### this could do with a subpool, but _getp looks like it will
250      * eat memory like it's going out of fashion anyway. */
251
252     rv = apr_memcache_getp(ctx->mc, p, buf, &data, &data_len, NULL);
253     if (rv) {
254         if (rv != APR_NOTFOUND) {
255             ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00791)
256                          "scache_mc: 'retrieve' FAIL");
257         }
258         return rv;
259     }
260     else if (data_len > *destlen) {
261         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00792)
262                      "scache_mc: 'retrieve' OVERFLOW");
263         return APR_ENOMEM;
264     }
265
266     memcpy(dest, data, data_len);
267     *destlen = data_len;
268
269     return APR_SUCCESS;
270 }
271
272 static apr_status_t socache_mc_remove(ap_socache_instance_t *ctx, server_rec *s,
273                                       const unsigned char *id,
274                                       unsigned int idlen, apr_pool_t *p)
275 {
276     char buf[MC_KEY_LEN];
277     apr_status_t rv;
278
279     if (socache_mc_id2key(ctx, id, idlen, buf, sizeof buf)) {
280         return APR_EINVAL;
281     }
282
283     rv = apr_memcache_delete(ctx->mc, buf, 0);
284
285     if (rv != APR_SUCCESS) {
286         ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(00793)
287                      "scache_mc: error deleting key '%s' ",
288                      buf);
289     }
290
291     return rv;
292 }
293
294 static void socache_mc_status(ap_socache_instance_t *ctx, request_rec *r, int flags)
295 {
296     /* TODO: Make a mod_status handler. meh. */
297 }
298
299 static apr_status_t socache_mc_iterate(ap_socache_instance_t *instance,
300                                        server_rec *s, void *userctx,
301                                        ap_socache_iterator_t *iterator,
302                                        apr_pool_t *pool)
303 {
304     return APR_ENOTIMPL;
305 }
306
307 static const ap_socache_provider_t socache_mc = {
308     "memcache",
309     0,
310     socache_mc_create,
311     socache_mc_init,
312     socache_mc_destroy,
313     socache_mc_store,
314     socache_mc_retrieve,
315     socache_mc_remove,
316     socache_mc_status,
317     socache_mc_iterate
318 };
319
320 #endif /* HAVE_APU_MEMCACHE */
321
322 static void *create_server_config(apr_pool_t *p, server_rec *s)
323 {
324     socache_mc_svr_cfg *sconf = apr_pcalloc(p, sizeof(socache_mc_svr_cfg));
325     
326     sconf->ttl = MC_DEFAULT_SERVER_TTL;
327
328     return sconf;
329 }
330
331 static const char *socache_mc_set_ttl(cmd_parms *cmd, void *dummy,
332                                       const char *arg)
333 {
334     socache_mc_svr_cfg *sconf = ap_get_module_config(cmd->server->module_config,
335                                                      &socache_memcache_module);
336     int i;
337
338     i = atoi(arg);
339
340     if ((i < 1) || (i > 1800)) {
341         return apr_pstrcat(cmd->pool, cmd->cmd->name,
342                            " must be a number between 1 and 1800.", NULL);
343     }
344
345     /* apr_memcache_server_create needs a ttl in usec. */
346     sconf->ttl = i * 1000 * 1000;
347
348     return NULL;
349 }
350
351 static void register_hooks(apr_pool_t *p)
352 {
353 #ifdef HAVE_APU_MEMCACHE
354     ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "memcache",
355                          AP_SOCACHE_PROVIDER_VERSION,
356                          &socache_mc);
357 #endif
358 }
359
360 static const command_rec socache_memcache_cmds[] = {
361     AP_INIT_TAKE1("MemcacheConnTTL", socache_mc_set_ttl, NULL, RSRC_CONF,
362                   "TTL used for the connection with the memcache server(s), in seconds"),
363     { NULL }
364 };
365
366 AP_DECLARE_MODULE(socache_memcache) = {
367     STANDARD20_MODULE_STUFF,
368     NULL,                     /* create per-dir    config structures */
369     NULL,                     /* merge  per-dir    config structures */
370     create_server_config,     /* create per-server config structures */
371     NULL,                     /* merge  per-server config structures */
372     socache_memcache_cmds,    /* table of config file commands       */
373     register_hooks            /* register hooks                      */
374 };