HTTPServer

HTTP 1.1 in C++; Class Project developed over the time of 10 weeks

I have just completed my UCLA Major Capstone class, CS130, and I feel so happy I got so much out of it. Over the span of 10 weeks, my team and I completed a fully functioning HTTP 1.1 server. The task was split across 8 separate assignments, and the frequency of the assignments really gave me an understanding of how it will feel working in industry. I'll share my learnings here as a memory of "drakes-ghost-hackers".

Assignment 1 (technically Assignment 2 on our syllabus):

  • This assignment was essentially the commit of our provided skeleton code to setup our server. I created the Virtual Machine instance on the Google Cloud Platform and connected the instance to our Gerrit (our class's specific version of Git) repository.
  • Our class has the concept of a Team Lead and Individual Contributors within a team. Team Leads are not allowed to edit the code, but are allowed to write documentation, edit the GCP console, and must conduct code reviews. Individual Contributors are allowed to make changes, but must ask the Team Lead to verify their work. We rotated who the Team Lead was on a weekly basis, and this week I was the Tech Lead of our project.
  • I would have to say it definitely took me some time to learn how to read complex code changes, and right off the bat I learned that making small commits not only lessens the risk of bad code, but it makes it easier for the reviewer to understand what is being changed.

Assignment 2:

  • This week I became an individual contributor. Our main focus was to create more unit and integration tests, and this is where I really learned what line coverage meant.
  • From my understanding, we have a CMakeList file that acts as the instructions for how to compile our code. This is not limited to just C/C++ files but you can include external libraries. So for example, we could add your Python Integration tests as exectuables, or the BOOST libraries we need for logging (as one of our future assignments requires).
  • From there, when we run make which compiles our code, GCC can run all our tests as "black boxes", and if a line in the source code is executed, it tallies it, and divides it by all the total blocks.
  • We ensured a high line coverage at all times, above 90% for all assignments.

Assignment 3:

  • This week we focused on two goals. One, adding proper log files to our code base in conjunction with the BOOST library we have been using. Two, allowing our server to serve static files.
  • As an individual contributor, I was in charge of mainly our server's static files capabilities. This included a lot of refactoring, as we needed to create specific handler factories for different requests. I made sure that, through some logic, that if we were given an echo request, it would call the echo request handler and deal with the request accordingly. If it were given a static request, it would call the static request handler.
  • Some example code:
RequestHandlerFactory::RequestHandlerFactory() 
    : static_request_handler_(std::make_unique<StaticRequestHandler>("static/")), 
      echo_request_handler_(std::make_unique<EchoRequestHandler>()) {
}

std::string RequestHandlerFactory::HandleRequestHelper(const HttpRequest& request) {
    if (request.uri.compare(0, 7, "/static") == 0) {
        // to be updated to static_request_handler
        return echo_request_handler_->HandleRequest(request);
    } else {
        return echo_request_handler_->HandleRequest(request);
    }
}

Assignment 4:

  • As an individual contributor, I helped with team with making many refactoring changes. We spent a lot of time trying to make the line coverage as high as possible, as well as make changes in adding comments and readability.

Assignment 5:

  • The class had voted on a CommonAPI, which meant that we again had to make many additions to the codebase to follow our CommonAPI spec as well as maintain the same functionality.
  • Instead of having just one handler factory deal with every request, we had a dispatcher that created an instance of every request handler, like a 404 request handler or echo request handler, and those handlers would deal with sending HTTP Request and returning HTTP Response objects.

Assignment 6:

  • We focused on created CRUD handler implementation, which featured GET, POST, PUT, and DELETE.

Assignment 7:

  • We focused on deploying our CRUD handler API onto our VM instance. We also created uptime checks witha /health handler, that made sure our server was healthy every minute.

Assignment 8:

  • This was our biggest assignment of the quarter, which was to add any feature that we preferred to the server. Our group had chosen to add both server Keep-Alive as well as a caching layer.
  • I worked on the Keep-Alive implementation, which mocked what HTTP 1.1 did. I ensured that sessions stayed open during multiple requests, which specifically meant if the request did not ask to close the session, it would continue to read and relay responses.
  • Here's a code snippet of out implementation
std::string connection_header = "";
auto& headers = req->GetHeaders();
auto it = headers.find("Connection");
if (it != headers.end()) {
    connection_header = it->second;
}

if (connection_header == "close") {
    should_close_ = true;
    BOOST_LOG_SEV(lg, trivial::info) << "Client requested Connection: close";
} else {
    should_close_ = false;
}
// Use singleton dispatcher to handle request
std::string response_str = Dispatcher::getInstance().Dispatch(*req)->ToString();

if (!response_str.empty())
{
    boost::asio::async_write(socket_,
    boost::asio::buffer(response_str),
    boost::bind(&session::handle_write, this,
        boost::asio::placeholders::error));
}