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

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

mod_tile: Fix expiry header. Update output of /status and /dirty

File size: 14.1 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
47enum tileState { tileMissing, tileOld, tileCurrent };
48
49static int error_message(request_rec *r, const char *format, ...)
50                 __attribute__ ((format (printf, 2, 3)));
51
52static int error_message(request_rec *r, const char *format, ...)
53{
54    va_list ap;
55    va_start(ap, format);
56    int len;
57    char *msg;
58
59    len = vasprintf(&msg, format, ap);
60
61    if (msg) {
62        //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", msg);
63        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "%s", msg);
64        r->content_type = "text/plain";
65        if (!r->header_only)
66            ap_rputs(msg, r);
67        free(msg);
68    }
69
70    return OK;
71}
72
73
74int socket_init(request_rec *r)
75{
76    const char *spath = RENDER_SOCKET;
77    int fd;
78    struct sockaddr_un addr;
79
80    //fprintf(stderr, "Starting rendering client\n");
81
82    fd = socket(PF_UNIX, SOCK_STREAM, 0);
83    if (fd < 0) {
84        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "failed to create unix socket");
85        return FD_INVALID;
86    }
87
88    bzero(&addr, sizeof(addr));
89    addr.sun_family = AF_UNIX;
90    strncpy(addr.sun_path, spath, sizeof(addr.sun_path));
91
92    if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
93        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "socket connect failed for: %s", spath);
94        close(fd);
95        return FD_INVALID;
96    }
97    return fd;
98}
99
100
101int request_tile(request_rec *r, int x, int y, int z, const char *filename, int dirtyOnly)
102{
103    struct protocol cmd;
104    //struct pollfd fds[1];
105    static int fd = FD_INVALID;
106    int ret = 0;
107    int retry = 1;
108 
109    if (fd == FD_INVALID) {
110        fd = socket_init(r);
111
112        if (fd == FD_INVALID) {
113            //fprintf(stderr, "Failed to connect to renderer\n");
114            return 0;
115        } else {
116            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Connected to renderer");
117        }
118    }
119
120    bzero(&cmd, sizeof(cmd));
121
122    cmd.ver = PROTO_VER;
123    cmd.cmd = dirtyOnly ? cmdDirty : cmdRender;
124    cmd.z = z;
125    cmd.x = x;
126    cmd.y = y;
127    strcpy(cmd.path, filename);
128
129    //fprintf(stderr, "Requesting tile(%d,%d,%d)\n", z,x,y);
130    do {
131        ret = send(fd, &cmd, sizeof(cmd), 0);
132
133        if (ret == sizeof(cmd))
134            break;
135
136        if (errno != EPIPE)
137            return 0;
138 
139         close(fd);
140         fd = socket_init(r);
141         if (fd == FD_INVALID)
142             return 0;
143         ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Reconnected to renderer");
144    } while (retry--);
145
146    if (!dirtyOnly) {
147        struct timeval tv = { REQUEST_TIMEOUT, 0 };
148        fd_set rx;
149        int s;
150
151        while (1) {
152            FD_ZERO(&rx);
153            FD_SET(fd, &rx);
154            s = select(fd+1, &rx, NULL, NULL, &tv);
155            if (s == 1) {
156                bzero(&cmd, sizeof(cmd));
157                ret = recv(fd, &cmd, sizeof(cmd), 0);
158                if (ret != sizeof(cmd)) {
159                    if (errno == EPIPE) {
160                        close(fd);
161                        fd = FD_INVALID;
162                    }
163                    //perror("recv error");
164                    break;
165                }
166                //fprintf(stderr, "Completed tile(%d,%d,%d)\n", z,x,y);
167                if (cmd.x == x && cmd.y == y && cmd.z == z) {
168                    if (cmd.cmd == cmdDone)
169                        return 1;
170                    else
171                        return 0;
172                }
173            } else if (s == 0) {
174                break;
175            } else {
176                if (errno == EPIPE) {
177                    close(fd);
178                    fd = FD_INVALID;
179                    break;
180                }
181            }
182        }
183    }
184    return 0;
185}
186
187static int getPlanetTime(request_rec *r)
188{
189    static time_t last_check;
190    static time_t planet_timestamp;
191    time_t now = time(NULL);
192    struct stat buf;
193
194    // Only check for updates periodically
195    if (now < last_check + 300)
196        return planet_timestamp;
197
198    last_check = now;
199    if (stat(PLANET_TIMESTAMP, &buf)) {
200        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Planet timestamp file " PLANET_TIMESTAMP " is missing");
201        // Make something up
202        planet_timestamp = now - 3 * 24 * 60 * 60;
203    } else {
204        if (buf.st_mtime != planet_timestamp) {
205            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Planet file updated at %s", ctime(&buf.st_mtime));
206            planet_timestamp = buf.st_mtime;
207        }
208    }
209    return planet_timestamp;
210}
211
212enum tileState tile_state(request_rec *r, const char *filename)
213{
214    // FIXME: Apache already has most, if not all, this info recorded in r->fileinfo, use this instead!
215    struct stat buf;
216
217    if (stat(filename, &buf))
218        return tileMissing; 
219
220    if (buf.st_mtime < getPlanetTime(r))
221        return tileOld;
222
223    return tileCurrent;
224}
225
226static apr_status_t expires_filter(ap_filter_t *f, apr_bucket_brigade *b)
227{
228    request_rec *r = f->r;
229    apr_time_t expires, holdoff, nextPlanet;
230    apr_table_t *t = r->headers_out;
231    enum tileState state = tile_state(r, r->filename);
232    char *timestr;
233
234    /* Append expiry headers ... */
235
236    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "expires(%s), uri(%s), filename(%s), path_info(%s)\n",
237    //              r->handler, r->uri, r->filename, r->path_info);
238
239    // current tiles will expire after next planet dump is due
240    // or after 1 hour if the planet dump is late or tile is due for re-render
241    nextPlanet = (state == tileCurrent) ? apr_time_from_sec(getPlanetTime(r) + PLANET_INTERVAL) : 0;
242    holdoff = r->request_time + apr_time_from_sec(60 * 60);
243    expires = MAX(holdoff, nextPlanet);
244
245    apr_table_mergen(t, "Cache-Control",
246                     apr_psprintf(r->pool, "max-age=%" APR_TIME_T_FMT,
247                     apr_time_sec(expires - r->request_time)));
248    timestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
249    apr_rfc822_date(timestr, expires);
250    apr_table_setn(t, "Expires", timestr);
251
252    ap_remove_output_filter(f);
253    return ap_pass_brigade(f->next, b);
254}
255
256
257static int serve_tile(request_rec *r, const char *rel_path)
258{
259    // Redirect request to the tile
260    r->method = apr_pstrdup(r->pool, "GET");
261    r->method_number = M_GET;
262    apr_table_unset(r->headers_in, "Content-Length");
263    ap_internal_redirect_handler(rel_path, r);
264    return OK;
265}
266
267static int serve_blank(request_rec *r)
268{
269    return serve_tile(r, IMG_PATH "/blank-000000.png");
270}
271
272int get_load_avg(request_rec *r)
273{
274    FILE *loadavg = fopen("/proc/loadavg", "r");
275    int avg = 1000;
276
277    if (!loadavg) {
278        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "failed to read /proc/loadavg");
279        return 1000;
280    }
281    if (fscanf(loadavg, "%d", &avg) != 1) {
282        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "failed to parse /proc/loadavg");
283        fclose(loadavg);
284        return 1000;
285    }
286    fclose(loadavg);
287
288    return avg;
289}
290
291static int tile_dirty(request_rec *r, int x, int y, int z, const char *path)
292{
293    request_tile(r, x,y,z,path, 1);
294    return error_message(r, "Rendering request submitted");
295}
296
297
298
299
300static int get_tile(request_rec *r, int x, int y, int z, const char *abs_path, const char *rel_path)
301{
302    int avg = get_load_avg(r);
303    enum tileState state;
304
305    if (avg > MAX_LOAD_ANY) {
306        // we're too busy to send anything now
307        return error_message(r, "error: Load above MAX_LOAD_ANY threshold %d > %d", avg, MAX_LOAD_ANY);
308    }
309
310    state = tile_state(r, abs_path);
311
312    switch (state) {
313        case tileCurrent:
314            return serve_tile(r, rel_path);
315            break;
316        case tileOld:
317            if (avg > MAX_LOAD_OLD) {
318               // Too much load to render it now, mark dirty but return old tile
319               tile_dirty(r, x, y, z, abs_path);
320               return serve_tile(r, rel_path);
321            }
322            break;
323        case tileMissing:
324            if (avg > MAX_LOAD_MISSING) {
325               tile_dirty(r, x, y, z, abs_path);
326               return error_message(r, "error: File missing and load above MAX_LOAD_MISSING threshold %d > %d", avg, MAX_LOAD_MISSING);
327            }
328            break;
329    }
330
331    if (request_tile(r, x,y,z,abs_path, 0))
332        return serve_tile(r, rel_path);
333
334    if (state == tileOld)
335        return serve_tile(r, rel_path);
336
337    return error_message(r, "rendering failed for %s", rel_path);
338}
339
340static int tile_status(request_rec *r, int x, int y, int z, const char *path)
341{
342    // FIXME: Apache already has most, if not all, this info recorded in r->fileinfo, use this instead!
343    struct stat buf;
344    time_t now;
345    int old;
346    char MtimeStr[32]; // At least 26 according to man ctime_r
347    char AtimeStr[32]; // At least 26 according to man ctime_r
348    char *p;
349
350    if (stat(path, &buf))
351        return error_message(r, "Unable to find a tile at %s", path);
352
353    now = time(NULL);
354    old = (buf.st_mtime < getPlanetTime(r));
355
356    MtimeStr[0] = '\0';
357    ctime_r(&buf.st_mtime, MtimeStr);
358    AtimeStr[0] = '\0';
359    ctime_r(&buf.st_atime, AtimeStr);
360
361    if ((p = strrchr(MtimeStr, '\n')))
362        *p = '\0';
363    if ((p = strrchr(AtimeStr, '\n')))
364        *p = '\0';
365
366    //return error_message(r, "Tile is %s. Last rendered at %s. Last accessed at %s", old ? "due to be rendered" : "clean", MtimeStr, AtimeStr);
367    return error_message(r, "Tile is %s. Last rendered at %s", old ? "due to be rendered" : "clean", MtimeStr);
368}
369
370static const char *xyz_to_path(char *path, size_t len, int x, int y, int z)
371{
372#ifdef DIRECTORY_HASH
373    // Directory hashing is optimised for z18 max (2^18 tiles split into 2 levels of 2^9)
374    //snprintf(path, PATH_MAX, WWW_ROOT HASH_PATH "/%d/%03d/%03d/%03d/%03d.png", z,
375    //                 x/512 x%512, y/512, y%512);
376
377    // We attempt to cluseter the tiles so that a 16x16 square of tiles will be in a single directory
378    // Hash stores our 40 bit result of mixing the 20 bits of the x & y co-ordinates
379    // 4 bits of x & y are used per byte of output
380    unsigned char i, hash[5];
381
382    for (i=0; i<5; i++) {
383        hash[i] = ((x & 0x0f) << 4) | (y & 0x0f);
384        x >>= 4;
385        y >>= 4;
386    }
387    snprintf(path, PATH_MAX, WWW_ROOT HASH_PATH "/%d/%u/%u/%u/%u/%u.png", z, hash[4], hash[3], hash[2], hash[1], hash[0]);
388#else
389    snprintf(path, PATH_MAX, WWW_ROOT TILE_PATH "/%d/%d/%d.png", z, x, y);
390#endif
391    return path + strlen(WWW_ROOT);
392}
393
394
395static int tile_handler(request_rec *r)
396{
397    int x, y, z, n, limit;
398    char option[11];
399    int oob;
400    char abs_path[PATH_MAX];
401    const char *rel_path;
402
403    option[0] = '\0';
404
405    if(strcmp(r->handler, "tile"))
406        return DECLINED;
407
408    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "handler(%s), uri(%s), filename(%s), path_info(%s)",
409    //              r->handler, r->uri, r->filename, r->path_info);
410
411    if (!strncmp(r->uri, HASH_PATH, strlen(HASH_PATH))) {
412        // Add cache expiry headers on the hashed tiles
413        ap_add_output_filter("MOD_TILE", NULL, r, r->connection);
414        return DECLINED;
415    }
416
417    /* URI = .../<z>/<x>/<y>.png[/option] */
418    n = sscanf(r->uri, TILE_PATH "/%d/%d/%d.png/%10s", &z, &x, &y, option);
419    /* The original rewrite config matched anything that ended with 3 numbers */
420    //if (n < 3)
421    //        n = sscanf(r->uri, TILE_PATH "%*[^0-9]%d/%d/%d.png/%10s", &z, &x, &y, option);
422#if 0
423    if (n < 3)
424            n = sscanf(r->uri, TILE_PATH "//%d/%d/%d.png/%10s", &z, &x, &y, option);
425    if (n < 3)
426            n = sscanf(r->uri, TILE_PATH "/%*[^/]/%d/%d/%d.png/%10s", &z, &x, &y, option);
427    if (n < 3)
428            n = sscanf(r->uri, TILE_PATH "/%*[^/]//%d/%d/%d.png/%10s", &z, &x, &y, option);
429    if (n < 3)
430            n = sscanf(r->uri, TILE_PATH "/%*[^/]/%*[^/]/%d/%d/%d.png/%10s", &z, &x, &y, option);
431#endif
432    if (n < 3) {
433        //return error_message(r, "unable to process: %s", r->path_info);
434        return DECLINED;
435    }
436
437    // Generate the tile filename
438    rel_path = xyz_to_path(abs_path, sizeof(abs_path), x, y, z);
439    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "abs_path(%s), rel_path(%s)", abs_path, rel_path);
440    //ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "z(%d) x(%d) y(%d)", z, x, y);
441
442    // Validate tile co-ordinates
443    oob = (z < 0 || z > MAX_ZOOM);
444    if (!oob) {
445         // valid x/y for tiles are 0 ... 2^zoom-1
446        limit = (1 << z) - 1;
447        oob =  (x < 0 || x > limit || y < 0 || y > limit);
448    }
449
450    if (n == 3) {
451        ap_add_output_filter("MOD_TILE", NULL, r, r->connection);
452        return oob ? serve_blank(r) : get_tile(r, x, y, z, abs_path, rel_path);
453    }
454
455    if (n == 4) {
456        if (oob)
457            return error_message(r, "The tile co-ordinates that you specified are invalid");
458        if (!strcmp(option, "status"))
459            return tile_status(r, x, y, z, abs_path);
460        if (!strcmp(option, "dirty"))
461            return tile_dirty(r, x, y, z, abs_path);
462        return error_message(r, "Unknown option");
463    }
464    return DECLINED;
465}
466
467static void register_hooks(__attribute__((unused)) apr_pool_t *p)
468{
469    ap_register_output_filter("MOD_TILE", expires_filter, NULL, APR_HOOK_MIDDLE);
470    ap_hook_handler(tile_handler, NULL, NULL, APR_HOOK_FIRST);
471}
472
473module AP_MODULE_DECLARE_DATA tile_module =
474{
475    STANDARD20_MODULE_STUFF,
476    NULL,           /* dir config creater */
477    NULL,           /* dir merger --- default is to override */
478    NULL,           /* server config */
479    NULL,           /* merge server config */
480    NULL,           /* command apr_table_t */
481    register_hooks  /* register hooks */
482};
Note: See TracBrowser for help on using the repository browser.