Skip to main content

Multipart

Struct Multipart 

Source
pub struct Multipart<const LIMIT: usize>(pub MultipartForm);
Expand description

Extractor for a multipart/form-data body of at most LIMIT bytes.

It reads HTML forms with enctype="multipart/form-data" (file inputs) and API clients (curl -F avatar=@me.png). Take the text fields with MultipartForm::form or MultipartForm::text and the files with MultipartForm::file; check files with Validator::file before storing them.

The whole body is read into memory, then split in place: file parts are slices of that one buffer. The rejection is a response, not an Error in the handler:

  • 400 when the content type is not multipart/form-data with a boundary, the body cannot be read, or it is malformed;
  • 413 “The request is too large (maximum is 6 MB)” when Content-Length announces more than LIMIT (before anything is read) or as soon as the body passes it.

Errors are HTML pages for browsers (Accept: text/html, feature html) and JSON (like ApiError) otherwise.

Free plan: keep LIMIT in the tens of MB. A Worker has 128 MB, the body is held in memory while R2 gets a copy, and Cloudflare refuses request bodies over 100 MB on the Free plan before they reach the Worker. Splitting takes about 1.2 ms of CPU per 10 MB in WebAssembly (memchr’s substring search, measured in V8). For a single large file, stream the raw body with store_body instead.

§Examples

use axum::{extract::State, response::Redirect};
use ocre::storage::{self, Multipart, Rules};
use ocre::{Ctx, Result, Validator};
use serde::Deserialize;

const IMAGE: Rules = Rules { max_bytes: 10 * 1024 * 1024, content_types: &["image/png", "image/jpeg"] };
const FORM_LIMIT: usize = IMAGE.max_bytes as usize + 1024 * 1024;

#[derive(Deserialize)]
struct NewPhoto {
    title: String,
}

async fn create(State(ctx): State<Ctx>, Multipart(mut form): Multipart<FORM_LIMIT>) -> Result<Redirect> {
    let fields: NewPhoto = form.form()?;
    let image = form.file("image"); // None when no file was chosen
    let mut v = Validator::new();
    v.required("title", &fields.title);
    if let Some(image) = &image {
        v.file("image", image, &IMAGE);
    }
    v.finish()?;
    if let Some(image) = image {
        let stored = storage::store(&ctx, "photos/image", image).await?;
    }
    Ok(Redirect::to("/photos"))
}

Tuple Fields§

§0: MultipartForm

Trait Implementations§

Source§

impl<const LIMIT: usize> Debug for Multipart<LIMIT>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<const LIMIT: usize, S: Send + Sync> FromRequest<S> for Multipart<LIMIT>

Source§

type Rejection = Response<Body>

If the extractor fails it’ll use this “rejection” type. A rejection is a kind of error that can be converted into a response.
Source§

async fn from_request(req: Request, _state: &S) -> Result<Self, Self::Rejection>

Perform the extraction.

Auto Trait Implementations§

§

impl<const LIMIT: usize> Freeze for Multipart<LIMIT>

§

impl<const LIMIT: usize> RefUnwindSafe for Multipart<LIMIT>

§

impl<const LIMIT: usize> Send for Multipart<LIMIT>

§

impl<const LIMIT: usize> Sync for Multipart<LIMIT>

§

impl<const LIMIT: usize> Unpin for Multipart<LIMIT>

§

impl<const LIMIT: usize> UnsafeUnpin for Multipart<LIMIT>

§

impl<const LIMIT: usize> UnwindSafe for Multipart<LIMIT>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<S, T> Upcast<T> for S
where T: UpcastFrom<S> + ?Sized, S: ?Sized,

Source§

fn upcast(&self) -> &T
where Self: ErasableGeneric, T: Sized + ErasableGeneric<Repr = Self::Repr>,

Perform a zero-cost type-safe upcast to a wider ref type within the Wasm bindgen generics type system. Read more
Source§

fn upcast_into(self) -> T
where Self: Sized + ErasableGeneric, T: Sized + ErasableGeneric<Repr = Self::Repr>,

Perform a zero-cost type-safe upcast to a wider type within the Wasm bindgen generics type system. Read more
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V