Actix web file upload (Actix multipart)

Shanur
2 min readMay 4, 2024

--

It’s time for a quick and dirty tutorial on Actix Multipart

First things first, you gotta add the actix-multipart crate to your Cargo.toml file. It's like inviting a new player to your team. Just throw this line in there.

actix-multipart = "0.6.1"

Then add another dependency calledtokio, the asynchronous runtime. We're using version 1.37.0 because we're all about living on the edge.

tokio = {version="1.37.0", features=["macros", "rt-multi-thread", "fs"]}

Now, let’s talk about those fancy features:

  • macros: This enables the #[tokio::main] macro, which is like a magic wand for creating an asynchronous main function.
  • rt-multi-thread: This feature adds the power of multi-threading, allowing Tokio to run on multiple threads.
  • fs: This feature grants Tokio the ability to work with the file system, reading and writing files. This will provide us with async file operations.

Now let’s look at the code

pub async fn save_file(mut payload: Multipart, file_path: std::path::PathBuf) -> Result<HttpResponse, actix_web::error::Error> {
let mut file = tokio::fs::File::create(file_path).await?;

while let Some(field) = payload.next().await {
let mut field = match field {
Ok(field) => field,
Err(e) => return Err(actix_web::error::ErrorBadRequest(e.to_string())),
};

if field.name() == "file" {
// Write the file content to the file
while let Some(chunk) = field.next().await {
let chunk = match chunk {
Ok(chunk) => chunk,
Err(e) => return Err(actix_web::error::ErrorBadRequest(e.to_string()))
};

let _ = file.write_all(&chunk).await?;
}
}
}

Ok(HttpResponse::Ok().body("File saved successfully"))
}

First off, this function is marked as async, which means it's got the attention span of a goldfish on cocaine. It takes in two parameters: payload, which is a Multipart object that contains the file data, and file_path, which is a PathBuf telling us where to dump this file.

let mut file = tokio::fs::File::create(file_path).await?;

The above line starts by creating a new file using tokio::fs::File::create, because apparently, we're too good for the standard library. If that fails, it'll return an error.

Then, we enter a loop that keeps grabbing fields from the payload. We’ll write the file content to the file in chunks. But seriously why file though ?

if field.name() == "file" {
// Write the file content to the file
while let Some(chunk) = field.next().await {
let chunk = match chunk {
Ok(chunk) => chunk,
Err(e) => return Err(actix_web::error::ErrorBadRequest(e.to_string()))
};

let _ = file.write_all(&chunk).await?;
}
}

That’s because if you look at the curl request carefully, you can see that the file is being named file

curl --location 'http://localhost:8080/templates?name=welcome.hbs' \
--form 'file=@"/Users/shanurrahman/Downloads/welcome.hbs"'

If anything goes wrong during this process, we’ll return a BadRequest error with a message that's about as helpful as a screen door on a submarine.

Finally, if everything works out, we’ll return an Ok response with a body that says "File saved successfully", because we're all about positive reinforcement here.

You can find the complete code here on gitlab.

--

--