Building a HTTP Client with Reqwest | Rust
Tags: rust, http, client, reqwest
Published: 2023-10-24 04:11:05

---
title: Building a HTTP Client with Reqwest | Rust
published: true
date: 2023-10-23 14:45:31 UTC
tags: #rust #http #client #reqwest
canonical_url: https://carlosmv.hashnode.dev/building-a-http-client-with-reqwest-rust
---

In this article, we are going to build a basic HTTP client with Reqwest to test REST APIs

This program will have just basic functionalities, like showing the body and the status code from GET, and POST requests.

Also, we will add a feature that allows the program to read the URL from a text file. And to read the URL and the HTTP method we want to execute from a TOML file.

### Requirements

- Rust installed

- A REST API to test the HTTP client.

**cargo.toml**

```
[dependencies]
tokio = { version = "1.15", features = ["full"] }
reqwest = { version = "0.11.22", features = ["json"] }

```

**main.rs**

```rust
//main.rs
use reqwest::Error;

async fn get_request() -> Result<(), Error> {
    let response = reqwest::get("https://www.fruityvice.com/api/fruit/apple").await?;
    println!("Status: {}", response.status());

    let body = response.text().await?;
    println!("Body:\n{}", body);

    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    get_request().await?;
    Ok(())
}

```

In the above, we create the `get_resquest()` function to make a GET request and show in the console the status code of the response and its body.

Then we call the `get_request()` function in the `main()` function to make the request when the program runs.

**POST requests**

```rust
//main.rs
async fn post_request() -> Result<(), Error> {
    let url = "http://localhost:4000/tasks";
    let json_data = r#"{"title":"Problems during installation","status":"todo","priority":"medium","label":"bug"}"#;

    let client = reqwest::Client::new();

    let response = client
        .post(url)
        .header("Content-Type", "application/json")
        .body(json_data.to_owned())
        .send()
        .await?;

    println!("Status Code: {}", response.status());

    let response_body = response.text().await?;

    println!("Response body: \n{}", response_body);

    Ok(())

}

#[tokio::main]
async fn main() -> Result<(), Error> {
  ...

    post_request().await?;
    Ok(())
}

```

Here we create the `post_request()` function, to make a POST request to an API. Inside the `post_request()` function we defined the URL and JSON data we want to add to the server. When this function is called, it will print in the console the status code and the body of the response.

Then we call the `post_request()` function in the `main()` function and run the `cargo run` command in the console.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697333476056/99678ce9-a698-482e-9dab-6f20e7643fb7.png)

**PUT Request**

```rust
//main.rs
async fn put_request() -> Result<(), Error> {
    let url = "http://localhost:4000/tasks/7";
    let json_data = r#"{"title":"Problems during installation","status":"todo","priority":"low","label":"bug"}"#;

    let client = reqwest::Client::new();

    let response = client
        .put(url)
        .header("Content-Type", "application/json")
        .body(json_data.to_owned())
        .send()
        .await?;

    println!("Status code: {}", response.status());

    let response_body = response.text().await?;

    println!("Response body: \n{}", response_body);

    Ok(())
}

```

Here we create the `put_request()` function, to make a PUT request to an API. Inside the `put_request()` function we defined the URL and JSON data we want to update to the server. When this function is called, it will print in the console the status code and the body of the response.

Then, we add the `put_request()` function to the `main()` function and run the program.

Make sure to delete the `post_request()` function from the `main()` function, so the program does not call it when it runs.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697336622583/6594353e-03ec-4773-bd8e-cb23e12313e8.png)

**DELETE Request**

```rust
//main.rs
async fn delete_request() -> Result<(), Error> {
    let url = "http://localhost:4000/tasks/5";

    let client = reqwest::Client::new();

    let response = client
        .delete(url)
        .send()
        .await?;

    println!("Status code: {}", response.status());

    let response_body = response.text().await?;

    println!("Response body: \n{}", response_body);

    Ok(())
}

```

Here we create the `delete_request()` function, to make a DELETE request to an API. Inside the `delete_request()` function we defined the URL with the parameter of the row we want to delete. When this function is called, it will print in the console the status code and the body of the response.

Then we call the `delete_request()` function in the `main()` function and run the `cargo run` command in the console.

```rust
//main.rs
#[tokio::main]
async fn main() -> Result<(), Error> {
    let file_path = "./urls.txt";
    let url_vector = read_file_lines_to_vec(&file_path.to_string());

    println!("{:?}", url_vector);

    delete_request().await?;
    Ok(())
}

```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697337365862/ed946cff-b5c5-41aa-b490-a211da684e9b.png)

**Complete code**

```rust
//main.rs
use reqwest::Error;
mod helpers;
use helpers::{read_file_lines_to_vec};

async fn get_request() -> Result<(), Error> {

    let file_path = "./urls.txt";
    let url_vector = read_file_lines_to_vec(&file_path.to_string());
    match &url_vector {
        // If the operation was successful, make requests to urls in the file.
        Ok(file_contents) => {
            for url in file_contents {
                let response = reqwest::get(url).await?;
                println!("Status code: {}", response.status());

                let body = response.text().await?;
                println!("Response body:\n{}", body);
            }
        }

        // If the operation failed, print the error message to the console.
        Err(error) => {
            println!("Error reading file: {}", error);
        }
    }
    Ok(())

}

async fn post_request() -> Result<(), Error> {
    let url = "http://localhost:4000/tasks";
    let json_data = r#"{"title":"Problems during installation","status":"todo","priority":"medium","label":"bug"}"#;

    let client = reqwest::Client::new();

    let response = client
        .post(url)
        .header("Content-Type", "application/json")
        .body(json_data.to_owned())
        .send()
        .await?;

    println!("Status code: {}", response.status());

    let response_body = response.text().await?;

    println!("Response body: \n{}", response_body);

    Ok(())

}

async fn put_request() -> Result<(), Error> {
    let url = "http://localhost:4000/tasks/7";
    let json_data = r#"{"title":"Problems during installation","status":"todo","priority":"low","label":"bug"}"#;

    let client = reqwest::Client::new();

    let response = client
        .put(url)
        .header("Content-Type", "application/json")
        .body(json_data.to_owned())
        .send()
        .await?;

    println!("Status code: {}", response.status());

    let response_body = response.text().await?;

    println!("Response body: \n{}", response_body);

    Ok(())
}

async fn delete_request() -> Result<(), Error> {
    let url = "http://localhost:4000/tasks/5";

    let client = reqwest::Client::new();

    let response = client
        .delete(url)
        .send()
        .await?;

    println!("Status code: {}", response.status());

    let response_body = response.text().await?;

    println!("Response body: \n{}", response_body);

    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let file_path = "./urls.txt";
    let url_vector = read_file_lines_to_vec(&file_path.to_string());

    println!("{:?}", url_vector);

    delete_request().await?;
    Ok(())
}

```

Using just a text file will make this program difficult to use. The `.txt` file for just making a GET request to multiple URLs will be fine. But what if we want to use the other HTTP methods?

So, we are going to add another feature, to read a config file where we write the URLs we want to make the requests and the HTTP methods we want to use.

### Adding Config file

As a config file, we are going to use a TOML file. To parse this file we have to add a TOML parser and Serde to the project's dependencies.

**cargo.toml**

```
...
serde = {version = "1.0", features = ["derive"]}
toml = "0.8.2"

```

Now, we create a `config.toml` file in the project's root directory.

**config.toml**

```
[config]
url = "http://localhost:4000/tasks/8"
method = "DELETE"

```

**main.rs**

Here we have to create the struct of the data we want to deserialize from the TOML file.

```rust
use serde::Deserialize;
use std::fs;
use serde_json::Value;

#[derive(Deserialize)]
struct Data {
   config: Config,
}
#[derive(Deserialize)]
struct Config {
    url: String,
    method: String,
}

```

I going to comment on the code that opens the `.txt` file in the `main()` function. And write the code that opens the `.toml` file.

```rust
//main.rs
#[tokio::main]
async fn main()-> Result<(), Error> {
    //let file_path = "./urls.txt";
   // let url_vector = read_file_lines_to_vec(&file_path.to_string());

  ...  
    let filename = "config.toml";

    let contents = match fs::read_to_string(filename) {

        Ok(c) => c,

        Err(error) => {
            (&error).to_string()     
        }
    };

```

Now, we parse the content of the TOML file.

```
let data: Data = match toml::from_str(&contents) {
        Ok(d) => d,

        Err(error) => {

            eprintln!("Unable to load data because `{}`", error);

            std::process::exit(1);
        }
    };

```

For POST requests and PUT requests, we need to send JSON data.

Something I figured out was to use a JSON parser and extract the body from a JSON file.

For this, we have to add `serde_json` as a dependency

```
serde_json = "1.0"

```

In the main function, we write the code to open and parse the JSON file.

```rust
#[tokio::main]
async fn main()-> Result<(), Error> {
   ...

    let body = {
        let file_content = fs::read_to_string("./body.json").expect("Error reading file");
        serde_json::from_str::<Value>(&file_content).expect("Error serializing to JSON")
    };

 Ok(())

}

```

Finally, we add the control flow.

```rust
//main.rs
#[tokio::main]
async fn main()-> Result<(), Error> {
    ...

    if data.config.method == "DELETE" {
        delete_request(data.config.url).await;
    } else if data.config.method == "POST" {
        post_request(data.config.url, body.to_string()).await;

    } else if data.config.method == "PUT" {
       put_request(data.config.url, body.to_string()).await;
    } else {

        get_request(data.config.url).await;
    }

    Ok(())

}

```

Now, we create the `http_method.rs` file and move all the functions related to the HTTP requests to this new file.

Then, create a function with the control flow in the `main.rs` file.

```
//main.rs
async fn method_control(http_method: &str, url: String, body: String
) -> Result<(), reqwest::Error> {

    match http_method {
        "POST" => post_request(url, body).await,
        "PUT" => put_request(url, body).await,
        "DELETE" => delete_request(url).await,
        _ => get_request(url).await,

    }
}

```

Now, let's try our HTTP client.

Write the URL and the HTTP method in the `config.toml` file.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1698072007990/bb765d48-566a-410f-93cf-56e30c53695b.png)

Define the body you want to send to the API.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1698072021803/89b887df-8319-4d17-b0a2-8eaa2016d14c.png)

Then run the `cargo run` command in the console.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1698072080088/c281126a-bb3e-467b-8ce6-562ab01ae294.png)

Complete the `http_method.rs` file.

```rust
use reqwest::Error;

pub async fn get_request(url: String) -> Result<(), Error> {

    let response = reqwest::get(url).await?;
    println!("Status code: {}", response.status());

    let body = response.text().await?;
    println!("Response body:\n{}", body);
    Ok(())

}

pub async fn post_request(url: String, json_data: String) -> Result<(), Error> {

    let client = reqwest::Client::new();

    let response = client
        .post(url)
        .header("Content-Type", "application/json")
        .body(json_data.to_owned())
        .send()
        .await?;

    println!("Status code: {}", response.status());

    let response_body = response.text().await?;

    println!("Response body: \n{}", response_body);

    Ok(())

}

pub async fn put_request(url: String, json_data: String) -> Result<(), Error> {

    let client = reqwest::Client::new();

    let response = client
        .put(url)
        .header("Content-Type", "application/json")
        .body(json_data.to_owned())
        .send()
        .await?;

    println!("Status code: {}", response.status());

    let response_body = response.text().await?;

    println!("Response body: \n{}", response_body);

    Ok(())
}

pub async fn delete_request(url: String) -> Result<(), Error> {

    let client = reqwest::Client::new();

    let response = client
        .delete(url)
        .send()
        .await?;

    println!("Status code: {}", response.status());

    let response_body = response.text().await?;

    println!("Response body: \n{}", response_body);

    Ok(())
}

```

Complete `main.rs` file.

```rust
use serde::Deserialize;
use std::fs;
use serde_json::Value;

mod http_methods;
use http_methods::{get_request, post_request, put_request, delete_request};

#[derive(Deserialize)]
struct Data {
   config: Config,
}
#[derive(Deserialize)]
struct Config {
    url: String,
    method: String,
}

#[tokio::main]
async fn main()-> Result<(), Error> {

    let filename = "config.toml";

    let contents = match fs::read_to_string(filename) {      
        Ok(c) => c,   
        Err(error) => {
            (&error).to_string()       
        }
    };

    let data: Data = match toml::from_str(&contents) {    
        Ok(d) => d,
        Err(error) => {   
            eprintln!("Unable to load data because `{}`", error);
            std::process::exit(1);
        }
    };

    let body = {
        let file_content = fs::read_to_string("./body.json").expect("Error reading file");
        serde_json::from_str::<Value>(&file_content).expect("Error serializing to JSON")
    };

    let result = method_flow(&data.config.method, data.config.url, body.to_string()).await;

    match result {
        Ok(contents) => contents,
        Err(e) => println!("Error during the request: {}", e),
    }
    Ok(())

}

async fn method_control(http_method: &str, url: String, body: String) -> Result<(), reqwest::Error> {

    match http_method {
        "POST" => post_request(url, body).await,
        "PUT" => put_request(url, body).await,
        "DELETE" => delete_request(url).await,
        _ => get_request(url).await,
    }
}

```

### Conclusion

In this article, we learn how to use the Reqwest crate to make HTTP requests. And also we built a HTTP client to take its input from a text file and a configuration file.

I built this program just for learning purposes and learn how to use the Reqwest crate. But I want it to build something different than just a program that makes GET requests.

The source code is [here](https://github.com/carlosm27/reqwest-demo).

Thank you for taking the time to read this article.

If you have any recommendations about other packages, architectures, how to improve my code, my English, or anything; please leave a comment or contact me through [**Twitter**](https://twitter.com/Carlos_marcv), or [**LinkedIn**](https://www.linkedin.com/in/carlos-marcano-a2135a134/).

### Resources

[Reqwest documentation](https://docs.rs/reqwest/latest/reqwest/)

[Making HTTP Requests in Rust With Reqwest](https://blog.logrocket.com/making-http-requests-rust-reqwest/)

[Making HTTP requests in Rust with Reqwest](https://www.makeuseof.com/rust-reqwest-http-requests/)

[How to Work With TOML Files in Rust](https://www.makeuseof.com/working-with-toml-files-in-rust/)

[Rust Load a TOML File](https://codingpackets.com/blog/rust-load-a-toml-file/)