#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. Я нахожу это приятным, но я бы включил другие варианты использования для загрузки, если это возможно. Проницательный подход, ИМХО.