First off: let me just say that Rocket is very nice and has been a pleasure to use, kudos!
I looked around the docs and couldn't find any support for serving a set of static files from a directory. This is something that I've seen supported in every web framework I've ever used. As an arbitrary example, here's how it works in Flask. This is a pretty common pattern to serve things like CSS files and JS scripts, so I think it would be useful to have. Obviously one can serve individual files by just having a handler that returns a File, but this seems like generally useful functionality. Ideally one would simply be able to do something like rocket::ignite().mount("/static", StaticFiles(some_path).
I am not sure if this helps.. Have you seen the the static_files example ?
Indeed, this is covered in the static_files example.
I had not seen that, thanks! I did eventually find the bit about segments parameters which has that same code as an example, but I still think a helper that simplified this extremely common use case would be nice. It took me a while to find that part of the documentation because I was looking around for "static file serving" and I did not expect to find it under "Dynamic Segments". :)
@luser Perhaps something in contrib that you can simply mount. So it would look something like:
use rocket_contrib::StaticFiles;
rocket::ignite().mount("/path_to_serve_on", StaticFiles::from("/path_to_serve_from"));
This would presumably serve files from the /path_to_serve_from directory at the path /path_to_serve_on.
I think something like this would be nice, though I'm not sure if it's actually possible to do this in a clean way at the moment. I'll look into it.
That sounds great!
I think the way to do it right now is sufficiently simple, just showing how in the guide under "Serving Static Files" or something alike would suffice.
I think it would be best to offer something that supports HTTP caching.
I have a question I am using bootstrap only (/css/bootstrap.min.css, /css/bootstrap-theme.min.css)
It shows errors in console (GET /css/bootstrap-theme.min.css.map:) ??
any reason about this =?

@marti1125 Your browser is sending a request for the source map files (*.map) but they don't exist in the directory. Presumably you have developer tools open.
@SergioBenitez yep it is =D
Without actually running it, it looks like the static files example would be vulnerable to a path traversal attack. Is there anything preventing this?
Since the provided example was not enough for, since it didn't even support the Last-Modified header, I wrote my own Fairing for rocket.
Maybe we could build upon this, stabilize it somewhere in the future and get it in contrib then.
https://crates.io/crates/rocket_static_fs
Sadly docs.rs doesn't build the docs cause of an outdated rustc version. But basically the example from the docs shares your apps src folder under /src/:
#![feature(plugin)]
#![plugin(rocket_codegen)]
extern crate rocket;
extern crate rocket_static_fs;
#[get("/")]
fn index() -> &'static str {
"Hellos, world!"
}
fn main() {
rocket::ignite()
.attach(rocket_static_fs::StaticFileServer::new("src", "/src/").unwrap())
.mount("/", routes![index])
.launch();
}
I just remembered that tilpner/includedir is another solution to this, if you don't mind pulling your entire website into your executable.
Edit: it's definitely not a comprehensive solution, but it's an interesting place to start.
@quadrupleslap Including everything is the binary is cool, but this also doesn't handle things like Not-Modified. My small project is aimed to provide a pretty solid solution for static file serving with different backends and support for caching, ranges, compression, directory listing. It's not there yet, but since I need these features, it won't be long until it has these properly implemented.
@SergioBenitez Is it still planned to allow paths starting with a single dot as described in https://github.com/SergioBenitez/Rocket/issues/560#issuecomment-364695939 ?
The plan in my previous comment covers your use-case as well by relaxing the constraints on PathBuf. I'll close this out and track in #239.
Doing StaticFiles::from("/path_to_serve_from").allow_hidden() would not work for my use case:
#![feature(plugin)]
#![plugin(rocket_codegen)]
extern crate rocket;
extern crate rocket_contrib;
use std::path::{Path, PathBuf};
use std::fs;
use rocket::response::{NamedFile, Responder};
use rocket::response::content::Html;
use rocket::{Request, Response};
use rocket::http::Status;
use rocket::config::{Config, Environment};
fn list_dir<P: AsRef<Path>>(dir: P) -> String {
let dir = dir.as_ref();
let rows = fs::read_dir(&dir).ok().map_or_else(|| "error reading dir".to_string(), |x| x.filter_map(|e| e.ok()).map(|entry| {
let abs_path = entry.path();
let rel_path = abs_path.strip_prefix(dir).unwrap();
let path_str = rel_path.to_string_lossy();
let url = path_str.to_string() + if abs_path.is_dir() { "/" } else { "" };
format!(r#"<li><a href="{}">{}</a>"#, url, url)
}).collect::<String>());
let dir = dir.to_string_lossy();
let dir = if dir == "." { "/".into() } else { dir.replace('\\', "/") };
format!(r#"
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><html>
<title>Directory listing for {}</title>
<body>
<h2>Directory listing for {}</h2>
<hr>
<ul>
{}
</ul>
<hr>
</body>
</html>
"#, dir, dir, rows)
}
#[get("/")]
fn index() -> Html<String> {
Html(list_dir("."))
}
enum PathResp { File(PathBuf), Dir(PathBuf) }
impl Responder<'static> for PathResp {
fn respond_to(self, req: &Request) -> Result<Response<'static>, Status> {
match self {
PathResp::File(path) => NamedFile::open(path).ok().respond_to(req),
PathResp::Dir(path) => Html(list_dir(&path)).respond_to(req)
}
}
}
#[get("/<path..>")]
fn path(path: PathBuf) -> PathResp {
if path.is_dir() { PathResp::Dir(path) } else { PathResp::File(path) }
}
fn main() {
let config = Config::build(Environment::Staging)
.address("0.0.0.0").port(8000).finalize().unwrap();
rocket::custom(config, true).mount("/", routes![index, path]).launch();
}
(The UnsafePBuf workaround would work but I'd prefer if the FromSegments impl for PathBuf allowed paths starting with a single dot.)
@Boscop In case you just need a simplistic, yet functional static file server for rocket, you may want to take a look at https://crates.io/crates/rocket_static_fs
@SergioBenitez I tried to use the static_files example to setup a static site while supporting other api endpoints as well. However I get this collisions error when loading it up with cargo run:
Mounting '/':
=> GET /
=> GET /<file..>
=> GET /api/hello
=> GET /api/test
=> POST /api/upload
=> GET /api/upload/<id>
Error: GET /api/upload/<id> and GET /<file..> collide!
Error: Rocket failed to launch due to routing collisions.
So basically I have setup the static serving on / like
#[get("/")]
fn index() -> io::Result<NamedFile> {
NamedFile::open("dist/index.html")
}
// allow html to reference any file with path /static under folder "static"
#[get("/<file..>")]
fn files(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("dist/").join(file)).ok()
}
Then added some api endpoint like
#[get("/api/upload/<id>")]
fn retrieve(id: PasteID) -> Option<File> {
let filename = format!("tmp/{id}", id = id);
File::open(&filename).ok()
}
BTW, the static html and js files are build/generated by a vue project, which as a single index.html and it reference all the built js and css directly via /js/.... or /css/..... So if I mount the static file endpoint not on root / then the js frontend app won't work normally.
How can I solve this? 馃 Thank you.
Oh I just looked carefully at https://github.com/SergioBenitez/Rocket/issues/723 again it seems I can add rank to solve it. #[get("/<file..>", rank = 10)]
Is this the recommended way?
With ec130f9, you can now do the following:
use rocket_contrib::static_files::StaticFiles;
fn main() {
rocket::ignite()
.mount("/path_to_serve_on", StaticFiles::from("/path_to_serve_from"))
.launch();
}
This sound awesome, will give a try :D Thank you @SergioBenitez 馃挴
@SergioBenitez Hey Sergio again, if I would like to make some user authentication, so that for example if a user has not logged in and want to visit /, then the user will be redirected to some login page (so that all the static files cannot be accessed), I cannot use your recommended way above, right?
I guess I will have to use something like:
#[get("/")]
fn index(_user: User) -> io::Result<NamedFile> {
NamedFile::open("dist/index.html")
}
// allow html to reference any file with path /static under folder "static"
#[get("/<file..>", rank = 10)] // use rank here to allow other api endpoint available as well
fn files(file: PathBuf, _user: User) -> Option<NamedFile> {
NamedFile::open(Path::new("dist/").join(file)).ok()
}
with User setup like in this session example: https://github.com/SergioBenitez/Rocket/blob/master/examples/session/src/main.rs
Does my thoughts make any sense to you? Thanks in advance :)
@liufuyang I'm not sure I've understood what you're saying. I _think_ you're saying that you want:
If that's the case, then, just as would be the case in any other web framework with a pluggable static-files server, you're likely better off handling this yourself. In Rocket, the way you've illustrated looks about right.
@SergioBenitez Thank you.
Basically I am using a single Rocket server to host a group of API endpoints and a set of static html plus js code. And those html + js code will make a UI on the client side that does request on the APIs my Rocket server is hosting. Now I need to add some authentication mechanism with oauth2. So that's why I am asking about it :)
Most helpful comment
@luser Perhaps something in
contribthat you can simply mount. So it would look something like:This would presumably serve files from the
/path_to_serve_fromdirectory at the path/path_to_serve_on.I think something like this would be nice, though I'm not sure if it's actually possible to do this in a clean way at the moment. I'll look into it.