# HG changeset patch # User Adam Chlipala # Date 1246128240 14400 # Node ID a738002d5b4d9dfd821724b9990083b3cb1e04fa # Parent 60240acd15b99e26db7fc078e271c59ef2a107e1 Serving Hello via FastCGI diff -r 60240acd15b9 -r a738002d5b4d include/urweb.h --- a/include/urweb.h Sat Jun 27 12:38:23 2009 -0400 +++ b/include/urweb.h Sat Jun 27 14:44:00 2009 -0400 @@ -43,6 +43,7 @@ int uw_send(uw_context, int sock); int uw_print(uw_context, int fd); +int uw_output(uw_context ctx, int (*output)(void *data, char *buf, size_t len), void *data); int uw_set_input(uw_context, const char *name, char *value); int uw_set_file_input(uw_context, char *name, uw_Basis_file); diff -r 60240acd15b9 -r a738002d5b4d src/c/fastcgi.c --- 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)); diff -r 60240acd15b9 -r a738002d5b4d src/c/urweb.c --- a/src/c/urweb.c Sat Jun 27 12:38:23 2009 -0400 +++ b/src/c/urweb.c Sat Jun 27 14:44:00 2009 -0400 @@ -1102,6 +1102,20 @@ return uw_really_write(fd, ctx->page.start, ctx->page.front - ctx->page.start); } +int uw_output(uw_context ctx, int (*output)(void *data, char *buf, size_t len), void *data) { + int n = output(data, ctx->outHeaders.start, ctx->outHeaders.front - ctx->outHeaders.start); + + if (n < 0) + return n; + + n = output(data, "\r\n", 2); + + if (n < 0) + return n; + + return output(data, ctx->page.start, ctx->page.front - ctx->page.start); +} + static void uw_check_headers(uw_context ctx, size_t extra) { buf_check(&ctx->outHeaders, extra); }