rustreqwest

How to POST a file using reqwest?


The documentation for reqwest v0.9.18 shows the following example of posting a file:

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()?;

The latest documentation for reqwest v0.11 no longer includes this example, and trying to build it fails with the following error when calling body():

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

What is the updated method for sending a file?


Solution

  • The specific example you're linking to, was prior to the reqwest crate using async. If you want to use that exact example, then instead of reqwest::Client, you need to use reqwest::blocking::Client. This also requires enabling the blocking feature.

    To be clear, you can actually still find that example, it's just located in the docs for reqwest::blocking::RequestBuilder's body() method instead.

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

    Also check out reqwest's Form and RequestBuilder's multipart() method, as there for instance is a file() method.


    If you do want to use async, then you can use FramedRead from the tokio-util crate. Along with the TryStreamExt trait, from the futures crate.

    Just make sure to enable the stream feature for reqwest, and the codec feature for tokio-util.

    // 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
    }