Writing actix_web::web::Bytes into image file

This is working code to save a file in Actix when receiving a Multipart:

while let Ok(Some(mut field)) = payload.try_next().await {
            let filepath = format!(".{}", file_path);

            // File::create is blocking operation, use threadpool
            let mut f = web::block(|| std::fs::File::create(filepath))
                .await
                .unwrap();

            // Field in turn is stream of *Bytes* object
            while let Some(chunk) = field.next().await {
                let data = chunk.unwrap();
                // filesystem operations are blocking, we have to use threadpool
                f = web::block(move || f.write_all(&data).map(|_| f))
                    .await
                    .unwrap();
            }
        }

Basing myself on that code, I tried creating the following method:

pub async fn save_file(
        mut file_bytes: actix_web::web::Bytes,
        file_path: &String,
    ) -> Option<bool> {
        let filepath = format!(".{}", file_path);

        let mut f = web::block(|| std::fs::File::create(filepath))
            .await
            .unwrap();

        f = web::block(move || f.write_all(&file_bytes).map(|_| f))
            .await
            .unwrap();



        Some(true)
    }

Basically, I want to extract the data in actix_web::web::Bytes format while looping over my Multipart. And when the loop reaches the file-field (which is an image) I want to execute the save_file function.

This function works: It makes a new file, and the file has some content, but I can't open the JPEG because the file is corrupted or in a wrong format.

A small piece of the JPEG-file in text-editor:

Æ™ˆfl•r}ü%#ï=QÂjEÈñ¶:¸=ȱ8Ω‡«x_7fi–fl@ßüÍ‚XWÕıh~f‚DÇáP˛r≥foea'ª{¥ ˛[πù?$∑£¥£3¸|'∂œ±(⁄‘ì`rowØ<äÏœ,…ÍÓ.Å•‹™,d*=ÒıŒŒ◊‰+2YÑ-¨ëL2oq]'î!®ˆ-O§ ªd’S#µÕÕ|>ôOá∆7Ô¸øöõôùõs©|G˚ã0¿◊m†?MT=Ç~˛ÅK€Sµ•l"[®Ofi0C!]óŸœeFË}À*¿ :ß÷ßR-∑∂K€È±˜ÁœÔ<üd BÖG–ËI˘À¯Á†Õı±˘Åöû÷¸Ãã‚ûèèVVÓ|‰Ëˇ¶›Å˚W	˙·J/=h˝ªxëÒfl‰éɘEó¥˜y√!æ„Ì0ïœÒø÷3¸%_Ñ€˚ø@+wnAÊ˝†oÄfl™¸RÖ˙6ê"º~7ˇq`ó"˜!ı∆1≈gKõ‚<P:√™}¬NÀ®#¸yw~Zã±Lo«O›=ºÚÔ¯HœË∑‹)_—‘3‚qÑ*üàËO;Åˇ1)∫œ+eªÍà˛ònÕÃ.LM˝∫‹ó+ï»bP˛W^øæë…d≤ô\.√W°`1Ñø&'rˇc&¿ˇYπPªˇØaâ,‡?J\˙Á´ÔÔ’fg%›fl‡ñÉ›1Ùfl¶ó€
NˇoÔ¨,ˆoófj3	"˚!á˝:Éé¢ü»˛,’˚p["j@Ö?D˙óZMˇ…ù4˝œQømˇn _ò>EóÛó"ÌÉÄÕnmG˘ˇ{¯ˇÙ{≤q:¯‚Ù¢Õ–œÓë˝xG7)›õ›#\mπ.Sí˚Î;ßs%©øÊ+˙Äø“ë–J˚fiX•«ºmÁ2z’≥™ı’ÇÈ}̬^NaCx`*_uÇġ…
jÁ°Ì‚¨agCvÈÌÜû9F$¢„ˆ^T˝x•_’ãˇ¨¿üß{5Oa7©®_V˝lˆ˙ü{áƒgWõ?¸2∆˚
¨–3&µtéD

// CONTINUES LIKE THIS

This is how I call the save_file function when receiving my Multipart:

let mut field_name = "".to_string();
    while let Some(item) = payload.next().await {
        let mut field = item?;

        field_name = field
            .content_disposition()
            .unwrap()
            .get_name()
            .unwrap()
            .to_string();

        let mut field_data = "".to_string();
        let mut byte_data: Option<actix_web::web::Bytes> = None;
        while let Some(chunk) = field.next().await {

            if field_name == "file".to_string() {
                byte_data = Some(chunk.unwrap());
            } else {
                match std::str::from_utf8(&chunk?) {
                    Ok(data) => {
                        field_data = data.to_string();
                    }
                    Err(_) => {}
                }
            }
        }

        match &*field_name {
            "name" => form.name = field_data,
            "email" => form.email = field_data,
           // ...
            "file" => {
                let file_name = format!(
                    "/uploads/business-images/{}.jpg",
                    form.name.to_lowercase().replace(" ", "-"),
                );

                // CALLED HERE
                files::save_file(byte_data.unwrap(), &file_name).await;
            }
            _ => {
                println!("Invalid");
            }
        }
    }

How do I write the actix_web::web::Bytes into a file with the correct format without corrupting it? I don't understand that the code works when looping over the Multipart, but not when I try to execute it outside of a loop, passing the Bytes directly...

You seem to be only storing the byte_data for the last chunk. Is it possible that the file data is split across multiple chunks? In which case you either need to do what your reference is doing and write each chunk into the file as they come in, or you need to temporarily store all the file data chunks in a variable and then write all of them into a file.

1 Like

Smart AF.

Sharp analysis, you were right. This is the working method:

pub async fn save_file(
        mut file_bytes: std::vec::Vec<actix_web::web::Bytes>,
        file_path: &String,
    ) -> Option<bool> {
        let filepath = format!(".{}", file_path);

        // File::create is blocking operation, use threadpool
        let mut f = web::block(|| std::fs::File::create(filepath))
            .await
            .unwrap();

        // Field in turn is stream of *Bytes* object
        for data in file_bytes {
            // filesystem operations are blocking, we have to use threadpool
            f = web::block(move || f.write_all(&data).map(|_| f))
                .await
                .unwrap();
        }

        Some(true)
    }

I was indeed only saving the last chunk in this loop:


while let Some(chunk) = field.next().await {
                let data = chunk.unwrap();
              // ...
            }

Now I also understand what this code is doing... The data is sent in different parts, not as a whole.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.