I'm trying to post an image file using hyper like cURL does:
curl -F [email protected] https://httpbin.org/post --trace-ascii -
The result is:
{
"args": {},
"data": "",
"files": {
"smfile": "data:image/jpeg;base64,..."
},
"form": {},
"headers": {
"Accept": "/",
"Connection": "close",
"Content-Length": "1709",
"Content-Type": "multipart/form-data; boundary=------------------------58370e136081470e",
"Expect": "100-continue",
"Host": "httpbin.org",
"User-Agent": "curl/7.59.0"
},
"json": null,
"origin": "myip",
"url": "https://httpbin.org/post"
}
I learned that Content-Type should be set to multipart/form-data
with a boundary mark. Here's my code:
extern crate futures;
extern crate hyper;
extern crate hyper_tls;
extern crate tokio;
use futures::{future, Future};
use hyper::header::CONTENT_TYPE;
use hyper::rt::Stream;
use hyper::{Body, Client, Method, Request};
use hyper_tls::HttpsConnector;
use std::fs::File;
use std::io::prelude::*;
use std::io::{self, Write};
const BOUNDARY: &'static str = "------------------------ea3bbcf87c101592";
fn main() {
tokio::run(future::lazy(|| {
let https = HttpsConnector::new(4).unwrap();
let client = Client::builder().build::<_, hyper::Body>(https);
let mut req = Request::new(Body::from(image_data()));
req.headers_mut().insert(
CONTENT_TYPE,
format!("multipart/form-data; boundary={}", BOUNDARY)
.parse()
.unwrap(),
);
*req.method_mut() = Method::POST;
*req.uri_mut() = "https://httpbin.org/post".parse().unwrap();
client
.request(req)
.and_then(|res| {
println!("status: {}", res.status());
res.into_body().for_each(|chunk| {
io::stdout()
.write_all(&chunk)
.map_err(|e| panic!("stdout error: {}", e))
})
})
.map_err(|e| println!("request error: {}", e))
}));
}
fn image_data() -> Vec<u8> {
let mut result: Vec<u8> = Vec::new();
result.extend_from_slice(format!("--{}\r\n", BOUNDARY).as_bytes());
result
.extend_from_slice(format!("Content-Disposition: form-data; name=\"text\"\r\n").as_bytes());
result.extend_from_slice("title\r\n".as_bytes());
result.extend_from_slice(format!("--{}\r\n", BOUNDARY).as_bytes());
result.extend_from_slice(
format!("Content-Disposition: form-data; name=\"smfile\"; filename=\"11.jpg\"\r\n")
.as_bytes(),
);
result.extend_from_slice("Content-Type: image/jpeg\r\n\r\n".as_bytes());
let mut f = File::open("11.jpg").unwrap();
let mut file_data = Vec::new();
f.read_to_end(&mut file_data).unwrap();
result.append(&mut file_data);
result.extend_from_slice(format!("--{}--\r\n", BOUNDARY).as_bytes());
result
}
(complete code)
Note that a JPEG file named 11.jpg is needed to run this code. This can be any JPEG file.
httpbin shows that I posted nothing:
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Connection": "close",
"Content-Length": "1803",
"Content-Type": "multipart/form-data; boundary=------------------------ea3bbcf87c101592",
"Host": "httpbin.org"
},
"json": null,
"origin": "myip",
"url": "https://httpbin.org/post"
}
I have no idea how to fix this.
To do this, post the photo to the Memories resource with the Content-Type set to multipart/form-data . Provide the description of each resource in the first part. Include the title and the qualifiers and any other metadata. Provide the images in each subsequent part with the Content-Type set to image/jpeg .
Multipart form data: The ENCTYPE attribute of <form> tag specifies the method of encoding for the form data. It is one of the two ways of encoding the HTML form. It is specifically used when file uploading is required in HTML form. It sends the form data to server in multiple parts because of large size of file.
You aren't correctly placing a newline/carriage return pair before your final boundary.
Here's how I'd write your body generation code, requiring less allocation:
fn image_data() -> io::Result<Vec<u8>> {
let mut data = Vec::new();
write!(data, "--{}\r\n", BOUNDARY)?;
write!(data, "Content-Disposition: form-data; name=\"smfile\"; filename=\"11.jpg\"\r\n")?;
write!(data, "Content-Type: image/jpeg\r\n")?;
write!(data, "\r\n")?;
let mut f = File::open("11.jpg")?;
f.read_to_end(&mut data)?;
write!(data, "\r\n")?; // The key thing you are missing
write!(data, "--{}--\r\n", BOUNDARY)?;
Ok(data)
}
Calling this code can also be simplified:
fn main() {
let https = HttpsConnector::new(4).unwrap();
let client = Client::builder().build::<_, hyper::Body>(https);
let data = image_data().unwrap();
let req = Request::post("https://httpbin.org/post")
.header(CONTENT_TYPE, &*format!("multipart/form-data; boundary={}", BOUNDARY))
.body(data.into())
.unwrap();
tokio::run(future::lazy(move || {
client
.request(req)
.and_then(|res| {
println!("status: {}", res.status());
res.into_body().for_each(|chunk| {
io::stdout()
.write_all(&chunk)
.map_err(|e| panic!("stdout error: {}", e))
})
})
.map_err(|e| println!("request error: {}", e))
}));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With