Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ダウンロード

一時ディレクトリにファイルをダウンロードする

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

tempfile::Builder を使って一時ディレクトリを作成し、reqwest::get を使用して HTTP 経由でファイルを非同期にダウンロードします。

Response::url から取得した名前で、tempfile::TempDir::path 配下に対象の File を作成し、ダウンロードしたデータを io::copy でそこにコピーします。 一時ディレクトリは、プログラムの終了時に自動的に削除されます。

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(())
}

paste-rs にファイルを POST する

reqwest-badge cat-net-badge

reqwest::Client は、reqwest::RequestBuilder パターンに従って https://paste.rs への接続を確立します。URL を指定して Client::post を呼び出すと 送信先が設定され、RequestBuilder::body はファイルを読み取って送信する内容を設定し、 RequestBuilder::send はファイルのアップロードが完了してレスポンスが返るまで ブロックします。read_to_string はサーバーのレスポンスからメッセージを返し、 コンソールに表示します。

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(())
}

HTTP Range ヘッダーを使って部分ダウンロードを行う

reqwest-badge cat-net-badge

レスポンスの Content-Length を取得するために reqwest::blocking::Client::head を使用します。

続いて、コードは reqwest::blocking::Client::get を使用して、進捗メッセージを表示しながらコンテンツを 10240 バイト単位のチャンクでダウンロードします。このアプローチは、大きなファイルのメモリ使用量を抑えるのに役立ち、ダウンロードの再開も可能にします。

Range ヘッダーは 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)?;
  }
    
  println!("Finished with success!");
  Ok(())
}