DEV Community

pintuch
pintuch

Posted on

Rust - Reqwest examples

Introduction

There are 3 things that need to happen:

  • Building a client that can be reused across multiple requests
  • Performing the execution of the network request
  • Parsing the response
use std::collections::HashMap;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Build the client using the builder pattern
    let client = reqwest::Client::builder()
        .build()?;

    // Perform the actual execution of the network request
    let res = client
        .get("https://httpbin.org/ip")
        .send()
        .await?;

    // Parse the response body as Json in this case
    let ip = res
        .json::<HashMap<String, String>>()
        .await?;

    println!("{:?}", ip);
    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

Instead of parsing the json body as a dynamic hash-map, you could use a well-defined struct

use serde::Deserialize;

#[derive(Deserialize, Debug)]
struct Ip {
    origin: String
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Build the client using the builder pattern
    let client = reqwest::Client::builder()
        .build()?;

    // Perform the actual execution of the network request
    let res = client
        .get("https://httpbin.org/ip")
        .send()
        .await?;

    // Parse the response body as Json in this case
    let ip = res
        .json::<Ip>()
        .await?;

    println!("{:?}", ip);
    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

Query Strings

let client = reqwest::Client::builder()
    .build()?;

let res = client
    .get("http://httpbin.org")
    .query(&[("foo", "bar")])
    .send()?;
Enter fullscreen mode Exit fullscreen mode

POST JSON Data

The json(..) method on the RequestBuilder takes any value that can be serialized into JSON such as a HashMap or a Struct.

let mut map = HashMap::new();
map.insert("foo", "bar");
map.insert("buzz", "blah");

...
.json(&map)
Enter fullscreen mode Exit fullscreen mode
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Debug, Serialize, Deserialize)]
struct Person {
    first_name: String,
    last_name: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct PersonResponse {
    data: String,
    method: String,
    headers: HashMap<String, String>
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let p = Person { 
        first_name: "Foo".into(),
        last_name: "Bar".into(),
    };

    let res = reqwest::Client::new()
        .post("https://httpbin.org/anything")
        .json(&p)
        .send()
        .await?;

    let js = res
        .json::<PersonResponse>()
        .await?;

    let person: Person = serde_json::from_str(&js.data)?;
    println!("{:#?}", person);

    println!("Headers: {:#?}", js.headers);
    Ok(())
}

Enter fullscreen mode Exit fullscreen mode

POST Form data

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Person {
    first_name: String,
    last_name: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let p = Person { 
        first_name: "Foo".into(),
        last_name: "Bar".into(),
    };

    let res = reqwest::Client::new()
        .post("https://httpbin.org/anything")
        .form(&p)
        .send()
        .await?;

    let t = res
        .text()
        .await?;

    println!("{}", t);

//  $ cargo run
//      Finished dev [unoptimized + debuginfo] target(s) in 0.09s
//       Running `target/debug/reqwest-tut`
//  {
//    "args": {}, 
//    "data": "", 
//    "files": {}, 
//    "form": {
//      "first_name": "Foo", 
//      "last_name": "Bar"
//    }, 
//    "headers": {
//      "Accept": "*/*", 
//      "Content-Length": "28", 
//      "Content-Type": "application/x-www-form-urlencoded", 
//      "Host": "httpbin.org", 
//      "X-Amzn-Trace-Id": "Root=1-60453174-384d97870199933007fbb388"
//    }, 
//    "json": null, 
//    "method": "POST", 
//    "origin": "125.125.104.53", 
//    "url": "https://httpbin.org/anything"
//  }
//  
//  
    Ok(())
}

Enter fullscreen mode Exit fullscreen mode

Request Headers

You can set the headers by calling the header(.., ..) method on the request

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest::Client::builder()
        .build()?;

    let res = client
        .post("https://httpbin.org/anything")
        .body("arbitrary text")
        .header("X-Person-First", "Foo!")
        .header("X-Person-Last", "Bar!!")
        .send()
        .await?;

    let t = res
        .text()
        .await?;

    println!("{}", t);
// {
//   "args": {}, 
//   "data": "arbitrary text", 
//   "files": {}, 
//   "form": {}, 
//   "headers": {
//     "Accept": "*/*", 
//     "Content-Length": "14", 
//     "Host": "httpbin.org", 
//     "X-Amzn-Trace-Id": "Root=1-604538df-218a5bb97264e7130c298b23", 
//     "X-Person-First": "Foo!", 
//     "X-Person-Last": "Bar!!"
//   }, 
//   "json": null, 
//   "method": "POST", 
//   "origin": "49.206.4.160", 
//   "url": "https://httpbin.org/anything"
// }

    Ok(())
}

Enter fullscreen mode Exit fullscreen mode

You can also set default headers on the client that can be overriden while making individual requests

use reqwest::header;

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let mut headers = header::HeaderMap::new();
    headers.insert("X-HEADER-1", header::HeaderValue::from_static("val1"));
    headers.insert("X-HEADER-2", header::HeaderValue::from_static("val2"));

    let client = reqwest::Client::builder()
        .default_headers(headers)
        .build()?;

    let res = client
        .get("https://httpbin.org/anything")
        .body("whatever")
        .header("X-HEADER-1", "overriden val1")
        .send()
        .await?;

    println!("{}", res.text().await?);

    // {
    //   "args": {},
    //   "data": "whatever",
    //   "files": {},
    //   "form": {},
    //   "headers": {
    //     "Accept": "*/*",
    //     "Content-Length": "8",
    //     "Host": "httpbin.org",
    //     "X-Amzn-Trace-Id": "Root=1-60453dad-429e59a434bd460b0a48e7d5",
    //     "X-Header-1": "overriden val1",
    //     "X-Header-2": "val2"
    //   },
    //   "json": null,
    //   "method": "GET",
    //   "origin": "49.206.4.160",
    //   "url": "https://httpbin.org/anything"
    // }

    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

Status Code

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let client = reqwest::Client::builder().build()?;

    let res = client
        .get("https://httpbin.org/status/400")
        .send()
        .await?;

    match res.status() {
        reqwest::StatusCode::BAD_REQUEST => println!(
            "content-length:{:?} server:{:?}", 
            res.headers().get(reqwest::header::CONTENT_LENGTH),
            res.headers().get(reqwest::header::SERVER),
        ),
        status => println!("status: {}", status),
    }

    // content-length:Some("0") server:Some("gunicorn/19.9.0")
    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

Timeouts

Client Timeout

let client = reqwest::Client::builder()
    .timeout(std::time::Duration::from_millis(500))
    .build()?;

Enter fullscreen mode Exit fullscreen mode

Connect Timeout

let client = reqwest::Client::builder()
    .connect_timeout(std::time::Duration::from_millis(100))
    .build()?;
Enter fullscreen mode Exit fullscreen mode

Request Timeout

let res = client
    .get(&url)
    .timeout(std::time::Duration::from_millis(500))
    .send()
    .await;
Enter fullscreen mode Exit fullscreen mode

Download data

use std::fs::File;
use std::io;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest::Client::builder().build()?;

    let res = client
        .get("https://httpbin.org/image/png")
        .send()
        .await?
        .bytes()
        .await?;

    let mut data = res.as_ref();

    let mut f = File::create("i.png")?;

    io::copy(&mut data, &mut f)?;

    Ok(())
}

// $ file i.png 
// i.png: PNG image data, 100 x 100, 8-bit/color RGB, non-interlaced 
Enter fullscreen mode Exit fullscreen mode
Dependency configuration for the samples
[dependencies]
reqwest = { version = "0.11", features = ["json", "blocking", "cookies"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1.0" 
futures = "0.3"
Enter fullscreen mode Exit fullscreen mode

Discussion (1)

Collapse
email2vimalraj profile image
Vimalraj Selvam

Very useful