Downloads

Download a file to a temporary directory

reqwest-badge tempfile-badge cat-net-badge cat-filesystem-badge

Creates a temporary directory with tempfile::Builder and downloads a file over HTTP using reqwest::get asynchronously.

Creates a target File with name obtained from Response::url within tempfile::TempDir::path and copies downloaded data to it with io::copy. The temporary directory is automatically removed on program exit.

use anyhow::Result;
use std::io::Write;
use std::fs::File;
use tempfile::Builder;

fn main() -> Result<()> {
    let tmp_dir = Builder::new().prefix("example").tempdir()?;
    let target = "https://www.rust-lang.org/logos/rust-logo-512x512.png";
    let response = reqwest::blocking::get(target)?;

    let mut dest = {
        let fname = response
            .url()
            .path_segments()
            .and_then(|segments| segments.last())
            .and_then(|name| if name.is_empty() { None } else { Some(name) })
            .unwrap_or("tmp.bin");

        println!("file to download: '{}'", fname);
        let fname = tmp_dir.path().join(fname);
        println!("will be located under: '{:?}'", fname);
        File::create(fname)?
    };
    let content =  response.bytes()?;
    dest.write_all(&content)?;
    Ok(())
}

POST a file to paste-rs

reqwest-badge cat-net-badge

reqwest::Client establishes a connection to https://paste.rs following the reqwest::RequestBuilder pattern. Calling Client::post with a URL establishes the destination, RequestBuilder::body sets the content to send by reading the file, and RequestBuilder::send blocks until the file uploads and the response returns. read_to_string returns the message from the server response and displays in the console.

use anyhow::Result;
use std::fs::File;
use std::io::Read;

fn main() -> Result<()> {
    let paste_api = "https://paste.rs";
    let mut file = File::open("message")?;

    let mut contents = String::new();
    file.read_to_string(&mut contents)?;

    let client = reqwest::blocking::Client::new();
    let res = client.post(paste_api)
        .body(contents)
        .send()?;
    let response_text = res.text()?;
    println!("Your paste is located at: {}",response_text );
    Ok(())
}

Make a partial download with HTTP range headers

reqwest-badge cat-net-badge

Uses reqwest::blocking::Client::head to get the Content-Length of the response.

The code then uses reqwest::blocking::Client::get to download the content in chunks of 10240 bytes, while printing progress messages. This approach is useful to control memory usage for large files and allows for resumable downloads.

The Range header is defined in RFC7233.

use anyhow::{Result, anyhow};
use reqwest::header::{HeaderValue, CONTENT_LENGTH, RANGE};
use reqwest::StatusCode;
use std::fs::File;
use std::str::FromStr;

struct PartialRangeIter {
  start: u64,
  end: u64,
  buffer_size: u32,
}

impl PartialRangeIter {
  pub fn new(start: u64, end: u64, buffer_size: u32) -> Result<Self> {
    if buffer_size == 0 {
      return Err(anyhow!("invalid buffer_size, give a value greater than zero."));
    }
    Ok(PartialRangeIter {
      start,
      end,
      buffer_size,
    })
  }
}

impl Iterator for PartialRangeIter {
  type Item = HeaderValue;
  fn next(&mut self) -> Option<Self::Item> {
    if self.start > self.end {
      None
    } else {
      let prev_start = self.start;
      self.start += std::cmp::min(self.buffer_size as u64, self.end - self.start + 1);
      Some(HeaderValue::from_str(&format!("bytes={}-{}", prev_start, self.start - 1)).expect("string provided by format!"))
    }
  }
}

fn main() -> Result<()> {
  let url = "https://httpbin.org/range/102400?duration=2";
  const CHUNK_SIZE: u32 = 10240;
    
  let client = reqwest::blocking::Client::new();
  let response = client.head(url).send()?;
  let length = response
    .headers()
    .get(CONTENT_LENGTH)
    .ok_or_else(|| anyhow!("response doesn't include the content length"))?;
  let length = u64::from_str(length.to_str()?).map_err(|_| anyhow!("invalid Content-Length header"))?;
    
  let mut output_file = File::create("download.bin")?;
    
  println!("starting download...");
  for range in PartialRangeIter::new(0, length - 1, CHUNK_SIZE)? {
    println!("range {:?}", range);
    let mut response = client.get(url).header(RANGE, range).send()?;
    
    let status = response.status();
    if !(status == StatusCode::OK || status == StatusCode::PARTIAL_CONTENT) {
      return Err(anyhow!("Unexpected server response: {}", status));
    }
    std::io::copy(&mut response, &mut output_file)?;
  }
    
  let content = response.text()?;
  std::io::copy(&mut content.as_bytes(), &mut output_file)?;

  println!("Finished with success!");
  Ok(())
}