view 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 source
#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);
  }
}