summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Adam Chlipala <adamc@hcoop.net>2009-06-27 12:38:23 -0400
committerGravatar Adam Chlipala <adamc@hcoop.net>2009-06-27 12:38:23 -0400
commit74103b6a0ede67d270bb6850ae12578df62032fd (patch)
treeb3a424dbabd630f63bba97a1f80919800ee093fc
parent756283988dd7d7235c228b3b99e6c7f6c73bf122 (diff)
Successfully starting FastCGI sessions with Apache
-rw-r--r--Makefile.in2
-rw-r--r--src/c/fastcgi.c304
-rw-r--r--src/c/fastcgi.h113
-rw-r--r--src/c/http.c50
-rw-r--r--src/c/queue.c58
-rw-r--r--src/c/queue.h7
-rw-r--r--src/fastcgi.sig30
-rw-r--r--src/fastcgi.sml36
-rw-r--r--src/settings.sml2
-rw-r--r--src/sources3
10 files changed, 556 insertions, 49 deletions
diff --git a/Makefile.in b/Makefile.in
index d8d7b9c5..1aca9590 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -14,7 +14,7 @@ all: smlnj mlton c
smlnj: src/urweb.cm
mlton: bin/urweb
-OBJS := urweb request http cgi
+OBJS := urweb request queue http cgi fastcgi
c: $(OBJS:%=lib/c/%.o)
clean:
diff --git a/src/c/fastcgi.c b/src/c/fastcgi.c
new file mode 100644
index 00000000..ad78b24a
--- /dev/null
+++ b/src/c/fastcgi.c
@@ -0,0 +1,304 @@
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdarg.h>
+
+#include <pthread.h>
+
+#include "urweb.h"
+#include "request.h"
+#include "queue.h"
+
+#include "fastcgi.h"
+
+typedef struct {
+ unsigned char version;
+ unsigned char type;
+ unsigned char requestIdB1;
+ unsigned char requestIdB0;
+ unsigned char contentLengthB1;
+ unsigned char contentLengthB0;
+ unsigned char paddingLength;
+ unsigned char reserved;
+ unsigned char contentData[65535];
+} FCGI_Record;
+
+typedef struct {
+ FCGI_Record r;
+ int sock;
+} FCGI_Output;
+
+typedef struct {
+ char buf[sizeof(FCGI_Record) + 255];
+ int available, used, sock;
+} FCGI_Input;
+
+static FCGI_Output *fastcgi_output() {
+ FCGI_Output *o = malloc(sizeof(FCGI_Output));
+
+ o->r.version = FCGI_VERSION_1;
+ o->r.paddingLength = 0;
+ o->r.reserved = 0;
+
+ return o;
+}
+
+static FCGI_Input *fastcgi_input() {
+ FCGI_Input *i = malloc(sizeof(FCGI_Input));
+
+ i->available = i->used = 0;
+
+ return i;
+}
+
+static void fastcgi_input_reset(FCGI_Input *i) {
+ i->available = i->used = 0;
+}
+
+static int fastcgi_send(FCGI_Output *o,
+ unsigned char type,
+ unsigned short requestId,
+ unsigned short contentLength) {
+ o->r.type = type;
+ o->r.requestIdB1 = requestId >> 8;
+ o->r.requestIdB0 = requestId & 255;
+ o->r.contentLengthB1 = contentLength >> 8;
+ o->r.contentLengthB0 = contentLength & 255;
+ return uw_really_send(o->sock, &o->r, sizeof(o->r) - (65535 - contentLength));
+}
+
+#define LATEST(i) ((FCGI_Record *)(i->buf + i->used))
+
+static FCGI_Record *fastcgi_recv(FCGI_Input *i) {
+ while (1) {
+ ssize_t n;
+
+ if (i->available >= i->used + sizeof(FCGI_Record) - 65535
+ && i->available >= i->used + sizeof(FCGI_Record) - 65535
+ + ((LATEST(i)->contentLengthB1 << 8) & LATEST(i)->contentLengthB0)
+ + LATEST(i)->paddingLength) {
+ FCGI_Record *r = LATEST(i);
+
+ i->used += sizeof(FCGI_Record) - 65535
+ + ((LATEST(i)->contentLengthB1 << 8) & LATEST(i)->contentLengthB0)
+ + LATEST(i)->paddingLength;
+
+ return r;
+ }
+
+ if (i->used > 0) {
+ memmove(i->buf, i->buf + i->used, i->available - i->used);
+ i->available -= i->used;
+ i->used = 0;
+ }
+
+ n = recv(i->sock, i->buf + i->available, sizeof(i->buf) - i->available, 0);
+
+ if (n <= 0)
+ return NULL;
+
+ i->available += n;
+ }
+}
+
+static char *get_header(void *data, const char *h) {
+ return NULL;
+}
+
+static void on_success(uw_context ctx) { }
+
+static void on_failure(uw_context ctx) {
+ uw_write_header(ctx, "Status: 500 Internal Server Error\r\n");
+}
+
+static void write_stderr(FCGI_Output *o, const char *fmt, ...) {
+ int len;
+ va_list ap;
+ va_start(ap, fmt);
+
+ len = vsnprintf(o->r.contentData, 65535, fmt, ap);
+ if (len < 0)
+ fprintf(stderr, "vsnprintf() failed in log_error().\n");
+ else if (fastcgi_send(o, FCGI_STDERR, FCGI_NULL_REQUEST_ID, len))
+ fprintf(stderr, "fastcgi_send() failed in log_error().\n");
+}
+
+static void log_error(void *data, const char *fmt, ...) {
+ FCGI_Output *o = (FCGI_Output *)data;
+ va_list ap;
+ va_start(ap, fmt);
+
+ if (o) {
+ int len = vsnprintf(o->r.contentData, 65535, fmt, ap);
+ if (len < 0)
+ fprintf(stderr, "vsnprintf() failed in log_error().\n");
+ else if (fastcgi_send(o, FCGI_STDERR, FCGI_NULL_REQUEST_ID, len))
+ fprintf(stderr, "fastcgi_send() failed in log_error().\n");
+ } else
+ vfprintf(stderr, fmt, ap);
+}
+
+static void log_debug(void *data, const char *fmt, ...) {
+}
+
+static void *worker(void *data) {
+ int me = *(int *)data;
+ FCGI_Input *in = fastcgi_input();
+ FCGI_Output *out = fastcgi_output();
+ uw_context ctx = uw_request_new_context(out, log_error, log_debug);
+ uw_request_context rc = uw_new_request_context();
+
+ while (1) {
+ FCGI_Record *r;
+
+ in->sock = out->sock = uw_dequeue();
+
+ if (!(r = fastcgi_recv(in))) {
+ fprintf(stderr, "Error receiving initial message\n");
+ return NULL;
+ }
+
+ if (r->type != FCGI_BEGIN_REQUEST) {
+ write_stderr(out, "First message is not BEGIN_REQUEST\n");
+ goto done;
+ } else if (((FCGI_BeginRequestBody *)&r->contentData)->roleB0 != FCGI_RESPONDER) {
+ write_stderr(out, "First message is not BEGIN_REQUEST\n");
+ goto done;
+ }
+
+ if (!(r = fastcgi_recv(in))) {
+ fprintf(stderr, "Error receiving second message\n");
+ return NULL;
+ }
+ write_stderr(out, "Next message code: %d\n", r->type);
+
+ done:
+ close(in->sock);
+ fastcgi_input_reset(in);
+ uw_reset(ctx);
+ }
+}
+
+static void help(char *cmd) {
+ printf("Usage: %s [-t <thread-count>]\n", cmd);
+}
+
+static void sigint(int signum) {
+ printf("Exiting....\n");
+ exit(0);
+}
+
+static loggers ls = {NULL, log_error, log_debug};
+
+int main(int argc, char *argv[]) {
+ // The skeleton for this function comes from Beej's sockets tutorial.
+ struct sockaddr_in their_addr; // connector's address information
+ int sin_size, yes = 1;
+ int nthreads = 1, i, *names, opt;
+ char *fwsa = getenv("FCGI_WEB_SERVER_ADDRS"), *nthreads_s = getenv("URWEB_NUM_THREADS");
+
+ if (nthreads_s) {
+ nthreads = atoi(nthreads_s);
+ if (nthreads <= 0) {
+ fprintf(stderr, "Bad URWEB_NUM_THREADS value\n");
+ return 1;
+ }
+ }
+
+ signal(SIGINT, sigint);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGUSR1, sigint);
+ signal(SIGTERM, sigint);
+
+ while ((opt = getopt(argc, argv, "ht:")) != -1) {
+ switch (opt) {
+ case '?':
+ fprintf(stderr, "Unknown command-line option");
+ help(argv[0]);
+ return 1;
+
+ case 'h':
+ help(argv[0]);
+ return 0;
+
+ case 't':
+ nthreads = atoi(optarg);
+ if (nthreads <= 0) {
+ fprintf(stderr, "Invalid thread count\n");
+ help(argv[0]);
+ return 1;
+ }
+ break;
+
+ default:
+ fprintf(stderr, "Unexpected getopt() behavior\n");
+ return 1;
+ }
+ }
+
+ uw_request_init(NULL, log_error, log_debug);
+
+ names = calloc(nthreads, sizeof(int));
+
+ sin_size = sizeof their_addr;
+
+ {
+ pthread_t thread;
+ int name;
+
+ if (pthread_create(&thread, NULL, client_pruner, &ls)) {
+ fprintf(stderr, "Error creating pruner thread\n");
+ return 1;
+ }
+ }
+
+ for (i = 0; i < nthreads; ++i) {
+ pthread_t thread;
+ names[i] = i;
+ if (pthread_create(&thread, NULL, worker, &names[i])) {
+ fprintf(stderr, "Error creating worker thread #%d\n", i);
+ return 1;
+ }
+ }
+
+ while (1) {
+ int new_fd = accept(FCGI_LISTENSOCK_FILENO, (struct sockaddr *)&their_addr, &sin_size);
+
+ if (new_fd < 0) {
+ fprintf(stderr, "Socket accept failed\n");
+ return 1;
+ }
+
+ if (fwsa) {
+ char host[100], matched = 0;
+ char *ips, *sep;
+
+ if (getnameinfo((struct sockaddr *)&their_addr, sin_size, host, sizeof host, NULL, 0, NI_NUMERICHOST)) {
+ fprintf(stderr, "Remote IP determination failed\n");
+ return 1;
+ }
+
+ for (ips = fwsa; sep = strchr(ips, ','); ips = sep+1) {
+ if (!strncmp(ips, host, sep - ips)) {
+ matched = 1;
+ break;
+ }
+ }
+
+ if (!matched && strcmp(ips, host)) {
+ fprintf(stderr, "Remote address is not in FCGI_WEB_SERVER_ADDRS");
+ return 1;
+ }
+ }
+
+ uw_enqueue(new_fd);
+ }
+}
diff --git a/src/c/fastcgi.h b/src/c/fastcgi.h
new file mode 100644
index 00000000..826d808e
--- /dev/null
+++ b/src/c/fastcgi.h
@@ -0,0 +1,113 @@
+// This code comes from the FastCGI 1.0 spec at:
+// http://www.fastcgi.com/drupal/node/6?q=node/22
+
+/*
+ * Listening socket file number
+ */
+#define FCGI_LISTENSOCK_FILENO 0
+
+typedef struct {
+ unsigned char version;
+ unsigned char type;
+ unsigned char requestIdB1;
+ unsigned char requestIdB0;
+ unsigned char contentLengthB1;
+ unsigned char contentLengthB0;
+ unsigned char paddingLength;
+ unsigned char reserved;
+} FCGI_Header;
+
+/*
+ * Number of bytes in a FCGI_Header. Future versions of the protocol
+ * will not reduce this number.
+ */
+#define FCGI_HEADER_LEN 8
+
+/*
+ * Value for version component of FCGI_Header
+ */
+#define FCGI_VERSION_1 1
+
+/*
+ * Values for type component of FCGI_Header
+ */
+#define FCGI_BEGIN_REQUEST 1
+#define FCGI_ABORT_REQUEST 2
+#define FCGI_END_REQUEST 3
+#define FCGI_PARAMS 4
+#define FCGI_STDIN 5
+#define FCGI_STDOUT 6
+#define FCGI_STDERR 7
+#define FCGI_DATA 8
+#define FCGI_GET_VALUES 9
+#define FCGI_GET_VALUES_RESULT 10
+#define FCGI_UNKNOWN_TYPE 11
+#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
+
+/*
+ * Value for requestId component of FCGI_Header
+ */
+#define FCGI_NULL_REQUEST_ID 0
+
+typedef struct {
+ unsigned char roleB1;
+ unsigned char roleB0;
+ unsigned char flags;
+ unsigned char reserved[5];
+} FCGI_BeginRequestBody;
+
+typedef struct {
+ FCGI_Header header;
+ FCGI_BeginRequestBody body;
+} FCGI_BeginRequestRecord;
+
+/*
+ * Mask for flags component of FCGI_BeginRequestBody
+ */
+#define FCGI_KEEP_CONN 1
+
+/*
+ * Values for role component of FCGI_BeginRequestBody
+ */
+#define FCGI_RESPONDER 1
+#define FCGI_AUTHORIZER 2
+#define FCGI_FILTER 3
+
+typedef struct {
+ unsigned char appStatusB3;
+ unsigned char appStatusB2;
+ unsigned char appStatusB1;
+ unsigned char appStatusB0;
+ unsigned char protocolStatus;
+ unsigned char reserved[3];
+} FCGI_EndRequestBody;
+
+typedef struct {
+ FCGI_Header header;
+ FCGI_EndRequestBody body;
+} FCGI_EndRequestRecord;
+
+/*
+ * Values for protocolStatus component of FCGI_EndRequestBody
+ */
+#define FCGI_REQUEST_COMPLETE 0
+#define FCGI_CANT_MPX_CONN 1
+#define FCGI_OVERLOADED 2
+#define FCGI_UNKNOWN_ROLE 3
+
+/*
+ * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records
+ */
+#define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
+#define FCGI_MAX_REQS "FCGI_MAX_REQS"
+#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
+
+typedef struct {
+ unsigned char type;
+ unsigned char reserved[7];
+} FCGI_UnknownTypeBody;
+
+typedef struct {
+ FCGI_Header header;
+ FCGI_UnknownTypeBody body;
+} FCGI_UnknownTypeRecord;
diff --git a/src/c/http.c b/src/c/http.c
index a8f2efd4..e4e79848 100644
--- a/src/c/http.c
+++ b/src/c/http.c
@@ -14,45 +14,10 @@
#include "urweb.h"
#include "request.h"
+#include "queue.h"
int uw_backlog = 10;
-typedef struct node {
- int fd;
- struct node *next;
-} *node;
-
-static node front = NULL, back = NULL;
-
-static int empty() {
- return front == NULL;
-}
-
-static void enqueue(int fd) {
- node n = malloc(sizeof(struct node));
-
- n->fd = fd;
- n->next = NULL;
- if (back)
- back->next = n;
- else
- front = n;
- back = n;
-}
-
-static int dequeue() {
- int ret = front->fd;
-
- front = front->next;
- if (!front)
- back = NULL;
-
- return ret;
-}
-
-static pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;
-static pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER;
-
static char *get_header(void *data, const char *h) {
char *s = data;
int len = strlen(h);
@@ -103,13 +68,7 @@ static void *worker(void *data) {
while (1) {
char *back = buf;
- int sock;
-
- pthread_mutex_lock(&queue_mutex);
- while (empty())
- pthread_cond_wait(&queue_cond, &queue_mutex);
- sock = dequeue();
- pthread_mutex_unlock(&queue_mutex);
+ int sock = uw_dequeue();
printf("Handling connection with thread #%d.\n", me);
@@ -367,9 +326,6 @@ int main(int argc, char *argv[]) {
printf("Accepted connection.\n");
- pthread_mutex_lock(&queue_mutex);
- enqueue(new_fd);
- pthread_cond_broadcast(&queue_cond);
- pthread_mutex_unlock(&queue_mutex);
+ uw_enqueue(new_fd);
}
}
diff --git a/src/c/queue.c b/src/c/queue.c
new file mode 100644
index 00000000..1c82e0dc
--- /dev/null
+++ b/src/c/queue.c
@@ -0,0 +1,58 @@
+#include <stdlib.h>
+
+#include <pthread.h>
+
+typedef struct node {
+ int fd;
+ struct node *next;
+} *node;
+
+static node front = NULL, back = NULL;
+
+static int empty() {
+ return front == NULL;
+}
+
+static void enqueue(int fd) {
+ node n = malloc(sizeof(struct node));
+
+ n->fd = fd;
+ n->next = NULL;
+ if (back)
+ back->next = n;
+ else
+ front = n;
+ back = n;
+}
+
+static int dequeue() {
+ int ret = front->fd;
+
+ front = front->next;
+ if (!front)
+ back = NULL;
+
+ return ret;
+}
+
+static pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER;
+
+int uw_dequeue() {
+ int sock;
+
+ pthread_mutex_lock(&queue_mutex);
+ while (empty())
+ pthread_cond_wait(&queue_cond, &queue_mutex);
+ sock = dequeue();
+ pthread_mutex_unlock(&queue_mutex);
+
+ return sock;
+}
+
+void uw_enqueue(int new_fd) {
+ pthread_mutex_lock(&queue_mutex);
+ enqueue(new_fd);
+ pthread_cond_broadcast(&queue_cond);
+ pthread_mutex_unlock(&queue_mutex);
+}
diff --git a/src/c/queue.h b/src/c/queue.h
new file mode 100644
index 00000000..8297dd00
--- /dev/null
+++ b/src/c/queue.h
@@ -0,0 +1,7 @@
+#ifndef QUEUE_H
+#define QUEUE_H
+
+int uw_dequeue();
+void uw_enqueue(int);
+
+#endif
diff --git a/src/fastcgi.sig b/src/fastcgi.sig
new file mode 100644
index 00000000..c37fe68e
--- /dev/null
+++ b/src/fastcgi.sig
@@ -0,0 +1,30 @@
+(* Copyright (c) 2008-2009, Adam Chlipala
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - The names of contributors may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *)
+
+signature FASTCGI = sig
+
+end
diff --git a/src/fastcgi.sml b/src/fastcgi.sml
new file mode 100644
index 00000000..fbd24b5d
--- /dev/null
+++ b/src/fastcgi.sml
@@ -0,0 +1,36 @@
+(* Copyright (c) 2008-2009, Adam Chlipala
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - The names of contributors may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *)
+
+structure Fastcgi :> FASTCGI = struct
+
+open Settings
+
+val () = addProtocol {name = "fastcgi",
+ link = clibFile "request.o" ^ " " ^ clibFile "queue.o" ^ " " ^ clibFile "fastcgi.o",
+ persistent = true}
+
+end
diff --git a/src/settings.sml b/src/settings.sml
index 36521799..e9f3e4b9 100644
--- a/src/settings.sml
+++ b/src/settings.sml
@@ -262,7 +262,7 @@ fun clibFile s = OS.Path.joinDirFile {dir = Config.libC,
file = s}
val http = {name = "http",
- link = clibFile "request.o" ^ " " ^ clibFile "http.o",
+ link = clibFile "request.o" ^ " " ^ clibFile "queue.o" ^ " " ^ clibFile "http.o",
persistent = true}
val () = addProtocol http
diff --git a/src/sources b/src/sources
index 81b6f881..b176301d 100644
--- a/src/sources
+++ b/src/sources
@@ -19,6 +19,9 @@ settings.sml
cgi.sig
cgi.sml
+fastcgi.sig
+fastcgi.sml
+
print.sig
print.sml