Lab 6 Solutions

This handout was adapted from Jerry Cain’s Spring 2018 offering.

Problem 1: ThreadPool Thought Questions

Presented below are partial implementations of my own ThreadPool::wait and my ThreadPool::worker method.

void ThreadPool::wait() {
  lock_guard<mutex> lg(lock);
  done.wait(lock, [this]{ return count == 0; });
}

void ThreadPool::worker(size_t workerID) {
  while (true) {
    // code here waits for a thunk to be supplied and then executes it
    lock_guard<mutex> lg(lock);
    if (--count == 0) done.notify_all();
  }
}

Problem 2: Networking, Client/Server, Request/Response

Problem 3: Web Servers and Python Scripts

It’s high time you implement your own multithreaded HTTP web server, making use of a ThreadPool to manage the concurrency, and all of the multiprocessing functions (fork and all related functions) to launch another executable – in all cases, a Python script – to ingest the majority of the HTTP request from the client and publish the entire HTTP response back to it.

Here are the pertinent details:

Your implementation can make use of the following routine, which reads the first line of an HTTP request coming over the provided client socket (up to and including the \r\n), and surfaces the method (e.g. GET), the full URL (/add.py?one=11&two=50), the path (/add.py), and the protocol (HTTP/1.1) through the four strings shared by reference.

static void parseRequest(int client, string& method, string& url, string& path, string& protocol);

You’re to complete the implementation of the web server by fleshing out the details of the handleRequest function.

int main() {
   int server = createServerSocket(/* port = */ 80);
   runServer(server);
   return 0; // never gets here, but compiler doesn't know that
}
 
static const kNumThreads = 16;
static void runServer(int server) {
  ThreadPool pool(kNumThreads);
  while (true) {
    int client = accept(server, NULL, NULL);
    pool.schedule([client] { handleRequest(client); });
  }
}

static void handleRequest(int client) {
   // everything below represents a reasonable solution that meets all requirements
   string method, url, path, protocol;
   parseRequest(client, method, url, path, protocol);
   if (!endsWith(path, ".py") || path.find("..") != string::npos) { 
       close(client);
       return;
   } 
   pid_t pid = fork();
   if (pid == 0) {
      dup2(client, STDIN_FILENO);
      dup2(client, STDOUT_FILENO);
      close(client);
      string script = "./scripts" + path;
      const char *argv[] = {
         "python", script.c_str(), method.c_str(), url.c_str(), protocol.c_str(), NULL
      };
      execvp(argv[0], const_cast<char **>(argv));
      exit(0);
    }
    close(client);
    waitpid(pid, NULL, 0);
}