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 日

#121HTTP 服务器

Download history 233/week @ 2024-05-03 388/week @ 2024-05-10 224/week @ 2024-05-17 479/week @ 2024-05-24 253/week @ 2024-05-31 336/week @ 2024-06-07 389/week @ 2024-06-14 436/week @ 2024-06-21 208/week @ 2024-06-28 295/week @ 2024-07-05 293/week @ 2024-07-12 292/week @ 2024-07-19 562/week @ 2024-07-26 418/week @ 2024-08-02 211/week @ 2024-08-09 160/week @ 2024-08-16

1,409 每月下载量
3 个 Crates 中使用(通过 viz-core

MIT/Apache

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--

tests/hyper-body.rs

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.0MIT许可证下使用,具体由您选择。
除非您明确表示,否则您提交给本软件的任何贡献,根据Apache-2.0许可证定义,将如上所述双重许可,不附加任何额外条款或条件。

依赖项

~1.5–2.6MB
~48K SLoC