118

How can I make an HTTP request from Rust? I can't seem to find anything in the core library.

I don't need to parse the output, just make a request and check the HTTP response code.

Bonus marks if someone can show me how to URL encode the query parameters on my URL!

Alex Dean
  • 15,575
  • 13
  • 63
  • 74

9 Answers9

136

The easiest way to make HTTP requests in Rust is with the reqwest crate:

use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let resp = reqwest::blocking::get("https://httpbin.org/ip")?.text()?;
    println!("{:#?}", resp);
    Ok(())
}

In Cargo.toml:

[dependencies]
reqwest = { version = "0.11", features = ["blocking"] }

Async

Reqwest also supports making asynchronous HTTP requests using Tokio:

use std::error::Error;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let resp = reqwest::get("https://httpbin.org/ip")
        .await?
        .text()
        .await?;
    println!("{:#?}", resp);
    Ok(())
}

In Cargo.toml:

[dependencies]
reqwest = "0.11"
tokio = { version = "1", features = ["full"] }

Hyper

Reqwest is an easy to use wrapper around Hyper, which is a popular HTTP library for Rust. You can use it directly if you need more control over managing connections. A Hyper-based example is below and is largely inspired by an example in its documentation:

use hyper::{body::HttpBody as _, Client, Uri};
use std::error::Error;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let client = Client::new();

    let res = client
        .get(Uri::from_static("http://httpbin.org/ip"))
        .await?;

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

    let buf = hyper::body::to_bytes(res).await?;

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

In Cargo.toml:

[dependencies]
hyper = { version = "0.14", features = ["full"] }
tokio = { version = "1", features = ["full"] }

Original answer (Rust 0.6)

I believe what you're looking for is in the standard library. now in rust-http and Chris Morgan's answer is the standard way in current Rust for the foreseeable future. I'm not sure how far I can take you (and hope I'm not taking you the wrong direction!), but you'll want something like:

// Rust 0.6 -- old code
extern mod std;

use std::net_ip;
use std::uv;

fn main() {
    let iotask = uv::global_loop::get();
    let result = net_ip::get_addr("www.duckduckgo.com", &iotask);

    io::println(fmt!("%?", result));
}

As for encoding, there are some examples in the unit tests in src/libstd/net_url.rs.

Ibraheem Ahmed
  • 11,652
  • 2
  • 48
  • 54
Isaac Aggrey
  • 1,982
  • 3
  • 15
  • 19
  • No, you need to configure the client to use a TLS implementation for https. See https://hyper.rs/guides/client/configuration/ for details. – AphexMunky Jan 24 '18 at 14:05
  • doesn't work for hyper 13. see https://stackoverflow.com/a/60047494/8870055 below – stuart Feb 03 '20 at 21:36
36

Update: This answer refers to fairly ancient history. For the current best practices, please look at Isaac Aggrey's answer instead.


I've been working on rust-http, which has become the de facto HTTP library for Rust (Servo uses it); it's far from complete and very poorly documented at present. Here's an example of making a request and doing something with the status code:

extern mod http;
use http::client::RequestWriter;
use http::method::Get;
use http::status;
use std::os;

fn main() {
    let request = RequestWriter::new(Get, FromStr::from_str(os::args()[1]).unwrap());
    let response = match request.read_response() {
        Ok(response) => response,
        Err(_request) => unreachable!(), // Uncaught condition will have failed first
    };
    if response.status == status::Ok {
        println!("Oh goodie, I got me a 200 OK response!");
    } else {
        println!("That URL ain't returning 200 OK, it returned {} instead", response.status);
    }
}

Run this code with a URL as the sole command-line argument and it'll check the status code! (HTTP only; no HTTPS.)

Compare with src/examples/client/client.rs for an example that does a little more.

rust-http is tracking the master branch of rust. At present it'll work in the just-released Rust 0.8, but there are likely to be breaking changes soon. Actually, no version of rust-http works on Rust 0.8—there was a breaking change which can't be worked around in privacy rules just before the release, leaving something that rust-http depends on in extra::url inaccessible. This has since been fixed, but it leaves rust-http incompatible with Rust 0.8.


As for the query string encoding matter, at present that should be done with extra::url::Query (a typedef for ~[(~str, ~str)]). Appropriate functions for conversions:

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Chris Morgan
  • 86,207
  • 24
  • 208
  • 215
  • how to compile it correctly? Right now I put it in rust-http/src/examples/client2/main.rs and run (taken from 'make examples' output) rustc -O -Z debug-info src/examples/client2/main.rs -o build/examples/client2 -L build/ – rofrol Nov 18 '13 at 15:22
  • 1
    @rofrol: rust-http currently generates a couple as a precompilation step, so you can't conveniently install it through rustpkg (which would otherwise be `rustpkg install github.com/chris-morgan/rust-http`) just yet. The important thing is that when you compile it it must be able to find libhttp-*.so; that's what the `-L build/` is for, because that file is in the `build` directory. So for your code it could be `-L /path/to/rust-http/build/`, or you could copy it into your build directory, or something like that. Don't worry, things should be quite a bit better by Rust 0.9 time. – Chris Morgan Nov 19 '13 at 03:29
  • 14
    Note that rust-http is now marked as obsolete. Its author recommends Hyper instead https://github.com/hyperium/hyper – Andres Kievsky Feb 01 '16 at 22:29
  • heh, now it's replaced by hyper which is also poorly documented :> – agilob Aug 17 '16 at 12:45
  • There's Reqwest [sic] which is a simple wrapper around hyper. https://docs.rs/reqwest/0.3.0/reqwest/ – Powersource Jan 12 '17 at 19:05
  • I'm uncomfortable deleting this just because it's out of date...that doesn't seem to foreclose the possibility that someone might find it useful. If I could change the accepted answer, I would, but I can't. We'll need to wait for Alex to do that. It appears he's still an active user, so hopefully he'll see your comments on the question. – Cody Gray - on strike Aug 27 '17 at 07:10
  • 1
    @CodyGray: archaeologists are the only people that could find it useful. The answer itself hasn’t been at all relevant for three years or so; the code involved won’t run, and rust-http itself would require some fairly invasive changes to be able to run, because this was from some time before Rust stabilised (1.0). – Chris Morgan Aug 29 '17 at 05:19
23

Using curl bindings. Stick this in your Cargo.toml:

[dependencies.curl]
git = "https://github.com/carllerche/curl-rust"

...and this in the src/main.rs:

extern crate curl;

use curl::http;

fn main(){
  let resp = http::handle()
    .post("http://localhost:3000/login", "username=dude&password=sikrit")
    .exec().unwrap();

  println!("code={}; headers={}; body={}",
    resp.get_code(), resp.get_headers(), resp.get_body());    

}
dvdplm
  • 691
  • 7
  • 8
  • A quick and low-friction approach - nice! – Alex Dean Nov 25 '14 at 08:37
  • 2
    This answer proposes using C to do HTTP, but pure Rust solutions are more appropriate because they are simpler for a team to maintain, and also because Rust is safer than C. – Jeff Allen Aug 23 '17 at 07:11
  • 9
    @JeffAllen it may be more appropriate, but next to impossible to find. So I'd chose a sub-optimal that works to an "appropriate" that does not. – Andrew Savinykh Dec 28 '17 at 23:52
  • 9
    This would be much better than the other answer since it avoids dealing with "Futures", one of the most overly complicated, non-documented things I have ever seen. Too bad it doesn't work since there is no `curl::http` available (anymore) - only something called "easy" which is way to complicated on it's own and barely documented. – Philipp Ludwig Dec 09 '18 at 17:47
8

I prefer Crates with low dependency count, so I would recommend these:

MinReq (0 deps)

use minreq;

fn main() -> Result<(), minreq::Error> {
   let o = minreq::get("https://speedtest.lax.hivelocity.net").send()?;
   let s = o.as_str()?;
   print!("{}", s);
   Ok(())
}

HTTP_Req (35 deps)

use {http_req::error, http_req::request, std::io, std::io::Write};

fn main() -> Result<(), error::Error> {
   let mut a = Vec::new();
   request::get("https://speedtest.lax.hivelocity.net", &mut a)?;
   io::stdout().write(&a)?;
   Ok(())
}
Zombo
  • 1
  • 62
  • 391
  • 407
  • 2
    Just used minreq and can say it works really well. Be aware that it has 7 dependencies if you want HTTPS support. – pigeonburger Sep 21 '21 at 00:01
6

To elaborate on Isaac Aggrey's answer, here's an example of making a POST request with query parameters using the reqwest library.

Cargo.toml

[package]
name = "play_async"
version = "0.1.0"
edition = "2018"

[dependencies]
reqwest = "0.10.4"
tokio = { version = "0.2.21", features = ["macros"] }

Code

use reqwest::Client;

type Error = Box<dyn std::error::Error>;
type Result<T, E = Error> = std::result::Result<T, E>;

async fn post_greeting() -> Result<()> {
    let client = Client::new();
    let req = client
        // or use .post, etc.
        .get("https://webhook.site/1dff66fd-07ff-4cb5-9a77-681efe863747")
        .header("Accepts", "application/json")
        .query(&[("hello", "1"), ("world", "ABCD")]);

    let res = req.send().await?;
    println!("{}", res.status());

    let body = res.bytes().await?;

    let v = body.to_vec();
    let s = String::from_utf8_lossy(&v);
    println!("response: {} ", s);

    Ok(())
}

#[tokio::main]
async fn main() -> Result<()> {
    post_greeting().await?;

    Ok(())
}

Go to https://webhook.site and create your webhook link and change the code to match. You'll see the request was received on server in realtime.

This example was originally based on Bastian Gruber's example and has been updated for modern Rust syntax and newer crate versions.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Patrik Stas
  • 1,883
  • 17
  • 24
  • Do you have an example that uses a POST? HTTP forms allow for a GET and a POST, but this is using a GET. – tom May 27 '20 at 13:15
  • @tom literally replace `.get` with `.post`. – Shepmaster May 27 '20 at 13:30
  • That doesn't seem to be enough. My request needs to be sent with Content-Type "application/x-www-form-urlencoded". I tried adding a `header()` call with that and I'm getting back `HTTP Error 411. The request must be chunked or have a content length.` – tom May 27 '20 at 14:01
4

Building upon Patrik Stas' answer, if you want to do an HTTP form URL-encoded POST, here is what you have to do. In this case, it's to get an OAuth client_credentials token.

Cargo.toml

[dependencies]
reqwest = "0.10.4"
tokio = { version = "0.2.21", features = ["macros"] }

Code

use reqwest::{Client, Method};

type Error = Box<dyn std::error::Error>;
type Result<T, E = Error> = std::result::Result<T, E>;

async fn print_access_token() -> Result<()> {
    let client = Client::new();
    let host = "login.microsoftonline.com";
    let tenant = "TENANT";
    let client_id = "CLIENT_ID";
    let client_secret = "CLIENT_SECRET";
    let scope = "https://graph.microsoft.com/.default";
    let grant_type = "client_credentials";

    let url_string = format!("https://{}/{}/oauth2/v2.0/token", host, tenant);
    let body = format!(
        "client_id={}&client_secret={}&scope={}&grant_type={}",
        client_id, client_secret, scope, grant_type,
    );
    let req = client.request(Method::POST, &url_string).body(body);

    let res = req.send().await?;
    println!("{}", res.status());

    let body = res.bytes().await?;

    let v = body.to_vec();
    let s = String::from_utf8_lossy(&v);
    println!("response: {} ", s);

    Ok(())
}

#[tokio::main]
async fn main() -> Result<()> {
    print_access_token().await?;

    Ok(())
}

This will print something like the following.

200 OK
response: {"token_type":"Bearer","expires_in":3599,"ext_expires_in":3599,"access_token":"ACCESS_TOKEN"} 
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
tom
  • 1,331
  • 1
  • 15
  • 28
1

Using hyper "0.13"

Also using hyper-tls for HTTPS support.

File Cargo.toml

hyper = "0.13"
hyper-tls = "0.4.1"
tokio = { version = "0.2", features = ["full"] }

Code

extern crate hyper;
use hyper::Client;
use hyper::body::HttpBody as _;
use tokio::io::{stdout, AsyncWriteExt as _};
use hyper_tls::HttpsConnector;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // HTTP only
    // let client = Client::new();

    // http or https connections
    let client = Client::builder().build::<_, hyper::Body>(HttpsConnector::new());

    let mut resp = client.get("https://catfact.ninja/fact".parse()?).await?;

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

    while let Some(chunk) = resp.body_mut().data().await {
        stdout().write_all(&chunk?).await?;
    }

    Ok(())
}

Adapted from https://hyper.rs/guides/client/basic/

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
stuart
  • 1,785
  • 2
  • 26
  • 38
1

Dropping a version here that uses the surf crate (dual to the tide crate):

let res = surf::get("https://httpbin.org/get").await?;
assert_eq!(res.status(), 200);
dcow
  • 7,765
  • 3
  • 45
  • 65
1

Simple http request with this crate: wsd

fn test() {
    wsd::http::get("https://docs.rs/", |data| {
        println!("status = {}, data = {}", data.status(), data.text());
    });
}
Sunding Wei
  • 1,803
  • 17
  • 12