Rust HTTP Server

Keywords: Back End - TCP - SSL

Motivation

As a systems programmer, I want to understand the technologies I use at a very low level. Modern web stacks abstract away the process of handling TCP connections, serving SSL certificates, and parsing HTTP requests. The best way to understand a technology is to implement it from the ground up, I studied the HTTP 1.1 specification and wrote a server to host my website.

Architecture

At the most basic level, the server will translate the request URL into a local file path, and respond with the file matching that path if it exists. Request URLs are sanitized to prevent accessing files outside of the server's directory. Two tables are used for rewriting and routing URLs. This makes handling URLs predictable and robust. Because I route all traffic through Cloudflare, I decided to use its caching feature rather than implement a cache locally.

// Snippet from the request handling code
pub async fn construct_response(&self, request_url: &str) -> Response {
  let sanitized_url = rewrite_url(request_url);
  if sanitized_url != request_url {
    return Self::redirect(&sanitized_url);
  }

  let routed_url = self.perform_routing(&sanitized_url).await;
  if let Some(response) = 
    self.perform_redirecting(&routed_url).await {
    return response;
  }
  match self.get_resource(&routed_url).await {
    Some(bytes) => Response::from_data(bytes),
    None => Self::not_found(),
  }
}

Dependencies

My goal with this project was to provide a functional server using as few libraries as possible. The final project directly depends on four crates:

// Snippet from Cargo.toml
[dependencies]
log = "0.4"
log4rs = "1.3"
tiny_http = "0.12"
tokio = {version = "1", features = ["full"]}
The first two crates, log and log4rs, provide logging functions which aren't critical to the server's functionality. The tiny_http crate provides a simple wrapper for the standard library TCP stream, and performs SSL encryption. It is used as the backbone for many Rust web frameworks. While I could implement these features myself, I decided against it for security and browser compatibility reasons. Finally there is tokio, an async runtime. This is necessary because Rust does not provide a runtime by default, and building one is out of scope.