diff src/c/fastcgi.c @ 859:60240acd15b9

Successfully starting FastCGI sessions with Apache
author Adam Chlipala <adamc@hcoop.net>
date Sat, 27 Jun 2009 12:38:23 -0400
parents
children a738002d5b4d
line wrap: on
line diff
--- /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);
+  }
+}