Rust HTTP Requests Explanation

Why Use Rust for HTTP Requests?

Almost every application today needs to send or receive data over the web, interact with APIs, or communicate between services. And one of the most common tasks is making HTTP requests.

While many languages (like Python, JavaScript, or Go) offer easy ways to handle HTTP, Rust is becoming a powerful, and not just for speed, but also for safety, reliability, and control.

In this article, let’s understand why Rust is an excellent choice for HTTP requests, and why more developers and companies are switching to it for network-heavy projects.

When you make HTTP requests in Rust (using libraries like reqwest, hyper, or surf), they execute with minimal overhead.

This is especially valuable when:

  • You’re building microservices that handle thousands of requests per second.
  • Your application needs to fetch data from APIs at high frequency.
  • You’re working with real-time data, like live market feeds or IoT devices.

HTTP Request Methods in Rust

Common HTTP methods include:

  1. GET: Retrieve data from a server.
  2. POST: Send data to a server.
  3. PUT: Update data on a server.
  4. DELETE: Delete data from a server.

In Rust, we can make all these requests easily using the reqwest crate. It’s one of the most popular and user-friendly HTTP clients for async programming.

1. GET Request – Fetching Data

A GET request is used when you want to retrieve data from a server without modifying anything. It’s the most common HTTP method, like loading a webpage or reading data from an API.

Example: Fetching a post from an API

use reqwest;
use tokio;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Send a GET request to the API
let response = reqwest::get("https://jsonplaceholder.typicode.com/posts/1")
.await? // Wait for the response asynchronously
.text() // Convert the response body to a string
.await?;

println!("Response from server:\n{}", response);
Ok(())
}

Explanation

  1. reqwest::get: Sends a GET request to the given URL.
  2. await?: Waits for the server’s response asynchronously.
  3. text(): Converts the response body into a readable string.

2. POST Request – Sending Data

A POST request is used to send new data to a server, for example, adding a new user, submitting a form, or creating a new blog post.

Example: Sending JSON Data to a server

use reqwest::Client;
use tokio;
use serde_json::json;

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

// Prepare and send a POST request with JSON body
let response = client
.post("https://jsonplaceholder.typicode.com/posts")
.json(&json!({
"title": "Learning Rust HTTP",
"body": "This is a new post created with Rust.",
"userId": 101
}))
.send()
.await?;

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

Explanation

  1. Client::new() – Creates an HTTP client to send custom requests.
  2. .post() – Specifies the HTTP method as POST.
  3. .json() – Converts a Rust structure into JSON and sends it as the request body.
  4. .send() – Sends the request and waits for the response.

3. PUT Request – Updating Data

A PUT request is used to update existing data on the server. It usually replaces the entire resource with the new data you send.

Example: Updating Data

use reqwest::Client;
use tokio;
use serde_json::json;

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

// Update a post with new data
let response = client
.put("https://jsonplaceholder.typicode.com/posts/1")
.json(&json!({
"id": 1,
"title": "Updated Rust Title",
"body": "This post was updated using a PUT request.",
"userId": 101
}))
.send()
.await?;

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

Explanation

  • .put() – Specifies that we want to update data.
  • .json() – Sends the new version of the resource.
  • .send() – Executes the update request.

4. DELETE Request – Removing Data

A DELETE request tells the server to remove a specific resource. This could mean deleting a user, removing a product, or clearing a record from a database.

Example: Deleting Data

use reqwest::Client;
use tokio;

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

// Send a DELETE request
let response = client
.delete("https://jsonplaceholder.typicode.com/posts/1")
.send()
.await?;

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

Explanation

  • .delete() – Defines the request type as DELETE.
  • .send() – Sends the request to the server.
  • .status() – Checks the status code (e.g., 200 OK, 204 No Content) to verify if deletion was successful.

Advanced Features in Rust HTTP Requests

Once you understand the basic HTTP methods (GET, POST, PUT, DELETE), the next step is to explore advanced features that make your requests more powerful and production-ready. These include:

  • Error handling
  • Adding headers
  • Concurrency

Let’s learn each of these with simple examples.

Handling Errors

When you send an HTTP request, there’s always a chance it might fail, maybe the server is down, the URL is wrong, or the internet connection is lost. Rust safely handles these errors using the result type.

Here’s how you can check if the request was successful before processing it:

use reqwest;
use tokio;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let response = reqwest::get("https://jsonplaceholder.typicode.com/posts/1010").await?;

// Check status code
if response.status().is_success() {
let body = response.text().await?;
println!("Request succeeded!\nResponse Body:\n{}", body);
} else {
println!("Request failed with status: {}", response.status());
}

Ok(())
}

Adding Custom Headers

Sometimes, you need to send extra information with your request, such as:

  • User-Agent
  • Authorization
  • Content-Type

You can do this by adding headers to the request.

use reqwest::{Client, header};
use tokio;

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

let response = client
.get("https://httpbin.org/headers")
.header(header::USER_AGENT, "Rust HTTP Client 1.0")
.header(header::ACCEPT, "application/json")
.send()
.await?;

println!("📨 Headers sent successfully!");
println!("Response:\n{}", response.text().await?);

Ok(())
}

Concurrency – Sending Multiple Requests Together

One of Rust’s greatest strengths is concurrency, doing many tasks at once.

For HTTP, this means sending multiple requests in parallel instead of waiting for each one to finish before starting the next.

This is super useful when you need to fetch data from several endpoints quickly.

use reqwest;
use tokio;
use futures::future;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let urls = vec![
"https://jsonplaceholder.typicode.com/posts/1",
"https://jsonplaceholder.typicode.com/posts/2",
"https://jsonplaceholder.typicode.com/posts/3",
];

// Create a list of async GET requests
let requests = urls.iter().map(|&url| reqwest::get(url));

// Run them all together
let results = future::join_all(requests).await;

// Process responses
for (i, result) in results.into_iter().enumerate() {
match result {
Ok(resp) => {
let text = resp.text().await.unwrap_or("No content".to_string());
println!("Response {}:\n{}\n", i + 1, text);
}
Err(err) => {
println!("Request {} failed: {:?}", i + 1, err);
}
}
}

Ok(())
}
  • join_all() runs all HTTP requests at the same time.
  • The loop checks each result and handles errors individually.

Learn More About Rust Programming

Leave a Comment