changeset 859:60240acd15b9

Successfully starting FastCGI sessions with Apache
author Adam Chlipala <adamc@hcoop.net>
date Sat, 27 Jun 2009 12:38:23 -0400
parents 346cf1908a17
children a738002d5b4d
files Makefile.in src/c/fastcgi.c src/c/fastcgi.h src/c/http.c src/c/queue.c src/c/queue.h src/fastcgi.sig src/fastcgi.sml src/settings.sml src/sources
diffstat 10 files changed, 556 insertions(+), 49 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.in	Sat Jun 27 10:50:45 2009 -0400
+++ b/Makefile.in	Sat Jun 27 12:38:23 2009 -0400
@@ -14,7 +14,7 @@
 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:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/c/fastcgi.c	Sat Jun 27 12:38:23 2009 -0400
@@ -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);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/c/fastcgi.h	Sat Jun 27 12:38:23 2009 -0400
@@ -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;
--- a/src/c/http.c	Sat Jun 27 10:50:45 2009 -0400
+++ b/src/c/http.c	Sat Jun 27 12:38:23 2009 -0400
@@ -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 @@
 
   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 @@
 
     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);
   }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/c/queue.c	Sat Jun 27 12:38:23 2009 -0400
@@ -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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/c/queue.h	Sat Jun 27 12:38:23 2009 -0400
@@ -0,0 +1,7 @@
+#ifndef QUEUE_H
+#define QUEUE_H
+
+int uw_dequeue();
+void uw_enqueue(int);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/fastcgi.sig	Sat Jun 27 12:38:23 2009 -0400
@@ -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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/fastcgi.sml	Sat Jun 27 12:38:23 2009 -0400
@@ -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
--- a/src/settings.sml	Sat Jun 27 10:50:45 2009 -0400
+++ b/src/settings.sml	Sat Jun 27 12:38:23 2009 -0400
@@ -262,7 +262,7 @@
                                       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
--- a/src/sources	Sat Jun 27 10:50:45 2009 -0400
+++ b/src/sources	Sat Jun 27 12:38:23 2009 -0400
@@ -19,6 +19,9 @@
 cgi.sig
 cgi.sml
 
+fastcgi.sig
+fastcgi.sml
+
 print.sig
 print.sml