rustactix-web

Send an Image with an HttpResponse ActixWeb


i try to send an image with an HttpResponse with actix web my response looks like this my problem is that i can just return an static u8 but buffer is a [u8; 4096] and not static is there any way to make it possible to send an image?

 HttpResponse::Ok()
           .content_type("image/jpeg")
           .body(buffer)

buffer is:

let mut f = fs::File::open(x).expect("Somthing went wrong");
let mut buffer = [0;4096];
let n = f.read(&mut buffer[..]);

The full func:

fn img_response(x: PathBuf, y: Image)->HttpResponse{
    let mut f = fs::File::open(x).expect("Somthing went wrong");
    let mut buffer = [0;4096];
    let n = f.read(&mut buffer[..]);
    match y{
        Image::JPG =>{ 
            HttpResponse::Ok()
            .content_type("image/jpeg")
            .body(buffer)}
        Image::PNG =>{ 
            HttpResponse::Ok()
            .content_type("image/png")
            .body(buffer)}
        Image::ICO => {
            HttpResponse::Ok()
            .content_type("image/x-icon")
            .body(buffer)}
        }   
}

The func img_response get called in my index func

 match path.extension().unwrap().to_str().unwrap(){
"png" => {return img_response(path, Image::PNG);}
"jpeg" => {return img_response(path, Image::JPG);}
"ico" => {return img_response(path, Image::ICO);}
};

full code: https://github.com/Benn1x/Kiwi The Code Compressed:

#![allow(non_snake_case)]

use actix_web::{ web, App, HttpRequest,HttpResponse , HttpServer};
use mime;
use std::path::PathBuf;
use serde_derive::Deserialize;
use std::process::exit;
use toml;
use std::fs::read_to_string;
use actix_web::http::header::ContentType;
use std::fs;
use std::io::prelude::*;
use std::io;

fn img_response(x: PathBuf)->HttpResponse{
    let mut f = fs::File::open(x).expect("Somthing went wrong");
    let mut buffer = [0;4096];
    let n = f.read(&mut buffer[..]); 
    HttpResponse::Ok()
    .content_type("image/jpeg")
    .body(buffer)
}
async fn index(req: HttpRequest) -> HttpResponse {
let mut path: PathBuf = req.match_info().query("file").parse().unwrap();
match path.extension().unwrap().to_str().unwrap(){
            "jpeg" => {return img_response(path);}
            _ => {return img_response(path);}
            }   
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(move || {
        App::new()
            .route("/{file:.*}", web::get().to(index))
            .service(actix_files::Files::new("/", ".").index_file("index.html"))
        })  
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

This is the The main.rs but just with the func that returns an image


Solution

  • HttpResponse::Ok() returns a HttpResponseBuilder. Its method .body() takes a generic argument that has to implement the MessageBody trait.

    Now here is your problem: [u8; 4096] does not implement MessageBody. What does, however, implement MessageBody, is Vec<u8>.

    Therefore by modifying your static array to a dynamic vector, your code seems to compile:

    #![allow(non_snake_case)]
    
    use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer};
    use std::fs;
    use std::io::prelude::*;
    use std::path::PathBuf;
    
    fn img_response(x: PathBuf) -> HttpResponse {
        let mut f = fs::File::open(x).expect("Somthing went wrong");
        let mut buffer = vec![0; 4096];
        let n = f.read(&mut buffer[..]);
        HttpResponse::Ok().content_type("image/jpeg").body(buffer)
    }
    async fn index(req: HttpRequest) -> HttpResponse {
        let mut path: PathBuf = req.match_info().query("file").parse().unwrap();
        match path.extension().unwrap().to_str().unwrap() {
            "jpeg" => {
                return img_response(path);
            }
            _ => {
                return img_response(path);
            }
        }
    }
    
    #[actix_web::main]
    async fn main() -> std::io::Result<()> {
        HttpServer::new(move || {
            App::new()
                .route("/{file:.*}", web::get().to(index))
                .service(actix_files::Files::new("/", ".").index_file("index.html"))
        })
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
    }
    

    There are still problems with your code, though:

    Here is a working version of your code:

    #![allow(non_snake_case)]
    
    use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer};
    use std::fs;
    use std::io::prelude::*;
    use std::path::PathBuf;
    
    fn img_response(x: PathBuf) -> HttpResponse {
        let mut f = fs::File::open(x).expect("Somthing went wrong");
        let mut image_data = vec![];
        let mut buffer = [0; 4096];
    
        loop {
            let n = f.read(&mut buffer[..]).unwrap();
            if n == 0 {
                break;
            }
            image_data.extend_from_slice(&buffer[..n]);
        }
    
        HttpResponse::Ok()
            .content_type("image/jpeg")
            .body(image_data)
    }
    async fn index(req: HttpRequest) -> HttpResponse {
        let path: PathBuf = req.match_info().query("file").parse().unwrap();
        match path.extension().unwrap().to_str().unwrap() {
            "jpeg" => {
                return img_response(path);
            }
            _ => {
                return img_response(path);
            }
        }
    }
    
    #[actix_web::main]
    async fn main() -> std::io::Result<()> {
        HttpServer::new(move || {
            App::new()
                .route("/{file:.*}", web::get().to(index))
                .service(actix_files::Files::new("/", ".").index_file("index.html"))
        })
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
    }
    

    Note that for large image sizes, it would be benefitial to use the .streaming() body instead:

    #![allow(non_snake_case)]
    
    use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer};
    use async_stream::{try_stream, AsyncStream};
    use bytes::Bytes;
    use std::fs;
    use std::io::prelude::*;
    use std::path::PathBuf;
    
    fn img_response(x: PathBuf) -> HttpResponse {
        let stream: AsyncStream<Result<Bytes, Error>, _> = try_stream! {
            let mut f = fs::File::open(x)?;
            let mut buffer = [0; 4096];
            loop{
                let n = f.read(&mut buffer[..])?;
                if n == 0 {
                    break;
                }
                yield Bytes::copy_from_slice(&buffer[..n]);
            }
        };
    
        HttpResponse::Ok()
            .content_type("image/jpeg")
            .streaming(stream)
    }
    
    async fn index(req: HttpRequest) -> HttpResponse {
        let path: PathBuf = req.match_info().query("file").parse().unwrap();
        match path.extension().unwrap().to_str().unwrap() {
            "jpeg" => {
                return img_response(path);
            }
            _ => {
                return img_response(path);
            }
        }
    }
    
    #[actix_web::main]
    async fn main() -> std::io::Result<()> {
        HttpServer::new(move || {
            App::new()
                .route("/{file:.*}", web::get().to(index))
                .service(actix_files::Files::new("/", ".").index_file("index.html"))
        })
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
    }