31 个版本
0.5.5 | 2024 年 5 月 12 日 |
---|---|
0.5.4 | 2024 年 3 月 7 日 |
0.5.3 | 2024 年 1 月 1 日 |
0.5.2 | 2023 年 12 月 31 日 |
0.0.6 | 2020 年 6 月 29 日 |
#121 在 HTTP 服务器
1,409 每月下载量
在 3 个 Crates 中使用(通过 viz-core)
56KB
993 行
form-data
AsyncRead/AsyncWrite/Stream for `multipart/form-data` rfc7578
功能
-
Stream:
Form
,Field
-
AsyncRead/Read:
Field
,轻松读取/复制字段数据到任何地方。 -
Fast: Hyper 默认支持更大的缓冲区,超过 8KB,可能高达 512KB。
AsyncRead 限制为 8KB。如果我们想接收大缓冲区并将它们保存到写入器或文件中,请参阅 hyper 示例
-
将
max_buf_size
设置为 FormData,form_data.set_max_buf_size(512 * 1024)?;
-
使用
copy_to
,将大缓冲区复制到写入器(AsyncRead
),field.copy_to(&mut writer)
-
使用
copy_to_file
,将大缓冲区复制到文件(File
),field.copy_to_file(&mut file)
-
-
预解析部分标题
示例
请求有效载荷,示例来自 jaydenseric/graphql-multipart-request-spec。
--------------------------627436eaefdbc285
Content-Disposition: form-data; name="operations"
[{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } }, { "query": "mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }", "variables": { "files": [null, null] } }]
--------------------------627436eaefdbc285
Content-Disposition: form-data; name="map"
{ "0": ["0.variables.file"], "1": ["1.variables.files.0"], "2": ["1.variables.files.1"] }
--------------------------627436eaefdbc285
Content-Disposition: form-data; name="0"; filename="a.txt"
Content-Type: text/plain
Alpha file content.
--------------------------627436eaefdbc285
Content-Disposition: form-data; name="1"; filename="b.txt"
Content-Type: text/plain
Bravo file content.
--------------------------627436eaefdbc285
Content-Disposition: form-data; name="2"; filename="c.txt"
Content-Type: text/plain
Charlie file content.
--------------------------627436eaefdbc285--
use anyhow::Result;
use async_fs::File;
use bytes::BytesMut;
use tempfile::tempdir;
use futures_util::{
io::{self, AsyncReadExt, AsyncWriteExt},
stream::{self, TryStreamExt},
};
use http_body_util::StreamBody;
use form_data::*;
#[path = "./lib/mod.rs"]
mod lib;
use lib::{tracing_init, Limited};
#[tokio::test]
async fn hyper_body() -> Result<()> {
tracing_init()?;
let payload = File::open("tests/fixtures/graphql.txt").await?;
let stream = Limited::random_with(payload, 256);
let limit = stream.limit();
let body = StreamBody::new(stream);
let mut form = FormData::new(body, "------------------------627436eaefdbc285");
form.set_max_buf_size(limit)?;
while let Some(mut field) = form.try_next().await? {
assert!(!field.consumed());
assert_eq!(field.length, 0);
match field.index {
0 => {
assert_eq!(field.name, "operations");
assert_eq!(field.filename, None);
assert_eq!(field.content_type, None);
// reads chunks
let mut buffer = BytesMut::new();
while let Some(buf) = field.try_next().await? {
buffer.extend_from_slice(&buf);
}
assert_eq!(buffer, "[{ \"query\": \"mutation ($file: Upload!) { singleUpload(file: $file) { id } }\", \"variables\": { \"file\": null } }, { \"query\": \"mutation($files: [Upload!]!) { multipleUpload(files: $files) { id } }\", \"variables\": { \"files\": [null, null] } }]");
assert_eq!(field.length, buffer.len());
assert!(field.consumed());
tracing::info!("{:#?}", field);
}
1 => {
assert_eq!(field.name, "map");
assert_eq!(field.filename, None);
assert_eq!(field.content_type, None);
// reads bytes
let buffer = field.bytes().await?;
assert_eq!(buffer, "{ \"0\": [\"0.variables.file\"], \"1\": [\"1.variables.files.0\"], \"2\": [\"1.variables.files.1\"] }");
assert_eq!(field.length, buffer.len());
assert!(field.consumed());
tracing::info!("{:#?}", field);
}
2 => {
tracing::info!("{:#?}", field);
assert_eq!(field.name, "0");
assert_eq!(field.filename, Some("a.txt".into()));
assert_eq!(field.content_type, Some(mime::TEXT_PLAIN));
let dir = tempdir()?;
let filename = field.filename.as_ref().unwrap();
let filepath = dir.path().join(filename);
let mut writer = File::create(&filepath).await?;
let bytes = io::copy(field, &mut writer).await?;
writer.close().await?;
// async ?
let metadata = std::fs::metadata(&filepath)?;
assert_eq!(metadata.len(), bytes);
let mut reader = File::open(&filepath).await?;
let mut contents = Vec::new();
reader.read_to_end(&mut contents).await?;
assert_eq!(contents, "Alpha file content.\r\n".as_bytes());
dir.close()?;
}
3 => {
assert_eq!(field.name, "1");
assert_eq!(field.filename, Some("b.txt".into()));
assert_eq!(field.content_type, Some(mime::TEXT_PLAIN));
let mut buffer = Vec::with_capacity(4);
let bytes = field.read_to_end(&mut buffer).await?;
assert_eq!(buffer, "Bravo file content.\r\n".as_bytes());
assert_eq!(field.length, bytes);
assert_eq!(field.length, buffer.len());
tracing::info!("{:#?}", field);
}
4 => {
assert_eq!(field.name, "2");
assert_eq!(field.filename, Some("c.txt".into()));
assert_eq!(field.content_type, Some(mime::TEXT_PLAIN));
let mut string = String::new();
let bytes = field.read_to_string(&mut string).await?;
assert_eq!(string, "Charlie file content.\r\n");
assert_eq!(field.length, bytes);
assert_eq!(field.length, string.len());
tracing::info!("{:#?}", field);
}
_ => {}
}
}
let state = form.state();
let state = state
.try_lock()
.map_err(|e| Error::TryLockError(e.to_string()))?;
assert!(state.eof());
assert_eq!(state.total(), 5);
assert_eq!(state.len(), 1027);
Ok(())
}
许可协议
本软件许可在Apache License, Version 2.0或MIT许可证下使用,具体由您选择。除非您明确表示,否则您提交给本软件的任何贡献,根据Apache-2.0许可证定义,将如上所述双重许可,不附加任何额外条款或条件。
依赖项
~1.5–2.6MB
~48K SLoC