summaryrefslogtreecommitdiff
path: root/libafcgi.c
diff options
context:
space:
mode:
Diffstat (limited to 'libafcgi.c')
-rw-r--r--libafcgi.c874
1 files changed, 874 insertions, 0 deletions
diff --git a/libafcgi.c b/libafcgi.c
new file mode 100644
index 0000000..7be3b46
--- /dev/null
+++ b/libafcgi.c
@@ -0,0 +1,874 @@
+
+#include "libafcgi.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+
+typedef struct fastcgi_queue_link {
+ GList queue_link;
+ enum { FASTCGI_QUEUE_STRING, FASTCGI_QUEUE_BYTEARRAY } elem_type;
+} fastcgi_queue_link;
+
+/* some util functions */
+#define GSTR_LEN(x) ((x) ? (x)->str : ""), ((x) ? (x)->len : 0)
+#define GBARR_LEN(x) ((x)->data), ((x)->len)
+#define UNUSED(x) ((void)(x))
+#define ERROR(...) g_printerr("libafcgi.c:" G_STRINGIFY(__LINE__) ": " __VA_ARGS__)
+
+static void fd_init(int fd) {
+#ifdef _WIN32
+ int i = 1;
+#endif
+#ifdef FD_CLOEXEC
+ /* close fd on exec (cgi) */
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+#endif
+#ifdef O_NONBLOCK
+ fcntl(fd, F_SETFL, O_NONBLOCK | O_RDWR);
+#elif defined _WIN32
+ ioctlsocket(fd, FIONBIO, &i);
+#endif
+}
+
+static fastcgi_queue_link* fastcgi_queue_link_new_string(GString *s) {
+ fastcgi_queue_link *l = g_slice_new0(fastcgi_queue_link);
+ l->queue_link.data = s;
+ l->elem_type = FASTCGI_QUEUE_STRING;
+ return l;
+}
+
+static fastcgi_queue_link* fastcgi_queue_link_new_bytearray(GByteArray *a) {
+ fastcgi_queue_link *l = g_slice_new0(fastcgi_queue_link);
+ l->queue_link.data = a;
+ l->elem_type = FASTCGI_QUEUE_BYTEARRAY ;
+ return l;
+}
+
+static void fastcgi_queue_link_free(fastcgi_queue *queue, fastcgi_queue_link *l) {
+ switch (l->elem_type) {
+ case FASTCGI_QUEUE_STRING:
+ if (queue) queue->length -= ((GString*)l->queue_link.data)->len;
+ g_string_free(l->queue_link.data, TRUE);
+ break;
+ case FASTCGI_QUEUE_BYTEARRAY:
+ if (queue) queue->length -= ((GByteArray*)l->queue_link.data)->len;
+ g_byte_array_free(l->queue_link.data, TRUE);
+ break;
+ }
+ g_slice_free(fastcgi_queue_link, l);
+}
+
+static fastcgi_queue_link *fastcgi_queue_peek_head(fastcgi_queue *queue) {
+ return (fastcgi_queue_link*) g_queue_peek_head_link(&queue->queue);
+}
+
+static fastcgi_queue_link *fastcgi_queue_pop_head(fastcgi_queue *queue) {
+ return (fastcgi_queue_link*) g_queue_pop_head_link(&queue->queue);
+}
+
+void fastcgi_queue_clear(fastcgi_queue *queue) {
+ fastcgi_queue_link *l;
+ queue->offset = 0;
+ while (NULL != (l = fastcgi_queue_pop_head(queue))) {
+ fastcgi_queue_link_free(queue, l);
+ }
+ g_assert(0 == queue->length);
+}
+
+void fastcgi_queue_append_string(fastcgi_queue *queue, GString *buf) {
+ fastcgi_queue_link *l;
+ if (!buf) return;
+ if (!buf->len) { g_string_free(buf, TRUE); return; }
+ l = fastcgi_queue_link_new_string(buf);
+ g_queue_push_tail_link(&queue->queue, (GList*) l);
+ queue->length += buf->len;
+}
+
+void fastcgi_queue_append_bytearray(fastcgi_queue *queue, GByteArray *buf) {
+ fastcgi_queue_link *l;
+ if (!buf) return;
+ if (!buf->len) { g_byte_array_free(buf, TRUE); return; }
+ l = fastcgi_queue_link_new_bytearray(buf);
+ g_queue_push_tail_link(&queue->queue, (GList*) l);
+ queue->length += buf->len;
+}
+
+/* return values: 0 ok, -1 error, -2 con closed */
+gint fastcgi_queue_write(int fd, fastcgi_queue *queue, gsize max_write) {
+ gsize rem_write = max_write;
+ g_assert(rem_write <= G_MAXSSIZE);
+#ifdef TCP_CORK
+ int corked = 0;
+#endif
+
+#ifdef TCP_CORK
+ /* Linux: put a cork into the socket as we want to combine the write() calls
+ * but only if we really have multiple chunks
+ */
+ if (queue->queue.length > 1) {
+ corked = 1;
+ setsockopt(fd, IPPROTO_TCP, TCP_CORK, &corked, sizeof(corked));
+ }
+#endif
+
+ while (rem_write > 0 && queue->length > 0) {
+ fastcgi_queue_link *l = fastcgi_queue_peek_head(queue);
+ gsize towrite, datalen;
+ gssize res;
+ gchar *data;
+ switch (l->elem_type) {
+ case FASTCGI_QUEUE_STRING:
+ data = ((GString*) l->queue_link.data)->str;
+ datalen = towrite = ((GString*) l->queue_link.data)->len;
+ break;
+ case FASTCGI_QUEUE_BYTEARRAY:
+ data = (gchar*) ((GByteArray*) l->queue_link.data)->data;
+ datalen = towrite = ((GByteArray*) l->queue_link.data)->len;
+ break;
+ default:
+ g_error("invalid fastcgi_queue_link type\n");
+ }
+ towrite -= queue->offset; data += queue->offset;
+ if (towrite > rem_write) towrite = rem_write;
+ res = write(fd, data, towrite);
+ if (-1 == res) {
+#ifdef TCP_CORK
+ if (corked) {
+ corked = 0;
+ setsockopt(fd, IPPROTO_TCP, TCP_CORK, &corked, sizeof(corked));
+ }
+#endif
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ return 0; /* try again later */
+ case ECONNRESET:
+ case EPIPE:
+ return -2;
+ default:
+ ERROR("write to fd=%d failed, %s\n", fd, g_strerror(errno) );
+ return -1;
+ }
+ } else {
+ queue->offset += res;
+ rem_write -= res;
+ if (queue->offset == datalen) {
+ queue->offset = 0;
+ fastcgi_queue_link_free(queue, fastcgi_queue_pop_head(queue));
+ }
+ }
+ }
+
+#ifdef TCP_CORK
+ if (corked) {
+ corked = 0;
+ setsockopt(fd, IPPROTO_TCP, TCP_CORK, &corked, sizeof(corked));
+ }
+#endif
+
+ return 0;
+}
+
+
+static void ev_io_add_events(struct ev_loop *loop, ev_io *watcher, int events) {
+ if ((watcher->events & events) == events) return;
+ ev_io_stop(loop, watcher);
+ ev_io_set(watcher, watcher->fd, watcher->events | events);
+ ev_io_start(loop, watcher);
+}
+
+static void ev_io_rem_events(struct ev_loop *loop, ev_io *watcher, int events) {
+ if (0 == (watcher->events & events)) return;
+ ev_io_stop(loop, watcher);
+ ev_io_set(watcher, watcher->fd, watcher->events & ~events);
+ ev_io_start(loop, watcher);
+}
+/* end: some util functions */
+
+static const guint8 __padding[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+static void append_padding_str(GString *s, guint8 padlen) {
+ g_string_append_len(s, (const gchar*) __padding, padlen);
+}
+
+static void append_padding_bytearray(GByteArray *a, guint8 padlen) {
+ g_byte_array_append(a, __padding, padlen);
+}
+
+/* returns padding length */
+static guint8 stream_build_fcgi_record(GByteArray *buf, guint8 type, guint16 requestid, guint16 datalen) {
+ guint8 padlen = (8 - (datalen & 0x7)) % 8; /* padding must be < 8 */
+
+ /* alloc enough space */
+ g_byte_array_set_size(buf, FCGI_HEADER_LEN);
+ buf->len = 0;
+
+ buf->data[buf->len++] = FCGI_VERSION_1;
+ buf->data[buf->len++] = type;
+ buf->data[buf->len++] = (guint8) (requestid >> 8);
+ buf->data[buf->len++] = (guint8) (requestid);
+ buf->data[buf->len++] = (guint8) (datalen >> 8);
+ buf->data[buf->len++] = (guint8) (datalen);
+ buf->data[buf->len++] = padlen;
+ buf->data[buf->len++] = 0;
+ return padlen;
+}
+
+/* returns padding length */
+static guint8 stream_send_fcgi_record(fastcgi_queue *out, guint8 type, guint16 requestid, guint16 datalen) {
+ GByteArray *record = g_byte_array_sized_new(FCGI_HEADER_LEN);
+ guint8 padlen = stream_build_fcgi_record(record, type, requestid, datalen);
+ fastcgi_queue_append_bytearray(out, record);
+ return padlen;
+}
+
+static void stream_send_data(fastcgi_queue *out, guint8 type, guint16 requestid, const guint8 *data, size_t datalen) {
+ while (datalen > 0) {
+ guint16 tosend = (datalen > G_MAXUINT16) ? G_MAXUINT16 : datalen;
+ guint8 padlen = stream_send_fcgi_record(out, type, requestid, tosend);
+ GByteArray *buf = g_byte_array_sized_new(tosend + padlen);
+ g_byte_array_append(buf, data, tosend);
+ append_padding_bytearray(buf, padlen);
+ fastcgi_queue_append_bytearray(out, buf);
+ data += tosend;
+ datalen -= tosend;
+ }
+}
+
+/* kills string */
+static void stream_send_string(fastcgi_queue *out, guint8 type, guint16 requestid, GString *data) {
+ if (data->len > G_MAXUINT16) {
+ stream_send_data(out, type, requestid, (const guint8*) GSTR_LEN(data));
+ g_string_free(data, TRUE);
+ } else {
+ guint8 padlen = stream_send_fcgi_record(out, type, requestid, data->len);
+ append_padding_str(data, padlen);
+ fastcgi_queue_append_string(out, data);
+ }
+}
+
+/* kills bytearray */
+static void stream_send_bytearray(fastcgi_queue *out, guint8 type, guint16 requestid, GByteArray *data) {
+ if (data->len > G_MAXUINT16) {
+ stream_send_data(out, type, requestid, GBARR_LEN(data));
+ g_byte_array_free(data, TRUE);
+ } else {
+ guint8 padlen = stream_send_fcgi_record(out, type, requestid, data->len);
+ append_padding_bytearray(data, padlen);
+ fastcgi_queue_append_bytearray(out, data);
+ }
+}
+
+static void stream_send_end_request(fastcgi_queue *out, guint16 requestID, gint32 appStatus, enum FCGI_ProtocolStatus status) {
+ GByteArray *record;
+
+ record = g_byte_array_sized_new(16);
+ stream_build_fcgi_record(record, FCGI_END_REQUEST, requestID, 8);
+
+ /* alloc enough space */
+ g_byte_array_set_size(record, 16);
+ record->len = 8;
+
+ appStatus = htonl(appStatus);
+ g_byte_array_append(record, (const guchar*) &appStatus, sizeof(appStatus));
+ record->data[record->len++] = status;
+ g_byte_array_append(record, __padding, 3);
+ fastcgi_queue_append_bytearray(out, record);
+}
+
+
+static void write_queue(fastcgi_connection *fcon) {
+ if (fcon->closing) return;
+
+ if (fastcgi_queue_write(fcon->fd, &fcon->write_queue, 256*1024) < 0) {
+ fastcgi_connection_close(fcon);
+ return;
+ }
+
+ if (fcon->fsrv->callbacks->cb_wrote_data) {
+ fcon->fsrv->callbacks->cb_wrote_data(fcon);
+ }
+
+ if (!fcon->closing) {
+ if (fcon->write_queue.length > 0) {
+ ev_io_add_events(fcon->fsrv->loop, &fcon->fd_watcher, EV_WRITE);
+ } else {
+ ev_io_rem_events(fcon->fsrv->loop, &fcon->fd_watcher, EV_WRITE);
+ if (0 == fcon->requestID) {
+ if (!(fcon->flags & FCGI_KEEP_CONN)) {
+ fastcgi_connection_close(fcon);
+ }
+ }
+ }
+ }
+}
+
+static GByteArray* read_chunk(fastcgi_connection *fcon, guint maxlen) {
+ gssize res;
+ GByteArray *buf;
+ int tmp_errno;
+
+ buf = g_byte_array_sized_new(maxlen);
+ g_byte_array_set_size(buf, maxlen);
+ if (0 == maxlen) return buf;
+
+ res = read(fcon->fd, buf->data, maxlen);
+ if (res == -1) {
+ tmp_errno = errno;
+ g_byte_array_free(buf, TRUE);
+ errno = tmp_errno;
+ return NULL;
+ } else if (res == 0) {
+ g_byte_array_free(buf, TRUE);
+ errno = ECONNRESET;
+ return NULL;
+ } else {
+ g_byte_array_set_size(buf, res);
+ return buf;
+ }
+}
+
+/* read content + padding, but only returns content data. decrements counters */
+static GByteArray *read_content(fastcgi_connection *fcon) {
+ GByteArray *buf;
+
+ buf = read_chunk(fcon, fcon->content_remaining + fcon->padding_remaining);
+ if (!buf) return NULL;
+ if (buf->len > fcon->content_remaining) {
+ fcon->padding_remaining -= (buf->len - fcon->content_remaining);
+ g_byte_array_set_size(buf, fcon->content_remaining);
+ fcon->content_remaining = 0;
+ } else {
+ fcon->content_remaining -= buf->len;
+ }
+ return buf;
+}
+
+static gboolean read_append_chunk(fastcgi_connection *fcon, GByteArray *buf) {
+ gssize res;
+ int tmp_errno;
+ guint curlen = buf->len;
+ const guint maxlen = fcon->content_remaining + fcon->padding_remaining;
+ if (0 == maxlen) return TRUE;
+
+ g_byte_array_set_size(buf, curlen + maxlen);
+ res = read(fcon->fd, buf->data + curlen, maxlen);
+ if (res == -1) {
+ tmp_errno = errno;
+ g_byte_array_set_size(buf, curlen);
+ errno = tmp_errno;
+ return FALSE;
+ } else if (res == 0) {
+ g_byte_array_set_size(buf, curlen);
+ errno = ECONNRESET;
+ return FALSE;
+ } else {
+ /* remove padding data */
+ if (res > fcon->content_remaining) {
+ fcon->padding_remaining -= res - fcon->content_remaining;
+ res = fcon->content_remaining;
+ }
+ g_byte_array_set_size(buf, curlen + res);
+ fcon->content_remaining -= res;
+ return TRUE;
+ }
+}
+
+static gboolean read_key_value(fastcgi_connection *fcon, GByteArray *buf, guint *pos, gchar **key, guint *keylen, gchar **value, guint *valuelen) {
+ const unsigned char *data = (const unsigned char*) buf->data;
+ guint32 klen, vlen;
+ guint p = *pos, len = buf->len;
+
+ if (len - p < 2) return FALSE;
+
+ klen = data[p++];
+ if (klen & 0x80) {
+ if (len - p < 100) return FALSE;
+ klen = ((klen & 0x7f) << 24) | (data[p] << 16) | (data[p+1] << 8) | data[p+2];
+ p += 3;
+ }
+ vlen = data[p++];
+ if (vlen & 0x80) {
+ if (len - p < 100) return FALSE;
+ vlen = ((vlen & 0x7f) << 24) | (data[p] << 16) | (data[p+1] << 8) | data[p+2];
+ p += 3;
+ }
+ if (klen > FASTCGI_MAX_KEYLEN || vlen > FASTCGI_MAX_VALUELEN) {
+ fastcgi_connection_close(fcon);
+ return FALSE;
+ }
+ if (len - p < klen + vlen) return FALSE;
+ *key = (gchar*) &buf->data[p];
+ *keylen = klen;
+ p += klen;
+ *value = (gchar*) &buf->data[p];
+ *valuelen = vlen;
+ p += vlen;
+ *pos = p;
+ return TRUE;
+}
+
+static void parse_params(const fastcgi_callbacks *fcbs, fastcgi_connection *fcon) {
+ if (!fcon->current_header.contentLength) {
+ fcbs->cb_new_request(fcon);
+ g_byte_array_set_size(fcon->parambuf, 0);
+ } else {
+ guint pos = 0, keylen = 0, valuelen = 0;
+ gchar *key = NULL, *value = NULL;
+ while (read_key_value(fcon, fcon->parambuf, &pos, &key, &keylen, &value, &valuelen)) {
+ GString *gkey = g_string_new_len(key, keylen);
+ GString *gvalue = g_string_new_len(value, valuelen);
+ g_hash_table_insert(fcon->environ, gkey, gvalue);
+ }
+ if (!fcon->closing)
+ g_byte_array_remove_range(fcon->parambuf, 0, pos);
+ }
+}
+
+static void parse_get_values(fastcgi_connection *fcon) {
+ /* just send the request back and don't insert results */
+ GByteArray *tmp = g_byte_array_sized_new(0);
+ stream_send_bytearray(&fcon->write_queue, FCGI_GET_VALUES_RESULT, 0, fcon->buffer);
+ *fcon->buffer = *tmp;
+ /* TODO: provide get-values result */
+}
+
+static void read_queue(fastcgi_connection *fcon) {
+ gssize res;
+ GByteArray *buf;
+ const fastcgi_callbacks *fcbs = fcon->fsrv->callbacks;
+
+ for (;;) {
+ if (fcon->closing || fcon->read_suspended) return;
+
+ if (fcon->headerbuf_used < 8) {
+ const unsigned char *data = fcon->headerbuf;
+ res = read(fcon->fd, fcon->headerbuf + fcon->headerbuf_used, 8 - fcon->headerbuf_used);
+ if (0 == res) { errno = ECONNRESET; goto handle_error; }
+ if (-1 == res) goto handle_error;
+ fcon->headerbuf_used += res;
+ if (fcon->headerbuf_used < 8) return; /* need more data */
+
+ fcon->current_header.version = data[0];
+ fcon->current_header.type = data[1];
+ fcon->current_header.requestID = (data[2] << 8) | (data[3]);
+ fcon->current_header.contentLength = (data[4] << 8) | (data[5]);
+ fcon->current_header.paddingLength = data[6];
+ fcon->content_remaining = fcon->current_header.contentLength;
+ fcon->padding_remaining = fcon->current_header.paddingLength;
+ fcon->first = TRUE;
+ g_byte_array_set_size(fcon->buffer, 0);
+
+ if (fcon->current_header.version != FCGI_VERSION_1) {
+ fastcgi_connection_close(fcon);
+ return;
+ }
+ }
+
+ if (fcon->current_header.type != FCGI_BEGIN_REQUEST &&
+ (0 != fcon->current_header.requestID) && fcon->current_header.requestID != fcon->requestID) {
+ /* ignore packet data */
+ if (0 != fcon->content_remaining + fcon->padding_remaining) {
+ if (NULL == (buf = read_content(fcon))) goto handle_error;
+ g_byte_array_free(buf, TRUE);
+ }
+ if (0 == fcon->content_remaining + fcon->padding_remaining) {
+ fcon->headerbuf_used = 0;
+ }
+ continue;
+ }
+
+ if (fcon->first || fcon->content_remaining) {
+ fcon->first = FALSE;
+ switch (fcon->current_header.type) {
+ case FCGI_BEGIN_REQUEST:
+ if (8 != fcon->current_header.contentLength || 0 == fcon->current_header.requestID) goto error;
+ if (!read_append_chunk(fcon, fcon->buffer)) goto handle_error;
+ if (0 == fcon->content_remaining) {
+ if (fcon->requestID) {
+ stream_send_end_request(&fcon->write_queue, fcon->current_header.requestID, 0, FCGI_CANT_MPX_CONN);
+ } else {
+ unsigned char *data = (unsigned char*) fcon->buffer->data;
+ fcon->requestID = fcon->current_header.requestID;
+ fcon->role = (data[0] << 8) | (data[1]);
+ fcon->flags = data[2];
+ g_byte_array_set_size(fcon->parambuf, 0);
+ }
+ }
+ break;
+ case FCGI_ABORT_REQUEST:
+ if (0 != fcon->current_header.contentLength || 0 == fcon->current_header.requestID) goto error;
+ fcbs->cb_request_aborted(fcon);
+ break;
+ case FCGI_END_REQUEST:
+ goto error; /* invalid type */
+ case FCGI_PARAMS:
+ if (0 == fcon->current_header.requestID) goto error;
+ if (!read_append_chunk(fcon, fcon->parambuf)) goto handle_error;
+ parse_params(fcbs, fcon);
+ break;
+ case FCGI_STDIN:
+ if (0 == fcon->current_header.requestID) goto error;
+ buf = NULL;
+ if (0 != fcon->content_remaining &&
+ NULL == (buf = read_content(fcon))) goto handle_error;
+ if (fcbs->cb_received_stdin) {
+ fcbs->cb_received_stdin(fcon, buf);
+ } else {
+ g_byte_array_free(buf, TRUE);
+ }
+ break;
+ case FCGI_STDOUT:
+ goto error; /* invalid type */
+ case FCGI_STDERR:
+ goto error; /* invalid type */
+ case FCGI_DATA:
+ if (0 == fcon->current_header.requestID) goto error;
+ buf = NULL;
+ if (0 != fcon->content_remaining &&
+ NULL == (buf = read_content(fcon))) goto handle_error;
+ if (fcbs->cb_received_data) {
+ fcbs->cb_received_data(fcon, buf);
+ } else {
+ g_byte_array_free(buf, TRUE);
+ }
+ break;
+ case FCGI_GET_VALUES:
+ if (0 != fcon->current_header.requestID) goto error;
+ if (!read_append_chunk(fcon, fcon->buffer)) goto handle_error;
+ if (0 == fcon->content_remaining)
+ parse_get_values(fcon);
+ break;
+ case FCGI_GET_VALUES_RESULT:
+ goto error; /* invalid type */
+ break;
+ case FCGI_UNKNOWN_TYPE:
+ /* we didn't send anything fancy, so this is not expected */
+ goto error; /* invalid type */
+ default:
+ break;
+ }
+ }
+
+ if (0 == fcon->content_remaining) {
+ if (0 == fcon->padding_remaining) {
+ fcon->headerbuf_used = 0;
+ } else {
+ if (NULL == (buf = read_chunk(fcon, fcon->padding_remaining))) goto handle_error;
+ fcon->padding_remaining -= buf->len;
+ if (0 == fcon->padding_remaining) {
+ fcon->headerbuf_used = 0;
+ }
+ g_byte_array_free(buf, TRUE);
+ }
+ }
+
+ }
+
+ return;
+
+handle_error:
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ return; /* try again later */
+ case ECONNRESET:
+ break;
+ default:
+ ERROR("read from fd=%d failed, %s\n", fcon->fd, g_strerror(errno) );
+ break;
+ }
+
+error:
+ if (0 != fcon->requestID)
+ fcbs->cb_request_aborted(fcon);
+ fastcgi_connection_close(fcon);
+}
+
+static void fastcgi_connection_fd_cb(struct ev_loop *loop, ev_io *w, int revents) {
+ fastcgi_connection *fcon = (fastcgi_connection*) w->data;
+ UNUSED(loop);
+
+ if (revents & EV_READ) {
+ read_queue(fcon);
+ }
+
+ if (revents & EV_WRITE) {
+ write_queue(fcon);
+ }
+}
+
+static void _g_string_destroy(gpointer data) {
+ g_string_free(data, TRUE);
+}
+
+static fastcgi_connection *fastcgi_connecion_create(fastcgi_server *fsrv, gint fd, guint id) {
+ fastcgi_connection *fcon = g_slice_new0(fastcgi_connection);
+
+ fcon->fsrv = fsrv;
+ fcon->fcon_id = id;
+
+ fcon->buffer = g_byte_array_sized_new(0);
+ fcon->parambuf = g_byte_array_sized_new(0);
+ fcon->environ = g_hash_table_new_full((GHashFunc) g_string_hash, (GEqualFunc) g_string_equal, _g_string_destroy, _g_string_destroy);
+
+ fcon->fd = fd;
+ fd_init(fcon->fd);
+ ev_io_init(&fcon->fd_watcher, fastcgi_connection_fd_cb, fcon->fd, EV_READ);
+ fcon->fd_watcher.data = fcon;
+ ev_io_start(fcon->fsrv->loop, &fcon->fd_watcher);
+
+ return fcon;
+}
+
+static void fastcgi_connection_free(fastcgi_connection *fcon) {
+ fcon->fsrv->callbacks->cb_reset_connection(fcon);
+
+ if (fcon->fd != -1) {
+ ev_io_stop(fcon->fsrv->loop, &fcon->fd_watcher);
+ close(fcon->fd);
+ fcon->fd = -1;
+ }
+
+ fastcgi_queue_clear(&fcon->write_queue);
+ g_hash_table_destroy(fcon->environ);
+ g_byte_array_free(fcon->buffer, TRUE);
+ g_byte_array_free(fcon->parambuf, TRUE);
+
+ g_slice_free(fastcgi_connection, fcon);
+}
+
+void fastcgi_connection_close(fastcgi_connection *fcon) {
+ fcon->closing = TRUE;
+ if (fcon->fd != -1) {
+ ev_io_stop(fcon->fsrv->loop, &fcon->fd_watcher);
+ close(fcon->fd);
+ fcon->fd = -1;
+ }
+
+ fastcgi_queue_clear(&fcon->write_queue);
+
+ g_byte_array_set_size(fcon->buffer, 0);
+ g_byte_array_set_size(fcon->parambuf, 0);
+ g_hash_table_remove_all(fcon->environ);
+
+ ev_prepare_start(fcon->fsrv->loop, &fcon->fsrv->closing_watcher);
+}
+
+static void fastcgi_server_fd_cb(struct ev_loop *loop, ev_io *w, int revents) {
+ fastcgi_server *fsrv = (fastcgi_server*) w->data;
+ fastcgi_connection *fcon;
+ void (*cb_new_connection)(fastcgi_connection *fcon) = fsrv->callbacks->cb_new_connection;
+
+ g_assert(revents & EV_READ);
+
+ for (;;) {
+ gint fd = accept(fsrv->fd, NULL, NULL);
+ if (-1 == fd) {
+ switch (errno) {
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ /* we were stopped _before_ we had a connection */
+ case ECONNABORTED: /* this is a FreeBSD thingy */
+ /* we were stopped _after_ we had a connection */
+ return;
+ case EMFILE:
+ if (0 == fsrv->max_connections) {
+ fsrv->max_connections = fsrv->connections->len / 2;
+ } else {
+ fsrv->max_connections = fsrv->max_connections / 2;
+ }
+ ERROR("dropped connection limit to %u as we got EMFILE\n", fsrv->max_connections);
+ ev_io_rem_events(loop, w, EV_READ);
+ return;
+ default:
+ ERROR("accept failed on fd=%d with error: %s\nshutting down\n", fsrv->fd, g_strerror(errno));
+ fastcgi_server_stop(fsrv);
+ return;
+ }
+ }
+
+ fcon = fastcgi_connecion_create(fsrv, fd, fsrv->connections->len);
+ g_ptr_array_add(fsrv->connections, fcon);
+ if (cb_new_connection) {
+ cb_new_connection(fcon);
+ }
+
+ if (fsrv->connections->len >= fsrv->max_connections) {
+ ev_io_rem_events(loop, w, EV_READ);
+ return;
+ }
+
+ if (fsrv->do_shutdown) return;
+ }
+}
+
+static void fastcgi_cleanup_connections(fastcgi_server *fsrv) {
+ guint i;
+
+ for (i = 0; i < fsrv->connections->len; ) {
+ fastcgi_connection *fcon = g_ptr_array_index(fsrv->connections, i);
+ if (fcon->closing) {
+ fastcgi_connection *t_fcon;
+ guint l = fsrv->connections->len-1;
+ t_fcon = g_ptr_array_index(fsrv->connections, i) = g_ptr_array_index(fsrv->connections, l);
+ g_ptr_array_set_size(fsrv->connections, l);
+ t_fcon->fcon_id = i;
+ fastcgi_connection_free(fcon);
+ } else {
+ i++;
+ }
+ }
+}
+
+static void fastcgi_closing_cb(struct ev_loop *loop, ev_prepare *w, int revents) {
+ UNUSED(revents);
+ ev_prepare_stop(loop, w);
+ fastcgi_cleanup_connections((fastcgi_server*) w->data);
+}
+
+fastcgi_server *fastcgi_server_create(struct ev_loop *loop, gint socketfd, const fastcgi_callbacks *callbacks, guint max_connections) {
+ fastcgi_server *fsrv = g_slice_new0(fastcgi_server);
+
+ fsrv->callbacks = callbacks;
+
+ fsrv->max_connections = max_connections;
+
+ fsrv->connections = g_ptr_array_sized_new(fsrv->max_connections);
+
+ fsrv->loop = loop;
+ fsrv->fd = socketfd;
+ fd_init(fsrv->fd);
+ ev_io_init(&fsrv->fd_watcher, fastcgi_server_fd_cb, fsrv->fd, EV_READ);
+ fsrv->fd_watcher.data = fsrv;
+ ev_io_start(fsrv->loop, &fsrv->fd_watcher);
+
+ ev_prepare_init(&fsrv->closing_watcher, fastcgi_closing_cb);
+ fsrv->closing_watcher.data = fsrv;
+
+ return fsrv;
+}
+
+void fastcgi_server_stop(fastcgi_server *fsrv) {
+ if (fsrv->do_shutdown) return;
+ fsrv->do_shutdown = TRUE;
+
+ ev_io_stop(fsrv->loop, &fsrv->fd_watcher);
+ close(fsrv->fd);
+ fsrv->fd = -1;
+}
+
+void fastcgi_server_free(fastcgi_server *fsrv) {
+ guint i;
+ void (*cb_request_aborted)(fastcgi_connection *fcon) = fsrv->callbacks->cb_request_aborted;
+ if (!fsrv->do_shutdown) fastcgi_server_stop(fsrv);
+ ev_prepare_stop(fsrv->loop, &fsrv->closing_watcher);
+
+ for (i = 0; i < fsrv->connections->len; i++) {
+ fastcgi_connection *fcon = g_ptr_array_index(fsrv->connections, i);
+ cb_request_aborted(fcon);
+ fcon->closing = TRUE;
+ }
+ fastcgi_cleanup_connections(fsrv);
+ g_ptr_array_free(fsrv->connections, TRUE);
+
+ g_slice_free(fastcgi_server, fsrv);
+}
+
+void fastcgi_end_request(fastcgi_connection *fcon, gint32 appStatus, enum FCGI_ProtocolStatus status) {
+ gboolean had_data = (fcon->write_queue.length > 0);
+
+ if (0 == fcon->requestID) return;
+ stream_send_end_request(&fcon->write_queue, fcon->requestID, appStatus, status);
+ fcon->requestID = 0;
+ if (!had_data) write_queue(fcon);
+}
+
+void fastcgi_suspend_read(fastcgi_connection *fcon) {
+ fcon->read_suspended = TRUE;
+ ev_io_rem_events(fcon->fsrv->loop, &fcon->fd_watcher, EV_READ);
+}
+
+void fastcgi_resume_read(fastcgi_connection *fcon) {
+ fcon->read_suspended = FALSE;
+ ev_io_add_events(fcon->fsrv->loop, &fcon->fd_watcher, EV_READ);
+}
+
+void fastcgi_send_out(fastcgi_connection *fcon, GString *data) {
+ gboolean had_data = (fcon->write_queue.length > 0);
+ if (!data) {
+ stream_send_fcgi_record(&fcon->write_queue, FCGI_STDOUT, fcon->requestID, 0);
+ } else {
+ stream_send_string(&fcon->write_queue, FCGI_STDOUT, fcon->requestID, data);
+ }
+ if (!had_data) write_queue(fcon);
+}
+
+void fastcgi_send_err(fastcgi_connection *fcon, GString *data) {
+ gboolean had_data = (fcon->write_queue.length > 0);
+ if (!data) {
+ stream_send_fcgi_record(&fcon->write_queue, FCGI_STDERR, fcon->requestID, 0);
+ } else {
+ stream_send_string(&fcon->write_queue, FCGI_STDERR, fcon->requestID, data);
+ }
+ if (!had_data) write_queue(fcon);
+}
+
+void fastcgi_send_out_bytearray(fastcgi_connection *fcon, GByteArray *data) {
+ gboolean had_data = (fcon->write_queue.length > 0);
+ if (!data) {
+ stream_send_fcgi_record(&fcon->write_queue, FCGI_STDOUT, fcon->requestID, 0);
+ } else {
+ stream_send_bytearray(&fcon->write_queue, FCGI_STDOUT, fcon->requestID, data);
+ }
+ if (!had_data) write_queue(fcon);
+}
+
+void fastcgi_send_err_bytearray(fastcgi_connection *fcon, GByteArray *data) {
+ gboolean had_data = (fcon->write_queue.length > 0);
+ if (!data) {
+ stream_send_fcgi_record(&fcon->write_queue, FCGI_STDERR, fcon->requestID, 0);
+ } else {
+ stream_send_bytearray(&fcon->write_queue, FCGI_STDERR, fcon->requestID, data);
+ }
+ if (!had_data) write_queue(fcon);
+}
+
+char** fastcgi_build_env(fastcgi_connection *con) {
+ GPtrArray *env = g_ptr_array_new();
+ GHashTableIter iter;
+ gpointer pkey, pvalue;
+
+ g_hash_table_iter_init(&iter, con->environ);
+ while (g_hash_table_iter_next(&iter, &pkey, &pvalue)) {
+ GString *key = pkey, *value = pvalue;
+ char *s = g_malloc(key->len + value->len + 2);
+ memcpy(s, key->str, key->len);
+ memcpy(s + key->len + 1, value->str, value->len);
+ s[key->len] = '=';
+ s[key->len + value->len + 1] = '\0';
+ g_ptr_array_add(env, s);
+ }
+ g_ptr_array_add(env, NULL);
+
+ return (char**) g_ptr_array_free(env, FALSE);
+}
+
+const gchar* fastcgi_connection_environ_lookup(fastcgi_connection *fcon, const gchar* key, gsize keylen) {
+ GString s = { (gchar*) key, keylen, 0 };
+ GString *value = g_hash_table_lookup(fcon->environ, &s);
+ return (NULL != value) ? value->str : NULL;
+}