* rename files, render_file -> file_main, render_node -> node_main, mandelbrot -> render_mandelbrot
* make the error message stuff in common a /lot/ neater (a single function and a set of macros)
* clean up rest of code to use those new macros
committer: Tero Marttila <terom@fixme.fi>
#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 <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.h"
#include "render_remote.h"
#include "remote_node.h"
#include "remote_pool.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 remote_render_ctx *remote_ctx;
size_t bytes_sent;
int paused;
};
// cb func prototypes
void _render_http_written (struct evhttp_request *request, void *arg);
void _render_cleanup (struct render_request *ctx) {
// clean up
free(ctx);
}
void _render_sent (void *arg) {
struct render_request *ctx = arg;
// set chunk size
render_remote_set_recv(ctx->remote_ctx, MIN_CHUNK_SIZE, OVERFLOW_BUFFER);
// 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;
printf("render [%p]: sent headers\n", ctx);
}
void _render_data (struct evbuffer *buf, void *arg) {
struct render_request *ctx = arg;
size_t buf_size = EVBUFFER_LENGTH(buf);
// ignore empty buffers, a result of render_remote_shake()
if (buf_size == 0) {
printf("render [%p]: remote buffer is empty\n", ctx);
return;
}
// 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
printf("render [%p]: delaying data: %zu:%zu bytes\n", ctx, buf_size, ctx->bytes_sent);
return;
}
// move chunk to http buffers
evhttp_send_reply_chunk(ctx->http_request, buf);
printf("render [%p]: enqueued chunk: %zu/%zu bytes\n", 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;
}
void _render_done (void *arg) {
struct render_request *ctx = arg;
// if we are paused, just shove the data into the http buffers, they might become larger than they should be, but it's easier to just move the data there and let render_remote complete
if (ctx->paused) {
printf("render [%p]: done: flushing the rest of our data\n", ctx);
ctx->paused = 0;
render_remote_shake(ctx->remote_ctx);
}
// send end
evhttp_send_reply_end(ctx->http_request);
printf("render [%p]: done: %zu bytes\n", ctx, ctx->bytes_sent);
// the request is now done, clean up
_render_cleanup(ctx);
}
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");
}
printf("render [%p]: failed\n", ctx);
_render_cleanup(ctx);
}
void _render_http_lost (struct evhttp_request *req, void *arg) {
struct render_request *ctx = arg;
printf("render [%p]: lost http connection\n", ctx);
// cancel
render_remote_cancel(ctx->remote_ctx);
_render_cleanup(ctx);
}
void _render_http_written (struct evhttp_request *request, void *arg) {
struct render_request *ctx = arg;
printf("render [%p]: http available for write\n", ctx);
// unpause ourself
ctx->paused = 0;
// shake out the buffers
render_remote_shake(ctx->remote_ctx);
}
void _http_render_execute (struct evhttp_request *request, u_int32_t img_w, u_int32_t img_h) {
// render request context
struct render_request *req_ctx = calloc(1, sizeof(struct render_request));
if (!req_ctx)
ERROR("calloc");
req_ctx->http_request = request;
req_ctx->headers_sent = 0;
req_ctx->bytes_sent = 0;
// render context
struct render *render;
if (!(render = render_alloc()))
ERROR("render_alloc");
if (render_set_mode(render, RENDER_PNG))
ERROR("render_set_mode");
if (render_set_size(render, img_w, img_h))
ERROR("render_set_size");
if (render_region_full(render))
ERROR("render_region_full");
// pick a render_node
struct remote_node *node_info;
if ((node_info = remote_pool_get(&remote_pool)) == NULL)
ERROR("remote_pool_get");
// initiate the remote render operation
if ((req_ctx->remote_ctx = render_remote(render, node_info,
&_render_sent,
&_render_data,
&_render_done,
&_render_fail,
req_ctx
)) == NULL)
ERROR("render_remote");
// set close cb
evhttp_set_reply_abortcb(request, &_render_http_lost, req_ctx);
printf("render [%p]: started\n", req_ctx);
return;
error:
evhttp_send_error(request, 500, "Internal Server Error");
free(req_ctx);
}
/*
* HTTP request handler
*/
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;
u_short peer_port;
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_UINT, &img_w },
{ "h", QARG_UINT, &img_h },
{ NULL, QARG_END, NULL }
};
http_qarg_parse(request, qarg_spec);
// request log
printf("REQ: [%s:%d] method=%d, uri=%s, img_w=%d, img_h=%d\n", peer_address, peer_port, evhttp_request_get_type(request), uri, img_w, img_h);
// do it
_http_render_execute(request, img_w, img_h);
}
struct event ev_sigint;
void sigint_handler (int signal, short event, void *arg) {
printf("SIGINT: shutting down\n");
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;
char *host, *port;
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 (
parse_hostport(optarg, &host, &port)
|| remote_pool_add(&remote_pool, host, port)
)
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");
printf("Registered %d render nodes in our pool\n", 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 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_base_dispatch(ev_base))
WARNING("event_dispatch");
printf("SHUTDOWN\n");
// clean up
signals_deinit();
evhttp_free(http_server);
event_base_free(ev_base);
// successfull exit
return 0;
}