Weekly Rust Trivia is a problem-oriented series of articles that assist developers while learning Rust. Every article solves simple, everyday development tasks using the Rust standard library or leveraging popular and proven crates.
Question: How to download an image from the web and store it in a file using Rust?
The reqwest
crate allows us to create HTTP requests with ease, we can combine functionality provided by the crate with the fs
and io
modules from Rusts standard library to solve this trivia. We also use the anyhow
crate, to streamline error handling.
Today, we will create two implementations! The first implementation uses the blocking
client API provided by reqwest
. The second implementation will be non-blocking by leveraging the tokio
crate as async
runtime.
Blocking implementation
First let’s add the following dependencies using cargo add
:
# Add anyhow as a dependency
cargo add anyhow
# Add reqwest with blocking feature
cargo add reqwest -F blocking
Having the crates added to our project, we can move on and implement downloading and storing the image from an URL:
use std::{fs::File, io::copy};
use anyhow::Result;
fn download_image(url: &str, file_name: &str) -> Result<()> {
// Send an HTTP GET request to the URL
let mut response = reqwest::blocking::get(url)?;
// Create a new file to write the downloaded image to
let mut file = File::create(file_name)?;
// Copy the contents of the response to the file
copy(&mut response, &mut file)?;
Ok(())
}
In this example, we pass the URL of the image (url
) and the desired file name (file_name
) as arguments to the download_image
function, which returns a anyhow::Result<()>
.
First, we use reqwest::blocking::get
to issue the HTTP GET request for downloading the image from the given url
, which returns a reqwest::Response
once the download has finished. By adding the ?
(question mark operator), we immediately return any potential error that happens during the request. Next, we use File::create
to create the file on the local machine. Again ?
is used to return immediately in the case of any error. Finally, we use the copy
function from the io
module to copy the entire contents of the response body into the newly created file.
We can use the download_image
function as shown here:
fn main() {
let image_url = "https://www.rust-lang.org/static/images/rust-logo-blk.svg";
let file_name = "rust-logo-blk.svg";
match download_image_to(image_url, file_name) {
Ok(_) => println!("image saved successfully"),
Err(e) => println!("error while downloading image: {}", e),
}
}
Non-blocking (async) implementation
In contrast to the first iteration, we can use tokio
and leverage non-blocking APIs provided by reqwest
to solve the trivia by applying asynchronous programming techniques. First, let’s add the necessary dependencies:
# Add anyhow as a dependency
cargo add anyhow
# Add reqwest with blocking feature
cargo add reqwest -F blocking
# Add tokio with full featureset
cargo add tokio -F full
Having the crates added to our project, we can move on and implement downloading and storing the image from an URL:
use std::{fs::File, io::{copy, Cursor}};
use anyhow::Result;
async fn download_image_to(url: &str, file_name: &str) -> Result<()> {
// Send an HTTP GET request to the URL
let response = reqwest::get(url).await?;
// Create a new file to write the downloaded image to
let mut file = File::create(file_name)?;
// Create a cursor that wraps the response body
let mut content = Cursor::new(response.bytes().await?);
// Copy the content from the cursor to the file
copy(&mut content, &mut file)?;
Ok(())
}
In contrast to the previous implementation, we mark our download_image_to
function as async
. Instead of leveraging the blocking API of reqwest
, we call reqwest::get("").await?
and wait for the server to return a Response
. Next, we use std::io::Cursor
to create an in-memory reader for the received response, which we’ll then pass to the copy
function.
We can use the async download_image
function as shown here:
#[tokio::main]
async fn main() -> Result<()> {
let image_url = "https://www.rust-lang.org/static/images/rust-logo-blk.svg";
let file_name = "rust-logo-blk.svg";
match download_image_to(image_url, file_name).await {
Ok(_) => println!("image saved successfully"),
Err(e) => println!("error while downloading image: {}", e),
}
Ok(())
}