tiny-http

A tiny, dependency-light, low-level HTTP server library for Rust with blocking, thread-per-connection request handling.

Library
Cargo
v0.12.0
1,137stars
MIT OR Apache-2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
45/100Fair
Development Activity0
Maintenance20
Community72
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
75/100Good
Architecture82
Code Quality78
Innovation70
Learning Curve70

tiny_http is a minimal Rust crate that implements the HTTP protocol at a low level: accepting connections, parsing requests, handling pipelining, and encoding/decoding transfer bodies, while deliberately leaving routing, templating, and other application concerns to the caller or a framework built on top of it. It listens on a socket via Server::http() (or Server::https()/Server::http_unix()), dispatches each client connection to a worker thread from an internal, self-growing/self-shrinking thread pool, and exposes received requests through a simple blocking recv()/try_recv()/incoming_requests() API.

The crate is explicit about scope: it handles connection management, request parsing, pipelining, chunked/content-encoding, and optional HTTPS (via OpenSSL, Rustls, or native-tls, selectable through Cargo features), but not multipart parsing, routing, or templating. It underpins higher-level crates such as rouille, and is a common choice when a project wants a synchronous, dependency-light HTTP server without pulling in an async runtime.

What You Get

  • A Server::http()/Server::https()/Server::http_unix() constructor that binds a listener and immediately starts accepting connections on a background thread
  • A blocking recv(), non-blocking try_recv(), and iterator-style incoming_requests() for pulling parsed Request objects off an internal message queue
  • Automatic thread-per-connection dispatch via a self-managing task pool that grows on demand and lets idle threads die off after a few seconds
  • Request pipelining support with automatic response reordering, so multiple requests from the same client are served correctly without extra bookkeeping
  • Lazy body decoding — the request body (including chunked transfer-encoding, via the chunked_transfer crate) is only read and decoded if your code actually calls as_reader()
  • Optional HTTPS support behind mutually exclusive ssl-openssl, ssl-rustls, and ssl-native-tls Cargo features, so TLS dependencies are opt-in
  • A TestRequest helper (src/test.rs) for building fake requests in unit tests without standing up a real socket
  • A Request::upgrade() method for handing off the raw duplex stream on Connection: upgrade, the primitive used to implement WebSocket support

Common Use Cases

  • Embedding a lightweight HTTP endpoint inside a CLI tool or daemon (e.g. a local status/health endpoint) without bringing in an async stack
  • Serving simple static files or small JSON APIs from a script or utility where a full framework would be overkill
  • Acting as the transport layer underneath a higher-level micro-framework (as rouille does), so the framework owns routing and the server owns sockets
  • Prototyping or testing raw HTTP behavior (headers, pipelining, chunked encoding) where full control over the request/response cycle matters
  • Building CGI-style bridges (the crate ships a php-cgi.rs example) or WebSocket upgrade handlers that need direct access to the underlying stream

Under The Hood

Architecture tiny_http uses a layered, thread-based architecture: Server::new() spawns a background accept thread that loops on the bound listener and dispatches each accepted connection to a self-managing TaskPool (src/util/task_pool.rs), which grows when all worker threads are busy and lets idle threads expire after a few seconds; each worker parses the client’s request stream through Connection/RefinedTcpStream (src/connection.rs, src/util/refined_tcp_stream.rs) and pushes completed Request objects into a MessagesQueue<Message> (src/util/messages_queue.rs) that the public Server::recv()/try_recv()/incoming_requests() API drains. Body handling in src/request.rs is lazy and encoding-aware — small bodies are buffered eagerly, large ones go through EqualReader/FusedReader, and chunked bodies go through the chunked_transfer::Decoder — so parsing never consumes more than the declared content length. HTTPS is isolated behind a feature-gated SslContextImpl trait implemented separately for OpenSSL, Rustls, and native-tls (src/ssl/*.rs), keeping transport security decoupled from request/response logic. The most fragile interaction is request pipelining and response reordering, coordinated via NotifyOnDrop senders in request.rs, which would be the first thing to break if the core Request/Response abstraction were reshaped.

Tech Stack The crate is pure, synchronous Rust (edition 2018, MSRV 1.57) with no async runtime dependency — concurrency is handled entirely with std::net::TcpListener, std::thread, and std::sync::mpsc. Required dependencies are minimal: ascii, chunked_transfer, and httpdate, with log enabled by default but optional; openssl, rustls, rustls-pemfile, native-tls, and zeroize are all optional and gated behind mutually exclusive ssl-openssl/ssl-rustls/ssl-native-tls Cargo features so TLS toolchains are opt-in. Dev-dependencies (rustc-serialize, sha1, fdlimit) support the test suite only. CI (.github/workflows/ci.yaml) runs clippy and rustfmt checks plus build/test across stable, nightly, and the pinned 1.57 MSRV, each against all four feature combinations, and docs.rs is configured to build with the ssl-openssl feature.

Code Quality Testing is comprehensive for a library this size: dedicated integration test files under tests/ (input-tests.rs, network.rs, non-chunked-buffering.rs, promptness.rs, simple-test.rs, unblock-test.rs, unix-test.rs) exercise real socket behavior, complemented by a #[cfg(test)] unit test in request.rs and a purpose-built TestRequest helper (src/test.rs) that downstream consumers can also use in their own test suites. Error handling is explicit and typed — io::Result and a dedicated RequestCreationError enum with a From<IoError> conversion, rather than panics — though a handful of internal .unwrap() calls remain in Drop-related notification paths. The crate enforces #![forbid(unsafe_code)] and #![deny(rust_2018_idioms)] at the crate root, follows conventional Rust snake_case naming, and CI runs clippy and rustfmt on every push across all three supported Rust versions and all four feature sets, so lint and format regressions are caught automatically.

API Design The public surface is intentionally small and blocking, mirroring synchronous Rust idioms rather than async ones: Server::http()/https()/http_unix() to bind, then recv()/try_recv()/recv_timeout()/incoming_requests() to consume — the README’s own hello-world example gets a working server running in under fifteen lines with zero async setup. Response exposes typed constructors (from_string, from_file, empty) with a fluent header/status builder, and Request::upgrade() hands off the raw duplex stream for protocol upgrades like WebSockets. Unusually, the crate’s own documentation is explicit about what it deliberately does not do — routing, multipart parsing, templating, etags — pushing those concerns to consumers or higher-level frameworks like rouille, which keeps its learning curve low and its API surface honest about scope rather than growing into a kitchen-sink server.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search