How to serve CSS and JS files using Warp?

1.1k Views Asked by At

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/
1

There are 1 best solutions below

0
On BEST ANSWER

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()
}