web_main.c
author Tero Marttila <terom@fixme.fi>
Sat, 30 Aug 2008 19:13:15 +0300
changeset 49 10c7dce1a043
parent 27 1e79b4cc8f1b
permissions -rw-r--r--
autogenerate the memcache_test help output, and pipeline memcache requests
#include <sys/types.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
#include <assert.h>

#include <event2/event.h>
#include <event2/event_compat.h>
#include <event2/http.h>
#include <event2/event_struct.h>

#include "common.h"
#include "http.h"
#include "render_struct.h"
#include "render.h"
#include "remote_node.h"
#include "remote_pool.h"
#include "render_remote.h"
#include "config.h"
#include "tile.h"
#include "static.h"

#define MIN_CHUNK_SIZE 4096
#define OVERFLOW_BUFFER 4096

// do not do any userland socket output buffering
#define HTTP_BUFFER 0

// what event_base we're using
static struct event_base *ev_base;

// our render node pool
static struct remote_pool remote_pool;

// info on a render request
struct render_request {
    struct evhttp_request *http_request;

    int headers_sent;
    
    struct render_remote *render_info;

    size_t bytes_sent;

    int paused;
};

// cb func prototypes
static void _render_http_written (struct evhttp_request *request, void *arg);

static void _render_cleanup (struct render_request *ctx) {
    if (ctx->render_info)
        render_remote_free(ctx->render_info);

    free(ctx);
}

static void _render_sent (void *arg) {
    struct render_request *ctx = arg;

    // send headers
    evhttp_add_header(evhttp_request_get_output_headers(ctx->http_request), "Content-Type", "image/png");
    evhttp_send_reply_start(ctx->http_request, HTTP_OK, "OK");
    
    // setup flow-control
    evhttp_set_reply_notify(ctx->http_request, HTTP_BUFFER, &_render_http_written, ctx);

    ctx->headers_sent = 1;

    INFO("render [%p]: sent headers", ctx);
}

static void _render_data (struct evbuffer *buf, void *arg) {
    struct render_request *ctx = arg;

    size_t buf_size = EVBUFFER_LENGTH(buf);

    assert(buf_size > 0);   // shouldn't happen anymore with the new render_remote
    
    // check if we are paused
    if (ctx->paused) {
        // we are waiting for the HTTP send buffer to clear, so keep the data in the render buffer
        INFO("render [%p]: delaying data: %zu:%zu bytes", ctx, buf_size, ctx->bytes_sent);

        return;
    }

    // move chunk to http buffers
    evhttp_send_reply_chunk(ctx->http_request, buf);

    INFO("render [%p]: enqueued chunk: %zu/%zu bytes", ctx, buf_size, ctx->bytes_sent);

    // mark ourself as paused until httpd tells us to continue
    ctx->paused = 1;
    
    // keep a tally of total sent bytes
    ctx->bytes_sent += buf_size;
}

static void _render_done (void *arg) {
    struct render_request *ctx = arg;

    // send end
    evhttp_send_reply_end(ctx->http_request);

    INFO("render [%p]: done: %zu bytes", ctx, ctx->bytes_sent);
        
    // the request is now done, clean up
    _render_cleanup(ctx);
}

static void _render_fail (void *arg) {
    struct render_request *ctx = arg;

    if (ctx->headers_sent) {
        // just terminate the PNG stream where it is
        evhttp_send_reply_end(ctx->http_request);

    } else {
        evhttp_send_error(ctx->http_request, 500, "Internal Server Error");
    }
    
    INFO("render [%p]: failed", ctx);

    _render_cleanup(ctx);
}

static void _render_http_lost (struct evhttp_request *req, void *arg) {
    struct render_request *ctx = arg;

    INFO("render [%p]: lost http connection", ctx);

    // cancel
    render_remote_cancel(ctx->render_info);
    ctx->render_info = NULL;

    _render_cleanup(ctx);
}

static void _render_http_written (struct evhttp_request *request, void *arg) {
    struct render_request *ctx = arg;
    
    INFO("render [%p]: http available for write", ctx);
    
    // unpause ourself
    ctx->paused = 0;

    // any data waiting in the buffer?
    render_remote_flush(ctx->render_info);
}

static int _http_render_execute (struct evhttp_request *request, struct render *render) {
    // render request context
    struct render_request *ctx = calloc(1, sizeof(struct render_request));

    if (!ctx)
        ERROR("calloc");
    
    ctx->http_request = request;
    ctx->headers_sent = 0;
    ctx->bytes_sent = 0;
    
    // initiate the remote render operation
    if ((ctx->render_info = render_remote(render, &remote_pool,
        &_render_sent,
        &_render_data,
        &_render_done,
        &_render_fail,
        ctx
    )) == NULL)
        ERROR("render_remote");
    
    // set chunk size
    render_remote_set_recv(ctx->render_info, MIN_CHUNK_SIZE, OVERFLOW_BUFFER);

    // set close cb
    evhttp_set_reply_abortcb(request, &_render_http_lost, ctx);
    
    INFO("render [%p]: started", ctx);
    
    return 0;

error:
    _render_cleanup(ctx);

    return -1;
}

/*
 * HTTP request handlers
 */
void http_render (struct evhttp_request *request, void *arg) {
    // gather some info about the request
    const char *uri = evhttp_request_get_uri(request);
    char *peer_address;
    unsigned short peer_port;
    struct render render;

    evhttp_request_get_peer(request, &peer_address, &peer_port);
    
    u_int32_t img_w = 256, img_h = 256;

    // parse request arguments
    struct http_qarg qarg_spec[] = {
        { "w",      QARG_UINT32,    &img_w, QARG_OPTIONAL   },
        { "h",      QARG_UINT32,    &img_h, QARG_OPTIONAL   },
        { NULL,     QARG_END,       NULL,   0               }
    };

    if (http_qarg_parse(request, qarg_spec))
        goto error;
    
    // build the render op
    if (
            render_init(&render)
         || render_set_mode(&render, RENDER_PNG)
         || render_set_size(&render, img_w, img_h)
         || render_region_full(&render)
    )
        ERROR("render_*");

    // request log
    INFO("REQ: [%s:%d] method=%d, uri=%s, img_w=%u, img_h=%u", peer_address, peer_port, evhttp_request_get_type(request), uri, img_w, img_h);
    
    // do it
    if (_http_render_execute(request, &render))
        goto error;
    
    return;

error:
    evhttp_send_error(request, 500, "Internal Server Error");
}

void http_tile (struct evhttp_request *request, void *arg) {
    // gather some info about the request
    const char *uri = evhttp_request_get_uri(request);
    char *peer_address;
    unsigned short peer_port;
    struct render render;

    evhttp_request_get_peer(request, &peer_address, &peer_port);
    
    u_int32_t x, y, sw, sh;
    u_int16_t z;

    // parse request arguments
    struct http_qarg qarg_spec[] = {
        { "x",      QARG_UINT32,    &x,     QARG_REQUIRED   },
        { "y",      QARG_UINT32,    &y,     QARG_REQUIRED   },
        { "z",      QARG_UINT16,    &z,     QARG_REQUIRED   },
        { "sw",     QARG_UINT32,    &sw,    QARG_REQUIRED   },
        { "sh",     QARG_UINT32,    &sh,    QARG_REQUIRED   },
        { NULL,     QARG_END,       NULL,   0               }
    };

    if (http_qarg_parse(request, qarg_spec))
        goto error;

    // build the render op
    if (
            render_init(&render)
         || render_set_mode(&render, RENDER_PNG)
         || render_set_tile(&render, sw, sh, x, y, z)
    )
        ERROR("render_*");

    // request log
    INFO("REQ: [%s:%d] method=%d, uri=%s, pos=(%u, %u), zoom=%hu, screen=[%ux%u]", 
        peer_address, peer_port, evhttp_request_get_type(request), uri, x, y, z, sw, sh);
    
    // do it
    if (_http_render_execute(request, &render))
        goto error;
    
    return;

error:
    evhttp_send_error(request, 500, "Internal Server Error");
}

/*
 * Signal handling
 */
struct event ev_sigint;

void sigint_handler (int signal, short event, void *arg) {
    INFO("SIGINT: shutting down");
    
    if (event_base_loopexit(ev_base, NULL))
        err_exit("event_loopexit");
}

void signals_init () {
    // handle SIGINT
    signal_set(&ev_sigint, SIGINT, &sigint_handler, NULL);
    signal_add(&ev_sigint, NULL);
    
    // ignore SIGPIPE
    struct sigaction sigpipe;
    memset(&sigpipe, 0, sizeof(sigpipe));

    sigpipe.sa_handler = SIG_IGN;

    sigaction(SIGPIPE, &sigpipe, NULL);
}

void signals_deinit () {
    signal_del(&ev_sigint);
}

void log_null (int severity, const char *msg) {
    // ignore
}

int main (int argc, char **argv) {
    // libevent init
    ev_base = event_init();

    if (!ev_base)
        FATAL("event_init");
    
    // set up our render node pool
    remote_pool_init(&remote_pool);

    // process arguments
    int opt;
    int enable_debug = 0;

    while ((opt = getopt(argc, argv, "dp:r:")) != -1) {
        switch (opt) {
            case 'p':
                // populate the pool from a file
                if (remote_pool_load(&remote_pool, optarg))
                    return 1;

                break;

            case 'r':
                // add the given render node to the pool
                if (remote_pool_add(&remote_pool, optarg))
                    return 1;

                break;

            case 'd':
                // enable libevent debugging
                enable_debug = 1;
                break;

            default:
                err_exit("Usage: %s [-d] (-p pool_file | -r hostname[:port] | ...)", argv[0]);
        
        }
    }

    int pool_size = remote_pool_size(&remote_pool);

    if (!pool_size)
        FATAL("No remote render nodes given");
    

    INFO("Registered %d render nodes in our pool", pool_size);
    
    // per default it is enabled
    if (!enable_debug)
        event_set_log_callback(&log_null);
    
    // handle signals
    signals_init();

    // evhttp init
    struct evhttp *http_server = evhttp_new(ev_base);

    if (!http_server)
        FATAL("evhttp_new");
    
    // bind to the correct interface/port
    if (evhttp_bind_socket(http_server, "0.0.0.0", 8117))
        FATAL("evhttp_bind_socket");
    
    // add our http request handlers
    evhttp_set_cb(http_server, "/render",   &http_render,   NULL);
    evhttp_set_cb(http_server, "/tile",     &http_tile,     NULL);

    // and then static files
    struct static_file static_index;
    struct static_dir static_files;

    if (static_init(&static_index, http_server, "/", "static/index.html"))
        FATAL("static_init: index.html");

    if (static_dir_init(&static_files, http_server, "/static", "static"))
        FATAL("static_dir_init: static");

    // we shall now run
    INFO("RUN 0.0.0.0:8117");
    
    // run the libevent mainloop
    if (event_base_dispatch(ev_base))
        WARNING("event_dispatch");

    INFO("SHUTDOWN");
    
    // clean up
    signals_deinit();
    evhttp_free(http_server);
    event_base_free(ev_base);
    
    // successfull exit
    return 0;
}