trillium-rs / trillium

Trillium is a composable toolkit for building internet applications with async rust

Home Page:https://trillium.rs

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

allow to send static compiled file when 404

prabirshrestha opened this issue · comments

I have a trillium server that is backed by create-react-app.

In dev mode I would like to proxy directly to the server created by create-react-app, while in release mode I would like to serve the file that is embedded in the binary. Working sample of this can be found at https://github.com/prabirshrestha/objstor/pull/16.

Here is the main snippet:

use trillium::Handler;

#[cfg(not(debug_assertions))]
lazy_static_include::lazy_static_include_str! {
    INDEX_HTML => "../client/build/index.html",
}

#[cfg(debug_assertions)]
pub fn spa() -> impl Handler {
    use trillium_rustls::RustlsConnector;
    use trillium_tokio::TcpConnector;
    type Proxy = trillium_proxy::Proxy<RustlsConnector<TcpConnector>>;
    Proxy::new("http://localhost:3000")
}

#[cfg(not(debug_assertions))]
pub fn spa() -> impl Handler {
    use trillium_static_compiled::static_compiled;
    let handler = static_compiled!("../client/build").with_index_file("index.html");
    (handler, serve_index_file)
}

#[cfg(not(debug_assertions))]
async fn serve_index_file(conn: trillium::Conn) -> trillium::Conn {
    conn.with_header("content-type", "text/html; charset=UTF-8")
        .ok(INDEX_HTML.as_bytes())
}

I had to introduce lazy_static_include library and do a bit of custom code. It would be great if this was simplified as it is a common pattern for single page applications where there is usually just a single html file with most of the code logic in js. This could then be simplified as following.

#[cfg(not(debug_assertions))]
pub fn spa() -> impl Handler {
    trillium_static_compiled::static_compiled! 
        ("../client/build").with_index_file("index.html").with_fallback_file("index.html")
}

The other option would be to introduce static_compiled_file but then the file contents would be duplicated.

Why doesn't an include_str based handler work for this?

#[cfg(not(debug_assertions))]
async fn serve_index_file(conn: trillium::Conn) -> trillium::Conn {
    conn.with_header(trillium::KnownHeaderName::ContentType, "text/html; charset=UTF-8")
        .ok(include_str!("../client/build/index.html"))
}

This is identical to a "404 not found" handler that I've written a few times and realized it's actually far more expressive to just write as a handler fn

It works. Seems like I had to change my path to use ../../ instead of ../. Hence I was using lazy_static_include_str.

I went further and inlined it. Should be good for now.

use trillium::Handler;

#[cfg(debug_assertions)]
pub fn spa() -> impl Handler {
    use trillium_rustls::RustlsConnector;
    use trillium_tokio::TcpConnector;
    type Proxy = trillium_proxy::Proxy<RustlsConnector<TcpConnector>>;
    Proxy::new("http://localhost:3000")
}

#[cfg(not(debug_assertions))]
pub fn spa() -> impl Handler {
    (
        trillium_static_compiled::static_compiled!("../client/build").with_index_file("index.html"),
        |conn: trillium::Conn| async move {
            conn.with_header(
                trillium::KnownHeaderName::ContentType,
                "text/html; charset=UTF-8",
            )
            .ok(include_str!("../../client/build/index.html"))
        },
    )
}

Noticed that include_str! allows to add other macros while static_compiled! doesn't.

pub fn spa() -> impl Handler {
    use trillium_compression::compression;

    (
        compression(),
        trillium_static_compiled::static_compiled!(concat!(
            env!("CARGO_MANIFEST_DIR"),
            "/../client/build"
        ))
        .with_index_file("index.html"),
        |conn: trillium::Conn| async move {
            conn.with_header(
                trillium::KnownHeaderName::ContentType,
                "text/html; charset=UTF-8",
            )
            .ok(include_str!(concat!(
                env!("CARGO_MANIFEST_DIR"),
                "/../client/build",
                "/index.html"
            )))
        },
    )
}

Fails with:

error: no rules expected the token `!`
  --> app/src/spa.rs:16:58
   |
16 |         trillium_static_compiled::static_compiled!(concat!(
   |                                                          ^ no rules expected this token in macro call

Is it possible for static_compiled! to have the same behavior as include_str!?

It is not possible for static_compiled! to have the same behavior as include_str, but you should be able to do static_compiled!("$CARGO_MANIFEST_DIR/../client/build") although I believe that's the same as static_compiled!("../client/build")

I was hoping to create trillium-spa so that I could use the same the same path something similar to this.

spa!("https://localhost:3000", "../client/build", "index.html")

Would it be better to support with_fallback_file("index.html") or fallback_to_index_file(true)?

trillium_static_compiled::static_compiled!("../client/build")
  .with_index_file("index.html")
  .with_fallback_file("index.html")

I don't think I really understand your question about the spa macro, but I don't plan to add with_fallback_file to trillium-static-compiled for now, for the reasons above — it's quite easy for the end application to define a fall through handler with whatever arbitrary logic is needed for the application. You could always fork trillium-static-compiled and add whatever behavior it needs, though, or use it as the basis for trillium-spa instead of depending on trillium-static-compiled from trillium-spa