下载

将文件下载到临时目录

reqwest-badge tempdir-badge cat-net-badge cat-filesystem-badge

创建一个临时目录TempDir::new,并使用reqwest::get在 HTTP,同步下载一个文件。

创建一个目标File,姓名来自TempDir::path内部的Response::url,而io::copy就把下载的数据复制到其中。临时目录,会run函数返回时,自动移除。

#[macro_use]
extern crate error_chain;
extern crate reqwest;
extern crate tempdir;

use std::io::copy;
use std::fs::File;
use tempdir::TempDir;

error_chain! {
    foreign_links {
        Io(std::io::Error);
        HttpRequest(reqwest::Error);
    }
}

fn run() -> Result<()> {
    let tmp_dir = TempDir::new("example")?;
    let target = "https://www.rust-lang.org/logos/rust-logo-512x512.png";
    let mut response = reqwest::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)?
    };
    copy(&mut response, &mut dest)?;
    Ok(())
}

quick_main!(run);

POST 一个文件,到 paste.rs

reqwest-badge cat-net-badge

reqwest::Client建立起到 https://paste.rs 的一个连接,遵循着reqwest::RequestBuilder模式。调用Client::post,加上 目标 URL 参数,RequestBuilder::body通过读取文件,设置要发送的内容,以及RequestBuilder::send会堵塞,直到文件上载并返回响应。read_to_string返回响应,并显示在控制台中。

extern crate reqwest;

#[macro_use]
extern crate error_chain;

use std::fs::File;
use std::io::Read;
use reqwest::Client;

error_chain! {
    foreign_links {
        HttpRequest(reqwest::Error);
        IoError(::std::io::Error);
    }
}

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

    let mut response = Client::new().post(paste_api).body(file).send()?;
    let mut response_body = String::new();
    response.read_to_string(&mut response_body)?;
    println!("Your paste is located at: {}", response_body);
    Ok(())
}

quick_main!(run);

使用 HTTP range 标头,进行部分下载

reqwest-badge cat-net-badge

使用reqwest::Client::head,得到响应的内容长度

然后,代码使用reqwest::Client::get,在打印进度消息的同时,下载 10240 字节的内容。这个Range 标头指定块的大小和位置。

Range 标头在射频识别芯片中定义。

#[macro_use]
extern crate error_chain;
extern crate reqwest;

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


error_chain! {
    foreign_links {
        Io(std::io::Error);
        Reqwest(reqwest::Error);
        Header(reqwest::header::ToStrError);
    }
}

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 {
            Err("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);
            // 注意(unwrap): `HeaderValue::from_str` 仅在值不是 有效 ASCII 字符,才会失败
            // 因为,这个格式字符串是静态的,两个值是整数,
            // 也就是说,失败不会发生。
            Some(HeaderValue::from_str(&format!("bytes={}-{}", prev_start, self.start - 1)).unwrap())
        }
    }
}

fn run() -> Result<()> {
    let url = "https://httpbin.org/range/102400?duration=2";
    const CHUNK_SIZE: u32 = 10240;

    let client = reqwest::Client::new();
    let response = client.head(url).send()?;
    let length = response
        .headers()
        .get(CONTENT_LENGTH)
        .ok_or("response doesn't include the content length")?;
    let length = u64::from_str(length.to_str()?).map_err(|_| "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) {
            bail!("Unexpected server response: {}", status)
        }

        std::io::copy(&mut response, &mut output_file)?;
    }

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

quick_main!(run);