1 /******************************************************************************
2  * $Id$
3  *
4  * Project:  MapServer
5  * Purpose:  MapCache tile caching support file: memcache cache backend.
6  * Author:   Thomas Bonfort and the MapServer team.
7  *
8  ******************************************************************************
9  * Copyright (c) 1996-2011 Regents of the University of Minnesota.
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a
12  * copy of this software and associated documentation files (the "Software"),
13  * to deal in the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15  * and/or sell copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice shall be included in
19  * all copies of this Software or works derived from this Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27  * DEALINGS IN THE SOFTWARE.
28  *****************************************************************************/
29 
30 #include "mapcache.h"
31 #ifdef USE_MEMCACHE
32 
33 #include <apr_memcache.h>
34 
35 typedef struct mapcache_cache_memcache mapcache_cache_memcache;
36 /**\class mapcache_cache_memcache
37  * \brief a mapcache_cache on memcached servers
38  * \implements mapcache_cache
39  */
40 
41 struct mapcache_cache_memcache_server {
42     char* host;
43     int port;
44 };
45 
46 struct mapcache_cache_memcache {
47   mapcache_cache cache;
48   int nservers;
49   struct mapcache_cache_memcache_server *servers;
50   int detect_blank;
51 };
52 
53 struct mapcache_memcache_conn_param {
54   mapcache_cache_memcache *cache;
55 };
56 
57 struct mapcache_memcache_pooled_connection {
58   apr_memcache_t *memcache;
59   apr_pool_t *pool;
60 };
61 
mapcache_memcache_connection_constructor(mapcache_context * ctx,void ** conn_,void * params)62 void mapcache_memcache_connection_constructor(mapcache_context *ctx, void **conn_, void *params) {
63   struct mapcache_memcache_conn_param *param = params;
64   mapcache_cache_memcache *cache = param->cache;
65   struct mapcache_memcache_pooled_connection *pc;
66   int i;
67   pc = calloc(1,sizeof(struct mapcache_memcache_pooled_connection));
68   apr_pool_create(&pc->pool,NULL);
69   if(APR_SUCCESS != apr_memcache_create(pc->pool, cache->nservers, 0, &(pc->memcache))) {
70     ctx->set_error(ctx,500,"cache %s: failed to create memcache backend", cache->cache.name);
71     return;
72   }
73   for(i=0; i<param->cache->nservers; i++) {
74     apr_memcache_server_t *server;
75     if(APR_SUCCESS != apr_memcache_server_create(pc->pool,cache->servers[i].host,cache->servers[i].port,4,5,50,10000,&server)) {
76       ctx->set_error(ctx,500,"cache %s: failed to create server %s:%d",cache->cache.name,cache->servers[i].host,cache->servers[i].port);
77       return;
78     }
79     if(APR_SUCCESS != apr_memcache_add_server(pc->memcache,server)) {
80       ctx->set_error(ctx,500,"cache %s: failed to add server %s:%d",cache->cache.name,cache->servers[i].host,cache->servers[i].port);
81       return;
82     }
83   }
84   *conn_ = pc;
85 }
86 
mapcache_memcache_connection_destructor(void * conn_)87 void mapcache_memcache_connection_destructor(void *conn_) {
88   struct mapcache_memcache_pooled_connection *pc = conn_;
89   apr_pool_destroy(pc->pool);
90   free(pc);
91 }
92 
_mapcache_memcache_get_conn(mapcache_context * ctx,mapcache_cache_memcache * cache,mapcache_tile * tile)93 static mapcache_pooled_connection* _mapcache_memcache_get_conn(mapcache_context *ctx,
94         mapcache_cache_memcache *cache, mapcache_tile *tile) {
95   mapcache_pooled_connection *pc;
96   struct mapcache_memcache_conn_param param;
97 
98   param.cache = cache;
99 
100   pc = mapcache_connection_pool_get_connection(ctx,cache->cache.name, mapcache_memcache_connection_constructor, mapcache_memcache_connection_destructor, &param);
101   return pc;
102 }
103 
_mapcache_memcache_release_conn(mapcache_context * ctx,mapcache_pooled_connection * con)104 static void _mapcache_memcache_release_conn(mapcache_context *ctx, mapcache_pooled_connection *con) {
105   mapcache_connection_pool_release_connection(ctx, con);
106 }
107 
_mapcache_cache_memcache_has_tile(mapcache_context * ctx,mapcache_cache * pcache,mapcache_tile * tile)108 static int _mapcache_cache_memcache_has_tile(mapcache_context *ctx, mapcache_cache *pcache, mapcache_tile *tile)
109 {
110   char *key;
111   char *tmpdata;
112   int rv;
113   size_t tmpdatasize;
114   mapcache_cache_memcache *cache = (mapcache_cache_memcache*)pcache;
115   mapcache_pooled_connection *pc;
116   struct mapcache_memcache_pooled_connection *mpc;
117   pc = _mapcache_memcache_get_conn(ctx,cache,tile);
118   if(GC_HAS_ERROR(ctx))
119     return MAPCACHE_FALSE;
120   mpc = pc->connection;
121 
122   key = mapcache_util_get_tile_key(ctx, tile, NULL, " \r\n\t\f\e\a\b","#");
123   if(GC_HAS_ERROR(ctx)) {
124     rv = MAPCACHE_FALSE;
125     goto cleanup;
126   }
127   rv = apr_memcache_getp(mpc->memcache,ctx->pool,key,&tmpdata,&tmpdatasize,NULL);
128   if(rv != APR_SUCCESS) {
129     rv = MAPCACHE_FALSE;
130     goto cleanup;
131   }
132   if(tmpdatasize == 0) {
133     rv = MAPCACHE_FALSE;
134     goto cleanup;
135   }
136   rv = MAPCACHE_TRUE;
137 cleanup:
138   _mapcache_memcache_release_conn(ctx,pc);
139   return rv;
140 }
141 
_mapcache_cache_memcache_delete(mapcache_context * ctx,mapcache_cache * pcache,mapcache_tile * tile)142 static void _mapcache_cache_memcache_delete(mapcache_context *ctx, mapcache_cache *pcache, mapcache_tile *tile)
143 {
144   char *key;
145   int rv;
146   char errmsg[120];
147   mapcache_cache_memcache *cache = (mapcache_cache_memcache*)pcache;
148   mapcache_pooled_connection *pc;
149   struct mapcache_memcache_pooled_connection *mpc;
150   pc = _mapcache_memcache_get_conn(ctx,cache,tile);
151   GC_CHECK_ERROR(ctx);
152   mpc = pc->connection;
153   key = mapcache_util_get_tile_key(ctx, tile,NULL," \r\n\t\f\e\a\b","#");
154   if(GC_HAS_ERROR(ctx)) goto cleanup;
155 
156   rv = apr_memcache_delete(mpc->memcache,key,0);
157   if(rv != APR_SUCCESS && rv!= APR_NOTFOUND) {
158     ctx->set_error(ctx,500,"memcache: failed to delete key %s: %s", key, apr_strerror(rv,errmsg,120));
159     goto cleanup;
160   }
161 
162 cleanup:
163   _mapcache_memcache_release_conn(ctx,pc);
164 }
165 
166 /**
167  * \brief get content of given tile
168  *
169  * fills the mapcache_tile::data of the given tile with content stored on the memcache server
170  * \private \memberof mapcache_cache_memcache
171  * \sa mapcache_cache::tile_get()
172  */
_mapcache_cache_memcache_get(mapcache_context * ctx,mapcache_cache * pcache,mapcache_tile * tile)173 static int _mapcache_cache_memcache_get(mapcache_context *ctx, mapcache_cache *pcache, mapcache_tile *tile)
174 {
175   char *key;
176   int rv;
177   mapcache_cache_memcache *cache = (mapcache_cache_memcache*)pcache;
178   mapcache_pooled_connection *pc;
179   mapcache_buffer *encoded_data;
180   struct mapcache_memcache_pooled_connection *mpc;
181   pc = _mapcache_memcache_get_conn(ctx,cache,tile);
182   if(GC_HAS_ERROR(ctx)) {
183     return MAPCACHE_FAILURE;
184   }
185   mpc = pc->connection;
186   key = mapcache_util_get_tile_key(ctx, tile,NULL," \r\n\t\f\e\a\b","#");
187   if(GC_HAS_ERROR(ctx)) {
188     rv = MAPCACHE_FAILURE;
189     goto cleanup;
190   }
191   encoded_data = mapcache_buffer_create(0,ctx->pool);
192   rv = apr_memcache_getp(mpc->memcache,ctx->pool,key,(char**)&encoded_data->buf,&encoded_data->size,NULL);
193   if(rv != APR_SUCCESS) {
194     rv = MAPCACHE_CACHE_MISS;
195     goto cleanup;
196   }
197   if(encoded_data->size == 0) {
198     ctx->set_error(ctx,500,"memcache cache returned 0-length data for tile %d %d %d\n",tile->x,tile->y,tile->z);
199     rv = MAPCACHE_FAILURE;
200     goto cleanup;
201   }
202   /* extract the tile modification time from the end of the data returned */
203   memcpy(
204     &tile->mtime,
205     &(((char*)encoded_data->buf)[encoded_data->size-sizeof(apr_time_t)]),
206     sizeof(apr_time_t));
207 
208   ((char*)encoded_data->buf)[encoded_data->size-sizeof(apr_time_t)]='\0';
209   encoded_data->avail = encoded_data->size;
210   encoded_data->size -= sizeof(apr_time_t);
211   if(((char*)encoded_data->buf)[0] == '#' && encoded_data->size > 1) {
212     tile->encoded_data = mapcache_empty_png_decode(ctx,tile->grid_link->grid->tile_sx, tile->grid_link->grid->tile_sy ,encoded_data->buf,&tile->nodata);
213   } else {
214     tile->encoded_data = encoded_data;
215   }
216   rv = MAPCACHE_SUCCESS;
217 
218 cleanup:
219   _mapcache_memcache_release_conn(ctx,pc);
220 
221   return rv;
222 }
223 
224 /**
225  * \brief push tile data to memcached
226  *
227  * writes the content of mapcache_tile::data to the configured memcached instance(s)
228  * \returns MAPCACHE_FAILURE if there is no data to write, or if the tile isn't locked
229  * \returns MAPCACHE_SUCCESS if the tile has been successfully written
230  * \private \memberof mapcache_cache_memcache
231  * \sa mapcache_cache::tile_set()
232  */
_mapcache_cache_memcache_set(mapcache_context * ctx,mapcache_cache * pcache,mapcache_tile * tile)233 static void _mapcache_cache_memcache_set(mapcache_context *ctx, mapcache_cache *pcache, mapcache_tile *tile)
234 {
235   char *key, *data;
236   int rv;
237   /* set no expiration if not configured */
238   int expires =0;
239   apr_time_t now;
240   mapcache_buffer *encoded_data = NULL;
241   mapcache_cache_memcache *cache = (mapcache_cache_memcache*)pcache;
242   mapcache_pooled_connection *pc;
243   struct mapcache_memcache_pooled_connection *mpc;
244   pc = _mapcache_memcache_get_conn(ctx,cache,tile);
245   GC_CHECK_ERROR(ctx);
246   mpc = pc->connection;
247   key = mapcache_util_get_tile_key(ctx, tile,NULL," \r\n\t\f\e\a\b","#");
248   if(GC_HAS_ERROR(ctx)) goto cleanup;
249 
250   if(tile->tileset->auto_expire)
251     expires = tile->tileset->auto_expire;
252 
253   if(cache->detect_blank) {
254     if(!tile->raw_image) {
255       tile->raw_image = mapcache_imageio_decode(ctx, tile->encoded_data);
256       GC_CHECK_ERROR(ctx);
257     }
258     if(mapcache_image_blank_color(tile->raw_image) != MAPCACHE_FALSE) {
259       encoded_data = mapcache_buffer_create(5,ctx->pool);
260       ((char*)encoded_data->buf)[0] = '#';
261       memcpy(((char*)encoded_data->buf)+1,tile->raw_image->data,4);
262       encoded_data->size = 5;
263     }
264   }
265   if(!encoded_data) {
266     if(!tile->encoded_data) {
267       tile->encoded_data = tile->tileset->format->write(ctx, tile->raw_image, tile->tileset->format);
268       if(GC_HAS_ERROR(ctx)) goto cleanup;
269     }
270     encoded_data = tile->encoded_data;
271   }
272 
273   /* concatenate the current time to the end of the memcache data so we can extract it out
274    * when we re-get the tile */
275   data = calloc(1,encoded_data->size+sizeof(apr_time_t));
276   now = apr_time_now();
277   apr_pool_cleanup_register(ctx->pool, data, (void*)free, apr_pool_cleanup_null);
278   memcpy(data,encoded_data->buf,encoded_data->size);
279   memcpy(&(data[encoded_data->size]),&now,sizeof(apr_time_t));
280 
281   rv = apr_memcache_set(mpc->memcache,key,data,encoded_data->size+sizeof(apr_time_t),expires,0);
282   if(rv != APR_SUCCESS) {
283     ctx->set_error(ctx,500,"failed to store tile %d %d %d to memcache cache %s",
284                    tile->x,tile->y,tile->z,cache->cache.name);
285     goto cleanup;
286   }
287 
288 cleanup:
289   _mapcache_memcache_release_conn(ctx,pc);
290 }
291 
292 /**
293  * \private \memberof mapcache_cache_memcache
294  */
_mapcache_cache_memcache_configuration_parse_xml(mapcache_context * ctx,ezxml_t node,mapcache_cache * cache,mapcache_cfg * config)295 static void _mapcache_cache_memcache_configuration_parse_xml(mapcache_context *ctx, ezxml_t node, mapcache_cache *cache, mapcache_cfg *config)
296 {
297   ezxml_t cur_node;
298   int i = 0;
299   mapcache_cache_memcache *dcache = (mapcache_cache_memcache*)cache;
300   for(cur_node = ezxml_child(node,"server"); cur_node; cur_node = cur_node->next) {
301     dcache->nservers++;
302   }
303   if(!dcache->nservers) {
304     ctx->set_error(ctx,400,"memcache cache %s has no <server>s configured",cache->name);
305     return;
306   }
307   dcache->servers = apr_pcalloc(ctx->pool, dcache->nservers * sizeof(struct mapcache_cache_memcache_server));
308 
309   for(cur_node = ezxml_child(node,"server"); cur_node; cur_node = cur_node->next) {
310     ezxml_t xhost = ezxml_child(cur_node,"host");
311     ezxml_t xport = ezxml_child(cur_node,"port");
312     if(!xhost || !xhost->txt || ! *xhost->txt) {
313       ctx->set_error(ctx,400,"cache %s: <server> with no <host>",cache->name);
314       return;
315     } else {
316       dcache->servers[i].host = apr_pstrdup(ctx->pool,xhost->txt);
317     }
318 
319     if(!xport || !xport->txt || ! *xport->txt) {
320       ctx->set_error(ctx,400,"cache %s: <server> with no <port>", cache->name);
321       return;
322     } else {
323       char *endptr;
324       int iport = (int)strtol(xport->txt,&endptr,10);
325       if(*endptr != 0) {
326         ctx->set_error(ctx,400,"failed to parse value %s for memcache cache %s", xport->txt,cache->name);
327         return;
328       }
329       dcache->servers[i].port = iport;
330     }
331     i++;
332   }
333 
334   dcache->detect_blank = 0;
335   if ((cur_node = ezxml_child(node, "detect_blank")) != NULL) {
336     if(!strcasecmp(cur_node->txt,"true")) {
337       dcache->detect_blank = 1;
338     }
339   }
340 }
341 
342 /**
343  * \private \memberof mapcache_cache_memcache
344  */
_mapcache_cache_memcache_configuration_post_config(mapcache_context * ctx,mapcache_cache * cache,mapcache_cfg * cfg)345 static void _mapcache_cache_memcache_configuration_post_config(mapcache_context *ctx, mapcache_cache *cache,
346     mapcache_cfg *cfg)
347 {
348   mapcache_cache_memcache *dcache = (mapcache_cache_memcache*)cache;
349   if(!dcache->nservers) {
350     ctx->set_error(ctx,400,"cache %s has no servers configured",cache->name);
351   }
352 }
353 
354 
355 /**
356  * \brief creates and initializes a mapcache_memcache_cache
357  */
mapcache_cache_memcache_create(mapcache_context * ctx)358 mapcache_cache* mapcache_cache_memcache_create(mapcache_context *ctx)
359 {
360   mapcache_cache_memcache *cache = apr_pcalloc(ctx->pool,sizeof(mapcache_cache_memcache));
361   if(!cache) {
362     ctx->set_error(ctx, 500, "failed to allocate memcache cache");
363     return NULL;
364   }
365   cache->cache.metadata = apr_table_make(ctx->pool,3);
366   cache->cache.type = MAPCACHE_CACHE_MEMCACHE;
367   cache->cache._tile_get = _mapcache_cache_memcache_get;
368   cache->cache._tile_exists = _mapcache_cache_memcache_has_tile;
369   cache->cache._tile_set = _mapcache_cache_memcache_set;
370   cache->cache._tile_delete = _mapcache_cache_memcache_delete;
371   cache->cache.configuration_post_config = _mapcache_cache_memcache_configuration_post_config;
372   cache->cache.configuration_parse_xml = _mapcache_cache_memcache_configuration_parse_xml;
373   return (mapcache_cache*)cache;
374 }
375 
376 #else
mapcache_cache_memcache_create(mapcache_context * ctx)377 mapcache_cache* mapcache_cache_memcache_create(mapcache_context *ctx) {
378   ctx->set_error(ctx,400,"MEMCACHE support not compiled in this version");
379   return NULL;
380 }
381 #endif
382 
383 /* vim: ts=2 sts=2 et sw=2
384 */
385