Michiel Mulders Michiel loves the NodeJS and Golang programming languages. A backend/core blockchain developer and avid writer, he's very passionate about blockchain technology.

How to choose the right Rust HTTP client

6 min read 1708

How to Choose the Right Rust HTTP Client

It’s impossible to overstate the importance of HTTP clients, which is why you should do your homework when choosing the right HTTP client for your task at hand. Depending on the tech stack your project is built on or the number of HTTP calls you have to make, certain libraries will suit you well while others could be more trouble than they’re worth.

In this guide, we’ll walk through the main features of six Rust HTTP clients:

  1. curl-rust
  2. hyper
  3. reqwest
  4. Isahc
  5. Surf
  6. ureq

We’ll also demonstrate how to make GET and POST requests with each library. Let’s get started!

1. curl-rust

curl-rust offers libcurl bindings for Rust, meaning it includes an interface for the C-based HTTP library. In other words, you can use the functionality of libcurl with an additional layer of security provided by Rust.

If you want to completely remain in the realm of Rust and avoid the problems that C-based curl can produce, you may want to opt for one of the libraries that completely rely on Rust. Importantly, this library does not offer any support for asynchronous requests, meaning it’s slower and more resource-heavy than any of its asynchronous counterparts.

My advice is to only use this library if you actually need to use curl — for instance, if you’re working on a project that depends on a diverse tech stack and has already integrated curl. curl-rust comes packed with functionalities you don’t always need, like support for transfer protocols such as Telnet, SMTP, FTP, IMAP, or LDAP. There are more lightweight solutions out there that provide more tailored features for today’s web needs.

Here’s an example of a POST request using curl-rust. Notice the more complex syntax.

use std::io::Read;
use curl::easy::Easy;

fn main() {
    let mut data = "this is the body".as_bytes();

    let mut easy = Easy::new();
    easy.url("http://www.example.com/upload").unwrap();
    easy.post(true).unwrap();
    easy.post_field_size(data.len() as u64).unwrap();

    let mut transfer = easy.transfer();
    transfer.read_function(|buf| {
        Ok(data.read(buf).unwrap_or(0))
    }).unwrap();
    transfer.perform().unwrap();
}
  • GitHub stars: 651
  • All-time downloads on crates.io: 3,115,828
  • Recent downloads (last 90 days): 379,320

2. hyper

When the high-level libraries don’t offer all the functionalities you’re looking for, it might be a good idea to look into the hyper library. It’s relatively low-level, so it’s perfect for when you need more advanced functionalities.

Several of the libraries we’ll discuss in this article are based on hyper. It’s asynchronous by design and offers both client- and server-side APIs. It’s a solid, tried-and-true library that enjoys extensive production use.

If you don’t need any of the advanced features hyper offers, it may be worth checking out a more high-level library based on hyper. Hyper is meant to be a building block for other libraries and applications. If you want a more convenient HTTP client, hyper recommends checking out reqwest since it’s built on top of hyper (more on this later).

We made a custom demo for .
No really. Click here to check it out.

Moreover, hyper hosts a pretty active community of contributors and even runs a Discord server. The official website offers several to help you get started quickly with running an HTTP client or server. A deeper level of details can be found in the API reference — e.g., how the Body object is implemented.

Here’s how to make a GET request using reqwest. First, create a new client object, then pass it a URL from which to retrieve data. Next, use the new await syntax Rust supports to await the response.

let client = Client::new();

// Parse an `http::Uri`...
let uri = "http://httpbin.org/ip".parse()?;

// Await the response...
let resp = client.get(uri).await?;

println!("Response: {}", resp.status());

Here’s an overview of hyper’s advantages:

  • HTTP/1 and HTTP/2 support
  • Asynchronous design
  • Leading in performance through concurrency support
  • Well-tested HTTP client
  • Extensive production use
  • Client- and server-side API support

If you’re keeping score at home:

  • GitHub stars: 6,700
  • All-time downloads on crates.io: 12,714,705
  • Recent downloads (last 90 days): 2,409,351

3. reqwest

If you’re looking for a more high-level HTTP client, reqwest may suit your needs. The library offers an HTTP client built on the hyper library.

By default, reqwest includes a client that’s capable of making asynchronous requests. However, it can also be configured so a “blocking” client is used if you only need to make a small number of HTTP calls and don’t want the added complexity of using asynchronous functions. This is an excellent feature.

Features such as HTTP proxies, customizable redirect policies, and support for cookies make reqwest a top choice in terms of convenience. This cookie store can be extremely useful for authentication.

This example from the official documentation shows how to make a GET and POST request. As you can see, the syntax is very straightforward.

// 1. GET
let body = reqwest::get("https://www.rust-lang.org")
    .await?
    .text()
    .await?;

println!("body = {:?}", body);

// 2. POST
let client = reqwest::Client::new();
let res = client.post("http://httpbin.org/post")
    .body("the exact body that is sent")
    .send()
    .await?;

To summarize, reqwest offers the folllwing highlighted features.

  • Async and blocking client
  • Support for plain bodies, JSON, urlencoded, and multipart data
  • Customizable redirect policy
  • HTTP proxies
  • Uses system-native TLS
  • Cookies

As for its popularity:

  • GitHub stars: 3,100
  • All-time downloads on crates.io: 6,005,254
  • Recent downloads (last 90 days): 1,431,607

4. Isahc

Much like reqwest, Isahc offers a high-level HTTP client with both asynchronous and synchronous methods. However, unlike reqwest, Isahc uses curl under the hood. This can be useful if you want a client that needs to maintain compatibility with curl. It also offers all the usual benefits of using a stable and actively developed, popular library.

Below is an example of a simple synchronous GET request. In particular, the status, headers, and text functions make it very easy to retrieve commonly used information from the response object.

use isahc::prelude::*;

fn main() -> Result<(), isahc::Error> {
    // Send a GET request and wait for the response headers.
    // Must be `mut` so we can read the response body.
    let mut response = isahc::get("http://example.org")?;

    // Print some basic info about the response to standard output.
    println!("Status: {}", response.status());
    println!("Headers: {:#?}", response.headers());

    // Read the response body as text into a string and print it.
    print!("{}", response.text()?);

    Ok(())
}

Let’s run down Isahc’s most notable features:

  • Full support for HTTP/1.1 and HTTP/2
  • Configurable request timeouts
  • Fully asynchronous core with asynchronous and incremental reading and writing of request and response bodies
  • Both a synchronous and asynchronous API with support for async/await
  • Sessions and cookie persistence

The stats below show Isahc’s relative popularity.

  • GitHub stars: 250
  • All-time downloads on crates.io: 240,279
  • Recent downloads (last 90 days): 78,568

5. Surf

Surf is a completely modular client with an asynchronous design. It’s extensible through a powerful middleware system. The default native-client uses curl, but you can also use curl-client.

curl-client uses curl via Isahc as the HTTP server. If you don’t want to use a client built on curl, you can opt to use hyper-client, which uses hyper as the HTTP server.

Here’s a simple middleware example that prints details about each request. As you can see, it uses the classic req, client, and next arguments, which you’ll find in many other HTTP clients. The next argument allows you to pass the request to another middleware module or request a handler.

use futures::future::BoxFuture;
use surf::middleware::{Next, Middleware, Request, Response, HttpClient};
use std::time;

/// Log each request's duration
#[derive(Debug)]
pub struct Logger;

impl<C: HttpClient> Middleware<C> for Logger {
    fn handle<'a>(
        &'a self,
        req: Request,
        client: C,
        next: Next<'a, C>,
    ) -> BoxFuture<'a, Result<Response, surf::Exception>> {
        Box::pin(async move {
            println!("sending request to {}", req.uri());
            let now = time::Instant::now();
            let res = next.run(req, client).await?;
            println!("request completed ({:?})", now.elapsed());
            Ok(res)
        })
    }
}

Notable features of Surf include:

  • Extensible through a powerful middleware system
  • Reuses connections through the client interface
  • Fully streaming requests and responses
  • TLS/SSL enabled by default
  • HTTP/2 enabled by default

As for its popularity:

  • GitHub stars: 859
  • All-time downloads on crates.io: 154,512
  • Recent downloads (last 90 days): 62,960

6. ureq

ureq is a minimal request library, which can be useful if you’re interested in a minimal dependency tree. It’s designed for convenience and offers a blocking API to keep things simple. It doesn’t use any unsafe Rust, which makes it good for developers who want to stick to safe Rust.

This library is a particularly excellent choice if you value compile time. It doesn’t use a curl server nor does it offer asynchronous functionality. These are deliberate omissions to keep the library as minimal and lightweight as possible. For this reason, at the time of writing, there are no plans to migrate to asynchronous functionality.

Below is a small POST request example using ureq. Notice the simplicity of setting headers and body data.

let resp = ureq::post("https://myapi.example.com/ingest")
    .set("X-My-Header", "Secret")
    .send_json(serde_json::json!({
        "name": "martin",
        "rust": true
    }));

To reiterate, ureq’s main features include:

  • Minimal dependency tree
  • Straightforward API design
  • Blocking API only
  • No use of unsafe Rust

Finally, let’s look at ureq’s popularity at a glance:

  • GitHub stars: 332
  • All-time downloads on crates.io: 171,287
  • Recent downloads (last 90 days): 72,771

Choosing the best rust HTTP Client

If you want a low-level HTTP library in Rust, I recommend using hyper. It’s production-ready and fully written in Rust so you don’t have to worry too much about safety issues. Moreover, it’s the only library that mentions production-readiness.

For a more high-level HTTP library, I would go with reqwest. The library is built on top of hyper, so it offers many of the same advantages as hyper and has a more convenient syntax to boot.

Throughout this guide, we touched upon other libraries that can be useful in a range of other situations. If you have to work with curl, you should explore curl-rust, Isahc, or even Surf. If you’re looking for a lightweight library that doesn’t add much to compile time, ureq is the way to go. However, note that ureq only supports synchronous requests.

Hopefully, this comparison helps you select the best HTTP client for your next Rust project.

You come here a lot! We hope you enjoy the LogRocket blog. Could you fill out a survey about what you want us to write about?

    Which of these topics are you most interested in?
    ReactVueAngularNew frameworks
    Do you spend a lot of time reproducing errors in your apps?
    YesNo
    Which, if any, do you think would help you reproduce errors more effectively?
    A solution to see exactly what a user did to trigger an errorProactive monitoring which automatically surfaces issuesHaving a support team triage issues more efficiently
    Thanks! Interested to hear how LogRocket can improve your bug fixing processes? Leave your email:

    LogRocket: Full visibility into production Rust apps

    Debugging Rust applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking performance of your Rust apps, automatically surfacing errors, and tracking slow network requests and load time, try LogRocket.

    LogRocket is like a DVR for web apps, recording literally everything that happens on your Rust app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.

    Modernize how you debug your Rust apps — .

    Michiel Mulders Michiel loves the NodeJS and Golang programming languages. A backend/core blockchain developer and avid writer, he's very passionate about blockchain technology.

    2 Replies to “How to choose the right Rust HTTP client”

    Leave a Reply