Как ОПУБЛИКОВАТЬ файл с помощью reqwest?

#rust #reqwest

#Ржавчина #reqwest

Вопрос:

В документации для reqwest версии 0.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 версии 0.11 больше не включает этот пример, и попытка его сборки завершается неудачей со следующей ошибкой при вызове body() :

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

Каков обновленный метод отправки файла?

Комментарии:

1. для тела требуются байты (?), Поэтому просто сбросьте файл в байты?

2. Я не хотел отвлекать внимание от вопроса, но если есть другая библиотека, которая упрощает это вместо reqwest, я был бы рад переключиться. В идеале файл будет передаваться в потоковом режиме, а не считываться в память заранее.

Ответ №1:

Конкретный пример, на который вы ссылаетесь, был до того, как reqwest ящик использовал async. Если вы хотите использовать именно этот пример, то вместо reqwest::Client , вам нужно использовать reqwest::blocking::Client . Для этого также требуется включить эту blocking функцию.

Чтобы было понятно, вы все равно можете найти этот пример, он просто находится в документации для reqwest::blocking::RequestBuilder body() метода.

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

Также проверьте reqwest ‘s Form и RequestBuilder ‘s multipart() метод, так как там, например, есть file() метод.


Если вы действительно хотите использовать асинхронность, то вы можете использовать FramedRead из tokio-util ящика. Вместе с TryStreamExt чертой, из futures ящика.

Просто убедитесь , что включена stream функция для reqwest и codec функция для 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
}
 

Комментарии:

1. есть ли способ подключить какой-либо монитор к wrap_stream? Я пытаюсь создать индикатор выполнения для POST-вызова, как в bashupload.com/how_to_upload_progress_curl

2. Есть ли способ сделать это с помощью формы в асинхронном режиме, с помощью multipart?

Ответ №2:

стример ящиков может сделать это за вас с hyper включенной функцией:

 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 мегабайту

Ответ №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: amp;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. Модульное тестирование

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

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

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

Комментарии:

1. Это явно хуже, чем принятый ответ, имеет много шума и не обязательных частей, таких как формы, и не имеет объяснений.

2. Я нахожу это приятным, но я бы включил другие варианты использования для загрузки, если это возможно. Проницательный подход, ИМХО.