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

Last change on this file since 7079 was 7079, checked in by jonb, 12 years ago

mod_tile: Update render config settings with those used by tile. Add delay on out-of-bounds access to rate limit clients which just loop forever even though we send them an error

File size: 14.5 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
27module AP_MODULE_DECLARE_DATA tile_module;
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <stdarg.h>
33#include <sys/types.h>
34#include <sys/stat.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
102
103int request_tile(request_rec *r, int dirtyOnly)
104{
105    struct protocol cmd;
106    //struct pollfd fds[1];
107    static int fd = FD_INVALID;
108    int ret = 0;
109    int retry = 1;
110    int x, y, z, n, limit, oob;
111
112    /* URI = .../<z>/<x>/<y>.png[/option] */
113    n = sscanf(r->uri, TILE_PATH "/%d/%d/%d", &z, &x, &y);
114    if (n != 3)
115        return 0;
116
117    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "z(%d) x(%d) y(%d)", z, x, y);
118
119    // Validate tile co-ordinates
120    oob = (z < 0 || z > MAX_ZOOM);
121    if (!oob) {
122         // valid x/y for tiles are 0 ... 2^zoom-1
123        limit = (1 << z) - 1;
124        oob =  (x < 0 || x > limit || y < 0 || y > limit);
125    }
126
127    if (oob)
128        return 0;
129
130    if (fd == FD_INVALID) {
131        fd = socket_init(r);
132
133        if (fd == FD_INVALID) {
134            //fprintf(stderr, "Failed to connect to renderer\n");
135            return 0;
136        } else {
137            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Connected to renderer");
138        }
139    }
140
141    bzero(&cmd, sizeof(cmd));
142
143    cmd.ver = PROTO_VER;
144    cmd.cmd = dirtyOnly ? cmdDirty : cmdRender;
145    cmd.z = z;
146    cmd.x = x;
147    cmd.y = y;
148
149    //fprintf(stderr, "Requesting tile(%d,%d,%d)\n", z,x,y);
150    do {
151        ret = send(fd, &cmd, sizeof(cmd), 0);
152
153        if (ret == sizeof(cmd))
154            break;
155
156        if (errno != EPIPE)
157            return 0;
158 
159         close(fd);
160         fd = socket_init(r);
161         if (fd == FD_INVALID)
162             return 0;
163         ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Reconnected to renderer");
164    } while (retry--);
165
166    if (!dirtyOnly) {
167        struct timeval tv = { REQUEST_TIMEOUT, 0 };
168        fd_set rx;
169        int s;
170
171        while (1) {
172            FD_ZERO(&rx);
173            FD_SET(fd, &rx);
174            s = select(fd+1, &rx, NULL, NULL, &tv);
175            if (s == 1) {
176                bzero(&cmd, sizeof(cmd));
177                ret = recv(fd, &cmd, sizeof(cmd), 0);
178                if (ret != sizeof(cmd)) {
179                    if (errno == EPIPE) {
180                        close(fd);
181                        fd = FD_INVALID;
182                    }
183                    //perror("recv error");
184                    break;
185                }
186                //fprintf(stderr, "Completed tile(%d,%d,%d)\n", z,x,y);
187                if (cmd.x == x && cmd.y == y && cmd.z == z) {
188                    if (cmd.cmd == cmdDone)
189                        return 1;
190                    else
191                        return 0;
192                }
193            } else if (s == 0) {
194                break;
195            } else {
196                if (errno == EPIPE) {
197                    close(fd);
198                    fd = FD_INVALID;
199                    break;
200                }
201            }
202        }
203    }
204    return 0;
205}
206
207static int getPlanetTime(request_rec *r)
208{
209    static time_t last_check;
210    static time_t planet_timestamp;
211    time_t now = time(NULL);
212    struct stat buf;
213
214    // Only check for updates periodically
215    if (now < last_check + 300)
216        return planet_timestamp;
217
218    last_check = now;
219    if (stat(PLANET_TIMESTAMP, &buf)) {
220        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Planet timestamp file " PLANET_TIMESTAMP " is missing");
221        // Make something up
222        planet_timestamp = now - 3 * 24 * 60 * 60;
223    } else {
224        if (buf.st_mtime != planet_timestamp) {
225            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Planet file updated at %s", ctime(&buf.st_mtime));
226            planet_timestamp = buf.st_mtime;
227        }
228    }
229    return planet_timestamp;
230}
231
232static enum tileState tile_state_once(request_rec *r)
233{
234    // FIXME: Apache already has most, if not all, this info recorded in r->fileinfo, use this instead!
235    struct stat buf;
236
237    if (stat(r->filename, &buf))
238        return tileMissing; 
239
240    if (buf.st_mtime < getPlanetTime(r))
241        return tileOld;
242
243    return tileCurrent;
244}
245
246static enum tileState tile_state(request_rec *r)
247{
248    enum tileState state = tile_state_once(r);
249
250    if (state == tileMissing) {
251        // Try fallback to plain .png
252        char path[PATH_MAX];
253        int x, y, z, n;
254        /* URI = .../<z>/<x>/<y>.png[/option] */
255        n = sscanf(r->uri, TILE_PATH "/%d/%d/%d", &z, &x, &y);
256        if (n == 3) {
257            xyz_to_path(path, sizeof(path), x,y,z);
258            r->filename = apr_pstrdup(r->pool, path);
259            state = tile_state_once(r);
260            //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "png fallback %d/%d/%d",x,y,z);
261
262            if (state == tileMissing) {
263                // PNG not available either, if it gets rendered, it'll now be a .meta
264                xyz_to_meta(path, sizeof(path), x,y,z);
265                r->filename = apr_pstrdup(r->pool, path);
266            }
267        }
268    }
269    return state;
270}
271
272static void add_expiry(request_rec *r)
273{
274    apr_time_t expires, holdoff, nextPlanet;
275    apr_table_t *t = r->headers_out;
276    enum tileState state = tile_state(r);
277    char *timestr;
278
279    /* Append expiry headers ... */
280
281    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "expires(%s), uri(%s), filename(%s), path_info(%s)\n",
282    //              r->handler, r->uri, r->filename, r->path_info);
283
284    // current tiles will expire after next planet dump is due
285    // or after 1 hour if the planet dump is late or tile is due for re-render
286    nextPlanet = (state == tileCurrent) ? apr_time_from_sec(getPlanetTime(r) + PLANET_INTERVAL) : 0;
287    holdoff = r->request_time + apr_time_from_sec(60 * 60);
288    expires = MAX(holdoff, nextPlanet);
289
290    apr_table_mergen(t, "Cache-Control",
291                     apr_psprintf(r->pool, "max-age=%" APR_TIME_T_FMT,
292                     apr_time_sec(expires - r->request_time)));
293    timestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
294    apr_rfc822_date(timestr, expires);
295    apr_table_setn(t, "Expires", timestr);
296}
297
298static apr_status_t expires_filter(ap_filter_t *f, apr_bucket_brigade *b)
299{
300    request_rec *r = f->r;
301
302    add_expiry(r);
303
304    ap_remove_output_filter(f);
305    return ap_pass_brigade(f->next, b);
306}
307
308
309double get_load_avg(request_rec *r)
310{
311    double loadavg[1];
312    int n = getloadavg(loadavg, 1);
313
314    if (n < 1)
315        return 1000;
316    else
317        return loadavg[0];
318}
319
320static int tile_handler_dirty(request_rec *r)
321{
322    if(strcmp(r->handler, "tile_dirty"))
323        return DECLINED;
324
325    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "handler(%s), uri(%s), filename(%s), path_info(%s)",
326    //              r->handler, r->uri, r->filename, r->path_info);
327
328    request_tile(r, 1);
329    return error_message(r, "Tile submitted for rendering");
330}
331
332
333
334static int tile_storage_hook(request_rec *r)
335{
336    int avg;
337    enum tileState state;
338
339    if (!r->handler || strcmp(r->handler, "tile_serve"))
340        return DECLINED;
341
342    avg = get_load_avg(r);
343    state = tile_state(r);
344
345    switch (state) {
346        case tileCurrent:
347            return OK;
348            break;
349        case tileOld:
350            if (avg > MAX_LOAD_OLD) {
351               // Too much load to render it now, mark dirty but return old tile
352               request_tile(r, 1);
353               return OK;
354            }
355            break;
356        case tileMissing:
357            if (avg > MAX_LOAD_MISSING) {
358               request_tile(r, 1);
359               return HTTP_NOT_FOUND;
360            }
361            break;
362    }
363
364    if (request_tile(r, 0))
365        return OK;
366
367    if (state == tileOld)
368        return OK;
369
370    return HTTP_NOT_FOUND;
371}
372
373static int tile_handler_status(request_rec *r)
374{
375    // FIXME: Apache already has most, if not all, this info recorded in r->fileinfo, use this instead!
376    struct stat buf;
377    time_t now;
378    int old;
379    char MtimeStr[32]; // At least 26 according to man ctime_r
380    char AtimeStr[32]; // At least 26 according to man ctime_r
381    char *p;
382
383    if(strcmp(r->handler, "tile_status"))
384        return DECLINED;
385
386    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "handler(%s), uri(%s), filename(%s), path_info(%s)",
387    //              r->handler, r->uri, r->filename, r->path_info);
388
389    if (stat(r->filename, &buf))
390        return error_message(r, "Unable to find a tile at %s", r->filename);
391
392    now = time(NULL);
393    old = (buf.st_mtime < getPlanetTime(r));
394
395    MtimeStr[0] = '\0';
396    ctime_r(&buf.st_mtime, MtimeStr);
397    AtimeStr[0] = '\0';
398    ctime_r(&buf.st_atime, AtimeStr);
399
400    if ((p = strrchr(MtimeStr, '\n')))
401        *p = '\0';
402    if ((p = strrchr(AtimeStr, '\n')))
403        *p = '\0';
404
405    //return error_message(r, "Tile is %s. Last rendered at %s. Last accessed at %s", old ? "due to be rendered" : "clean", MtimeStr, AtimeStr);
406    return error_message(r, "Tile is %s. Last rendered at %s", old ? "due to be rendered" : "clean", MtimeStr);
407}
408
409static int tile_translate(request_rec *r)
410{
411    int x, y, z, n, limit;
412    char option[11];
413    int oob;
414    char abs_path[PATH_MAX];
415
416    option[0] = '\0';
417
418    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "translate uri(%s)", r->uri);
419
420    /* URI = .../<z>/<x>/<y>.png[/option] */
421    n = sscanf(r->uri, TILE_PATH "/%d/%d/%d.png/%10s", &z, &x, &y, option);
422    /* The original rewrite config matched anything that ended with 3 numbers */
423    //if (n < 3)
424    //        n = sscanf(r->uri, TILE_PATH "%*[^0-9]%d/%d/%d.png/%10s", &z, &x, &y, option);
425
426    if (n < 3)
427        return DECLINED;
428
429    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "z(%d) x(%d) y(%d)", z, x, y);
430
431    // Validate tile co-ordinates
432    oob = (z < 0 || z > MAX_ZOOM);
433    if (!oob) {
434         // valid x/y for tiles are 0 ... 2^zoom-1
435        limit = (1 << z) - 1;
436        oob =  (x < 0 || x > limit || y < 0 || y > limit);
437    }
438
439    if (oob) {
440        sleep(CLIENT_PENALTY);
441        return HTTP_NOT_FOUND;
442    }
443
444#if 1
445    // Generate the tile filename
446    xyz_to_meta(abs_path, sizeof(abs_path), x, y, z);
447    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "abs_path(%s), rel_path(%s)", abs_path, rel_path);
448    r->filename = apr_pstrdup(r->pool, abs_path);
449#else
450    //r->filename = apr_psprintf(r->pool, "tile:%d/%d/%d",z,x,y);
451#endif
452    if (n == 4) {
453        if (!strcmp(option, "status"))
454            r->handler = "tile_status";
455        else if (!strcmp(option, "dirty"))
456            r->handler = "tile_dirty";
457        else
458            return DECLINED;
459    } else 
460        r->handler = "tile_serve";
461
462    return OK;
463}
464
465
466
467static int tile_handler_serve(request_rec *r)
468{
469    int x, y, z, n, limit, oob;
470    char *buf;
471    size_t len;
472    const int tile_max = 1024 * 1024;
473
474    if(strcmp(r->handler, "tile_serve"))
475        return DECLINED;
476
477    /* URI = .../<z>/<x>/<y>.png[/option] */
478    n = sscanf(r->uri, TILE_PATH "/%d/%d/%d", &z, &x, &y);
479    if (n != 3)
480        return 0;
481
482    // Validate tile co-ordinates
483    oob = (z < 0 || z > MAX_ZOOM);
484    if (!oob) {
485         // valid x/y for tiles are 0 ... 2^zoom-1
486        limit = (1 << z) - 1;
487        oob =  (x < 0 || x > limit || y < 0 || y > limit);
488    }
489
490    if (oob) {
491        sleep(CLIENT_PENALTY);
492        return HTTP_NOT_FOUND;
493    }
494
495    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "serve handler(%s), uri(%s), filename(%s), path_info(%s)",
496    //              r->handler, r->uri, r->filename, r->path_info);
497
498    buf = malloc(tile_max);
499    if (!buf)
500        return HTTP_INTERNAL_SERVER_ERROR;
501
502    len = tile_read(x,y,z,buf, tile_max);
503    if (len > 0) {
504        ap_set_content_type(r, "image/png");
505        ap_set_content_length(r, len);
506        add_expiry(r);
507        ap_rwrite(buf, len, r);
508        free(buf);
509        //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "fulfilled via meta");
510        return OK;
511    }
512    free(buf);
513    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "len = %d", len);
514
515    return DECLINED;
516}
517
518
519
520static void register_hooks(__attribute__((unused)) apr_pool_t *p)
521{
522    ap_register_output_filter("MOD_TILE", expires_filter, NULL, APR_HOOK_MIDDLE);
523    ap_hook_handler(tile_handler_serve, NULL, NULL, APR_HOOK_MIDDLE);
524    ap_hook_handler(tile_handler_dirty, NULL, NULL, APR_HOOK_MIDDLE);
525    ap_hook_handler(tile_handler_status, NULL, NULL, APR_HOOK_MIDDLE);
526    ap_hook_translate_name(tile_translate, NULL, NULL, APR_HOOK_MIDDLE);
527    ap_hook_map_to_storage(tile_storage_hook, NULL, NULL, APR_HOOK_FIRST);
528}
529
530module AP_MODULE_DECLARE_DATA tile_module =
531{
532    STANDARD20_MODULE_STUFF,
533    NULL,           /* dir config creater */
534    NULL,           /* dir merger --- default is to override */
535    NULL,           /* server config */
536    NULL,           /* merge server config */
537    NULL,           /* command apr_table_t */
538    register_hooks  /* register hooks */
539};
Note: See TracBrowser for help on using the repository browser.