ダウンロード
一時ディレクトリにファイルをダウンロードする
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::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 ヘッダーを使って部分ダウンロードを行う
レスポンスの 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(())
}