spars-httpd

Crates.iospars-httpd
lib.rsspars-httpd
version0.0.1
created_at2025-08-22 21:32:44.744111+00
updated_at2025-08-22 21:32:44.744111+00
descriptionA small httpd for serving static files and SPAs
homepage
repositoryhttps://github.com/ckwalsh/spars
max_upload_size
id1806967
size97,302
(ckwalsh)

documentation

README

Spars - A low-feature, lightweight, HTTP 1.1 server for serving static files and SPAs

Spars is a lightweight (both binary size and memory usage) HTTP server optimized for safely and securely serving static files and Single Page Applications, particularly within containers.

Spars was written because I was annoyed at seeing so many nginx worker processes in the ps output of my homelab, serving random static websites. decided to use the opportunity to better understand http servers and the Rust language.

Features

  • Serve static files from filesystem
  • Index and 404 file support
  • Mime-Type detection based on file extension (common extensions only by default, expanded support available via the mime_guess cargo feature)
  • Configuration via ENV vars
  • Security: Ignore hidden files by default (except /.well-known/ directory)
  • Security: Files served via allowlist to eliminate path traversal attacks
  • Security: Rust Memory Safely Guarantees

Non-Features

Spars is intended for use behind a full-featured http proxy (with logging, etc) for low-qps static sites. It deliberately does not implement:

  • Logging
  • HTTP methods beyond GET/HEAD
  • Live file updates
  • Authentication

It is not expected that these features will be added to Spars unless they can be done without increasing binary size and/or memory usage. If you are looking to serve a high-QPS site, or needing any of these features, you should use a well-known httpd (nginx, apache, etc) instead.

Benchmarks

  • Data collected unscientifically by running wrk -t12 -c12 -d10s http://127.0.0.1:3000/index.html on my low powered homelab (AMD Ryzen 7 4800U) and collecting the output from /proc/<pid>/status.
  • "Hyper" data collected using the hyper.rs example. The hyper example uses the same handler as spars, but is driven by hyper::http1::Builder::serve_connection().
  • Nginx data collected by inspecting random nginx processes running on my homelab and picking the lowest resource usage.
Httpd Implementation QPS Disk Size Processes VmRSS VmHWM VmSize VmPeak
Spars - Plaid + 8kB stack 267.03 136K 1 1396 kB 1396 kB 593040 kB 593136 kB
Spars - Plaid + musl + 8kB stack 265.84 172K 1 328 kB 368 kB 500 kB 788 kB
Spars - Docker spars:latest ^ 98K 1 ^ ^ ^ ^
Spars - Release 266.24 644K 1 1372 kB 1372 kb 679432 kB 745064 kB
Spars - Release + musl 265.24 740K 1 796 kB 796 kB 3112 kB 17644 kB
Hyper - Plaid 267.94 260K 1 2472 kB 2472 kB 1693960 kB 1759484 kB
Hyper - Plaid + musl 267.43 304K 1 296 kB 620 kB 52412 kB 52764 kB
Hyper - Docker spars:examples-hyper ^ 148K 1 ^ ^ ^ ^
Hyper - Release 268.51 988k 1 2992 kB 2992 kb 1694796 kB 1694796 kB
Hyper - Release + musl 267.52 1.1M 1 984 kB 1368 kB 51124 kB 51480 kB
Nginx Default Docker Config - 21M 32 49864 kB 51764 kB 168568 kB 168568 kB

Plaid Builds

Barf: What the hell was that?

Lone Starr: Spaceball 1.

Barf: They've gone to plaid!

~ Spaceballs, 1987

The plaid build profile uses many of the techniques from johnthagen's min-sized-rust repo to optimize the release build, reducing the size of the final binary. This results binaries that are 4x smaller and minimally faster, but makes debugability impossible.

Using spars

Configuration

Spars is configured via ENV vars.

  • ROOT
    • Root dir where files will be served from
    • Default: ./public (/public within Docker)
  • INDEX_FILE
    • Filename read when a path ending in a slash is requested
    • Default: index.html
  • FALLBACK_PATH
    • HTTP path loaded when the requested path does not exist
    • Fallback responses are sent with the 200 OK status code
    • When set to an empty string or a the path does not exist, empty 404 responses will be sent instead
    • Default: /404.html
  • ALLOW_HIDDEN
    • Whether spars should serve files/directories starting with .
      • false | 0: Files where any path component starts with . will not be served
      • true | 1: Files where any path component starts with . will be served (DANGER!)
      • wellknown | Paths starting with /.well-known/ will be served, unless a following component starts with .
    • Default: wellknown
  • ADDR
    • Interface the http server will bind to
    • Supports both ipv4 and ipv6 values
    • Default: 127.0.0.1 (0.0.0.0 within Docker)
  • PORT
    • Port the http server will bind to
    • Spars can be bound to a random available port by setting a value of 0, and accessed by reading the file specified by the ADDR_FILE var.
    • Default: 3000
  • PID_FILE
    • If set, spars will write its process id to this file on startup
    • Default: unset
  • ADDR_FILE
    • If set, spars will write <IP>:<PORT> to this file upon binding the tcp listener
    • Default: unset

Docker

This repo includes a Dockerfile for building spars. This Dockerfile:

  • Builds spars
    • With the signal-handler feature, allowing it to be used as a container init processes
    • Using the plaid profile, reducing binary size
    • Against the x86_64-unknown-linux-musl target, producing a static binary
  • Compresses it with upx, further reducing the binary size
  • Configures spars with the following overridable ENV defaults:
    • ADDR: 0.0.0.0
    • PORT: 3000
    • ROOT: /public
    • INDEX_FILE: index.html
    • FALLBACK_PATH: /404.html
    • ALLOW_HIDDEN: wellknown

Planned work: publish spars to DockerHub and link it here.

Commit count: 27

cargo fmt