Can Actix render html file?

I have the follwing code:

use actix_web::{
    middleware, web, App, HttpRequest, HttpResponse, HttpServer, Responder, Result,
    http::{
        header::{self ,ContentType},
        Method, StatusCode,
    }
};


#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(web::resource("/form").route(web::get().to(show_html)))
    })
    .bind(("127.0.0.1", 8081))?
    .run()
    .await
}

async fn show_html(req: HttpRequest) -> Result<HttpResponse> {
    Ok(HttpResponse::build(StatusCode::OK)
        .content_type("text/html; charset=utf-8")
        .body(include_str!("../form.html"))
    )
}

The ../form.htm. has these code:

<!doctype html>
<html>
    <head>
        <title>CodeMirror</title>
        <script src="codemirror/lib/codemirror.js"></script>
        <link href="codemirror/lib/codemirror.css" rel = "stylesheet"/>
        <script src="codemirror/mode/xml/xml.js"></script>
        <link href = "codemirror/theme/dracula.css" rel = "stylesheet"/>
    </head>
    <body>
        <textarea id="editor"><p>I am a para ttt</p></textarea>
        <script>
            var editor=CodeMirror.fromTextArea(document.getElementById('editor'), {
                mode: "xml",
                theme: "dracula"
            });
        </script>
    </body>
</html>

When I open the url, it show the raw text like this:
图片
But the html is look like this:
图片
What should I do to render the html file like the second potograph?

Actix is serving only your form, not your scripts and style sheets. You have to serve those as well (probably the whole contents of the codemirror directory). I'd recommend using the actix-files crate for serving static files and directories. I think adding a Files service that serves your codemirror directory should do the trick:

use actix_files::Files;

App::new()
    .service(web::resource("/form").route(web::get().to(show_html)))
    .service(Files::new("/codemirror", "./codemirror"))    

(I'd also use actix-files to serve your html form as well, see i.e. NamedFile or this section of the actix documentation)

5 Likes

Thanks very much for your help.

After hours of retrying, I can't still make it success. Could you take a minute to speculate what I am doing wrong please?

  1. This is my main.rs:
use actix_web::{
    middleware, web, App, HttpRequest, HttpResponse, HttpServer, Responder, Result,
    get,
    http::{
        header::{self ,ContentType},
        Method, StatusCode,
    }
};
use actix_files::Files;


#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(say_hello)
            .service(Files::new("../templates", "./t").show_files_listing())
            .service(show_html)
    })
    .bind(("127.0.0.1", 8081))?
    .run()
    .await
}

#[get("/form")]
async fn show_html() -> impl Responder {
    HttpResponse::build(StatusCode::OK)
        .content_type("text/html; charset=utf-8")
        .body(include_str!("../templates/form2.html")
    )
}

#[get("/gg")]
async fn say_hello() -> impl Responder {
    HttpResponse::Ok()
        .content_type("text/plain")
        .body("hello")
}
  1. This is the form2.html
<!doctype html>
<html>
    <head>
        <title>CodeMirror</title>
        <script src="./t/codemirror.js"></script>
        <link href="./t/codemirror.css" rel = "stylesheet"/>
        <script src="./t/xml.js"></script>
        <link href = "./t/dracula.css" rel = "stylesheet"/>
    </head>
    <body>
        <textarea id="editor"><p>I am a para kkk</p></textarea>
        <script>
            var editor=CodeMirror.fromTextArea(document.getElementById('editor'), {
                mode: "xml",
                theme: "dracula"
            });
        </script>
    </body>
</html>
  1. This is my file system:
my_crate
....src
........main.rs

templates
....t
........codemirror.js
........codemirror.css
........xml.js
........dracula.css
....form2.html

You are literally putting the displayed text inside your textarea. The textarea will display whatever you put inside. I have a feeling this has nothing to do with Actix or style sheets.

3 Likes

No worries. I think these are reversed. The first argument is called mount_path and the second is called serve_from. According to the docs:

The first argument (mount_path) is the root URL at which the static files are served. For example, /assets will serve files at example.com/assets/....

The second argument (serve_from) is the location on disk at which files are loaded. This can be a relative path. For example, ./ would serve files from the current working directory.

Try reversing the order of your arguments to File::new("./t", "../templates") and see if it works?

2 Likes

As far as I understood OPs problem, these stylesheets are not loaded when the form (which works fine I assume from the screenshot) is loaded with actix. When the form is opened via file://path/to/form, the style is loaded correctly, resulting in the styled version of the same form that can be seen in the second screenshot.

1 Like

It still can't work, I tried the following four kinds, none of these works:

.service(Files::new("./t", "../templates").show_files_listing())

.service(Files::new("./t", "../templates/t").show_files_listing())

.service(Files::new("../templates", "./t").show_files_listing())

.service(Files::new("../templates/t", "./t").show_files_listing())

Hmm, could you try removing the leading dot from the path? Convert the path from the relative ./t to the absolute /t? I'm unsure whether actix serves a relative mounting point as expected or whether it will be served at localhost:8081/./t. Also, what do you see when you open localhost:8081/t in your browser? You should see the contents of your templates directory.

After I changed ./t to /t in main.rs:

.service(Files::new("/t", "../templates").show_files_listing())

and in form2.html:

<script src="/t/codemirror.js"></script>
<link href="/t/codemirror.css" rel = "stylesheet"/>
<script src="/t/xml.js"></script>
<link href = "/t/dracula.css" rel = "stylesheet"/>

Then I open the localhost:8081/t, it show nothing, as it is blank.

I'm unable to reproduce your error it seems like. I recreated your setup and this is what I see when I open localhost:8081/t in my browser:

Could you try removing your other endpoints and see if just:

        App::new()
            .service(Files::new("/t", "../templates").show_files_listing())

will serve your files?

P.S.: Just a sanity check, but you are running your web server from the root directory of your my_crate crate, right? Not something like cargo run --manifest-path my_crate/Cargo.toml from the directory with my_crate and templates in it?

1 Like

Yes, I run the web server from the root directory of my_crate like this:


Could it be problem about the Cargo.toml?

I am very grateful for your help. And sorry for wasting your time, because the problem turns out to be a stupid mistake:


As the screenshot shows, the first noting place must be ./templates, but the second noting place must be ../templates. It seems that async fn main runs in ./my_crate, but async fn show_html runs in ./my_crate/src.
After I changed to that, everything works fine.

Glad you figured it out! :smile:

Well, no, all your code runs in the same working directory. The include_str! macro works differently. include_str! knows nothing about your working directory, as it is executed at compile time, including the file as an UTF-8 string. It therefore includes a file relative to the file where include_str! is called:

The file is located relative to the current file (similarly to how modules are found).

Since you call include_str! from src/main.rs it will look for the file to include relative from the src/ directory. Unlike Actix which does look up your files at run time relative to your working directory.

2 Likes

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.