// webserve.m -- a very simple web server using fork() to handle requests

/* compile with:
cc -g -Wall -o webserve webserve.m
*/

#import <sys/types.h>           // for pid_t, amongst others
#import <sys/wait.h>            // for wait3
#import <unistd.h>              // for fork
#import <stdlib.h>              // for EXIT_SUCCESS, pipe, exec
#import <stdio.h>               // for printf
#import <errno.h>               // for errno
#import <string.h>              // for strerror
#import <sys/time.h>            // for struct timeval
#import <sys/resource.h>        // for struct rusage
#import <netinet/in.h>          // for sockaddr_in
#import <sys/socket.h>          // for socket(), AF_INET
#import <arpa/inet.h>           // for inet_ntoa
#import <unistd.h>              // for close
#import <arpa/inet.h>           // for inet_ntoa and friends
#import <assert.h>              // for assert

#define PORT_NUMBER 8080        // set to 80 to listen on the HTTP port

static int g_childSignaled;

// ----- child handling

// signal handler for SIGCHLD.  Just set a global value saying we have
// seen the signal.  We want to do more interesting stuff on child
// exits than are proper to do in a signal handler
// (runs in the parent)

void childExited (int signalNumber)
{
    g_childSignaled = 1;

} // childExited

// wait for children and print out some resource usage
// (runs in the parent)

void reapChildren ()
{
    pid_t childPid;
    int status;
    struct rusage resources;

    while (1) {

        childPid = wait3 (&status, WNOHANG, &resources);

        if (childPid < 0) {
            // even though the man page says that we shouldn't get this 
            // with WNOHANG as an option to wait3, it sometimes happens
            if (errno != ECHILD) {
                fprintf (stderr, "wait3 returned an error: %d/%s\n",
                         errno, strerror(errno));
            }
            break;

        } else if (childPid == 0) {
            // we have run out of children
            break;

        } else {
            // otherwise print some stuff to our log

            fprintf (stderr, "child %ld terminated %s\n",
                     (long)childPid, 
                     WIFEXITED(status) ? "normally" : "abnormally");
            fprintf (stderr, "    user time: %d seconds %d msec\n",
                   resources.ru_utime.tv_sec, resources.ru_utime.tv_usec);
            fprintf (stderr, "    system time: %d seconds %d msec\n",
                   resources.ru_stime.tv_sec, resources.ru_stime.tv_usec);
            fprintf (stderr, "    max RSS: %ld\n", resources.ru_maxrss);
        }
    }

    return;

} // reapChildren


// HTTP request handling

// these are some of the common HTTP response codes

#define HTTP_OK         200
#define HTTP_NOT_FOUND  404
#define HTTP_ERROR      500

// return a string to the browser

#define returnString(httpResult, string, channel) \
        returnBuffer((httpResult), (string), (strlen(string)), (channel))

// return a character buffer (not necessarily zero-terminated) 
// to the browser (runs in the child)

void returnBuffer (int httpResult, const char *content, 
                   int contentLength, FILE *commChannel)
{
    fprintf (commChannel, "HTTP/1.0 %d blah\n", httpResult);
    fprintf (commChannel, "Content-Type: text/html\n");
    fprintf (commChannel, "Content-Length: %d\n", contentLength);
    fprintf (commChannel, "\n");
    fwrite (content, contentLength, 1, commChannel );

} // returnBuffer

// stream back to the browser numbers being counted, with a pause between
// them.  The user should see the numbers appear every couple of seconds
// (runs in the child)

void returnNumbers (int number, FILE *commChannel)
{
    int min, max, i;
    min = MIN (number, 1);
    max = MAX (number, 1);
    
    fprintf (commChannel, "HTTP/1.0 %d blah\n", HTTP_OK);
    fprintf (commChannel, "Content-Type: text/html\n");

    // no content length as this is dynamic
    fprintf (commChannel, "\n"); 
    fprintf (commChannel, "<h2>The numbers from %d to %d</h2>\n", 
             min, max);

    for (i = min; i <= max; i++) {
        sleep (2);
        fprintf (commChannel, "%d\n", i);
        fflush (commChannel);
    }

    fprintf (commChannel, "<hr>Done\n");

} // returnNumbers

// return a file from the file system, relative to where the webserve
// is running.  Note that this does not look for any nasty characters
// like '..', so this function is a pretty big security hole
// (runs in the child)

void returnFile (const char *filename, FILE *commChannel)
{
    FILE *file;
    const char *mimetype = NULL;

    // try to guess the mime type.  
    // IE assumes all non-graphic files are HTML
    if (strstr(filename, ".m") != NULL) {
        mimetype = "text/plain";
    } else if (strstr(filename, ".h") != NULL) {
        mimetype = "text/plain";
    } else if (strstr(filename, ".txt") != NULL) {
        mimetype = "text/plain";
    } else if (strstr(filename, ".tgz") != NULL) {
        mimetype = "application/x-compressed";
    } else if (strstr(filename, ".html") != NULL) {
        mimetype = "text/html";
    } else if (strstr(filename, ".htm") != NULL) {
        mimetype = "text/html";
    }

    file = fopen (filename, "r");

    if (file == NULL) {
        returnString (HTTP_NOT_FOUND, 
                      "could not find your file.  Sorry.\n", 
                      commChannel);
    } else {
#define BUFFER_SIZE (8 * 1024)
        char *buffer[BUFFER_SIZE];
        int result;
        fprintf (commChannel, "HTTP/1.0 %d blah\n", HTTP_OK);
        if (mimetype != NULL) {
            fprintf (commChannel, "Content-Type: %s\n", mimetype);
        }
        fprintf (commChannel, "\n");
        while ((result = fread (buffer, 1, BUFFER_SIZE, file)) > 0) {
            fwrite (buffer, 1, result, commChannel);
        }
#undef BUFFER_SIZE
    }

} // returnFile

// using the method and the request (the path part of 
// the url), generate the data for the user and send 
// it back. (runs in the child)

void handleRequest (const char *method, const char 
                    *originalRequest, FILE *commChannel)
{
    char *request = strdup (originalRequest);// strsep used to split this
    char *chunk, *nextString;

    if (strcmp(method, "GET") != 0) {
        returnString (HTTP_ERROR, "only GETs are supported", 
                      commChannel);
        goto bailout;
    }
    
    nextString = request;
    chunk = strsep (&nextString, "/");  
                    // urls start with slashes, so chunk is ""
    chunk = strsep (&nextString, "/");  
                    // the leading part of the url

    if (strcmp(chunk, "numbers") == 0) {
        int number;

        // url of the form /numbers/5 to print numbers from 1 to 5
        chunk = strsep (&nextString, "/");
        number = atoi(chunk);
        returnNumbers (number, commChannel);

    } else if (strcmp(chunk, "file") == 0) {
        chunk = strsep (&nextString, ""); // get the rest of the string
        returnFile (chunk, commChannel);
    } else {
        returnString (HTTP_NOT_FOUND, "could not handle request.\n",
                      commChannel);
    }

bailout:
    fprintf (stderr, "child %ld handled request '%s'\n", 
             (long)getpid(), originalRequest);

    free (request);

} // handleRequest

// read the request from the browser, pull apart the elements of the
// request, and then dispatch it.  (runs in the child)

void dispatchRequest (int fd, struct sockaddr_in *address)
{
#define LINEBUFFER_SIZE 8192
    char linebuffer[LINEBUFFER_SIZE];
    FILE *commChannel;

    commChannel = fdopen (fd, "r+");
    if (commChannel == NULL) {
        fprintf (stderr, "could not open commChannel.  Error is %d/%s\n",
                 errno, strerror(errno));
    }

    // this is pretty lame in that it only reads the first line and 
    // assumes that is the request, subsequently ignoring any headers
    // that might be sent.

    if (fgets(linebuffer, LINEBUFFER_SIZE, commChannel) != NULL) {
        // ok, now figure out what they wanted
        char *requestElements[3], *nextString, *chunk;
        int i = 0;
        nextString = linebuffer;
        while ((chunk = strsep (&nextString, " "))) {
            requestElements[i] = chunk;
            i++;
        }
        if (i != 3) {
            returnString (HTTP_ERROR, "malformed request", commChannel);
            goto bailout;
        }
        
        handleRequest (requestElements[0], requestElements[1],
                       commChannel);
    } else {
        fprintf (stderr, "read an empty request.  exiting\n");
    }

bailout:
    fclose (commChannel);
    fflush (stderr);

    _exit (EXIT_SUCCESS);

} // dispatchRequest

// sit blocking on accept until either it breaks out with a signal
// (like SIGCHLD) or a new connection comes in.  If it is a new 
// connection, fork off a child to process the request

void acceptRequest (int listenSocket)
{
    struct sockaddr_in address;
    int addressLength = sizeof(address), result, fd;
    pid_t childPid;

    result = accept (listenSocket, (struct sockaddr *)&address, 
                     &addressLength);

    if (result == -1) {
        if (errno == EINTR) {
            // system call interrupted by a signal.  maybe by SIGCHLD?
            if (g_childSignaled) {
                // yes, we had gotten a SIGCHLD.  clean up after the kids
                g_childSignaled = 0;
                reapChildren();
                // note that g_childSignaled is cleared before
                // reapChildren is called, in case another SIGCHLD
                // happened during reapChildren, we will not lose it
                goto bailout;
            }
        } else {
            fprintf (stderr, "accept failed.  error: %d/%s\n",
                     errno, strerror(errno));
        }
        goto bailout;
    }
    fd = result;

    // fork off a child to do the work

    // child sends output to stderr, so make sure it is drained before
    // moving on
    fflush (stderr); 
    
    if ((childPid = fork())) {
        // parent
        if (childPid == -1) {
            fprintf (stderr, "fork failed.  Error: %d/%s\n",
                     errno, strerror(errno));
            goto bailout;
        }
        // lose the new connection since the parent doesn't need it
        // Note: if we do not do this, the connection to the browser 
        // will stay open after the child has handled and closed it
        close (fd); 

    } else {
        // child
        dispatchRequest (fd, &address);
    }

bailout:
    return;

} // acceptRequest


// ----- network stuff

// this is 100% stolen from chatterserver.m
// start listening on our server port (runs in parent)

int startListening ()
{
    int fd = -1, success = 0;
    int result;

    result = socket (AF_INET, SOCK_STREAM, 0);
    
    if (result == -1) {
        fprintf (stderr, "could not make a socket.  error: %d / %s\n",
                 errno, strerror(errno));
        goto bailout;
    }
    fd = result;

    {
        int yes = 1;
        result = setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, 
                             &yes, sizeof(int));
        if (result == -1) {
           fprintf (stderr, 
                "unable to setsockopt to reuseaddr. %d / %s\n", 
                errno, strerror(errno));
           goto bailout;
        }
    }

    // bind to an address and port
    {
        struct sockaddr_in address;
        address.sin_len = sizeof (struct sockaddr_in);
        address.sin_family = AF_INET;
        address.sin_port = htons (PORT_NUMBER);
        address.sin_addr.s_addr = htonl (INADDR_ANY);
        memset (address.sin_zero, 0, sizeof(address.sin_zero));

        result = bind (fd, (struct sockaddr *)&address, sizeof(address));
        if (result == -1) {
            fprintf (stderr, "could not bind socket.  error: %d / %s\n",
                     errno, strerror(errno));
            goto bailout;
        }
    }
    
    result = listen (fd, 8);

    if (result == -1) {
        fprintf (stderr, "listen failed.  error: %d /  %s\n",
                 errno, strerror(errno));
        goto bailout;
    }

    success = 1;

bailout:
    if (!success) {
        close (fd);
        fd = -1;
    }
    return (fd);

} // startListening

int main (int argc, char *argv[])
{
    int listenSocket;

    // install a signal handler to reap any children that have exited
    (void) signal (SIGCHLD, childExited);
    siginterrupt (SIGCHLD, 1);

    listenSocket = startListening ();

    while (1) {
        acceptRequest (listenSocket);
    }

    return (EXIT_SUCCESS);

} // main
