seanmonstar / reqwest

An easy and powerful Rust HTTP Client
https://docs.rs/reqwest
Apache License 2.0
9.79k stars 1.1k forks source link

Adding file function to async::multipart::Form #646

Closed visceralfield closed 6 days ago

visceralfield commented 5 years ago

In the interest of feature parity between multipart::Form and async::multipart::Form, would a file function similar to the existing one for multipart::Form for async::multipart::Form be in scope for this library? I'm thinking something using tokio::fs::File. I have yet to explore implementing this but if there is interest I can start experimenting and open a PR.

seanmonstar commented 5 years ago

Yes, I think this is especially easier in current master using tokio 0.2, since then tokio::fs works in any executor.

I also at times wonder if it'd be feasible to combine the two multipart builders...

visceralfield commented 5 years ago

Good to hear! I haven't used tokio directly much before, but I'll start a branch this weekend time permitting.

I think combining the multipart builders into one is worthwhile. I haven't looked at the internals of the builders, but logically separating Forms/Parts and the Client that sends them completely seems like a good design move.

gliderkite commented 4 years ago

Is there any update for this? Thanks.

cetra3 commented 4 years ago

You can do this with a few bits and pieces as I have done in lorikeet:

use tokio::fs::File;
use tokio_util::codec::{BytesCodec, FramedRead};

let file_name = path
    .file_name()
    .map(|val| val.to_string_lossy().to_string())
    .unwrap_or_default();

let file = File::open(&path)
    .await?;

let reader = Body::wrap_stream(FramedRead::new(file, BytesCodec::new()));
form.part(field_name, Part::stream(reader).file_name(file_name))

It would be nice to have this method back inside reqwest though.

taozhi8833998 commented 2 years ago

You can do this with a few bits and pieces as I have done in lorikeet:

use tokio::fs::File;
use tokio_util::codec::{BytesCodec, FramedRead};

let file_name = path
    .file_name()
    .map(|val| val.to_string_lossy().to_string())
    .unwrap_or_default();

let file = File::open(&path)
    .await?;

let reader = Body::wrap_stream(FramedRead::new(file, BytesCodec::new()));
form.part(field_name, Part::stream(reader).file_name(file_name))

It would be nice to have this method back inside reqwest though.

thx for the codes, it save my life. especially for the .file_name(file_name)

qbittorrentfan commented 1 year ago

Using the snippet provided above by @cetra3, i ended up with some weird errors. The backend logged some unknown headers with a numerical name, and provided two responses (200 and 400 respectively) to the same query. After some debugging i figured the backend did not support chunked requests.

In that case, it's useful to read_to_end the file and add it to the form with Part::bytes. That solved my problems.

Hope that helps someone else avoid the hours of hair pulling i've been through, and that async multipart can find its way into reqwest proper :)

NOTE: That's a weird edge-case but in that case reqwest interpreted receiving the two responses (200 and 400) by only reading the first response, making me believe the request went fine... although the backend didn't succeed. After some wireshark the situation was more clear to me, and i'm not sure if something should be done differently, but i think it's weird enough to mention.

NaokiM03 commented 1 week ago

This is available since v0.12.8.

@seanmonstar

I also at times wonder if it'd be feasible to combine the two multipart builders...

Is this the same idea now? Including wasm, there are the three multipart.

seanmonstar commented 6 days ago

I no longer worry about it. But if there's parts to deduplicate, cool!