如何使用 multipart/form-data 和 hyper post 图像?
How to post an image using multipart/form-data with hyper?
我正在尝试 post 使用 hyper 的图像文件,就像 cURL 所做的那样:
curl -F smfile=@11.jpg https://httpbin.org/post --trace-ascii -
结果是:
{
"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"
}
我了解到 Content-Type 应该设置为 multipart/form-data
并带有边界标记。这是我的代码:
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
}
请注意,运行 此代码需要名为 11.jpg 的 JPEG 文件。这可以是任何 JPEG 文件。
httpbin 显示我post什么也没编辑:
{
"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"
}
我不知道如何解决这个问题。
您没有在最终边界之前正确放置 newline/carriage return 对。
以下是我编写正文生成代码的方式,需要较少的分配:
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)
}
调用这段代码也可以简化:
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))
}));
}
我正在尝试 post 使用 hyper 的图像文件,就像 cURL 所做的那样:
curl -F smfile=@11.jpg https://httpbin.org/post --trace-ascii -
结果是:
{
"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"
}
我了解到 Content-Type 应该设置为 multipart/form-data
并带有边界标记。这是我的代码:
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
}
请注意,运行 此代码需要名为 11.jpg 的 JPEG 文件。这可以是任何 JPEG 文件。
httpbin 显示我post什么也没编辑:
{
"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"
}
我不知道如何解决这个问题。
您没有在最终边界之前正确放置 newline/carriage return 对。
以下是我编写正文生成代码的方式,需要较少的分配:
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)
}
调用这段代码也可以简化:
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))
}));
}