Did you not know that we can upload files in Actix-Web? Well, this post is about that. We will use the Actix-Web framework to create a simple web application that others can explore and apply in their Rust projects. This post uses IntelliJ IDEA for development and Postman for testing.
Create an Actix-Web Project
As you know, the first time we need to do is create a Rust project in IntelliJ IDEA (or via Cargo CLI). Then, we update the dependency configuration file Cargo.toml.
Dependency Configuration File – Cargo.toml
Your project name may differ from ours. We named the project actix-web-upload-file. Proceed with changing the file as follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | [package] name = "actix-web-upload-file" version = "0.1.0" authors = ["Karl San Gabriel"] edition = "2018" [dependencies] actix-web = "1.0.8" serde = { version = "1.0.101", features = ["derive"] } actix-multipart = "0.1.4" futures = "0.1.29" actix-form-data = "0.4.0" mime = "0.3.14" failure = "0.1.5" uuid = { version = "0.7.4", features = ["serde", "v4"] } |
In the dependencies section, we have eight crates that our project depends on. Also, a few of them turn on some features available to respective crates. If you have never used these crates before, IntelliJ will start downloading them also immediately after saving the file.
Rust Server Codes for Actix-Web – main.rs
We head now to creating the server codes. First, delete everything in the main.rs file. We are going to replace them with the following codes.
Step 1 – the use statements
1 2 3 4 5 6 7 8 9 10 11 | use std::path::PathBuf; use uuid::Uuid; use actix_multipart::Multipart; use actix_web::{ web::{post, resource, Data}, App, HttpResponse, HttpServer, }; use form_data::{handle_multipart, Error, Field, FilenameGenerator, Form}; use futures::Future; ... |
Ste 2 – Create struct for File Namer
Next, we define an empty struct and implement the FilenameGenerator trait. The struct will be responsible for naming the incoming file bytes from users who upload the file.
1 | struct FileNamer; |
Then, we implement the trait.
1 2 3 4 5 6 7 | impl FilenameGenerator for FileNamer { fn next_filename(&self, _: &mime::Mime) -> Option<PathBuf> { let mut p = PathBuf::new(); p.push(format!("uploaded-images/{}.jpg", Uuid::new_v4())); Some(p) } } |
Note that we are using Uuid to generate unique file names.
Step 3 – Create The Upload Handler
Then, we implement a file upload handler function that processes the content of incoming requests from clients, e.g., web browsers. Actix-web will use this function to process the file contents when users upload files using a specific URL.
1 2 3 4 5 6 7 8 | fn upload((mp, state): (Multipart, Data<Form>)) -> Box<dyn Future<Item = HttpResponse, Error = Error>> { Box::new( handle_multipart(mp, state.get_ref().clone()).map(|uploaded_content| { println!("Uploaded Content: {:?}", uploaded_content); HttpResponse::Created().finish() }), ) } |
Step 4 – Create a New Main function
The main function has the following codes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | fn main() -> Result<(), failure::Error> { let form = Form::new() .field("files", Field::array(Field::file(FileNamer))); println!("{:?}", form); HttpServer::new(move || { App::new() .data(form.clone()) .service(resource("/upload").route(post().to(upload))) }) .bind("127.0.0.1:8080").unwrap() .run() .unwrap(); Ok(()) } |
When we run the codes, it writes the following output to the console.
1 | Form(Map { inner: [("files", Array(Array { inner: File(filename_generator) }))] }) |
Upload Files in Actix-web Test
Then, we test our codes using Postman.
Ensure to use form-data.
This is the HTTP response after clicking the Send button.
The files are uploaded to [current-project-dir]/uploaded-images directory.
And those are the codes to handle upload files in Actix-Web
BASIC and Bearer Authentications (Optional)
Suppose we want to secure our application, we could use Actix-web BASIC or Bearer authentication.
Tested with Rust 1.37.0.