Travis Paul

Thoughts no one asked for and more.

Async-less HTTP Servers for Rust

Spool of thread
Photo by Sheen Photography on Unsplash

I recently went crate shopping for an HTTP server written in Rust for a new project at $WORK. I had already built out a POC using warp and it worked, but it just didn't feel entirely right. That didn't initially discourage me though. There's still quite a bit that doesn't feel right to me in Rust. (For example: omitting a semi-colon and the return keyword to return an expression may never feel right to me.) I had also just re-evaluated my use of threads for communicating with anonymous pipes and wondered if I should use async tasks instead. I had decided it wasn't worthwhile to do so and stuck with threads and started evaluating my use of async in the part of the project that needed HTTP support.

warp was a fine library, but the async ecosystem is young and involves a bit more dependencies that I really wanted to take on. The Rust Async book points out a few noteworthy points in The State of Asynchronous Rust:

Parts of async Rust are supported with the same stability guarantees as synchronous Rust. Other parts are still maturing and will change over time. With async Rust, you can expect:
  • Outstanding runtime performance for typical concurrent workloads.
  • More frequent interaction with advanced language features, such as lifetimes and pinning.
  • Some compatibility constraints, both between sync and async code, and between different async runtimes.
  • Higher maintenance burden, due to the ongoing evolution of async runtimes and language support.
  • In short, async Rust is more difficult to use and can result in a higher maintenance burden than synchronous Rust, but gives you best-in-class performance in return. All areas of async Rust are constantly improving, so the impact of these issues will wear off over time.

For my use case (not a public-facing web server) I'm not too concerned about squeezing every ounce of performance out of the hardware. I've already used up an innovation token on choosing Rust and I'm not sure I can spend another on async (yet). Threads may be boring (or not) but they're in the standard library and the performance is ample for my needs.

So what options are out there for non-async HTTP servers that also support TLS? Not a lot as far as I can tell. Most crates utilize async in some capacity (often built upon hyper) or don't support TLS. After surfing, the three that floated to the top of my list were:

  • tiny-http, a tiny but strong HTTP server in Rust.
  • Rouille, a Rust web micro-framework.
  • Humphrey, a performance-focused, dependency-free web server.

Rouille checked most of the boxes, though it has some features I don't need like form handling, CGI, proxying, and web socket support, but it's still micro compared to projects such as Rocket and Actix. Long term I could see switching to tiny-http once more development has been done on my project. Rouille is built on tiny-http, but you can use tiny-http by itself too.

At the moment I've resorted to using a forked version of Rouille, that depends on a forked version of tiny-http. So that I can get Rustls support and avoid OpenSSL shenanigans on Windows ARM64 (which is a target for this project.) Though, that just led me to new shenanigans in a Rustls dependency called ring. However, Windows on Arm is really more of a nice to have at the moment and I think I can use an unreleased version of ring when the time comes to shave that yak. Falling back to OpenSSL isn't out of the question either.

I also have to give kudos to the author of Humphrey, it is an impressive project and I might circle back around to it when I have more time for experimentation. I didn't get to spend too much time with it, but Rouille/tiny-http have a few more eyeballs and passed my initial tests so I will move forward with Rouille.