/* 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 "apu_version.h"
/* apr_memcache support requires >= 1.3 */
#if APU_MAJOR_VERSION > 1 || \
(APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION > 2)
#define HAVE_APU_MEMCACHE 1
#endif
#ifdef HAVE_APU_MEMCACHE
#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) ? "
" : "");
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, "Version: %s [%u bits], PID: %u, Uptime: %u hrs
\n",
stats->version , stats->pointer_size, stats->pid, stats->uptime/3600);
ap_rprintf(r, "Clients:: Structures: %u, Total: %u, Current: %u
\n",
stats->connection_structures, stats->total_connections, stats->curr_connections);
ap_rprintf(r, "Storage:: Total Items: %u, Current Items: %u, Bytes: %" APR_UINT64_T_FMT "
\n",
stats->total_items, stats->curr_items, stats->bytes);
ap_rprintf(r, "CPU:: System: %u, User: %u
\n",
(unsigned)stats->rusage_system, (unsigned)stats->rusage_user );
ap_rprintf(r, "Cache:: Gets: %u, Sets: %u, Hits: %u, Misses: %u
\n",
stats->cmd_get, stats->cmd_set, stats->get_hits, stats->get_misses);
ap_rprintf(r, "Net:: Input bytes: %" APR_UINT64_T_FMT ", Output bytes: %" APR_UINT64_T_FMT "
\n",
stats->bytes_read, stats->bytes_written);
ap_rprintf(r, "Misc:: Evictions: %" APR_UINT64_T_FMT ", MaxMem: %u, Threads: %u
\n",
stats->evictions, stats->limit_maxbytes, stats->threads);
ap_rputs("