Browse Source

Initial commit

master
Stefan Bühler 10 years ago
commit
1425b3fd12
9 changed files with 1194 additions and 0 deletions
  1. +13
    -0
      .gitignore
  2. +22
    -0
      COPYING
  3. +17
    -0
      Makefile.am
  4. +2
    -0
      README
  5. +27
    -0
      autogen.sh
  6. +61
    -0
      configure.ac
  7. +874
    -0
      libafcgi.c
  8. +167
    -0
      libafcgi.h
  9. +11
    -0
      libafcgi.pc.in

+ 13
- 0
.gitignore View File

@@ -0,0 +1,13 @@
Makefile.in
aclocal.m4
autom4te.cache
config.guess
config.sub
configure
depcomp
install-sh
libafcgi-config.h.in
ltmain.sh
m4/
missing
*~

+ 22
- 0
COPYING View File

@@ -0,0 +1,22 @@

The MIT License

Copyright (c) 2010 Stefan Bühler

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

+ 17
- 0
Makefile.am View File

@@ -0,0 +1,17 @@
EXTRA_DIST=autogen.sh libafcgi.pc.in

ACLOCAL_AMFLAGS=-I m4

AM_CFLAGS=$(GLIB_CFLAGS)

lib_LTLIBRARIES=libafcgi.la
libafcgi_la_SOURCES=libafcgi.c
libafcgi_la_LIBADD=$(GLIB_LIBS)
libafcgi_la_LDFLAGS= -version-info 0:0:0

pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libafcgi.pc

$(pkgconfig_DATA): config.status

include_HEADERS = libafcgi.h libafcgi-config.h

+ 2
- 0
README View File

@@ -0,0 +1,2 @@

libafcgi is a libev based asynchronous FastCGI library.

+ 27
- 0
autogen.sh View File

@@ -0,0 +1,27 @@
#!/bin/sh
# Run this to generate all the initial makefiles, etc.

LIBTOOLIZE=${LIBTOOLIZE:-libtoolize}
LIBTOOLIZE_FLAGS="--copy --force"
ACLOCAL=${ACLOCAL:-aclocal}
AUTOHEADER=${AUTOHEADER:-autoheader}
AUTOMAKE=${AUTOMAKE:-automake}
AUTOMAKE_FLAGS="--add-missing --copy"
AUTOCONF=${AUTOCONF:-autoconf}

ARGV0=$0

set -e


run() {
echo "$ARGV0: running \`$@'"
$@
}

run $LIBTOOLIZE $LIBTOOLIZE_FLAGS
run $ACLOCAL $ACLOCAL_FLAGS
run $AUTOHEADER
run $AUTOMAKE $AUTOMAKE_FLAGS
run $AUTOCONF
echo "Now type './configure ...' and 'make' to compile."

+ 61
- 0
configure.ac View File

@@ -0,0 +1,61 @@
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.63])
AC_INIT([libafcgi], [0.1.0], [lighttpd@stbuehler.de])
AC_CONFIG_SRCDIR([libafcgi.c])
AC_CONFIG_HEADERS([libafcgi-config.h])

AC_CONFIG_MACRO_DIR([m4])

AM_INIT_AUTOMAKE([-Wall -Werror foreign])

# Checks for programs.
AC_PROG_CC
AC_PROG_LIBTOOL
AC_PROG_MAKE_SET

# Checks for libraries.

# glib-2.0
PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.16.0, [
AC_DEFINE([HAVE_GLIB_H], [1], [glib.h])
],[AC_MSG_ERROR("glib-2.0 >= 2.16.0 not found")])

# lib ev
AC_CHECK_HEADERS([ev.h], [], [AC_MSG_ERROR("ev.h not found")])
AC_CHECK_LIB([ev], [ev_loop], [
LIBS="-lev ${LIBS}"
AC_DEFINE([HAVE_LIBEV], [1], [ev_loop in -lev])
], [AC_MSG_ERROR("libev not found")])

# Checks for header files.
AC_CHECK_HEADERS([arpa/inet.h fcntl.h stdlib.h string.h sys/socket.h unistd.h])

# Checks for typedefs, structures, and compiler characteristics.
AC_TYPE_PID_T
AC_TYPE_SIZE_T

# Checks for library functions.
AC_FUNC_FORK
AC_CHECK_FUNCS([dup2])

# check for extra compiler options (warning options)
if test "${GCC}" = "yes"; then
CFLAGS="${CFLAGS} -Wall -W -Wshadow -pedantic -std=gnu99"
fi

AC_ARG_ENABLE(extra-warnings,
AC_HELP_STRING([--enable-extra-warnings],[enable extra warnings (gcc specific)]),
[case "${enableval}" in
yes) extrawarnings=true ;;
no) extrawarnings=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-extra-warnings) ;;
esac],[extrawarnings=false])

if test x$extrawarnings = xtrue; then
CFLAGS="${CFLAGS} -g -O2 -g2 -Wall -Wmissing-declarations -Wdeclaration-after-statement -Wno-pointer-sign -Wcast-align -Winline -Wsign-compare -Wnested-externs -Wpointer-arith -Wl,--as-needed -Wformat-security"
fi

AC_CONFIG_FILES([Makefile libafcgi.pc])
AC_OUTPUT

+ 874
- 0
libafcgi.c View File

@@ -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;
}

+ 167
- 0
libafcgi.h View File

@@ -0,0 +1,167 @@
#ifndef _FCGI_CGI_FASTCGI_H
#define _FCGI_CGI_FASTCGI_H

#include "libafcgi-config.h"

/* NO multiplexing support */

#include <glib.h>
#include <ev.h>

/* FastCGI constants */
# define FCGI_VERSION_1 1
# define FCGI_HEADER_LEN 8

enum FCGI_Type {
FCGI_BEGIN_REQUEST = 1,
FCGI_ABORT_REQUEST = 2,
FCGI_END_REQUEST = 3,
FCGI_PARAMS = 4,
FCGI_STDIN = 5,
FCGI_STDOUT = 6,
FCGI_STDERR = 7,
FCGI_DATA = 8,
FCGI_GET_VALUES = 9,
FCGI_GET_VALUES_RESULT = 10,
FCGI_UNKNOWN_TYPE = 11
};
# define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)

enum FCGI_Flags {
FCGI_KEEP_CONN = 1
};

enum FCGI_Role {
FCGI_RESPONDER = 1,
FCGI_AUTHORIZER = 2,
FCGI_FILTER = 3
};

enum FCGI_ProtocolStatus {
FCGI_REQUEST_COMPLETE = 0,
FCGI_CANT_MPX_CONN = 1,
FCGI_OVERLOADED = 2,
FCGI_UNKNOWN_ROLE = 3
};

#define FASTCGI_MAX_KEYLEN 1024
#define FASTCGI_MAX_VALUELEN 64*1024
/* end FastCGI constants */

struct fastcgi_server;
typedef struct fastcgi_server fastcgi_server;

struct fastcgi_callbacks;
typedef struct fastcgi_callbacks fastcgi_callbacks;

struct fastcgi_connection;
typedef struct fastcgi_connection fastcgi_connection;

struct fastcgi_queue;
typedef struct fastcgi_queue fastcgi_queue;

struct fastcgi_server {
/* custom user data */
gpointer data;

/* private data */
gboolean do_shutdown;

const fastcgi_callbacks *callbacks;

guint max_connections;
GPtrArray *connections;
guint cur_requests;

gint fd;
struct ev_loop *loop;
ev_io fd_watcher;
ev_prepare closing_watcher;
};

struct fastcgi_callbacks {
void (*cb_new_connection)(fastcgi_connection *fcon); /* new connection accepted */
void (*cb_new_request)(fastcgi_connection *fcon); /* new request on connection, env/params are ready */
void (*cb_wrote_data)(fastcgi_connection *fcon);
void (*cb_received_stdin)(fastcgi_connection *fcon, GByteArray *data); /* data == NULL => eof */
void (*cb_received_data)(fastcgi_connection *fcon, GByteArray *data); /* data == NULL => eof */
void (*cb_request_aborted)(fastcgi_connection *fcon);
void (*cb_reset_connection)(fastcgi_connection *fcon); /* cleanup custom data before fcon is freed, not for keep-alive */
};

struct fastcgi_queue {
GQueue queue;
gsize offset; /* offset in first chunk */
gsize length;
gboolean closed;
};

struct fastcgi_connection {
/* custom user data */
gpointer data;

/* read/write */
GHashTable *environ; /* GString -> GString */

/* read only */
fastcgi_server *fsrv;
guint fcon_id; /* index in server con array */
gboolean closing; /* "dead" connection */

/* current request */
guint16 requestID;
guint16 role;
guint8 flags;

/* private data */
unsigned char headerbuf[8];
guint headerbuf_used;
gboolean first;

struct {
guint8 version;
guint8 type;
guint16 requestID;
guint16 contentLength;
guint8 paddingLength;
} current_header;

guint content_remaining, padding_remaining;

GByteArray *buffer, *parambuf;

gint fd;
ev_io fd_watcher;

gboolean read_suspended;

/* write queue */
fastcgi_queue write_queue;
};

fastcgi_server *fastcgi_server_create(struct ev_loop *loop, gint socketfd, const fastcgi_callbacks *callbacks, guint max_connections);
void fastcgi_server_stop(fastcgi_server *fsrv); /* stop accepting new connections, closes listening socket */
void fastcgi_server_free(fastcgi_server *fsrv);

void fastcgi_suspend_read(fastcgi_connection *fcon);
void fastcgi_resume_read(fastcgi_connection *fcon);

void fastcgi_end_request(fastcgi_connection *fcon, gint32 appStatus, enum FCGI_ProtocolStatus status);
void fastcgi_send_out(fastcgi_connection *fcon, GString *data);
void fastcgi_send_err(fastcgi_connection *fcon, GString *data);
void fastcgi_send_out_bytearray(fastcgi_connection *fcon, GByteArray *data);
void fastcgi_send_err_bytearray(fastcgi_connection *fcon, GByteArray *data);

void fastcgi_connection_close(fastcgi_connection *fcon); /* shouldn't be needed */

void fastcgi_queue_append_string(fastcgi_queue *queue, GString *buf);
void fastcgi_queue_append_bytearray(fastcgi_queue *queue, GByteArray *buf);
void fastcgi_queue_clear(fastcgi_queue *queue);

/* return values: 0 ok, -1 error, -2 con closed */
gint fastcgi_queue_write(int fd, fastcgi_queue *queue, gsize max_write);

char** fastcgi_build_env(fastcgi_connection *con);
const gchar* fastcgi_connection_environ_lookup(fastcgi_connection *fcon, const gchar* key, gsize keylen);

#endif

+ 11
- 0
libafcgi.pc.in View File

@@ -0,0 +1,11 @@
prefix=@prefix@
exec_prefix=@exec_prefix@
libdir=@libdir@
includedir=@includedir@

Name: libafcgi
Description: asynchronous FastCGI library
Version: @VERSION@
Requires: glib-2.0
Libs: -L${libdir} -lafcgi
Cflags: -I${includedir}

Loading…
Cancel
Save