Sling Academy
Home/Rust/Downloading Files in Rust via HTTP for CLI Tools

Downloading Files in Rust via HTTP for CLI Tools

Last updated: January 07, 2025

As developers look to harness the capabilities of Rust in creating efficient command-line interface (CLI) tools, one common requirement is downloading files via HTTP. Leveraging Rust's comprehensive ecosystem, particularly its asynchronous programming model, makes this task both powerful and straightforward.

Setting Up Your Rust Project

To get started, you need to have Rust and Cargo installed on your machine. If you haven’t already, head over to the Rust official installation page and follow the instructions.

Create a new Rust project using Cargo:

$ cargo new download_cli --bin

This will set up a new binary project. Navigate into the project directory:

$ cd download_cli

Add Required Dependencies

To perform HTTP requests, we'll use the reqwest crate, a popular and ergonomic HTTP client for Rust. Modify your Cargo.toml to add the following dependencies:

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

Basic HTTP Download Implementation

Let's write a simple function to download a file over HTTP. Start by opening src/main.rs and implementing the function:

use std::fs::File;
use std::io::{self, copy};
use std::path::Path;
use reqwest::blocking;

fn download_file(url: &str, destination: &Path) -> Result<(), Box<dyn std::error::Error>> {
    let response = blocking::get(url)?;
    let mut dest = File::create(destination)?;
    let content = response.bytes()?;
    copy(&mut content.as_ref(), &mut dest)?;
    Ok(())
}

This simple function performs a blocking HTTP GET request to the specified URL and writes the response body to the destination file.

Error Handling

In Rust, handling errors is a crucial part due to its emphasis on safety and control. Extend the above code to provide more informative error messages:

fn download_file(url: &str, destination: &Path) -> Result<(), Box<dyn std::error::Error>> {
    let response = blocking::get(url).map_err(|e| format!("Failed to send request: {}", e))?;
    let mut dest = File::create(destination).map_err(|e| format!("Failed to create file: {}", e))?;
    let content = response.bytes().map_err(|e| format!("Failed to read response bytes: {}", e))?;
    copy(&mut content.as_ref(), &mut dest).map_err(|e| format!("Failed to copy content: {}", e))?;
    Ok(())
}

CLI Application with Argument Parsing

To make this function accessible from the command line, we'll integrate argument parsing. Use the clap crate, a streamlined library for creating clear and concise CLI command structures:

[dependencies]
clap = { version = "4", features = ["derive"] }

Modify your main.rs to handle command-line arguments:

use clap::Parser;
use std::path::PathBuf;

#[derive(Parser)]
struct Cli {
    /// The URL to download from
    url: String,
    /// The file path where to save
    destination: PathBuf,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args = Cli::parse();
    download_file(&args.url, &args.destination).map_err(|e| println!("Application error: {}", e))
}

After adding these elements to the program, run your CLI tool from the terminal like this:

$ cargo run -- "http://example.com/file" "output.file"

This simple yet effective method allows you to download files directly from the internet to specified destinations using Rust, wrapping up our tour of building a file-downloading tool for the command line.

Next Article: Understanding Domain Name Resolution in Rust

Previous Article: Working with Cookies and Sessions in Rust Web Applications

Series: Networking in Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior
  • Enforcing runtime invariants with generic phantom types in Rust