initial code, render_file(local+remote), render_node, web_main(local)
authorTero Marttila <terom@fixme.fi>
Fri, 30 May 2008 14:24:23 +0300
changeset 0 5b010627d7ed
child 1 6aa1a0d1f88d
initial code, render_file(local+remote), render_node, web_main(local)

committer: Tero Marttila <terom@fixme.fi>
Makefile
common.c
common.h
mandelbrot.c
mandelbrot.h
render_file.c
render_node.c
web_main.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile	Fri May 30 14:24:23 2008 +0300
@@ -0,0 +1,9 @@
+LDFLAGS = -L/usr/local/lib -levent -lpng
+CFLAGS = -Wall -g
+
+render_file: mandelbrot.o common.o
+
+web_main: mandelbrot.o common.o
+
+render_node: mandelbrot.o common.o
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common.c	Fri May 30 14:24:23 2008 +0300
@@ -0,0 +1,44 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <string.h>
+
+#include "common.h"
+
+void die (const char *msg) {
+    perror(msg);
+    exit(EXIT_FAILURE);
+}
+
+void error (const char *fmt, ...) {
+    va_list va;
+
+    va_start(va, fmt);
+    vfprintf(stderr, fmt, va);
+    va_end(va);
+
+    fprintf(stderr, "\n");
+}
+
+void perr_exit (const char *fmt, ...) {
+    va_list va;
+
+    va_start(va, fmt);
+    vfprintf(stderr, fmt, va);
+    va_end(va);
+
+    fprintf(stderr, ": %s\n", strerror(errno));
+    exit(EXIT_FAILURE);
+}
+
+void err_exit (const char *fmt, ...) {
+    va_list va;
+
+    va_start(va, fmt);
+    vfprintf(stderr, fmt, va);
+    va_end(va);
+
+    fprintf(stderr, "\n");
+    exit(EXIT_FAILURE);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common.h	Fri May 30 14:24:23 2008 +0300
@@ -0,0 +1,6 @@
+void die (const char *msg);
+void error (const char *fmt, ...);
+void perr_exit (const char *fmt, ...);
+void err_exit (const char *fmt, ...);
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mandelbrot.c	Fri May 30 14:24:23 2008 +0300
@@ -0,0 +1,224 @@
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <arpa/inet.h>
+
+#include <png.h>
+
+#include "mandelbrot.h"
+#include "common.h"
+
+
+#define DETAIL 255
+
+#define absdelta(a, b) (a>b ? a-b : b-a)
+
+void render_ctx_set (struct render_ctx *ctx, render_ctx_write_cb write_fn, render_ctx_flush_cb flush_fn, void *arg) {
+    memset(ctx, 0, sizeof(*ctx));
+    
+    ctx->write_fn = write_fn;
+    ctx->flush_fn = flush_fn;
+    ctx->cb_arg = arg;
+}
+
+void render_ctx_stream (struct render_ctx *ctx, FILE *fh) {
+    memset(ctx, 0, sizeof(*ctx));
+
+    ctx->stream = fh;
+}
+
+void user_write_data(png_structp png_ptr, png_bytep data, png_size_t length) {
+    struct render_ctx *ctx = (struct render_ctx *) png_get_io_ptr(png_ptr);
+
+    if (ctx->write_fn)
+        switch (ctx->write_fn(data, length, ctx->cb_arg)) {
+            case RENDER_CB_ERR :
+                ctx->_error = 1;
+                break;
+
+            case RENDER_CB_OK :
+                // great!
+                break;
+        }
+}
+
+void user_flush_data(png_structp png_ptr) {
+    struct render_ctx *ctx = (struct render_ctx *) png_get_io_ptr(png_ptr);
+
+    if (ctx->flush_fn)
+        switch (ctx->flush_fn(ctx->cb_arg)) {
+            case RENDER_CB_ERR :
+                ctx->_error = 1;
+                break;
+
+            case RENDER_CB_OK :
+                // great!
+                break;
+        }
+}
+
+int _validate_render (
+        u_int32_t img_w, u_int32_t img_h,
+        double x1, double y1, double x2, double y2
+) {
+    if (
+            (img_w == 0 || img_h == 0)      // zero-size image
+         || (x1 >= x2 || y1 >= y2)          // invalid region
+    )
+        return MANDELBROT_ERR_INVALID;
+    else
+        return MANDELBROT_OK;
+}
+
+
+int mandelbrot_render_region (
+        struct render_ctx *ctx, 
+        u_int32_t img_w, u_int32_t img_h, 
+        double x1, double y1, double x2, double y2
+) {
+    u_int32_t img_x, img_y;
+    double x0, y0, x, y, _x, _y, w_scale, h_scale;
+    u_int8_t iter;
+    u_int8_t *row;
+
+    // validate!
+    int valid = _validate_render(img_w, img_h, x1, y1, x2, y2);
+
+    if (valid != MANDELBROT_OK)
+        return valid;
+
+    // clear out any potential error in ctx
+    ctx->_error = 0;
+    
+    // calcluate the scale factors
+    w_scale = img_w/absdelta(x1, x2);
+    h_scale = img_h/absdelta(y1, y2);
+    
+    // malloc the memory used to render each row
+    row = (u_int8_t *) malloc(img_w);
+
+    if (!row)
+        return MANDELBROT_ERR_MALLOC;
+
+    // libpng initialization
+    png_structp png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+
+    if (!png_ptr) {
+        free(row);
+        return MANDELBROT_ERR_PNG_INIT;
+    }
+
+    png_infop info_ptr = png_create_info_struct(png_ptr);
+
+    if (!info_ptr) {
+        png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
+        free(row);
+        return MANDELBROT_ERR_PNG_INIT;
+    }
+    
+    if (ctx->stream) {
+        // use normal libpng I/O
+        png_init_io(png_ptr, ctx->stream);
+    } else {
+        // setup our custom I/O callbacks
+        png_set_write_fn(png_ptr, ctx, &user_write_data, &user_flush_data);
+    }
+
+    // some PNG metadata
+    png_set_IHDR(png_ptr, info_ptr, img_w, img_h, 8, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+    // write out the PNG header
+    png_write_info(png_ptr, info_ptr);
+
+    // possible error return
+    if (ctx->_error) {
+        free(row);
+        png_destroy_write_struct(&png_ptr, &info_ptr);
+        return MANDELBROT_ERR_CB;
+    }
+
+    // start rendering!
+    for (img_y=0; img_y < img_h; img_y++) {
+        // render the current row
+        for (img_x=0; img_x < img_w; img_x++) {
+            x = 0;
+            y = 0;
+            x0 = img_x/w_scale + x1;
+            y0 = img_y/h_scale + y1;
+            iter = DETAIL; 
+
+            while (x*x + y*y < (2*2) && iter > 0) {
+                _x = x*x - y*y + x0;
+                _y = 2*x*y + y0;
+
+                x = _x;
+                y = _y;
+
+                iter--;
+            }
+            
+            row[img_x] = iter;
+        }
+        
+        // write the raw pixels to libpng
+        png_write_row(png_ptr, row);
+        
+        // check for user errors return
+        if (ctx->_error) {
+            free(row);
+            png_destroy_write_struct(&png_ptr, &info_ptr);
+            return MANDELBROT_ERR_CB;
+        }
+    }
+    
+    // finished writing
+    png_write_end(png_ptr, info_ptr);
+    
+    // clean up
+    png_destroy_write_struct(&png_ptr, &info_ptr);
+    free(row);
+
+    // check for user errors return
+    if (ctx->_error)
+        return MANDELBROT_ERR_CB;
+    
+    // return succesfully
+    return MANDELBROT_OK;
+}
+
+int mandelbrot_render_region_timed (
+        struct render_ctx *ctx, 
+        u_int32_t img_w, u_int32_t img_h, 
+        double x1, double y1, double x2, double y2,
+        double *duration
+) {
+    clock_t t1 = clock();
+    int ret = mandelbrot_render_region(ctx, img_w, img_h, x1, y1, x2, y2);
+    clock_t t2 = clock();
+
+    *duration = ((double)(t2 - t1))/CLOCKS_PER_SEC;
+
+    return ret;
+}
+
+int render_cmd_set (
+        struct render_cmd *cmd,
+        u_int32_t img_w, u_int32_t img_h, 
+        double x1, double y1, double x2, double y2
+) {
+    // validate!
+    int valid = _validate_render(img_w, img_h, x1, y1, x2, y2);
+
+    if (valid != MANDELBROT_OK)
+        return valid;
+
+   cmd->img_w = htonl(img_w);
+   cmd->img_h = htonl(img_h);
+   cmd->x1 = x1;
+   cmd->y1 = y1;
+   cmd->x2 = x2;
+   cmd->y2 = y2;
+
+   return MANDELBROT_OK;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mandelbrot.h	Fri May 30 14:24:23 2008 +0300
@@ -0,0 +1,117 @@
+/*
+ * code to render a mandelbrot
+ */
+
+#include <sys/types.h>
+
+typedef int (*render_ctx_write_cb)(const unsigned char *data, size_t length, void *arg);
+typedef int (*render_ctx_flush_cb)(void *arg);
+
+#define RENDER_CB_OK 0
+#define RENDER_CB_ERR 1
+
+struct render_ctx {
+    /*
+     * If we just use normal FILE* stream operations
+     */
+    FILE *stream;
+
+    /*
+     * These callback functions are used to handle the PNG data as it's rendered.
+     *
+     * If they return RENDER_CB_OK, all is fine and rendering will continue normally.
+     * If they return RENDER_CB_ERR, rendering will abort.
+     */
+
+    // called to handle the output data
+    render_ctx_write_cb write_fn;
+
+    // called when the output data should be flushed - can be safely ignored if not needed
+    render_ctx_flush_cb flush_fn;
+
+    // the callback argument
+    void *cb_arg;
+
+    // error status
+    int _error;
+};
+
+/*
+ * initialize the given struct render_ctx
+ */
+void render_ctx_set (struct render_ctx *ctx, render_ctx_write_cb write_fn, render_ctx_flush_cb flush_fn, void *arg);
+void render_ctx_stream (struct render_ctx *ctx, FILE *fh);
+
+/*
+ * return codes for mandelbrot_render
+ */
+#define MANDELBROT_OK 0
+#define MANDELBROT_ERR_MALLOC 1
+#define MANDELBROT_ERR_PNG_INIT 2
+#define MANDELBROT_ERR_CB 3
+#define MANDELBROT_ERR_INVALID 4
+
+/*
+ * render an <img_w>x<img_h> PNG image of the mandelbrot region bounded by (x1, y1) -> (x2, y2)
+ */
+int mandelbrot_render_region (
+        struct render_ctx *ctx, 
+        u_int32_t img_w, u_int32_t img_h, 
+        double x1, double y1, double x2, double y2
+);
+
+int mandelbrot_render_region_timed (
+        struct render_ctx *ctx, 
+        u_int32_t img_w, u_int32_t img_h, 
+        double x1, double y1, double x2, double y2,
+        double *duration
+);
+
+/*
+ * remote rendering
+ */
+
+#pragma pack(push)
+#pragma pack(1)
+
+struct render_cmd {
+    u_int32_t   img_w;
+    u_int32_t   img_h;
+
+    double      x1;
+    double      y1;
+    double      x2;
+    double      y2;
+};
+
+#pragma pack(pop)
+
+#define RENDER_PORT 6159
+#define RENDER_CMD_SIZE sizeof(struct render_cmd)
+
+/*
+ * build the command to send for remote rendering
+ */
+int render_cmd_set (
+        struct render_cmd *cmd,
+        u_int32_t img_w, u_int32_t img_h, 
+        double x1, double y1, double x2, double y2
+);
+
+/* 
+ * The "full" mandelbrot region
+ */
+#define REGION_X1 -2.0
+#define REGION_Y1 -1.5
+#define REGION_X2 1.0
+#define REGION_Y2 1.5
+
+#define mandelbrot_render_full(ctx, img_w, img_h) \
+    mandelbrot_render_region(ctx, img_w, img_h, REGION_X1, REGION_Y1, REGION_X2, REGION_Y2)
+
+#define mandelbrot_render_full_timed(ctx, img_w, img_h, duration) \
+    mandelbrot_render_region_timed(ctx, img_w, img_h, REGION_X1, REGION_Y1, REGION_X2, REGION_Y2, duration)
+
+#define render_cmd_set_full(cmd, img_w, img_h) \
+    render_cmd_set(cmd, img_w, img_h, REGION_X1, REGION_Y1, REGION_X2, REGION_Y2)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/render_file.c	Fri May 30 14:24:23 2008 +0300
@@ -0,0 +1,173 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include "mandelbrot.h"
+#include "common.h"
+
+static int verbose;
+
+int file_write (const unsigned char *data, size_t length, void *arg) {
+    size_t ret = fwrite(data, length, 1, (FILE *) arg);
+    
+    return (ret == 1) ? RENDER_CB_OK : RENDER_CB_ERR;
+}
+
+void render_local (int img_w, int img_h, FILE *output) {
+    struct render_ctx ctx;
+
+    render_ctx_set(&ctx, &file_write, NULL, output);
+
+    double duration;
+
+    if (mandelbrot_render_full_timed(
+            &ctx, 
+            img_w, img_h, 
+            &duration
+    ) != MANDELBROT_OK)
+        err_exit("mandelbrot_render_region failed");
+    
+    if (verbose) 
+        fprintf(stdout, "rendered %dx%d mandelbrot in %f seconds\n", img_w, img_h, duration);
+}
+
+void render_remote (int img_w, int img_h, FILE *output, FILE *remote) {
+    struct render_cmd cmd;
+
+    if (render_cmd_set_full(&cmd, img_w, img_h) != MANDELBROT_OK) {
+        err_exit("mandelbrot_render_remote failed");
+    }
+
+    int ret = fwrite((void *) &cmd, sizeof(cmd), 1, remote);
+    
+    if (ret != 1)
+        perr_exit("fwrite(remote)");
+        
+    if (verbose)
+        printf("sent render command of %zu bytes\n", sizeof(cmd));
+
+    // shuffle data around
+    while (!feof(remote)) {
+        char buf[1024];
+
+        int bytes = fread(buf, 1, sizeof(buf), remote);
+
+        if (bytes)
+            if (fwrite(buf, bytes, 1, output) != 1)
+                perr_exit("fwrite(output)");
+    }
+    
+    if (verbose)
+        printf("got PNG data\n");
+
+    if (ferror(remote))
+        perr_exit("fread(remote)");
+}
+
+FILE *open_remote (char *remote) {
+    char *addr_str, *port_str = NULL;
+
+    addr_str = strsep(&remote, ":");
+
+    if (remote)
+        port_str = remote;
+    
+    struct sockaddr_in addr;
+
+    addr.sin_family = AF_INET;
+    addr.sin_port = htons(port_str ? atoi(port_str) : RENDER_PORT);
+    
+    if (inet_aton(addr_str, &addr.sin_addr) == 0)
+        err_exit("invalid address: %s", addr_str);
+
+    int sock;
+    
+    if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == -1)
+        perr_exit("socket");
+
+    if (verbose)
+        printf("connect(%s, %d)\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
+    
+    if (connect(sock, (const struct sockaddr *) &addr, sizeof(addr)) == -1)
+        perr_exit("connect(%s, %d)", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
+    
+    FILE *fh = fdopen(sock, "w+");
+
+    if (!fh || ferror(fh))
+        perr_exit("fdopen");
+
+    setbuffer(fh, NULL, 0);
+
+    return fh;
+}
+
+int main (int argc, char **argv) {
+    int opt;
+    
+    FILE *output = NULL, *remote = NULL;
+    int img_w = 256, img_h = 256;
+    
+
+    while ((opt = getopt(argc, argv, "w:h:o:vr:")) != -1) {
+        switch(opt) {
+            case 'w' :
+                img_w = atoi(optarg);
+                break;
+
+            case 'h' :
+                img_h = atoi(optarg);
+                break;
+
+            case 'o' :
+                if (output)
+                    err_exit("Only use -o once");
+
+                output = fopen(optarg, "w");
+
+                if (!output)
+                    die(optarg);
+
+                break;
+
+            case 'v' :
+                verbose = 1;
+                break;
+
+            case 'r' :
+                if (remote)
+                    err_exit("Only use -r once");
+
+                remote = open_remote(optarg);
+
+                break;
+
+            default :
+                err_exit("Usage: %s [-w img_w] [-h img_h] [-o output_file] [-v] [-r host[:port]]", argv[0]);
+        }
+    }
+
+    if (!output)
+        output = stdout;
+    
+    if (remote) {
+        if (verbose)
+            fprintf(stderr, "Render [%dx%d] mandelbrot remotely\n", img_w, img_h);
+
+        render_remote(img_w, img_h, output, remote);
+
+        fclose(remote);
+    } else {
+        if (verbose)
+            fprintf(stderr, "Render [%dx%d] mandelbrot locally\n", img_w, img_h);
+
+        render_local(img_w, img_h, output);
+    }
+
+    fclose(output);
+
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/render_node.c	Fri May 30 14:24:23 2008 +0300
@@ -0,0 +1,127 @@
+#include <stdio.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <stdlib.h>
+
+#include "mandelbrot.h"
+#include "common.h"
+
+int read_int (FILE *fh, u_int32_t *i) {
+    printf("read_int: fread(%p, %zu, 1, %p)... ", i, sizeof(*i), fh);
+    fflush(stdout);
+    int ret = fread(i, sizeof(*i), 1, fh);
+    printf("ok: %d\n", ret);
+    
+    if (ret == 0) {
+        error("EOF");
+        return 0;
+    } else if (ret != 1) {
+        perror("read_int");
+        return 0;
+    }
+
+    *i = ntohl(*i);
+
+    printf("read_int: %i\n", *i);
+
+    return 1;
+}
+
+int read_double (FILE *fh, double *dbl) {
+    int ret = fread(dbl, sizeof(*dbl), 1, fh);
+
+    if (ret != 1) {
+        perror("read_double");
+        return 0;
+    }
+
+    printf("read_double: %f\n", *dbl);
+
+    return 1;
+}
+
+void handle_client (int sock) {
+    // open it as a FILE*
+    FILE *fh = fdopen(sock, "r+");
+    
+    // read the parameters
+    u_int32_t img_w, img_h;
+    double x1, y1, x2, y2;
+
+    if (
+        !read_int(fh, &img_w) ||
+        !read_int(fh, &img_h) ||
+        !read_double(fh, &x1) ||
+        !read_double(fh, &y1) ||
+        !read_double(fh, &x2) ||
+        !read_double(fh, &y2)
+    ) {
+        error("ERR: invalid arguments");
+        fclose(fh);
+        return;
+    }
+
+    printf("RENDER: [%ux%u] (%f, %f) -> (%f, %f): ", img_w, img_h, x1, y1, x2, y2);
+
+    double duration;
+
+    // set up the render_ctx
+    struct render_ctx ctx;
+    render_ctx_stream(&ctx, fh);
+    
+    // render!
+    mandelbrot_render_region_timed(
+        &ctx,
+        img_w, img_h,
+        x1, y1, x2, y2,
+        &duration
+    );
+
+    printf("time=%fs\n", duration);
+    
+    // close the FILE* and socket
+    fclose(fh);
+    
+    return;
+}
+
+int main (int argc, char** argv) {
+    int ssock, sock;
+    struct sockaddr_in addr;
+    socklen_t addr_len;
+
+    // create the socket
+    if ((ssock = socket(PF_INET, SOCK_STREAM, 0)) == -1)
+        die("socket");
+
+    addr.sin_family = AF_INET;
+    addr.sin_port = htons(RENDER_PORT);
+    addr.sin_addr.s_addr = INADDR_ANY;
+
+    if (argc > 1)
+        addr.sin_port = htons(atoi(argv[1]));
+    
+    if (bind(ssock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) == -1)
+        die("bind");
+    
+    if (listen(ssock, 1) == -1)
+        die("listen");
+    
+    printf("RUN: %s:%hu\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
+
+    // main accept loop
+    while (1) {
+        addr_len = sizeof(struct sockaddr_in);
+
+        // accept a new client
+        if ((sock = accept(ssock, (struct sockaddr *) &addr, &addr_len)) == -1)
+            die("accept");
+        
+        printf("ACCEPT: %s:%hu\n", inet_ntoa(addr.sin_addr), addr.sin_port);
+        
+        // handle their resquest
+        handle_client(sock);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web_main.c	Fri May 30 14:24:23 2008 +0300
@@ -0,0 +1,123 @@
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <event.h>
+#include <evhttp.h>
+
+#include "mandelbrot.h"
+#include "common.h"
+
+struct http_render_state {
+    struct evbuffer *buf;
+
+} _http_render_state = { NULL };
+
+int _http_render_cb (const unsigned char *data, size_t length, void *arg) {
+
+    // create a buffer
+    struct evbuffer *buf = evbuffer_new();
+
+    if (!buf) {
+        error("_http_render_cb: evbuffer_new failed");
+        return RENDER_CB_ERR;
+    }
+    
+    // add the data to it
+    if (evbuffer_add(buf, data, length)) {
+        error("_http_render_cb: evbuffer_add(%d) failed", length);
+        return RENDER_CB_ERR;
+    }
+    
+    // send it
+    evhttp_send_reply_chunk((struct evhttp_request *) arg, buf);
+
+    printf("_http_render_cb: sent chunk: %zu\n", length);
+    
+    // free the buffer (!!!)
+    evbuffer_free(buf);
+    
+    // return ok
+    return RENDER_CB_OK;
+}
+
+void http_render (struct evhttp_request *request, void *arg) {
+    // gather some info about the request
+    const char *uri = evhttp_request_uri(request);
+    char *peer_address;
+    u_short peer_port;
+
+    evhttp_connection_get_peer(request->evcon, &peer_address, &peer_port);
+    
+    // request arguments
+    u_int32_t img_w = 256, img_h = 256;
+    struct evkeyval *qarg;
+    struct evkeyvalq qargs;
+
+    evhttp_parse_query(uri, &qargs);
+
+    TAILQ_FOREACH(qarg, &qargs, next) {
+        if (strcmp(qarg->key, "w") == 0)
+            img_w = strtol(qarg->value, NULL, 10);
+        else if (strcmp(qarg->key, "h") == 0)
+            img_h = strtol(qarg->value, NULL, 10);
+    }
+
+    // clean up the qargs (badly named functions :< )
+    evhttp_clear_headers(&qargs);
+
+    // request log
+    printf("REQ: [%s:%d] uri=%s, img_w=%d, img_h=%d\n", peer_address, peer_port, uri, img_w, img_h);
+    
+    // set headers
+    evhttp_add_header(request->output_headers, "Content-Type", "image/png");
+    evhttp_send_reply_start(request, HTTP_OK, "OK");
+    
+    // render
+    struct render_ctx ctx;
+    render_ctx_set(&ctx, &_http_render_cb, NULL, request);
+
+    int err = mandelbrot_render_full(&ctx, img_w, img_h);
+
+    if (err) {
+        fprintf(stderr, "ERR: mandelbrot_render_full(%d, %d): %d\n", img_w, img_h, err);
+    }
+    
+    // reply end
+    evhttp_send_reply_end(request);
+}
+
+int main (void) {
+    // libevent/http init
+    struct event_base *ev_base = event_init();
+
+    if (!ev_base)
+        die("event_init");
+
+    struct evhttp *http_server = evhttp_new(ev_base);
+
+    if (!http_server)
+        die("evhttp_new");
+    
+    // bind to the correct interface/port
+    if (evhttp_bind_socket(http_server, "0.0.0.0", 8117))
+        die("evhttp_bind_socket");
+    
+    // add our http request handler
+    evhttp_set_cb(http_server, "/render", &http_render, NULL);
+    
+    // we shall now run
+    printf("RUN 0.0.0.0:8117\n");
+    
+    // run the libevent mainloop
+    if (event_dispatch())
+        die("event_dispatch");
+    
+    // clean up
+    evhttp_free(http_server);
+    
+    // successfull exit
+    return 0;
+}