diff src/c/fastcgi.c @ 860:a738002d5b4d

Serving Hello via FastCGI
author Adam Chlipala <adamc@hcoop.net>
date Sat, 27 Jun 2009 14:44:00 -0400
parents 60240acd15b9
children bd153951c794
line wrap: on
line diff
--- a/src/c/fastcgi.c	Sat Jun 27 12:38:23 2009 -0400
+++ b/src/c/fastcgi.c	Sat Jun 27 14:44:00 2009 -0400
@@ -65,11 +65,9 @@
 
 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.requestIdB1 = o->r.requestIdB0 = 0;
   o->r.contentLengthB1 = contentLength >> 8;
   o->r.contentLengthB0 = contentLength & 255;
   return uw_really_send(o->sock, &o->r, sizeof(o->r) - (65535 - contentLength));
@@ -83,12 +81,12 @@
 
     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)->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)->contentLengthB1 << 8) | LATEST(i)->contentLengthB0)
         + LATEST(i)->paddingLength;
       
       return r;
@@ -109,16 +107,30 @@
   }
 }
 
-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 int write_stdout(void *data, char *buf, size_t len) {
+  FCGI_Output *o = (FCGI_Output *)data;
+  while (len > 0) {
+    size_t len2 = len;
+    if (len2 > 65535)
+      len2 = 65535;
+    memcpy(o->r.contentData, buf, len2);
+    if (fastcgi_send(o, FCGI_STDOUT, len2)) {
+      fprintf(stderr, "fastcgi_send() failed in write_stdout().\n");
+      return -1;
+    }
+    buf += len2;
+    len -= len2;
+  }
+
+  return 0;
+}
+
 static void write_stderr(FCGI_Output *o, const char *fmt, ...) {
   int len;
   va_list ap;
@@ -126,9 +138,14 @@
 
   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");
+    fprintf(stderr, "vsnprintf() failed in write_stderr().\n");
+  else if (fastcgi_send(o, FCGI_STDERR, len))
+    fprintf(stderr, "fastcgi_send() failed in write_stderr().\n");
+}
+
+static void close_stream(FCGI_Output *o, unsigned char type) {
+  if (fastcgi_send(o, type, 0))
+    fprintf(stderr, "fastcgi_send() failed in close_stream().\n");
 }
 
 static void log_error(void *data, const char *fmt, ...) {
@@ -140,7 +157,7 @@
     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))
+    else if (fastcgi_send(o, FCGI_STDERR, len))
       fprintf(stderr, "fastcgi_send() failed in log_error().\n");
   } else
     vfprintf(stderr, fmt, ap);
@@ -149,21 +166,125 @@
 static void log_debug(void *data, const char *fmt, ...) {
 }
 
+static int read_funny_len(char **buf, int *len) {
+  if (*len <= 0)
+    return -1;
+
+  if ((*buf)[0] >> 7 == 0) {
+    int r = (*buf)[0];
+    ++*buf;
+    --*len;
+    return r;
+  }
+  else if (*len < 4)
+    return -1;
+  else {
+    int r = (((*buf)[3] & 0x7f) << 24) + ((*buf)[2] << 16) + ((*buf)[1] << 8) + (*buf)[0];
+    *buf += 4;
+    *len -= 4;
+    return r;
+  }
+}
+
+typedef struct {
+  char *name, *value;
+  unsigned name_len, value_len;
+} nvp;
+
+static char *search_nvps(nvp *nvps, const char *h) {
+  for (; nvps->name; ++nvps)
+    if (!strcmp(h, nvps->name))
+      return nvps->value;
+
+  return NULL;
+}
+
+typedef struct {
+  nvp *nvps;
+  char *uppercased;
+  int n_nvps, uppercased_len;
+} headers;
+
+static char *get_header(void *data, const char *h) {
+  headers *hs = (headers *)data;
+  size_t len = strlen(h);
+  char *s, *r;
+  const char *saved_h = h;
+
+  if (len > hs->uppercased_len) {
+    hs->uppercased_len = len;
+    hs->uppercased = realloc(hs->uppercased, len + 6);
+  }
+
+  strcpy(hs->uppercased, "HTTP_");
+  for (s = hs->uppercased+5; *h; ++h)
+    *s++ = *h == '-' ? '_' : toupper(*h);
+  *s = 0;
+
+  if (!strcasecmp(saved_h, "Content-length")
+      || !strcasecmp(saved_h, "Content-type"))
+    return search_nvps(hs->nvps, hs->uppercased + 5);
+  else
+    return search_nvps(hs->nvps, hs->uppercased);
+}
+
+static int read_nvp(char *buf, int len, nvp *nv) {
+  int nameLength, valueLength;
+
+  if ((nameLength = read_funny_len(&buf, &len)) < 0)
+    return -1;
+  if ((valueLength = read_funny_len(&buf, &len)) < 0)
+    return -1;
+  if (len < nameLength + valueLength)
+    return -1;
+
+  if (nameLength+1 > nv->name_len) {
+    nv->name_len = nameLength+1;
+    nv->name = realloc(nv->name, nv->name_len);
+  }
+  if (valueLength+1 > nv->value_len) {
+    nv->value_len = valueLength+1;
+    nv->value = realloc(nv->value, nv->value_len);
+  }
+
+  memcpy(nv->name, buf, nameLength);
+  nv->name[nameLength] = 0;
+
+  memcpy(nv->value, buf + nameLength, valueLength);
+  nv->value[valueLength] = 0;
+
+  return 0;
+}
+
 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();
+  headers hs;
+  size_t body_size = 0;
+  char *body = malloc(0);
+  size_t path_size = 0;
+  char *path_buf = malloc(0);
+
+  hs.uppercased = malloc(0);
+  hs.uppercased_len = 0;
+  hs.nvps = malloc(sizeof(nvp));
+  hs.n_nvps = 1;
 
   while (1) {
     FCGI_Record *r;
+    size_t used_nvps = 0;
+    int body_len, body_read;
+    char *s;
+    char *method, *path, *path_info, *query_string;
 
     in->sock = out->sock = uw_dequeue();
 
     if (!(r = fastcgi_recv(in))) {
       fprintf(stderr, "Error receiving initial message\n");
-      return NULL;
+      goto done;
     }
     
     if (r->type != FCGI_BEGIN_REQUEST) {
@@ -174,14 +295,140 @@
       goto done;
     }
 
-    if (!(r = fastcgi_recv(in))) {
-      fprintf(stderr, "Error receiving second message\n");
-      return NULL;
+    while (1) {
+      char *buf;
+
+      if (!(r = fastcgi_recv(in))) {
+        write_stderr(out, "Error receiving environment variables\n");
+        goto done;
+      }
+
+      if (r->type != FCGI_PARAMS) {
+        write_stderr(out, "Expected FCGI_PARAMS but got %d\n", r->type);
+        goto done;
+      }
+
+      if (r->contentLengthB1 == 0 && r->contentLengthB0 == 0)
+        break;
+
+      if (used_nvps == hs.n_nvps-1) {
+        ++hs.n_nvps;
+        hs.nvps = realloc(hs.nvps, hs.n_nvps * sizeof(nvp));
+        hs.nvps[used_nvps].name = malloc(0);
+        hs.nvps[used_nvps].value = malloc(0);
+        hs.nvps[used_nvps].name_len = 0;
+        hs.nvps[used_nvps].value_len = 0;
+      }
+
+      if (read_nvp(r->contentData, (r->contentLengthB1 << 8) | r->contentLengthB0, &hs.nvps[used_nvps]) < 0) {
+        write_stderr(out, "Error reading FCGI_PARAMS name-value pair\n");
+        goto done;
+      }
+
+      ++used_nvps;
     }
-    write_stderr(out, "Next message code: %d\n", r->type);
+
+    hs.nvps[used_nvps].name = NULL;
+
+    if (s = get_header(&hs, "Content-Length")) {
+      body_len = atoi(s);
+      if (body_len < 0) {
+        write_stderr(out, "Invalid Content-Length\n");
+        goto done;
+      }
+    } else
+      body_len = 0;
+
+    if (body_len > body_size) {
+      body_size = body_len;
+      body = realloc(body, body_size);
+    }
+
+    for (body_read = 0; body_read < body_len; ) {
+      char *buf;
+      int this_len;
+
+      if (!(r = fastcgi_recv(in))) {
+        write_stderr(out, "Error receiving STDIN\n");
+        goto done;
+      }
+
+      if (r->type != FCGI_STDIN) {
+        write_stderr(out, "Expected FCGI_STDIN but got %d\n", r->type);
+        goto done;
+      }
+
+      if (r->contentLengthB1 == 0 && r->contentLengthB0 == 0) {
+        write_stderr(out, "End of STDIN\n");
+        break;
+      }
+
+      this_len = (r->contentLengthB1 << 8) | r->contentLengthB0;
+
+      if (body_read + this_len > body_len) {
+        write_stderr(out, "Too much STDIN\n");
+        goto done;
+      }
+
+      memcpy(&body[body_read], r->contentData, this_len);
+      body_read += this_len;
+    }
+
+    if (!(method = search_nvps(hs.nvps, "REQUEST_METHOD"))) {
+      write_stderr(out, "REQUEST_METHOD not set\n");
+      goto done;
+    }
+
+    if (!(path = search_nvps(hs.nvps, "SCRIPT_NAME"))) {
+      write_stderr(out, "SCRIPT_NAME not set\n");
+      goto done;
+    }
+    
+    if (path_info = search_nvps(hs.nvps, "PATH_INFO")) {
+      int len1 = strlen(path), len2 = strlen(path_info);
+      int len = len1 + len2 + 1;
+
+      if (len > path_size) {
+        path_size = len;
+        path_buf = realloc(path_buf, path_size);
+      }
+
+      sprintf(path_buf, "%s%s", path, path_info);
+      path = path_buf;
+    }
+
+    if (!(query_string = search_nvps(hs.nvps, "QUERY_STRING")))
+      query_string = "";
+
+    uw_set_headers(ctx, get_header, &hs);
+    {
+      request_result rr;
+      FCGI_EndRequestBody *erb = (FCGI_EndRequestBody *)out->r.contentData;
+
+      rr = uw_request(rc, ctx, method, path, query_string, body, body_read,
+                      on_success, on_failure,
+                      out, log_error, log_debug,
+                      in->sock);
+
+      if (rr == KEEP_OPEN)
+        goto done2;
+
+      uw_output(ctx, write_stdout, out);
+      close_stream(out, FCGI_STDOUT);
+      close_stream(out, FCGI_STDERR);
+
+      if (rr == SERVED)
+        erb->appStatusB3 = erb->appStatusB2 = erb->appStatusB1 = erb->appStatusB0 = 0;
+      else
+        erb->appStatusB3 = erb->appStatusB2 = erb->appStatusB1 = erb->appStatusB0 = 0xFF;
+
+      erb->protocolStatus = FCGI_REQUEST_COMPLETE;
+      fastcgi_send(out, FCGI_END_REQUEST, sizeof(FCGI_EndRequestBody));
+    }
 
   done:
     close(in->sock);
+  done2:
     fastcgi_input_reset(in);
     uw_reset(ctx);
   }
@@ -244,6 +491,7 @@
     }
   }
 
+  uw_set_on_success("");
   uw_request_init(NULL, log_error, log_debug);
 
   names = calloc(nthreads, sizeof(int));