changeset 855:28e42b22424d

Initial implementation of protocols in Settings
author Adam Chlipala <adamc@hcoop.net>
date Tue, 23 Jun 2009 15:56:04 -0400
parents 158d980889ac
children 86ec89baee01
files Makefile.in src/c/driver.c src/c/http.c src/compiler.sml src/scriptcheck.sml src/settings.sig src/settings.sml
diffstat 7 files changed, 400 insertions(+), 354 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.in	Tue Jun 23 15:40:35 2009 -0400
+++ b/Makefile.in	Tue Jun 23 15:56:04 2009 -0400
@@ -13,7 +13,9 @@
 
 smlnj: src/urweb.cm
 mlton: bin/urweb
-c: lib/c/urweb.o lib/c/request.o lib/c/driver.o
+
+OBJS := urweb request http
+c: $(OBJS:%=lib/c/%.o)
 
 clean:
 	rm -f src/*.mlton.grm.* src/*.mlton.lex.* \
--- a/src/c/driver.c	Tue Jun 23 15:40:35 2009 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,349 +0,0 @@
-#define _GNU_SOURCE
-
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <unistd.h>
-#include <signal.h>
-
-#include <pthread.h>
-
-#include <mhash.h>
-
-#include "urweb.h"
-#include "request.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);
-  char *p;
-
-  while (p = strchr(s, ':')) {
-    if (p - s == len && !strncasecmp(s, h, len)) {
-      return p + 2;
-    } else {
-      if ((s = strchr(p, 0)) && s[1] != 0)
-        s += 2;
-      else
-        return NULL;
-    }
-  }
-  
-  return NULL;
-}
-
-static void *worker(void *data) {
-  int me = *(int *)data;
-  uw_context ctx = uw_request_new_context();
-  size_t buf_size = 2;
-  char *buf = malloc(buf_size);
-  uw_request_context rc = uw_new_request_context();
-
-  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);
-
-    printf("Handling connection with thread #%d.\n", me);
-
-    while (1) {
-      int r;
-      char *method, *path, *query_string, *headers, *body, *s, *s2;
-
-      if (back - buf == buf_size - 1) {
-        char *new_buf;
-        buf_size *= 2;
-        new_buf = realloc(buf, buf_size);
-        back = new_buf + (back - buf);
-        buf = new_buf;
-      }
-
-      r = recv(sock, back, buf_size - 1 - (back - buf), 0);
-
-      if (r < 0) {
-        fprintf(stderr, "Recv failed\n");
-        break;
-      }
-
-      if (r == 0) {
-        printf("Connection closed.\n");
-        break;
-      }
-
-      back += r;
-      *back = 0;
-
-      if ((body = strstr(buf, "\r\n\r\n"))) {
-        request_result rr;
-
-        body[0] = body[1] = 0;
-        body += 4;
-
-        if ((s = strcasestr(buf, "\r\nContent-Length: ")) && s < body) {
-          int clen;
-
-          if (sscanf(s + 18, "%d\r\n", &clen) != 1) {
-            fprintf(stderr, "Malformed Content-Length header\n");
-            break;
-          }
-
-          while (back - body < clen) {
-            if (back - buf == buf_size - 1) {
-              char *new_buf;
-              buf_size *= 2;
-              new_buf = realloc(buf, buf_size);
-
-              back = new_buf + (back - buf);
-              body = new_buf + (body - buf);
-              s = new_buf + (s - buf);
-
-              buf = new_buf;
-            }
-
-            r = recv(sock, back, buf_size - 1 - (back - buf), 0);
-
-            if (r < 0) {
-              fprintf(stderr, "Recv failed\n");
-              close(sock);
-              goto done;
-            }
-
-            if (r == 0) {
-              fprintf(stderr, "Connection closed.\n");
-              close(sock);
-              goto done;
-            }
-
-            back += r;
-            *back = 0;      
-          }
-        }
-
-        if (!(s = strstr(buf, "\r\n"))) {
-          fprintf(stderr, "No newline in request\n");
-          close(sock);
-          goto done;
-        }
-
-        *s = 0;
-        headers = s + 2;
-        method = s = buf;
-
-        if (!strsep(&s, " ")) {
-          fprintf(stderr, "No first space in HTTP command\n");
-          close(sock);
-          goto done;
-        }
-        path = s;
-
-        if (s = strchr(path, ' '))
-          *s = 0;
-
-        if (s = strchr(path, '?')) {
-          *s = 0;
-          query_string = s+1;
-        }
-        else
-          query_string = NULL;
-
-        s = headers;
-        while (s2 = strchr(s, '\r')) {
-          s = s2;
-
-          if (s[1] == 0)
-            break;
-
-          *s = 0;
-          s += 2;
-        }
-
-        uw_set_headers(ctx, get_header, headers);
-
-        rr = uw_request(rc, ctx, method, path, query_string, body, back - body, sock);
-        uw_send(ctx, sock);
-
-        if (rr == SERVED || rr == FAILED)
-          close(sock);
-        else if (rr != KEEP_OPEN)
-          fprintf(stderr, "Illegal uw_request return code: %d\n", rr);
-
-        break;
-      }
-    }
-
-  done:
-    uw_reset(ctx);
-  }
-}
-
-static void help(char *cmd) {
-  printf("Usage: %s [-p <port>] [-t <thread-count>]\n", cmd);
-}
-
-static void sigint(int signum) {
-  printf("Exiting....\n");
-  exit(0);
-}
-
-int main(int argc, char *argv[]) {
-  // The skeleton for this function comes from Beej's sockets tutorial.
-  int sockfd;  // listen on sock_fd
-  struct sockaddr_in my_addr;
-  struct sockaddr_in their_addr; // connector's address information
-  int sin_size, yes = 1;
-  int uw_port = 8080, nthreads = 1, i, *names, opt;
- 
-  signal(SIGINT, sigint);
-  signal(SIGPIPE, SIG_IGN); 
-
-  while ((opt = getopt(argc, argv, "hp:t:")) != -1) {
-    switch (opt) {
-    case '?':
-      fprintf(stderr, "Unknown command-line option");
-      help(argv[0]);
-      return 1;
-
-    case 'h':
-      help(argv[0]);
-      return 0;
-
-    case 'p':
-      uw_port = atoi(optarg);
-      if (uw_port <= 0) {
-        fprintf(stderr, "Invalid port number\n");
-        help(argv[0]);
-        return 1;
-      }
-      break;
-
-    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();
-
-  names = calloc(nthreads, sizeof(int));
-
-  sockfd = socket(PF_INET, SOCK_STREAM, 0); // do some error checking!
-
-  if (sockfd < 0) {
-    fprintf(stderr, "Listener socket creation failed\n");
-    return 1;
-  }
-
-  if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0) {
-    fprintf(stderr, "Listener socket option setting failed\n");
-    return 1;
-  }
-
-  my_addr.sin_family = AF_INET;         // host byte order
-  my_addr.sin_port = htons(uw_port);    // short, network byte order
-  my_addr.sin_addr.s_addr = INADDR_ANY; // auto-fill with my IP
-  memset(my_addr.sin_zero, '\0', sizeof my_addr.sin_zero);
-
-  if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof my_addr) < 0) {
-    fprintf(stderr, "Listener socket bind failed\n");
-    return 1;
-  }
-
-  if (listen(sockfd, uw_backlog) < 0) {
-    fprintf(stderr, "Socket listen failed\n");
-    return 1;
-  }
-
-  sin_size = sizeof their_addr;
-
-  printf("Listening on port %d....\n", uw_port);
-
-  {
-    pthread_t thread;
-    int name;
-
-    if (pthread_create(&thread, NULL, client_pruner, &name)) {
-      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(sockfd, (struct sockaddr *)&their_addr, &sin_size);
-
-    if (new_fd < 0) {
-      fprintf(stderr, "Socket accept failed\n");
-      return 1;
-    }
-
-    printf("Accepted connection.\n");
-
-    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/http.c	Tue Jun 23 15:56:04 2009 -0400
@@ -0,0 +1,349 @@
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <pthread.h>
+
+#include <mhash.h>
+
+#include "urweb.h"
+#include "request.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);
+  char *p;
+
+  while (p = strchr(s, ':')) {
+    if (p - s == len && !strncasecmp(s, h, len)) {
+      return p + 2;
+    } else {
+      if ((s = strchr(p, 0)) && s[1] != 0)
+        s += 2;
+      else
+        return NULL;
+    }
+  }
+  
+  return NULL;
+}
+
+static void *worker(void *data) {
+  int me = *(int *)data;
+  uw_context ctx = uw_request_new_context();
+  size_t buf_size = 2;
+  char *buf = malloc(buf_size);
+  uw_request_context rc = uw_new_request_context();
+
+  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);
+
+    printf("Handling connection with thread #%d.\n", me);
+
+    while (1) {
+      int r;
+      char *method, *path, *query_string, *headers, *body, *s, *s2;
+
+      if (back - buf == buf_size - 1) {
+        char *new_buf;
+        buf_size *= 2;
+        new_buf = realloc(buf, buf_size);
+        back = new_buf + (back - buf);
+        buf = new_buf;
+      }
+
+      r = recv(sock, back, buf_size - 1 - (back - buf), 0);
+
+      if (r < 0) {
+        fprintf(stderr, "Recv failed\n");
+        break;
+      }
+
+      if (r == 0) {
+        printf("Connection closed.\n");
+        break;
+      }
+
+      back += r;
+      *back = 0;
+
+      if ((body = strstr(buf, "\r\n\r\n"))) {
+        request_result rr;
+
+        body[0] = body[1] = 0;
+        body += 4;
+
+        if ((s = strcasestr(buf, "\r\nContent-Length: ")) && s < body) {
+          int clen;
+
+          if (sscanf(s + 18, "%d\r\n", &clen) != 1) {
+            fprintf(stderr, "Malformed Content-Length header\n");
+            break;
+          }
+
+          while (back - body < clen) {
+            if (back - buf == buf_size - 1) {
+              char *new_buf;
+              buf_size *= 2;
+              new_buf = realloc(buf, buf_size);
+
+              back = new_buf + (back - buf);
+              body = new_buf + (body - buf);
+              s = new_buf + (s - buf);
+
+              buf = new_buf;
+            }
+
+            r = recv(sock, back, buf_size - 1 - (back - buf), 0);
+
+            if (r < 0) {
+              fprintf(stderr, "Recv failed\n");
+              close(sock);
+              goto done;
+            }
+
+            if (r == 0) {
+              fprintf(stderr, "Connection closed.\n");
+              close(sock);
+              goto done;
+            }
+
+            back += r;
+            *back = 0;      
+          }
+        }
+
+        if (!(s = strstr(buf, "\r\n"))) {
+          fprintf(stderr, "No newline in request\n");
+          close(sock);
+          goto done;
+        }
+
+        *s = 0;
+        headers = s + 2;
+        method = s = buf;
+
+        if (!strsep(&s, " ")) {
+          fprintf(stderr, "No first space in HTTP command\n");
+          close(sock);
+          goto done;
+        }
+        path = s;
+
+        if (s = strchr(path, ' '))
+          *s = 0;
+
+        if (s = strchr(path, '?')) {
+          *s = 0;
+          query_string = s+1;
+        }
+        else
+          query_string = NULL;
+
+        s = headers;
+        while (s2 = strchr(s, '\r')) {
+          s = s2;
+
+          if (s[1] == 0)
+            break;
+
+          *s = 0;
+          s += 2;
+        }
+
+        uw_set_headers(ctx, get_header, headers);
+
+        rr = uw_request(rc, ctx, method, path, query_string, body, back - body, sock);
+        uw_send(ctx, sock);
+
+        if (rr == SERVED || rr == FAILED)
+          close(sock);
+        else if (rr != KEEP_OPEN)
+          fprintf(stderr, "Illegal uw_request return code: %d\n", rr);
+
+        break;
+      }
+    }
+
+  done:
+    uw_reset(ctx);
+  }
+}
+
+static void help(char *cmd) {
+  printf("Usage: %s [-p <port>] [-t <thread-count>]\n", cmd);
+}
+
+static void sigint(int signum) {
+  printf("Exiting....\n");
+  exit(0);
+}
+
+int main(int argc, char *argv[]) {
+  // The skeleton for this function comes from Beej's sockets tutorial.
+  int sockfd;  // listen on sock_fd
+  struct sockaddr_in my_addr;
+  struct sockaddr_in their_addr; // connector's address information
+  int sin_size, yes = 1;
+  int uw_port = 8080, nthreads = 1, i, *names, opt;
+ 
+  signal(SIGINT, sigint);
+  signal(SIGPIPE, SIG_IGN); 
+
+  while ((opt = getopt(argc, argv, "hp:t:")) != -1) {
+    switch (opt) {
+    case '?':
+      fprintf(stderr, "Unknown command-line option");
+      help(argv[0]);
+      return 1;
+
+    case 'h':
+      help(argv[0]);
+      return 0;
+
+    case 'p':
+      uw_port = atoi(optarg);
+      if (uw_port <= 0) {
+        fprintf(stderr, "Invalid port number\n");
+        help(argv[0]);
+        return 1;
+      }
+      break;
+
+    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();
+
+  names = calloc(nthreads, sizeof(int));
+
+  sockfd = socket(PF_INET, SOCK_STREAM, 0); // do some error checking!
+
+  if (sockfd < 0) {
+    fprintf(stderr, "Listener socket creation failed\n");
+    return 1;
+  }
+
+  if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0) {
+    fprintf(stderr, "Listener socket option setting failed\n");
+    return 1;
+  }
+
+  my_addr.sin_family = AF_INET;         // host byte order
+  my_addr.sin_port = htons(uw_port);    // short, network byte order
+  my_addr.sin_addr.s_addr = INADDR_ANY; // auto-fill with my IP
+  memset(my_addr.sin_zero, '\0', sizeof my_addr.sin_zero);
+
+  if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof my_addr) < 0) {
+    fprintf(stderr, "Listener socket bind failed\n");
+    return 1;
+  }
+
+  if (listen(sockfd, uw_backlog) < 0) {
+    fprintf(stderr, "Socket listen failed\n");
+    return 1;
+  }
+
+  sin_size = sizeof their_addr;
+
+  printf("Listening on port %d....\n", uw_port);
+
+  {
+    pthread_t thread;
+    int name;
+
+    if (pthread_create(&thread, NULL, client_pruner, &name)) {
+      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(sockfd, (struct sockaddr *)&their_addr, &sin_size);
+
+    if (new_fd < 0) {
+      fprintf(stderr, "Socket accept failed\n");
+      return 1;
+    }
+
+    printf("Accepted connection.\n");
+
+    pthread_mutex_lock(&queue_mutex);
+    enqueue(new_fd);
+    pthread_cond_broadcast(&queue_cond);
+    pthread_mutex_unlock(&queue_mutex);
+  }
+}
--- a/src/compiler.sml	Tue Jun 23 15:40:35 2009 -0400
+++ b/src/compiler.sml	Tue Jun 23 15:56:04 2009 -0400
@@ -883,14 +883,13 @@
 
 fun compileC {cname, oname, ename, libs, profile, debug, link = link'} =
     let
+        val proto = Settings.currentProtocol ()
         val urweb_o = clibFile "urweb.o"
-        val request_o = clibFile "request.o"
-        val driver_o = clibFile "driver.o"
 
         val compile = "gcc " ^ Config.gccArgs ^ " -Wstrict-prototypes -Werror -O3 -I " ^ Config.includ
                       ^ " -c " ^ cname ^ " -o " ^ oname
         val link = "gcc -Werror -O3 -lm -lmhash -pthread " ^ libs ^ " " ^ urweb_o ^ " " ^ oname
-                   ^ " " ^ request_o ^ " " ^ driver_o ^ " -o " ^ ename
+                   ^ " " ^ #link proto ^ " -o " ^ ename
 
         val (compile, link) =
             if profile then
--- a/src/scriptcheck.sml	Tue Jun 23 15:40:35 2009 -0400
+++ b/src/scriptcheck.sml	Tue Jun 23 15:56:04 2009 -0400
@@ -73,6 +73,8 @@
 
 fun classify (ds, ps) =
     let
+        val proto = Settings.currentProtocol ()
+
         fun inString {needle, haystack} =
             let
                 val (_, suffix) = Substring.position needle (Substring.full haystack)
@@ -158,10 +160,18 @@
 
         val (pull_ids, push_ids) = foldl decl (IS.empty, IS.empty) ds
 
+        val foundBad = ref false
+
         val ps = map (fn (ek, x, n, ts, t, _) =>
                          (ek, x, n, ts, t,
                           if IS.member (push_ids, n) then
-                              ServerAndPullAndPush
+                              (if not (#supportsPush proto) andalso not (!foundBad) then
+                                   (foundBad := true;
+                                    ErrorMsg.error ("This program needs server push, but the current protocol ("
+                                                    ^ #name proto ^ ") doesn't support that."))
+                               else
+                                   ();
+                               ServerAndPullAndPush)
                           else if IS.member (pull_ids, n) then
                               ServerAndPull
                           else
--- a/src/settings.sig	Tue Jun 23 15:40:35 2009 -0400
+++ b/src/settings.sig	Tue Jun 23 15:56:04 2009 -0400
@@ -85,4 +85,16 @@
     val getMimeRules : unit -> rule list
     val checkMime : string -> bool
 
+    (* Web protocols that generated programs may speak *)
+    type protocol = {
+        name : string,      (* Call it this on the command line *)
+        link : string,      (* Pass these linker arguments *)
+        supportsPush : bool (* Is Ur/Web message-passing supported? *)
+    }
+    val addProtocol : protocol -> unit
+    val getProtocol : string -> protocol option
+
+    val setProtocol : protocol -> unit
+    val currentProtocol : unit -> protocol
+
 end
--- a/src/settings.sml	Tue Jun 23 15:40:35 2009 -0400
+++ b/src/settings.sml	Tue Jun 23 15:56:04 2009 -0400
@@ -248,4 +248,27 @@
                     (CharVector.all (fn ch => Char.isAlphaNum ch orelse ch = #"/" orelse ch = #"-" orelse ch = #"."))
                     mime
 
+
+type protocol = {
+     name : string,
+     link : string,
+     supportsPush : bool
+}
+val protocols = ref ([] : protocol list)
+fun addProtocol p = protocols := p :: !protocols
+fun getProtocol s = List.find (fn p => #name p = s) (!protocols)
+
+fun clibFile s = OS.Path.joinDirFile {dir = Config.libC,
+                                      file = s}
+
+val http = {name = "http",
+            link = clibFile "request.o" ^ " " ^ clibFile "http.o",
+            supportsPush = true}
+
+val () = addProtocol http
+
+val curProto = ref http
+fun setProtocol p = curProto := p
+fun currentProtocol () = !curProto
+
 end