curlite 0.1.0

Small blocking HTTP client on top of libcurl: forms, multipart and raw bodies without an async runtime
Documentation

curlite

CI crates.io docs.rs

A small blocking HTTP client on top of libcurl. For programs that call HTTP services and want to stay small: no async runtime, one libcurl handle per thread with connection reuse, request bodies held in memory once, and multipart/form-data in both directions.

use curlite::Multipart;

let response = curlite::post("https://example.com/upload?debug=1")
    .header("Authorization", "Bearer secret")
    .multipart(
        Multipart::new()
            .text("description", "quarterly report")
            .file("file", "q3.pdf", mime::APPLICATION_PDF, std::fs::read("q3.pdf")?),
    )
    .send()?;

println!("{}", response.status());
for part in response.parts()? {
    println!("{}: {} bytes", part.name, part.body.len());
}

Bodies are body(mime, bytes), form(pairs) or multipart(..); everything else (query, header, timeout, follow_redirects) is on the builder. Responses are read into memory and expose status(), headers(), body(), text(), form() and parts().

The params module adds a parameter model for services that exchange named values and files, where the body format follows from the parameters: one body parameter is sent raw, several text parameters as a form, anything with a file as multipart. Responses parse back into the same model. This is the interface of the CPEE process engine, which curlite was written for.

TLS and linking

TLS comes from libcurl. By default the system libcurl is linked dynamically, with whatever TLS library it was built against. The static-curl, static-ssl and rustls features are passed through to the curl crate to build and link libcurl statically instead.

Where the memory goes

curlite is the client developed for the bachelor's thesis Where the Memory Goes: Design and Evaluation of a Synchronous Rust HTTP Client for Process Instances on Edge Devices (TUM, 2026). The thesis replaced the reqwest/tokio stack of a Rust process engine with this client and measured both on an Orange Pi Zero 2W, for one process instance and for up to 50 concurrent instances, in six build configurations that differ in the TLS library and the linking.

Short version: the async runtime was not where the memory went.

reqwest(rustls, vendored) curlite(rustls, vendored) curlite(OpenSSL, system) curlite(wolfSSL, tiny libcurl)
Peak RSS, one instance 9.88 MiB 9.69 MiB 19.40 MiB 7.97 MiB
Private memory per instance, 50 instances 1.70 MiB 2.00 MiB 5.99 MiB 1.95 MiB
Shipped to the device 10.67 MiB 10.04 MiB (system libraries) smallest
16 MiB download, max RSS of the process 50.7 MiB 20.0 MiB
  • With many instances, everything except written pages amortizes across processes. What remains is dominated by the TLS library: OpenSSL costs 3–4 MiB of private memory per process more than wolfSSL or rustls. The HTTP client accounts for about 0.3 MiB, the Rust linking mode for another 0.3 MiB.
  • For a single instance, the smallest footprint comes from a tiny libcurl with wolfSSL, 1.4–1.9 MiB RSS below the reqwest baseline. Only a curl-based client makes that configuration available.
  • curlite holds a request body once and a response once. reqwest peaked at about three times the payload for a 16 MiB download.
  • Per request, both clients cost the same. The curl/rustls build pays a one-time 0.47 s for the initialization of aws-lc's entropy source.

The full measurements, including the heap profiles, are in the thesis; the original client and the measurement setup are in the Bachelor-Thesis repository.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.