Search code examples
rustrust-axum

Axum: Send image as response


I'm currently trying to build an endpoint that will return an image (not in in bytes nor b64) to the user based on path but I cannot figure out how to do that with Axum and Rust.

The template of the method should look like that I guess:


#[derive(Deserialize)]
pub struct QueryParams {
    path: String,
}

pub async fn get_image(
    Query(QueryParams { path }): Query<QueryParams>
) -> impl IntoResponse {
}

And I already know the equivalent of what I want in C#

public ActionResult GetImage(string path)
{
    FileExtensionContentTypeProvider fileProvider = new();

    if (path.IsNullOrEmpty())
    {
        return NotFound("Parameter 'path' cannot be null or empty.");
    }
    if (!Directory.Exists(Path.GetDirectoryName(path)))
    {
        return NotFound($"Folder '{Path.GetDirectoryName(path)}' not found.");
    }
    if (!System.IO.File.Exists(path))
    {
        return NotFound($"File '{Path.GetFileName(path)}' not found.");
    }
    if (!fileProvider.TryGetContentType(path, out string? contentType))
    {
        throw new ArgumentException(
                $"Could not determine MIME type of {Path.GetFileName(path)}.");
    }

    return PhysicalFile(path, contentType);
}

I already tried to use the crate image, tower-http with the ServeFile struct but I can't figure out how to make it work properly.

If anyone could provide me some explanation or help it would be greatly appreciated!


Solution

  • Here is the answer that came out from this discussion

    
    #[derive(Deserialize)]
    pub struct QueryParams {
        path: String,
    }
    
    pub async fn get_image(
        Query(QueryParams { path }): Query<QueryParams>
    ) -> impl IntoResponse {
        let filename = match PathBuf::from(path).file_name() {
            Some(name) => name,
            None => return Err((StatusCode::BAD_REQUEST, "File name couldn't be determined".to_string()))
        };
        let file = match tokio::fs::File::open(path).await {
            Ok(file) => file,
            Err(err) => return Err((StatusCode::NOT_FOUND, format!("File not found: {}", err)))
        };
        let content_type = match mime_guess::from_path(&path).first_raw() {
            Some(mime) => mime,
            None => return Err((StatusCode::BAD_REQUEST, "MIME Type couldn't be determined".to_string()))
        };
    
        let stream = ReaderStream::new(file);
        let body = StreamBody::new(stream);
    
        let headers = [
            (header::CONTENT_TYPE, content_type),
            (
                header::CONTENT_DISPOSITION,
                &format!("attachment; filename=\"{:?}\"", filename),
            ),
        ];
        Ok((headers, body))
    }