diff --git a/Cargo.toml b/Cargo.toml
index c20fb79f..e60c19fc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,6 +15,7 @@ version = "0.1.0"
[features]
default = []
ws = ["tokio-tungstenite", "sha-1", "base64"]
+multipart = ["multer", "mime"]
[dependencies]
async-trait = "0.1"
@@ -38,6 +39,8 @@ tokio-tungstenite = { optional = true, version = "0.14" }
sha-1 = { optional = true, version = "0.9.6" }
base64 = { optional = true, version = "0.13" }
headers = { optional = true, version = "0.3" }
+multer = { optional = true, version = "2.0.0" }
+mime = { optional = true, version = "0.3" }
[dev-dependencies]
askama = "0.10.5"
diff --git a/examples/multipart_form.rs b/examples/multipart_form.rs
new file mode 100644
index 00000000..4dcbd6d1
--- /dev/null
+++ b/examples/multipart_form.rs
@@ -0,0 +1,59 @@
+use axum::{
+ extract::{ContentLengthLimit, Multipart},
+ prelude::*,
+};
+use std::net::SocketAddr;
+
+#[tokio::main]
+async fn main() {
+ tracing_subscriber::fmt::init();
+
+ // build our application with some routes
+ let app = route("/", get(show_form).post(accept_form))
+ .layer(tower_http::trace::TraceLayer::new_for_http());
+
+ // run it with hyper
+ let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
+ tracing::debug!("listening on {}", addr);
+ hyper::Server::bind(&addr)
+ .serve(app.into_make_service())
+ .await
+ .unwrap();
+}
+
+async fn show_form() -> response::Html<&'static str> {
+ response::Html(
+ r#"
+
+
+
+
+
+
+
+ "#,
+ )
+}
+
+async fn accept_form(
+ ContentLengthLimit(mut multipart): ContentLengthLimit<
+ Multipart,
+ {
+ 250 * 1024 * 1024 /* 250mb */
+ },
+ >,
+) {
+ while let Some(field) = multipart.next_field().await.unwrap() {
+ let name = field.name().unwrap().to_string();
+ let data = field.bytes().await.unwrap();
+
+ println!("Length of `{}` is {} bytes", name, data.len());
+ }
+}
diff --git a/src/extract/mod.rs b/src/extract/mod.rs
index ce6e3550..1cbc8989 100644
--- a/src/extract/mod.rs
+++ b/src/extract/mod.rs
@@ -171,16 +171,11 @@
//! # };
//! ```
-use crate::{
- body::{BoxBody, BoxStdError},
- response::IntoResponse,
- util::ByteStr,
-};
+use crate::{response::IntoResponse, util::ByteStr};
use async_trait::async_trait;
use bytes::{Buf, Bytes};
use futures_util::stream::Stream;
use http::{header, HeaderMap, Method, Request, Uri, Version};
-use http_body::Body;
use rejection::*;
use serde::de::DeserializeOwned;
use std::{
@@ -198,6 +193,15 @@ pub mod rejection;
#[doc(inline)]
pub use self::extractor_middleware::extractor_middleware;
+#[cfg(feature = "multipart")]
+#[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
+pub mod multipart;
+
+#[cfg(feature = "multipart")]
+#[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
+#[doc(inline)]
+pub use self::multipart::Multipart;
+
/// Types that can be created from requests.
///
/// See the [module docs](crate::extract) for more details.
@@ -552,10 +556,13 @@ where
/// # };
/// ```
#[derive(Debug)]
-pub struct BodyStream(BoxBody);
+pub struct BodyStream(B);
-impl Stream for BodyStream {
- type Item = Result;
+impl Stream for BodyStream
+where
+ B: http_body::Body + Unpin,
+{
+ type Item = Result;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll