mirror of
https://github.com/actix/actix-web.git
synced 2024-11-21 17:11:08 +00:00
docs(multipart): improve crate root docs
This commit is contained in:
parent
16125bd3be
commit
e97e28db4f
6 changed files with 131 additions and 106 deletions
|
@ -4,13 +4,14 @@ version = "0.7.2"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Jacob Halsey <jacob@jhalsey.com>",
|
"Jacob Halsey <jacob@jhalsey.com>",
|
||||||
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
]
|
]
|
||||||
description = "Multipart form support for Actix Web"
|
description = "Multipart request & form support for Actix Web"
|
||||||
keywords = ["http", "web", "framework", "async", "futures"]
|
keywords = ["http", "actix", "web", "multipart", "form"]
|
||||||
homepage = "https://actix.rs"
|
homepage.workspace = true
|
||||||
repository = "https://github.com/actix/actix-web"
|
repository.workspace = true
|
||||||
license = "MIT OR Apache-2.0"
|
license.workspace = true
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
|
@ -15,14 +15,18 @@
|
||||||
|
|
||||||
<!-- cargo-rdme start -->
|
<!-- cargo-rdme start -->
|
||||||
|
|
||||||
Multipart form support for Actix Web.
|
Multipart request & form support for Actix Web.
|
||||||
|
|
||||||
|
The [`Multipart`] extractor aims to support all kinds of `multipart/*` requests, including `multipart/form-data`, `multipart/related` and `multipart/mixed`. This is a lower-level extractor which supports reading [multipart fields](Field), in the order they are sent by the client.
|
||||||
|
|
||||||
|
Due to additional requirements for `multipart/form-data` requests, the higher level [`MultipartForm`] extractor and derive macro only supports this media type.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use actix_web::{post, App, HttpServer, Responder};
|
use actix_web::{post, App, HttpServer, Responder};
|
||||||
|
|
||||||
use actix_multipart::form::{json::Json as MPJson, tempfile::TempFile, MultipartForm};
|
use actix_multipart::form::{json::Json as MpJson, tempfile::TempFile, MultipartForm};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -34,7 +38,7 @@ struct Metadata {
|
||||||
struct UploadForm {
|
struct UploadForm {
|
||||||
#[multipart(limit = "100MB")]
|
#[multipart(limit = "100MB")]
|
||||||
file: TempFile,
|
file: TempFile,
|
||||||
json: MPJson<Metadata>,
|
json: MpJson<Metadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/videos")]
|
#[post("/videos")]
|
||||||
|
@ -63,6 +67,8 @@ curl -v --request POST \
|
||||||
-F file=@./Cargo.lock
|
-F file=@./Cargo.lock
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[`MultipartForm`]: struct@form::MultipartForm
|
||||||
|
|
||||||
<!-- cargo-rdme end -->
|
<!-- cargo-rdme end -->
|
||||||
|
|
||||||
[More available in the examples repo →](https://github.com/actix/examples/tree/master/forms/multipart)
|
[More available in the examples repo →](https://github.com/actix/examples/tree/master/forms/multipart)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//! Process and extract typed data from a multipart stream.
|
//! Extract and process typed data from fields of a `multipart/form-data` request.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
//! Multipart form support for Actix Web.
|
//! Multipart request & form support for Actix Web.
|
||||||
|
//!
|
||||||
|
//! The [`Multipart`] extractor aims to support all kinds of `multipart/*` requests, including
|
||||||
|
//! `multipart/form-data`, `multipart/related` and `multipart/mixed`. This is a lower-level
|
||||||
|
//! extractor which supports reading [multipart fields](Field), in the order they are sent by the
|
||||||
|
//! client.
|
||||||
|
//!
|
||||||
|
//! Due to additional requirements for `multipart/form-data` requests, the higher level
|
||||||
|
//! [`MultipartForm`] extractor and derive macro only supports this media type.
|
||||||
//!
|
//!
|
||||||
//! # Examples
|
//! # Examples
|
||||||
//!
|
//!
|
||||||
|
@ -45,6 +53,8 @@
|
||||||
//! -F 'json={"name": "Cargo.lock"};type=application/json' \
|
//! -F 'json={"name": "Cargo.lock"};type=application/json' \
|
||||||
//! -F file=@./Cargo.lock
|
//! -F file=@./Cargo.lock
|
||||||
//! ```
|
//! ```
|
||||||
|
//!
|
||||||
|
//! [`MultipartForm`]: struct@form::MultipartForm
|
||||||
|
|
||||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||||
|
|
|
@ -483,7 +483,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
use futures_test::stream::StreamTestExt as _;
|
use futures_test::stream::StreamTestExt as _;
|
||||||
use futures_util::{future::lazy, stream, StreamExt as _};
|
use futures_util::{stream, StreamExt as _};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||||
|
|
||||||
|
@ -718,100 +718,6 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_basic() {
|
|
||||||
let (_, payload) = h1::Payload::create(false);
|
|
||||||
let mut payload = PayloadBuffer::new(payload);
|
|
||||||
|
|
||||||
assert_eq!(payload.buf.len(), 0);
|
|
||||||
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
|
|
||||||
assert_eq!(None, payload.read_max(1).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_eof() {
|
|
||||||
let (mut sender, payload) = h1::Payload::create(false);
|
|
||||||
let mut payload = PayloadBuffer::new(payload);
|
|
||||||
|
|
||||||
assert_eq!(None, payload.read_max(4).unwrap());
|
|
||||||
sender.feed_data(Bytes::from("data"));
|
|
||||||
sender.feed_eof();
|
|
||||||
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(Some(Bytes::from("data")), payload.read_max(4).unwrap());
|
|
||||||
assert_eq!(payload.buf.len(), 0);
|
|
||||||
assert!(payload.read_max(1).is_err());
|
|
||||||
assert!(payload.eof);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn test_err() {
|
|
||||||
let (mut sender, payload) = h1::Payload::create(false);
|
|
||||||
let mut payload = PayloadBuffer::new(payload);
|
|
||||||
assert_eq!(None, payload.read_max(1).unwrap());
|
|
||||||
sender.set_error(PayloadError::Incomplete(None));
|
|
||||||
lazy(|cx| payload.poll_stream(cx)).await.err().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn read_max() {
|
|
||||||
let (mut sender, payload) = h1::Payload::create(false);
|
|
||||||
let mut payload = PayloadBuffer::new(payload);
|
|
||||||
|
|
||||||
sender.feed_data(Bytes::from("line1"));
|
|
||||||
sender.feed_data(Bytes::from("line2"));
|
|
||||||
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
|
|
||||||
assert_eq!(payload.buf.len(), 10);
|
|
||||||
|
|
||||||
assert_eq!(Some(Bytes::from("line1")), payload.read_max(5).unwrap());
|
|
||||||
assert_eq!(payload.buf.len(), 5);
|
|
||||||
|
|
||||||
assert_eq!(Some(Bytes::from("line2")), payload.read_max(5).unwrap());
|
|
||||||
assert_eq!(payload.buf.len(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn read_exactly() {
|
|
||||||
let (mut sender, payload) = h1::Payload::create(false);
|
|
||||||
let mut payload = PayloadBuffer::new(payload);
|
|
||||||
|
|
||||||
assert_eq!(None, payload.read_exact(2));
|
|
||||||
|
|
||||||
sender.feed_data(Bytes::from("line1"));
|
|
||||||
sender.feed_data(Bytes::from("line2"));
|
|
||||||
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(Some(Bytes::from_static(b"li")), payload.read_exact(2));
|
|
||||||
assert_eq!(payload.buf.len(), 8);
|
|
||||||
|
|
||||||
assert_eq!(Some(Bytes::from_static(b"ne1l")), payload.read_exact(4));
|
|
||||||
assert_eq!(payload.buf.len(), 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn read_until() {
|
|
||||||
let (mut sender, payload) = h1::Payload::create(false);
|
|
||||||
let mut payload = PayloadBuffer::new(payload);
|
|
||||||
|
|
||||||
assert_eq!(None, payload.read_until(b"ne").unwrap());
|
|
||||||
|
|
||||||
sender.feed_data(Bytes::from("line1"));
|
|
||||||
sender.feed_data(Bytes::from("line2"));
|
|
||||||
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Some(Bytes::from("line")),
|
|
||||||
payload.read_until(b"ne").unwrap()
|
|
||||||
);
|
|
||||||
assert_eq!(payload.buf.len(), 6);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Some(Bytes::from("1line2")),
|
|
||||||
payload.read_until(b"2").unwrap()
|
|
||||||
);
|
|
||||||
assert_eq!(payload.buf.len(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_multipart_from_error() {
|
async fn test_multipart_from_error() {
|
||||||
let err = Error::ContentTypeMissing;
|
let err = Error::ContentTypeMissing;
|
||||||
|
|
|
@ -151,3 +151,105 @@ impl PayloadBuffer {
|
||||||
self.buf.extend_from_slice(&buf);
|
self.buf.extend_from_slice(&buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use actix_http::h1;
|
||||||
|
use futures_util::future::lazy;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn basic() {
|
||||||
|
let (_, payload) = h1::Payload::create(false);
|
||||||
|
let mut payload = PayloadBuffer::new(payload);
|
||||||
|
|
||||||
|
assert_eq!(payload.buf.len(), 0);
|
||||||
|
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
|
||||||
|
assert_eq!(None, payload.read_max(1).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn eof() {
|
||||||
|
let (mut sender, payload) = h1::Payload::create(false);
|
||||||
|
let mut payload = PayloadBuffer::new(payload);
|
||||||
|
|
||||||
|
assert_eq!(None, payload.read_max(4).unwrap());
|
||||||
|
sender.feed_data(Bytes::from("data"));
|
||||||
|
sender.feed_eof();
|
||||||
|
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(Some(Bytes::from("data")), payload.read_max(4).unwrap());
|
||||||
|
assert_eq!(payload.buf.len(), 0);
|
||||||
|
assert!(payload.read_max(1).is_err());
|
||||||
|
assert!(payload.eof);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn err() {
|
||||||
|
let (mut sender, payload) = h1::Payload::create(false);
|
||||||
|
let mut payload = PayloadBuffer::new(payload);
|
||||||
|
assert_eq!(None, payload.read_max(1).unwrap());
|
||||||
|
sender.set_error(PayloadError::Incomplete(None));
|
||||||
|
lazy(|cx| payload.poll_stream(cx)).await.err().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn read_max() {
|
||||||
|
let (mut sender, payload) = h1::Payload::create(false);
|
||||||
|
let mut payload = PayloadBuffer::new(payload);
|
||||||
|
|
||||||
|
sender.feed_data(Bytes::from("line1"));
|
||||||
|
sender.feed_data(Bytes::from("line2"));
|
||||||
|
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
|
||||||
|
assert_eq!(payload.buf.len(), 10);
|
||||||
|
|
||||||
|
assert_eq!(Some(Bytes::from("line1")), payload.read_max(5).unwrap());
|
||||||
|
assert_eq!(payload.buf.len(), 5);
|
||||||
|
|
||||||
|
assert_eq!(Some(Bytes::from("line2")), payload.read_max(5).unwrap());
|
||||||
|
assert_eq!(payload.buf.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn read_exactly() {
|
||||||
|
let (mut sender, payload) = h1::Payload::create(false);
|
||||||
|
let mut payload = PayloadBuffer::new(payload);
|
||||||
|
|
||||||
|
assert_eq!(None, payload.read_exact(2));
|
||||||
|
|
||||||
|
sender.feed_data(Bytes::from("line1"));
|
||||||
|
sender.feed_data(Bytes::from("line2"));
|
||||||
|
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(Some(Bytes::from_static(b"li")), payload.read_exact(2));
|
||||||
|
assert_eq!(payload.buf.len(), 8);
|
||||||
|
|
||||||
|
assert_eq!(Some(Bytes::from_static(b"ne1l")), payload.read_exact(4));
|
||||||
|
assert_eq!(payload.buf.len(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn read_until() {
|
||||||
|
let (mut sender, payload) = h1::Payload::create(false);
|
||||||
|
let mut payload = PayloadBuffer::new(payload);
|
||||||
|
|
||||||
|
assert_eq!(None, payload.read_until(b"ne").unwrap());
|
||||||
|
|
||||||
|
sender.feed_data(Bytes::from("line1"));
|
||||||
|
sender.feed_data(Bytes::from("line2"));
|
||||||
|
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Some(Bytes::from("line")),
|
||||||
|
payload.read_until(b"ne").unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(payload.buf.len(), 6);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Some(Bytes::from("1line2")),
|
||||||
|
payload.read_until(b"2").unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(payload.buf.len(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue