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, ¶m);
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