source: subversion/applications/utils/mod_tile/mod_tile.c @ 9098

Last change on this file since 9098 was 9098, checked in by jonb, 11 years ago

mod_tile: Fix negative expiry times. Closes #1040

File size: 15.2 KB
Line 
1#include "apr.h"
2#include "apr_strings.h"
3#include "apr_thread_proc.h"    /* for RLIMIT stuff */
4#include "apr_optional.h"
5#include "apr_buckets.h"
6#include "apr_lib.h"
7#include "apr_poll.h"
8
9#define APR_WANT_STRFUNC
10#define APR_WANT_MEMFUNC
11#include "apr_want.h"
12
13#include "util_filter.h"
14#include "ap_config.h"
15#include "httpd.h"
16#include "http_config.h"
17#include "http_request.h"
18#include "http_core.h"
19#include "http_protocol.h"
20#include "http_main.h"
21#include "http_log.h"
22#include "util_script.h"
23#include "ap_mpm.h"
24#include "mod_core.h"
25#include "mod_cgi.h"
26#include "util_md5.h"
27
28module AP_MODULE_DECLARE_DATA tile_module;
29
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <stdarg.h>
34#include <sys/types.h>
35#include <sys/socket.h>
36#include <sys/un.h>
37#include <unistd.h>
38#include <fcntl.h>
39#include <errno.h>
40#include <limits.h>
41#include <time.h>
42
43#include "gen_tile.h"
44#include "protocol.h"
45#include "render_config.h"
46#include "store.h"
47#include "dir_utils.h"
48
49enum tileState { tileMissing, tileOld, tileCurrent };
50
51static int error_message(request_rec *r, const char *format, ...)
52                 __attribute__ ((format (printf, 2, 3)));
53
54static int error_message(request_rec *r, const char *format, ...)
55{
56    va_list ap;
57    va_start(ap, format);
58    int len;
59    char *msg;
60
61    len = vasprintf(&msg, format, ap);
62
63    if (msg) {
64        //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", msg);
65        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "%s", msg);
66        r->content_type = "text/plain";
67        if (!r->header_only)
68            ap_rputs(msg, r);
69        free(msg);
70    }
71
72    return OK;
73}
74
75
76int socket_init(request_rec *r)
77{
78    const char *spath = RENDER_SOCKET;
79    int fd;
80    struct sockaddr_un addr;
81
82    //fprintf(stderr, "Starting rendering client\n");
83
84    fd = socket(PF_UNIX, SOCK_STREAM, 0);
85    if (fd < 0) {
86        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "failed to create unix socket");
87        return FD_INVALID;
88    }
89
90    bzero(&addr, sizeof(addr));
91    addr.sun_family = AF_UNIX;
92    strncpy(addr.sun_path, spath, sizeof(addr.sun_path));
93
94    if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
95        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "socket connect failed for: %s", spath);
96        close(fd);
97        return FD_INVALID;
98    }
99    return fd;
100}
101
102static pthread_key_t key;
103static pthread_once_t key_once = PTHREAD_ONCE_INIT;
104
105static void pfd_free(void *ptr)
106{
107    int *pfd = ptr;
108
109    if (*pfd != FD_INVALID)
110        close(*pfd);
111    free(pfd);
112}
113
114static void make_key(void)
115{
116    (void) pthread_key_create(&key, pfd_free);
117}
118
119
120int request_tile(request_rec *r, int dirtyOnly)
121{
122    struct protocol cmd;
123    int *pfd;
124    int ret = 0;
125    int retry = 1;
126    int x, y, z, n, limit, oob;
127
128    /* URI = .../<z>/<x>/<y>.png[/option] */
129    n = sscanf(r->uri, TILE_PATH "/%d/%d/%d", &z, &x, &y);
130    if (n != 3)
131        return 0;
132
133    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "z(%d) x(%d) y(%d)", z, x, y);
134
135    // Validate tile co-ordinates
136    oob = (z < 0 || z > MAX_ZOOM);
137    if (!oob) {
138         // valid x/y for tiles are 0 ... 2^zoom-1
139        limit = (1 << z) - 1;
140        oob =  (x < 0 || x > limit || y < 0 || y > limit);
141    }
142
143    if (oob)
144        return 0;
145
146    (void) pthread_once(&key_once, make_key);
147    if ((pfd = pthread_getspecific(key)) == NULL) {
148        pfd = malloc(sizeof(*pfd));
149        if (!pfd)
150            return 0;
151        *pfd = FD_INVALID;
152        (void) pthread_setspecific(key, pfd);
153    }
154
155    if (*pfd == FD_INVALID) {
156        *pfd = socket_init(r);
157
158        if (*pfd == FD_INVALID) {
159            //fprintf(stderr, "Failed to connect to renderer\n");
160            return 0;
161        } else {
162            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Connected to renderer");
163        }
164    }
165
166    bzero(&cmd, sizeof(cmd));
167
168    cmd.ver = PROTO_VER;
169    cmd.cmd = dirtyOnly ? cmdDirty : cmdRender;
170    cmd.z = z;
171    cmd.x = x;
172    cmd.y = y;
173
174    //fprintf(stderr, "Requesting tile(%d,%d,%d)\n", z,x,y);
175    do {
176        ret = send(*pfd, &cmd, sizeof(cmd), 0);
177
178        if (ret == sizeof(cmd))
179            break;
180
181        if (errno != EPIPE)
182            return 0;
183
184         close(*pfd);
185         *pfd = socket_init(r);
186         if (*pfd == FD_INVALID)
187             return 0;
188         ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Reconnected to renderer");
189    } while (retry--);
190
191    if (!dirtyOnly) {
192        struct timeval tv = { REQUEST_TIMEOUT, 0 };
193        fd_set rx;
194        int s;
195
196        while (1) {
197            FD_ZERO(&rx);
198            FD_SET(*pfd, &rx);
199            s = select((*pfd)+1, &rx, NULL, NULL, &tv);
200            if (s == 1) {
201                bzero(&cmd, sizeof(cmd));
202                ret = recv(*pfd, &cmd, sizeof(cmd), 0);
203                if (ret != sizeof(cmd)) {
204                    if (errno == EPIPE) {
205                        close(*pfd);
206                        *pfd = FD_INVALID;
207                    }
208                    //perror("recv error");
209                    break;
210                }
211                //fprintf(stderr, "Completed tile(%d,%d,%d)\n", z,x,y);
212                if (cmd.x == x && cmd.y == y && cmd.z == z) {
213                    if (cmd.cmd == cmdDone)
214                        return 1;
215                    else
216                        return 0;
217                }
218            } else if (s == 0) {
219                break;
220            } else {
221                if (errno == EPIPE) {
222                    close(*pfd);
223                    *pfd = FD_INVALID;
224                    break;
225                }
226            }
227        }
228    }
229    return 0;
230}
231
232
233
234static apr_time_t getPlanetTime(request_rec *r)
235{
236    static apr_time_t last_check;
237    static apr_time_t planet_timestamp;
238    static pthread_mutex_t planet_lock = PTHREAD_MUTEX_INITIALIZER;
239    apr_time_t now = r->request_time;
240    struct apr_finfo_t s;
241
242    pthread_mutex_lock(&planet_lock);
243    // Only check for updates periodically
244    if (now < last_check + apr_time_from_sec(300)) {
245        pthread_mutex_unlock(&planet_lock);
246        return planet_timestamp;
247    }
248
249    last_check = now;
250    if (apr_stat(&s, PLANET_TIMESTAMP, APR_FINFO_MIN, r->pool) != APR_SUCCESS) {
251        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Planet timestamp file " PLANET_TIMESTAMP " is missing");
252        // Make something up
253        planet_timestamp = now - apr_time_from_sec(3 * 24 * 60 * 60);
254    } else {
255        if (s.mtime != planet_timestamp) {
256            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Planet file updated");
257            planet_timestamp = s.mtime;
258        }
259    }
260    pthread_mutex_unlock(&planet_lock);
261    return planet_timestamp;
262}
263
264static enum tileState tile_state_once(request_rec *r)
265{
266    apr_status_t rv;
267    apr_finfo_t *finfo = &r->finfo;
268
269    if (!(finfo->valid & APR_FINFO_MTIME)) {
270        rv = apr_stat(finfo, r->filename, APR_FINFO_MIN, r->pool);
271        if (rv != APR_SUCCESS)
272            return tileMissing;
273    }
274
275    if (finfo->mtime < getPlanetTime(r))
276        return tileOld;
277
278    return tileCurrent;
279}
280
281static enum tileState tile_state(request_rec *r)
282{
283    enum tileState state = tile_state_once(r);
284#ifdef METATILE
285    if (state == tileMissing) {
286        // Try fallback to plain PNG
287        char path[PATH_MAX];
288        int x, y, z, n;
289        /* URI = .../<z>/<x>/<y>.png[/option] */
290        n = sscanf(r->uri, TILE_PATH "/%d/%d/%d", &z, &x, &y);
291        if (n == 3) {
292            xyz_to_path(path, sizeof(path), x,y,z);
293            r->filename = apr_pstrdup(r->pool, path);
294            state = tile_state_once(r);
295            //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "png fallback %d/%d/%d",x,y,z);
296
297            if (state == tileMissing) {
298                // PNG not available either, if it gets rendered, it'll now be a .meta
299                xyz_to_meta(path, sizeof(path), x,y,z);
300                r->filename = apr_pstrdup(r->pool, path);
301            }
302        }
303    }
304#endif
305    return state;
306}
307
308static void add_expiry(request_rec *r)
309{
310    apr_time_t expires, holdoff, planetTimestamp;
311    apr_table_t *t = r->headers_out;
312    enum tileState state = tile_state(r);
313    char *timestr;
314
315    /* Append expiry headers ... */
316
317    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "expires(%s), uri(%s), filename(%s), path_info(%s)\n",
318    //              r->handler, r->uri, r->filename, r->path_info);
319
320    // We estimate an expiry based on when the next planet dump is (or was) due
321    // If we are past this time already then round up to request time
322    // Then add a randomisation of up to 3 hours
323    planetTimestamp = (state == tileCurrent) ? (getPlanetTime(r) + apr_time_from_sec(PLANET_INTERVAL)) : getPlanetTime(r);
324    holdoff = apr_time_from_sec(3 * 60 * 60) * (rand() / (RAND_MAX + 1.0));
325    expires = MAX(r->request_time, planetTimestamp) + holdoff;
326
327    apr_table_mergen(t, "Cache-Control",
328                     apr_psprintf(r->pool, "max-age=%" APR_TIME_T_FMT,
329                     apr_time_sec(expires - r->request_time)));
330    timestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
331    apr_rfc822_date(timestr, expires);
332    apr_table_setn(t, "Expires", timestr);
333}
334
335
336double get_load_avg(request_rec *r)
337{
338    double loadavg[1];
339    int n = getloadavg(loadavg, 1);
340
341    if (n < 1)
342        return 1000;
343    else
344        return loadavg[0];
345}
346
347static int tile_handler_dirty(request_rec *r)
348{
349    if(strcmp(r->handler, "tile_dirty"))
350        return DECLINED;
351
352    request_tile(r, 1);
353    return error_message(r, "Tile submitted for rendering\n");
354}
355
356
357
358static int tile_storage_hook(request_rec *r)
359{
360    int avg;
361    enum tileState state;
362
363    if (!r->handler)
364        return DECLINED;
365
366    // Any status request is OK
367    if (!strcmp(r->handler, "tile_status"))
368        return OK;
369
370    if (strcmp(r->handler, "tile_serve") && strcmp(r->handler, "tile_dirty"))
371        return DECLINED;
372
373    avg = get_load_avg(r);
374    state = tile_state(r);
375
376    switch (state) {
377        case tileCurrent:
378            return OK;
379            break;
380        case tileOld:
381            if (avg > MAX_LOAD_OLD) {
382               // Too much load to render it now, mark dirty but return old tile
383               request_tile(r, 1);
384               return OK;
385            }
386            break;
387        case tileMissing:
388            if (avg > MAX_LOAD_MISSING) {
389               request_tile(r, 1);
390               return HTTP_NOT_FOUND;
391            }
392            break;
393    }
394
395    if (request_tile(r, 0)) {
396        // Need to update fileinfo for new rendered tile
397        apr_stat(&r->finfo, r->filename, APR_FINFO_MIN, r->pool);
398        return OK;
399    }
400
401    if (state == tileOld)
402        return OK;
403
404    return HTTP_NOT_FOUND;
405}
406
407static int tile_handler_status(request_rec *r)
408{
409    enum tileState state;
410    char time_str[APR_CTIME_LEN];
411
412    if(strcmp(r->handler, "tile_status"))
413        return DECLINED;
414
415    state = tile_state(r);
416    if (state == tileMissing)
417        return error_message(r, "Unable to find a tile at %s\n", r->filename);
418    apr_ctime(time_str, r->finfo.mtime);
419
420    return error_message(r, "Tile is %s. Last rendered at %s\n", (state == tileOld) ? "due to be rendered" : "clean", time_str);
421}
422
423static int tile_translate(request_rec *r)
424{
425    int x, y, z, n, limit;
426    char option[11];
427    int oob;
428    char abs_path[PATH_MAX];
429
430    option[0] = '\0';
431
432    /* URI = .../<z>/<x>/<y>.png[/option] */
433    n = sscanf(r->uri, TILE_PATH "/%d/%d/%d.png/%10s", &z, &x, &y, option);
434    /* The original rewrite config matched anything that ended with 3 numbers */
435    //if (n < 3)
436    //        n = sscanf(r->uri, TILE_PATH "%*[^0-9]%d/%d/%d.png/%10s", &z, &x, &y, option);
437
438    if (n < 3)
439        return DECLINED;
440
441    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "z(%d) x(%d) y(%d)", z, x, y);
442
443    // Validate tile co-ordinates
444    oob = (z < 0 || z > MAX_ZOOM);
445    if (!oob) {
446         // valid x/y for tiles are 0 ... 2^zoom-1
447        limit = (1 << z) - 1;
448        oob =  (x < 0 || x > limit || y < 0 || y > limit);
449    }
450
451    if (oob) {
452        sleep(CLIENT_PENALTY);
453        return HTTP_NOT_FOUND;
454    }
455
456
457    // Generate the tile filename
458#ifdef METATILE
459    xyz_to_meta(abs_path, sizeof(abs_path), x, y, z);
460#else
461    xyz_to_path(abs_path, sizeof(abs_path), x, y, z);
462#endif
463    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "abs_path(%s), rel_path(%s)", abs_path, rel_path);
464    r->filename = apr_pstrdup(r->pool, abs_path);
465
466    if (n == 4) {
467        if (!strcmp(option, "status"))
468            r->handler = "tile_status";
469        else if (!strcmp(option, "dirty"))
470            r->handler = "tile_dirty";
471        else
472            return DECLINED;
473    } else 
474        r->handler = "tile_serve";
475
476    return OK;
477}
478
479
480
481static int tile_handler_serve(request_rec *r)
482{
483    int x, y, z, n, limit, oob;
484    unsigned char *buf;
485    int len;
486    const int tile_max = 1024 * 1024;
487    apr_status_t errstatus;
488
489    if(strcmp(r->handler, "tile_serve"))
490        return DECLINED;
491
492    /* URI = .../<z>/<x>/<y>.png[/option] */
493    n = sscanf(r->uri, TILE_PATH "/%d/%d/%d", &z, &x, &y);
494    if (n != 3)
495        return 0;
496
497    // Validate tile co-ordinates
498    oob = (z < 0 || z > MAX_ZOOM);
499    if (!oob) {
500         // valid x/y for tiles are 0 ... 2^zoom-1
501        limit = (1 << z) - 1;
502        oob =  (x < 0 || x > limit || y < 0 || y > limit);
503    }
504
505    if (oob) {
506        sleep(CLIENT_PENALTY);
507        return HTTP_NOT_FOUND;
508    }
509
510    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "serve handler(%s), uri(%s), filename(%s), path_info(%s)",
511    //              r->handler, r->uri, r->filename, r->path_info);
512
513    // FIXME: It is a waste to do the malloc + read if we are fulfilling a HEAD or returning a 304.
514    buf = malloc(tile_max);
515    if (!buf)
516        return HTTP_INTERNAL_SERVER_ERROR;
517
518    len = tile_read(x, y, z, buf, tile_max);
519    if (len > 0) {
520#if 0
521        // Set default Last-Modified and Etag headers
522        ap_update_mtime(r, r->finfo.mtime);
523        ap_set_last_modified(r);
524        ap_set_etag(r);
525#else
526        // Use MD5 hash as only cache attribute.
527        // If a tile is re-rendered and produces the same output
528        // then we can continue to use the previous cached copy
529        char *md5 = ap_md5_binary(r->pool, buf, len);
530        apr_table_setn(r->headers_out, "ETag",
531                        apr_psprintf(r->pool, "\"%s\"", md5));
532#endif
533        ap_set_content_type(r, "image/png");
534        ap_set_content_length(r, len);
535        add_expiry(r);
536        if ((errstatus = ap_meets_conditions(r)) != OK) {
537            free(buf);
538            return errstatus;
539        } else {
540            ap_rwrite(buf, len, r);
541            free(buf);
542            return OK;
543        }
544    }
545    free(buf);
546    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "len = %d", len);
547
548    return DECLINED;
549}
550
551
552
553static void register_hooks(__attribute__((unused)) apr_pool_t *p)
554{
555    ap_hook_handler(tile_handler_serve, NULL, NULL, APR_HOOK_MIDDLE);
556    ap_hook_handler(tile_handler_dirty, NULL, NULL, APR_HOOK_MIDDLE);
557    ap_hook_handler(tile_handler_status, NULL, NULL, APR_HOOK_MIDDLE);
558    ap_hook_translate_name(tile_translate, NULL, NULL, APR_HOOK_MIDDLE);
559    ap_hook_map_to_storage(tile_storage_hook, NULL, NULL, APR_HOOK_FIRST);
560}
561
562module AP_MODULE_DECLARE_DATA tile_module =
563{
564    STANDARD20_MODULE_STUFF,
565    NULL,           /* dir config creater */
566    NULL,           /* dir merger --- default is to override */
567    NULL,           /* server config */
568    NULL,           /* merge server config */
569    NULL,           /* command apr_table_t */
570    register_hooks  /* register hooks */
571};
Note: See TracBrowser for help on using the repository browser.