diff options
-rw-r--r-- | Makefile.in | 2 | ||||
-rw-r--r-- | src/c/fastcgi.c | 304 | ||||
-rw-r--r-- | src/c/fastcgi.h | 113 | ||||
-rw-r--r-- | src/c/http.c | 50 | ||||
-rw-r--r-- | src/c/queue.c | 58 | ||||
-rw-r--r-- | src/c/queue.h | 7 | ||||
-rw-r--r-- | src/fastcgi.sig | 30 | ||||
-rw-r--r-- | src/fastcgi.sml | 36 | ||||
-rw-r--r-- | src/settings.sml | 2 | ||||
-rw-r--r-- | src/sources | 3 |
10 files changed, 556 insertions, 49 deletions
diff --git a/Makefile.in b/Makefile.in index d8d7b9c5..1aca9590 100644 --- a/Makefile.in +++ b/Makefile.in @@ -14,7 +14,7 @@ all: smlnj mlton c 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: diff --git a/src/c/fastcgi.c b/src/c/fastcgi.c new file mode 100644 index 00000000..ad78b24a --- /dev/null +++ b/src/c/fastcgi.c @@ -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); + } +} diff --git a/src/c/fastcgi.h b/src/c/fastcgi.h new file mode 100644 index 00000000..826d808e --- /dev/null +++ b/src/c/fastcgi.h @@ -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; diff --git a/src/c/http.c b/src/c/http.c index a8f2efd4..e4e79848 100644 --- a/src/c/http.c +++ b/src/c/http.c @@ -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 @@ static void *worker(void *data) { 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 @@ int main(int argc, char *argv[]) { 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); } } diff --git a/src/c/queue.c b/src/c/queue.c new file mode 100644 index 00000000..1c82e0dc --- /dev/null +++ b/src/c/queue.c @@ -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); +} diff --git a/src/c/queue.h b/src/c/queue.h new file mode 100644 index 00000000..8297dd00 --- /dev/null +++ b/src/c/queue.h @@ -0,0 +1,7 @@ +#ifndef QUEUE_H +#define QUEUE_H + +int uw_dequeue(); +void uw_enqueue(int); + +#endif diff --git a/src/fastcgi.sig b/src/fastcgi.sig new file mode 100644 index 00000000..c37fe68e --- /dev/null +++ b/src/fastcgi.sig @@ -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 diff --git a/src/fastcgi.sml b/src/fastcgi.sml new file mode 100644 index 00000000..fbd24b5d --- /dev/null +++ b/src/fastcgi.sml @@ -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 diff --git a/src/settings.sml b/src/settings.sml index 36521799..e9f3e4b9 100644 --- a/src/settings.sml +++ b/src/settings.sml @@ -262,7 +262,7 @@ fun clibFile s = OS.Path.joinDirFile {dir = Config.libC, 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 diff --git a/src/sources b/src/sources index 81b6f881..b176301d 100644 --- a/src/sources +++ b/src/sources @@ -19,6 +19,9 @@ settings.sml cgi.sig cgi.sml +fastcgi.sig +fastcgi.sml + print.sig print.sml |