rustweb-applicationsrust-warp

How to serve CSS and JS files using Warp?


I want Warp to serve the current working directory. Here is the entire main.rs:

#[tokio::main]
async fn main() {
    let current_dir = std::env::current_dir().expect("failed to read current directory");
    warp::serve(warp::fs::dir(current_dir))
        .run(([127, 0, 0, 1], 3030))
        .await;
}

With the following dependencies:

[dependencies]
tokio = { version = "1.5", features = ["full"] }
warp = "0.3"

Then I run it on directory www with the following structure:

www
├── foo
|   └── index.html
|   └── style.css
└── bar
    └── index.html
    └── style.css

The HTML pages are served, but their referenced CSS files are not. The HTML pages reference their respective CSS file using <link rel="stylesheet" href="style.css">

I have this working using node.js express, but with Warp it attempts to load www/style.css, rather than www/foo/style.css and www/bar/style.css.

It works if I change the href to "foo/style.css" and "bar/style.css", but I would like to avoid that if possible. Is there something I can change on Warp's end to fix this?

Edit: I learned that the pages render with the CSS properly if the URL contains a trailing slash.

So this doesn't work:

http://localhost:3030/foo
http://localhost:3030/bar

But this does:

http://localhost:3030/foo/
http://localhost:3030/bar/

Solution

  • Thanks to @Kitsu's comment for a similar question, I learned that this is currently an open issue.

    I ended up using a slightly modified version of kolektiv's solution from that discussion. It gets the path using warp::path::full(), then redirects with the trailing slash if needed. Here is code working as intended for my example.

    cargo.toml's dependencies:

    [dependencies]
    tokio = { version = "1.5", features = ["full"] }
    warp = "0.3"
    

    main.rs:

    use std::str::FromStr;
    use warp::{filters::BoxedFilter, http::Uri, path::FullPath, redirect, Filter, Reply};
    
    #[tokio::main]
    async fn main() {
        let current_dir = std::env::current_dir().expect("failed to read current directory");
    
        let routes = root_redirect().or(warp::fs::dir(current_dir));
    
        warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
    }
    
    fn root_redirect() -> BoxedFilter<(impl Reply,)> {
        warp::path::full()
            .and_then(move |path: FullPath| async move {
                let path = path.as_str();
    
                // do not redirect if the path ends in a trailing slash
                // or contains a period (indicating a specific file, e.g. style.css)
                if path.ends_with("/") || path.contains(".") {
                    return Err(warp::reject());
                }
    
                Ok(redirect::redirect(
                    Uri::from_str(&[path, "/"].concat()).unwrap(),
                ))
            })
            .boxed()
    }