Rust – Multipart Form Data Parser Examples

Step by step examples to teach you how to parse Multipart Form Data.

Learn how to parse Multipart Form Data using a simple library in Rust.

[lwptoc]

1. rousan/multer-rs

An async parser for multipart/form-data content-type in Rust.

Multipart Form Data Parser Tutorial

An async parser for multipart/form-data content-type in Rust.

It accepts a Stream of Bytes as a source, so that It can be plugged into any async Rust environment e.g. any async server.

Install

Add this to your Cargo.toml:

[dependencies]
multer = "2.0"

Basic Example

use bytes::Bytes;
use futures::stream::Stream;
// Import multer types.
use multer::Multipart;
use std::convert::Infallible;
use futures::stream::once;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Generate a byte stream and the boundary from somewhere e.g. server request body.
    let (stream, boundary) = get_byte_stream_from_somewhere().await;

    // Create a Multipart instance from that byte stream and the boundary.
    let mut multipart = Multipart::new(stream, boundary);

    // Iterate over the fields, use next_field() to get the next field.
    while let Some(mut field) = multipart.next_field().await? {
        // Get field name.
        let name = field.name();
        // Get the field's filename if provided in "Content-Disposition" header.
        let file_name = field.file_name();

        println!("Name: {:?}, File Name: {:?}", name, file_name);

        // Process the field data chunks e.g. store them in a file.
        while let Some(chunk) = field.chunk().await? {
            // Do something with field chunk.
            println!("Chunk: {:?}", chunk);
        }
    }

    Ok(())
}

// Generate a byte stream and the boundary from somewhere e.g. server request body.
async fn get_byte_stream_from_somewhere() -> (impl Stream<Item = Result<Bytes, Infallible>>, &'static str) {
    let data = "--X-BOUNDARYrnContent-Disposition: form-data; name="my_text_field"rnrnabcdrn--X-BOUNDARY--rn";
    let stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) });

    (stream, "X-BOUNDARY")
}

Prevent Denial of Service (DoS) Attacks

This crate also provides some APIs to prevent potential DoS attacks with fine grained control. It's recommended to add some constraints on field (specially text field) size to prevent DoS attacks exhausting the server's memory.

An example:

use multer::{Multipart, Constraints, SizeLimit};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create some constraints to be applied to the fields to prevent DoS attack.
    let constraints = Constraints::new()
         // We only accept my_text_field and my_file_field fields,
         // For any unknown field, we will throw an error.
         .allowed_fields(vec!["my_text_field", "my_file_field"])
         .size_limit(
             SizeLimit::new()
                 // Set 15mb as size limit for the whole stream body.
                 .whole_stream(15 * 1024 * 1024)
                 // Set 10mb as size limit for all fields.
                 .per_field(10 * 1024 * 1024)
                 // Set 30kb as size limit for our text field only.
                 .for_field("my_text_field", 30 * 1024),
         );

    // Create a Multipart instance from a stream and the constraints.
    let mut multipart = Multipart::with_constraints(some_stream, "X-BOUNDARY", constraints);

    while let Some(field) = multipart.next_field().await.unwrap() {
        let content = field.text().await.unwrap();
        assert_eq!(content, "abcd");
    } 

    Ok(())
}

Full Example

Let us look at a full Multipart Form Data Parser Example.

Step 1. Write Code

Finally we need to write our code as follows:

(a). simple_example.rs

use std::convert::Infallible;

use bytes::Bytes;
use futures_util::stream::Stream;
// Import multer types.
use multer::Multipart;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Generate a byte stream and the boundary from somewhere e.g. server request
    // body.
    let (stream, boundary) = get_byte_stream_from_somewhere().await;

    // Create a Multipart instance from that byte stream and the boundary.
    let mut multipart = Multipart::new(stream, boundary);

    // Iterate over the fields, use next_field() to get the next field.
    while let Some(field) = multipart.next_field().await? {
        // Get field name.
        let name = field.name();
        // Get the field's filename if provided in "Content-Disposition" header.
        let file_name = field.file_name();

        println!("Name: {:?}, File Name: {:?}", name, file_name);

        // Read field content as text.
        let content = field.text().await?;
        println!("Content: {:?}", content);
    }

    Ok(())
}

// Generate a byte stream and the boundary from somewhere e.g. server request
// body.
async fn get_byte_stream_from_somewhere() -> (impl Stream<Item = Result<Bytes, Infallible>>, &'static str) {
    let data = "--X-BOUNDARYrnContent-Disposition: form-data; name="my_text_field"rnrnabcdrn--X-BOUNDARYrnContent-Disposition: form-data; name="my_file_field"; filename="a-text-file.txt"rnContent-Type: text/plainrnrnHello worldnHellornWorldrAgainrn--X-BOUNDARY--rn";
    let stream = futures_util::stream::iter(
        data.chars()
            .map(|ch| ch.to_string())
            .map(|part| Ok(Bytes::copy_from_slice(part.as_bytes()))),
    );

    (stream, "X-BOUNDARY")
}

(b). prevent_dos_attack.rs

use std::convert::Infallible;

use bytes::Bytes;
use futures_util::stream::Stream;
// Import multer types.
use multer::{Constraints, Multipart, SizeLimit};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Generate a byte stream and the boundary from somewhere e.g. server request
    // body.
    let (stream, boundary) = get_byte_stream_from_somewhere().await;

    // Create some constraints to be applied to the fields to prevent DoS attacks.
    let constraints = Constraints::new()
        // We only accept my_text_field and my_file_field fields,
        // For any unknown field, we will throw an error.
        .allowed_fields(vec!["my_text_field", "my_file_field"])
        .size_limit(
            SizeLimit::new()
                // Set 15mb as size limit for the whole stream body.
                .whole_stream(15 * 1024 * 1024)
                // Set 10mb as size limit for all fields.
                .per_field(10 * 1024 * 1024)
                // Set 30kb as size limit for our text field only.
                .for_field("my_text_field", 30 * 1024),
        );

    // Create a Multipart instance from that byte stream and the constraints.
    let mut multipart = Multipart::with_constraints(stream, boundary, constraints);

    // Iterate over the fields, use next_field() to get the next field.
    while let Some(field) = multipart.next_field().await? {
        // Get field name.
        let name = field.name();
        // Get the field's filename if provided in "Content-Disposition" header.
        let file_name = field.file_name();

        println!("Name: {:?}, File Name: {:?}", name, file_name);

        // Read field content as text.
        let content = field.text().await?;
        println!("Content: {:?}", content);
    }

    Ok(())
}

// Generate a byte stream and the boundary from somewhere e.g. server request
// body.
async fn get_byte_stream_from_somewhere() -> (impl Stream<Item = Result<Bytes, Infallible>>, &'static str) {
    let data = "--X-BOUNDARYrnContent-Disposition: form-data; name="my_text_field"rnrnabcdrn--X-BOUNDARYrnContent-Disposition: form-data; name="my_file_field"; filename="a-text-file.txt"rnContent-Type: text/plainrnrnHello worldnHellornWorldrAgainrn--X-BOUNDARY--rn";
    let stream = futures_util::stream::iter(
        data.chars()
            .map(|ch| ch.to_string())
            .map(|part| Ok(Bytes::copy_from_slice(part.as_bytes()))),
    );

    (stream, "X-BOUNDARY")
}

(c). parse_async_read.rs

use multer::Multipart;
use tokio::io::AsyncRead;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Generate an AsyncRead and the boundary from somewhere e.g. server request
    // body.
    let (reader, boundary) = get_async_reader_from_somewhere().await;

    // Create a Multipart instance from that async reader and the boundary.
    let mut multipart = Multipart::with_reader(reader, boundary);

    // Iterate over the fields, use next_field() to get the next field.
    while let Some(mut field) = multipart.next_field().await? {
        // Get field name.
        let name = field.name();
        // Get the field's filename if provided in "Content-Disposition" header.
        let file_name = field.file_name();

        println!("Name: {:?}, File Name: {:?}", name, file_name);

        // Process the field data chunks e.g. store them in a file.
        let mut field_bytes_len = 0;
        while let Some(field_chunk) = field.chunk().await? {
            // Do something with field chunk.
            field_bytes_len += field_chunk.len();
        }

        println!("Field Bytes Length: {:?}", field_bytes_len);
    }

    Ok(())
}

// Generate an AsyncRead and the boundary from somewhere e.g. server request
// body.
async fn get_async_reader_from_somewhere() -> (impl AsyncRead, &'static str) {
    let data = "--X-BOUNDARYrnContent-Disposition: form-data; name="my_text_field"rnrnabcdrn--X-BOUNDARYrnContent-Disposition: form-data; name="my_file_field"; filename="a-text-file.txt"rnContent-Type: text/plainrnrnHello worldnHellornWorldrAgainrn--X-BOUNDARY--rn";

    (data.as_bytes(), "X-BOUNDARY")
}

(d). hyper_server_example.rs

use std::{convert::Infallible, net::SocketAddr};

use hyper::server::Server;
use hyper::service::{make_service_fn, service_fn};
use hyper::{header::CONTENT_TYPE, Body, Request, Response, StatusCode};
// Import the multer types.
use multer::Multipart;

// A handler for incoming requests.
async fn handle(req: Request<Body>) -> Result<Response<Body>, Infallible> {
    // Extract the multipart/form-data boundary from the headers.
    let boundary = req
        .headers()
        .get(CONTENT_TYPE)
        .and_then(|ct| ct.to_str().ok())
        .and_then(|ct| multer::parse_boundary(ct).ok());

    // Send BAD_REQUEST status if the content-type is not multipart/form-data.
    if boundary.is_none() {
        return Ok(Response::builder()
            .status(StatusCode::BAD_REQUEST)
            .body(Body::from("BAD REQUEST"))
            .unwrap());
    }

    // Process the multipart e.g. you can store them in files.
    if let Err(err) = process_multipart(req.into_body(), boundary.unwrap()).await {
        return Ok(Response::builder()
            .status(StatusCode::INTERNAL_SERVER_ERROR)
            .body(Body::from(format!("INTERNAL SERVER ERROR: {}", err)))
            .unwrap());
    }

    Ok(Response::new(Body::from("Success")))
}

// Process the request body as multipart/form-data.
async fn process_multipart(body: Body, boundary: String) -> multer::Result<()> {
    // Create a Multipart instance from the request body.
    let mut multipart = Multipart::new(body, boundary);

    // Iterate over the fields, next_field method will return the next field if
    // available.
    while let Some(mut field) = multipart.next_field().await? {
        // Get the field name.
        let name = field.name();

        // Get the field's filename if provided in "Content-Disposition" header.
        let file_name = field.file_name();

        // Get the "Content-Type" header as mime::Mime type.
        let content_type = field.content_type();

        println!(
            "Name: {:?}, FileName: {:?}, Content-Type: {:?}",
            name, file_name, content_type
        );

        // Process the field data chunks e.g. store them in a file.
        let mut field_bytes_len = 0;
        while let Some(field_chunk) = field.chunk().await? {
            // Do something with field chunk.
            field_bytes_len += field_chunk.len();
        }

        println!("Field Bytes Length: {:?}", field_bytes_len);
    }

    Ok(())
}

#[tokio::main]
async fn main() {
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));

    let make_svc = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(handle)) });

    let server = Server::bind(&addr).serve(make_svc);

    println!("Server running at: {}", addr);
    if let Err(e) = server.await {
        eprintln!("server error: {}", e);
    }
}

Documentation

Docs.

Reference

Download the code below:

No. Link
1. Download Full Code
2. Read more here.
3. Follow code author here.

Read More.


More

Here are some more examples related to Multipart Form Data Parser.