Static image with actix-web

Hello,

I'm trying to make a very simple HTTP server returning one result page that includes an image created by the server. I'm using the simple example (Static Files) but I get errors.

Here's an excerpt of the code:

use actix_web::{web, App, HttpResponse, HttpServer, HttpRequest, Error};
use actix_web::Result as ActixResult;
use actix_files::NamedFile;

fn main() {
    let server = HttpServer::new(|| {
        App::new()
            .route("/", web::get().to(get_index))
            .route("/{id}.png", web::get().to(get_image))
            .route("/calc", web::post().to(post_temp_rh))
    });
// ...
    server
        .bind(url).expect("error binding server to address")
        .run().expect("error running server");
}

// ... 

fn get_image(req_: HttpRequest) -> ActixResult<NamedFile> {
    Ok(NamedFile::open("result.png")?)
}

Just for information, the HTML created by post_temp_rh() has an image tag, that I'm using in the .route("/{id}.png", and which looks like this, where <id> is a timestamp (to avoid caching side-effects) :

<img src="result.<id>.png">

There is only one image, so I'm not bothering parsing the filename in get_image.

Before I added the route to get_image, it was compiling and working perfectly. When I added this route, I got these messages:

error[E0277]: the trait bound `fn(HttpRequest) -> Result<NamedFile, actix_web::Error> {get_image}: Factory<_, _, _>` is not satisfied
   --> src\main.rs:68:54
    |
68  |             .route("/result.gif\\?.*", web::get().to(get_image))
    |                                                   -- ^^^^^^^^^ the trait `Factory<_, _, _>` is not implemented for `fn(HttpRequest) -> Result<NamedFile, actix_web::Error> {get_image}`
    |                                                   |
    |                                                   required by a bound introduced by this call
    |
note: required by a bound in `Route::to`
   --> C:\Users\tom\.cargo\registry\src\github.com-1ecc6299db9ec823\actix-web-2.0.0\src\route.rs:228:12
    |
228 |         F: Factory<T, R, U>,
    |            ^^^^^^^^^^^^^^^^ required by this bound in `Route::to`

First I found out that I had to replace Result by ActixResult, an unfortunate choice of type since Result is already used in Rust and needs 2 parameters. I tried by making it async, but that didn't change anything (and I don't need that).

Any idea about what I'm doing wrong?

Since this post had been hidden, I didn't wait for an answer and implemented another solution.

Also, for the sake of completeness, I was using actix-web version 1, from an example in a recent book. I see there are versions 2, 3 and 4 but they generate other errors. I think that the newer versions (and
using NamedFile as attempted above) more or less require an asynchronous design and the use of 'service'.

The fact is I don't see any good documentation (like most of those 3rd-party crates), so it's mostly a guessing game.

I used this to return the image:

fn get_image(req: HttpRequest) -> Result<HttpResponse, Error> {
    match std::fs::read("result.png") {
        Ok(image_content) => {
            Ok(HttpResponse::Ok()
                .content_type("image/png")
                .body(image_content))
        },
        Err(e) => {
            println!("get_image({}): error, {:?}", req.match_info().query("id").parse::<String>().unwrap(), e);
            Err(actix_web::Error::from(e))
        }
    }
}

Making get_image async makes the code compile for me using

actix-web = "4.0.1"
actix-files = "0.6.0"

and by adding #[actix_web::main] over main I was able to run the code and get the image

use actix_files::NamedFile;
use actix_web::web;
use actix_web::App;
use actix_web::HttpRequest;
use actix_web::HttpServer;
use actix_web::Result as ActixResult;

#[actix_web::main]
async fn main() {
    let server = HttpServer::new(|| App::new().route("/{id}.png", web::get().to(get_image)));
    let _ = server
        .bind("0.0.0.0:8080")
        .expect("error binding server to address")
        .run()
        .await;
}

async fn get_image(_req_: HttpRequest) -> ActixResult<NamedFile> {
    Ok(NamedFile::open("result.png")?)
}

You can also avoid using ActixResult if you want by using std::io::Result, which is the type returned by NamedFile::open

async fn get_image(_req_: HttpRequest) -> std::io::Result<NamedFile> {
    NamedFile::open("result.png")
}
1 Like

Thanks! This seem like a good alternative, but it's async and requires a lot of modification in the current code, so I couldn't make it work. For example it requires the actix-rt dependency, then adding attributes to main and each function returning a page, not using route but service, and so on.

I'm only learning, so when I'm more comfortable with Rust I may try that if I find some proper documentation on the actix crates (the current state is confusing at best, so it's necessary to dig into the source code).

I'll also have to check if that doesn't have too much impact on memory since the idea is to make a similar server run on a Raspberry.

There might be some confusion here still, both version 1 and 4 of actix-web depend on actix_rt so it will get pulled to your project either way and in 4 the important parts should be re-exported under actix_web::rt so it's not necessary to add it to your own Cargo.toml. The code I posted uses 4.0.1 with route and does not have an attribute over get_image, so these are not necessary in 4.0.1. The change to async is significant, however.

Have you taken a look at https://actix.rs/docs/?

You're right, I was mistaken about actix-rt, I see it in the dependencies in Cargo.lock.

I could not make it work for the get_index and other URL path handlers (*). It's probably a small change, but the documentation (yes, I looked at the link you mention) is only about the async way. Maybe if I search again on Stack Overflow or here, but if I have to jump to version 4 and asynchronous code, I may as well follow the crate's idioms and use the attributes.

Either way, I don't see a way to use std::io::Result<NamedFile> with synchronous code, I think it's not meant to be used like that. Perhaps it was introduced later. Besides, it's only version 0.6, so maybe a little early to use it.

EDIT: (*) they look like this, with INDEX_PAGE being a constant &str:

fn get_index() -> HttpResponse {
    HttpResponse::Ok()
        .content_type("text/html")
        .body(INDEX_PAGE)
}
1 Like

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.