如何使用reqwest进行文件上传?

12
reqwest v0.9.18 的文档展示了一个上传文件的例子:
let file = fs::File::open("from_a_file.txt")?;
let client = reqwest::Client::new();
let res = client.post("http://httpbin.org/post")
    .body(file)
    .send()?;

reqwest v0.11 的最新文档不再包含此示例,当调用 body() 时尝试构建会失败,并出现以下错误:

the trait `From<std::fs::File>` is not implemented for `Body`

如何更新发送文件的方法?


正文需要字节,所以只需将文件转储为字节即可? - Netwave
我不想偏离问题,但如果有另一个库可以轻松完成这个任务而不是使用reqwest,我很乐意切换。理想情况下,文件应该是流式传输的,而不是提前全部读入内存。 - Eric
3个回答

21

你链接的具体示例是在 reqwest crate 使用异步之前的。 如果你想使用这个确切的示例,那么需要使用 reqwest::blocking::Client,而不是 reqwest::Client。 这还需要启用blocking功能。

明确一点,你仍然可以找到那个示例,只是它位于reqwest::blocking::RequestBuilderbody()方法的文档中。

// reqwest = { version = "0.11", features = ["blocking"] }
use reqwest::blocking::Client;
use std::fs::File;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file = File::open("from_a_file.txt")?;

    let client = Client::new();
    let res = client.post("http://httpbin.org/post")
        .body(file)
        .send()?;

    Ok(())
}

还要查看reqwestFormRequestBuildermultipart()方法,例如有一个file()方法。


如果您想使用异步,则可以使用来自tokio-util crateFramedRead。还需要从futures crate中使用TryStreamExt trait。

请确保为reqwest启用stream功能,并为tokio-util启用codec功能。

// futures = "0.3"
use futures::stream::TryStreamExt;

// reqwest = { version = "0.11", features = ["stream"] }
use reqwest::{Body, Client};

// tokio = { version = "1.0", features = ["full"] }
use tokio::fs::File;

// tokio-util = { version = "0.6", features = ["codec"] }
use tokio_util::codec::{BytesCodec, FramedRead};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file = File::open("from_a_file.txt").await?;

    let client = reqwest::Client::new();
    let res = client
        .post("http://httpbin.org/post")
        .body(file_to_body(file))
        .send()
        .await?;

    Ok(())
}

fn file_to_body(file: File) -> Body {
    let stream = FramedRead::new(file, BytesCodec::new());
    let body = Body::wrap_stream(stream);
    body
}

有没有办法在wrap_stream上连接一些监视器?我正在尝试为POST调用制作进度条,就像https://bashupload.com/how_to_upload_progress_curl中的那样。 - ozkanpakdil
1
有没有一种使用异步和多部分表单来完成这个的方法? - Eray Erdin

3
如果您想使用 multipart/form-data 并且已经在使用 Tokio,这种方法可能会对您有所帮助。

1. 设置依赖项

# Cargo.toml

[dependencies]
tokio = { version = "1.19", features = ["macros", "rt-multi-thread"] }
reqwest = { version = "0.11.11", features = ["stream","multipart","json"] }
tokio-util = { version = "0.7.3", features = ["codec"] }

2. 使用multipart/form-data上传文件

use reqwest::{multipart, Body, Client};
use tokio::fs::File;
use tokio_util::codec::{BytesCodec, FramedRead};

async fn reqwest_multipart_form(url: &str) -> anyhow::Result<String> {
    let client = Client::new();
    let file = File::open(".gitignore").await?;

    // read file body stream
    let stream = FramedRead::new(file, BytesCodec::new());
    let file_body = Body::wrap_stream(stream);

    //make form part of file
    let some_file = multipart::Part::stream(file_body)
        .file_name("gitignore.txt")
        .mime_str("text/plain")?;

    //create the multipart form
    let form = multipart::Form::new()
        .text("username", "seanmonstar")
        .text("password", "secret")
        .part("file", some_file);

    //send request
    let response = client.post(url).multipart(form).send().await?;
    let result = response.text().await?;

    Ok(result)
}

3. Unit Testing

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_post_form_file() {
        let url = "http://httpbin.org/post?a=1&b=true";
        let get_json = reqwest_multipart_form(url).await.unwrap();

        println!("users: {:#?}", get_json);
    }
}

这明显比被接受的答案更糟糕,有很多噪音和不必要的部分,比如表单,并且没有解释。 - Chayim Friedman
我认为这很好,但如果可能的话,我会包括其他上传用例。在我看来,这是一种有见地的方法。 - Esteban Borai
这是唯一一个对我实际起作用的答案;我需要多部分表单。谢谢。 - Carlos Sanchez

0

使用启用了hyper特性的crate streamer可以为您完成此操作:

use hyper::{Body, Request}:
let file = File::open("from_a_file.txt").unwrap();
let mut streaming = Streamer::new(file)
// optional, set the field name
// streaming.meta.set_name("txt"); 
// optional, set the file name
streaming.meta.set_filename("from_a_file.txt");
// length sent as a chunk, the default is 64kB if not set
streaming.meta.set_buf_len(1024 * 1024); 

let body: Body = streaming.streaming();
// build a request 
let request: Request<Body> = Request::post("<uri-here>").body(body).expect("failed to build a request");

streamer 将以 1 兆字节的块流式传输您的文件


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接