HW3c: HTTP Server, phase C

Part 10: Printing request statistics on SIGUSR1 (20 points)

Recall that, in HW3a part2, we implemented a special admin URL /statistics to fetch a web server request statistics page. In this part, we will implement an alternate mechanism to print statistics.

Tasks

  1. Modify the code from HW3a part4 so that when the web server receives a SIGUSR1 signal, it will print the statistics at that time to standard error.

  2. Test it by sending the signal with the kill command while the web server is blocked on accept call.

  3. Test it by sending the signal with the kill command while a child process is in the middle of serving an HTTP client. Describe what happens and explain why.

Requirements and hints

Deliverables

Part 11: Server-side bash scripts (0 points)

This part is optional and will not be graded. You may skip to part 12.

In this part, we will enable server-side bash scripts. When a requested URL is an executable script, the web server will run it using /bin/bash, and send out the output of the script.

The web server will ensure that the script will not run longer than a fixed amount of time. The server will also terminate the script if the HTTP client (i.e. the browser) closes the TCP connection while the script is still running.

Getting this right is actually pretty hard. You are not expected to handle every single corner cases. (In fact, our solution doesn’t handle all cases either.) But you can get close. We suggest you approach this in the following order:

  1. Implement support for server-side scripts

  2. Terminate the script when the HTTP connection is closed

  3. If the script does not respond to SIGTERM (because it’s catching it or ignoring it), send SIGKILL.

  4. Limit the time that the script can run even if the HTTP client is willing to wait.

Part 12: Pre-forked pool of processes (20 points)

Recall that in HW3b part6 we pre-created a pool of worker threads. Here, we will pre-fork a pool of worker child processes.

What the child processes do is also similar to what the threads did in part6. The child processes will all be in an infinite loop repeatedly calling accept(). In part13, we will change this model in a similar way we did in part7. In part7, we passed open socket descriptors to worker threads using a blocking queue. In part13, we will pass open socket descriptors to worker processes using a UNIX domain socket.

Tasks

  1. Pre-fork a fixed number of processes. Each child process will run a for (;;) loop, in which it will call accept() and handle the client connection.

  2. Perform benchmark testing to compare this implementation with HW3b part6 where we had threads instead of processes. Do you see any difference? For this comparison, which would be more revealing, serving big files or tiny files, if at all?

  3. Change the code so that only the parent process will handle SIGUSR1 for dumping statistics.

Deliverables

  1. The new version of http-server.c

  2. Performance testing result

Part 13: Passing socket descriptors to child processes (20 points)

In this part, instead of all the child processes calling accept(), only the parent process will call accept(), and it will pass each connected socket to a child process (chosen by round robin) through a UNIX domain socket.

Here are sendConnection() & recvConnection() functions that sends and receives open file descriptors through a UNIX domain socket. (You don’t need to understand this code. These are for you to copy & paste, and use it in your http-server.c.)

// Send clntSock through sock.
// sock is a UNIX domain socket.
static void sendConnection(int clntSock, int sock)
{
    struct msghdr msg;
    struct iovec iov[1];

    union {
      struct cmsghdr cm;
      char control[CMSG_SPACE(sizeof(int))];
    } ctrl_un;
    struct cmsghdr *cmptr;

    msg.msg_control = ctrl_un.control;
    msg.msg_controllen = sizeof(ctrl_un.control);

    cmptr = CMSG_FIRSTHDR(&msg);
    cmptr->cmsg_len = CMSG_LEN(sizeof(int));
    cmptr->cmsg_level = SOL_SOCKET;
    cmptr->cmsg_type = SCM_RIGHTS;
    *((int *) CMSG_DATA(cmptr)) = clntSock;

    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    iov[0].iov_base = "FD";
    iov[0].iov_len = 2;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    if (sendmsg(sock, &msg, 0) != 2)
        die("Failed to send connection to child");
}

// Returns an open file descriptor received through sock.
// sock is a UNIX domain socket.
static int recvConnection(int sock)
{
    struct msghdr msg;
    struct iovec iov[1];
    ssize_t n;
    char buf[64];

    union {
      struct cmsghdr cm;
      char control[CMSG_SPACE(sizeof(int))];
    } ctrl_un;
    struct cmsghdr *cmptr;

    msg.msg_control = ctrl_un.control;
    msg.msg_controllen = sizeof(ctrl_un.control);

    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    iov[0].iov_base = buf;
    iov[0].iov_len = sizeof(buf);
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    for (;;) {
        n = recvmsg(sock, &msg, 0);
        if (n == -1) {
            if (errno == EINTR)
                continue;
            die("Error in recvmsg");
        }
        // Messages with client connections are always sent with 
        // "FD" as the message. Silently skip unsupported messages.
        if (n != 2 || buf[0] != 'F' || buf[1] != 'D')
            continue;

        if ((cmptr = CMSG_FIRSTHDR(&msg)) != NULL
            && cmptr->cmsg_len == CMSG_LEN(sizeof(int))
            && cmptr->cmsg_level == SOL_SOCKET
            && cmptr->cmsg_type == SCM_RIGHTS)
            return *((int *) CMSG_DATA(cmptr));
    }
}

Tasks

  1. Pre-fork a fixed number of processes. Each child process will run a for (;;) loop, in which it will call recvConnection() and handle the client connection it receives.

  2. Perform benchmark testing to compare this implementation with HW3b part7 where we had a fixed number of worker threads receiving open sockets from a blocking queue.

Requirements and hints

Part 14: Daemonization (0 points)

This part is optional and will not be graded.

In this part, we will make our web server a daemon process. Daemons in UNIX systems are programs that run as background processes typically providing essential system services to users and other programs. See APUE chapter 13 for more information.

Tasks

Daemonizing your web server is super-easy. Here are all you have to do:

  1. At program start-up (i.e. in the beginning of the main() function maybe after checking arguments), call daemonize() from APUE 13.3.

  2. The daemonize() function will detach the running process from its controlling terminal, so printing to stdout or stderr won’t work anymore. You need to replace the printf() and fprintf() statements with syslog(), described in APUE 13.4.

If you are doing this part, I recommend that you read APUE chapter 13 to learn about daemon processes.

Good luck!


Acknowledgment

This series of assignments were co-designed by Jae Woo Lee and Jan Janak as a prototype for a mini-course on advanced UNIX systems and network programming.

Jan Janak wrote the solution code.

Jae Woo Lee is a lecturer, and Jan Janak is a researcher, both at Columbia University. Jan Janak is a founding developer of the SIP Router Project, the leading open-source VoIP platform.


Last updated: 2014–03–15